'GET', 'callback' => array( $this, 'rest_status' ), 'permission_callback' => array( $this, 'can_manage' ), ) ); register_rest_route( CUMPLECONSENT_REST_NS, '/policy', array( 'methods' => 'POST', 'callback' => array( $this, 'rest_policy' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'publish' => array( 'type' => 'boolean', 'default' => false, 'description' => 'Publicar la página (si es falso, solo actualiza el contenido).', ), 'page_id' => array( 'type' => 'integer', 'default' => 0, 'description' => 'Página concreta a actualizar. Si no se indica, se usa la asignada o la existente con el slug habitual.', ), ), ) ); register_rest_route( CUMPLECONSENT_REST_NS, '/rights-page', array( 'methods' => 'POST', 'callback' => array( $this, 'rest_rights_page' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'enabled' => array( 'type' => 'boolean', 'default' => false, 'description' => 'Habilitar además el canal de derechos.', ), 'page_id' => array( 'type' => 'integer', 'default' => 0, ), 'title' => array( 'type' => 'string', 'default' => '', ), ), ) ); } // ── GET /status ────────────────────────────────────────────────────────── /** * Estado del cumplimiento en el sitio, con los pendientes concretos. * * @return \WP_REST_Response */ public function rest_status() { $score = Policy::score(); $policy = Policy::url(); $pending = array(); if ( ! Settings::is_ready() ) { $pending[] = __( 'Falta completar la identidad del responsable y activar el aviso.', 'cumpleconsent' ); } if ( '' === $policy ) { $pending[] = __( 'No hay política de privacidad publicada ni enlazada (art. 14 ter).', 'cumpleconsent' ); } if ( (int) $score['ok'] < 12 ) { $pending[] = sprintf( /* translators: %d: literales cubiertos */ __( 'La política cubre %d de los 12 contenidos del art. 14 ter.', 'cumpleconsent' ), (int) $score['ok'] ); } if ( 'equal' !== (string) Settings::get( 'reject_style', 'equal' ) ) { $pending[] = __( '“Rechazar” debe tener el mismo peso visual que “aceptar” (art. 12).', 'cumpleconsent' ); } if ( Settings::get( 'cookie_wall', false ) ) { $pending[] = __( 'El muro de cookies compromete la libertad del consentimiento (art. 12).', 'cumpleconsent' ); } $sin_declarar = Transfers::undeclared(); if ( $sin_declarar ) { $pending[] = sprintf( /* translators: %d: cantidad de proveedores */ __( '%d proveedor(es) detectado(s) sin declarar como destinatario o como transferencia internacional (art. 14 ter d) y h)).', 'cumpleconsent' ), count( $sin_declarar ) ); } if ( ! Settings::get( 'rights_enabled', false ) ) { $pending[] = __( 'El canal de solicitudes de derechos está deshabilitado (art. 11).', 'cumpleconsent' ); } if ( 0 === (int) Settings::get( 'policy_page_id', 0 ) && '' !== $policy ) { $pending[] = __( 'El aviso enlaza una política que no está asignada en los ajustes: asígnala para poder regenerarla y versionarla.', 'cumpleconsent' ); } return new \WP_REST_Response( array( 'plugin' => array( 'version' => CUMPLECONSENT_VERSION, 'enabled' => (bool) Settings::get( 'enabled', false ), 'ready' => Settings::is_ready(), ), 'notice' => array( 'version' => Policy::version(), 'hash' => Notice::hash(), ), 'policy' => array( 'pageId' => (int) Settings::get( 'policy_page_id', 0 ), 'url' => $policy, 'score' => (int) $score['score'], 'ok' => (int) $score['ok'], 'partial' => (int) $score['partial'], 'missing' => (int) $score['missing'], 'detail' => $score['coverage'], ), 'rights' => array( 'enabled' => (bool) Settings::get( 'rights_enabled', false ), 'pageId' => (int) Settings::get( 'rights_page_id', 0 ), 'url' => Rights::page_url(), ), 'transfers' => array( 'declared' => Transfers::count_declared(), 'rows' => Transfers::rows(), 'undeclared' => $sin_declarar, ), 'retention' => array( 'logMonths' => (int) Settings::get( 'log_retention_months', 24 ), 'consentMonths' => (int) Settings::get( 'consent_months', 12 ), ), 'pending' => $pending, ), 200 ); } // ── POST /policy ───────────────────────────────────────────────────────── /** * Genera (o actualiza) la página de la política con los datos del sitio. * * @param \WP_REST_Request $request Petición. * @return \WP_REST_Response */ public function rest_policy( $request ) { $publish = (bool) $request->get_param( 'publish' ); $page_id = (int) $request->get_param( 'page_id' ); if ( ! $page_id ) { $page_id = (int) Settings::get( 'policy_page_id', 0 ); } if ( ! $page_id ) { $page_id = self::find_page( array( 'politica-de-privacidad', 'privacidad', 'politica-de-datos', 'politica-privacidad' ), array( 'política de privacidad', 'politica de privacidad', 'política de datos' ) ); } if ( $page_id ) { Settings::save( array( 'policy_page_id' => $page_id ) ); } $id = Policy::create_page( $publish ); $mode = (string) Settings::get( 'policy_last_mode' ); if ( ! $id ) { return new \WP_REST_Response( array( 'ok' => false, 'message' => __( 'No se pudo crear ni actualizar la página de la política.', 'cumpleconsent' ), ), 500 ); } $score = Policy::score(); return new \WP_REST_Response( array( 'ok' => true, 'id' => $id, 'mode' => $mode, // nueva | actualizada | borrador 'kept_page' => 'borrador' === $mode ? (int) Settings::get( 'policy_page_id', 0 ) : 0, 'status' => get_post_status( $id ), 'link' => get_permalink( $id ), 'ok_count' => (int) $score['ok'], 'score' => (int) $score['score'], 'missing' => self::missing_labels( $score ), 'caches' => Plugin::purge_caches(), ), 200 ); } // ── POST /rights-page ──────────────────────────────────────────────────── /** * Crea o reutiliza la página con el formulario de derechos y la asigna. * * @param \WP_REST_Request $request Petición. * @return \WP_REST_Response */ public function rest_rights_page( $request ) { $page_id = (int) $request->get_param( 'page_id' ); if ( ! $page_id ) { $page_id = (int) Settings::get( 'rights_page_id', 0 ); } if ( ! $page_id ) { $page_id = self::find_page_with_shortcode(); } if ( ! $page_id ) { $page_id = self::find_page( array( 'tus-derechos', 'derechos', 'solicitud-de-derechos', 'ejercer-derechos' ), array( 'tus derechos', 'derechos sobre tus datos' ) ); } if ( ! $page_id ) { $title = trim( (string) $request->get_param( 'title' ) ); if ( '' === $title ) { $title = __( 'Tus derechos sobre tus datos', 'cumpleconsent' ); } $creada = wp_insert_post( array( 'post_title' => sanitize_text_field( $title ), 'post_name' => 'tus-derechos', 'post_content' => self::rights_page_content(), 'post_status' => 'publish', 'post_type' => 'page', 'comment_status' => 'closed', 'ping_status' => 'closed', 'post_author' => get_current_user_id(), ), true ); if ( is_wp_error( $creada ) || ! $creada ) { return new \WP_REST_Response( array( 'ok' => false, 'message' => __( 'No se pudo crear la página del canal de derechos.', 'cumpleconsent' ), ), 500 ); } $page_id = (int) $creada; } elseif ( false === strpos( (string) get_post_field( 'post_content', $page_id ), '[cumpleconsent_derechos]' ) ) { // La página existe y no tiene el formulario: se lo agregamos sin borrar el resto. $post = get_post( $page_id ); if ( $post instanceof \WP_Post ) { wp_update_post( array( 'ID' => $page_id, 'post_content' => rtrim( (string) $post->post_content ) . "\n\n[cumpleconsent_derechos]", ) ); } } $datos = array( 'rights_page_id' => (int) $page_id ); if ( $request->get_param( 'enabled' ) ) { $datos['rights_enabled'] = true; } Settings::save( $datos ); return new \WP_REST_Response( array( 'ok' => true, 'id' => (int) $page_id, 'status' => get_post_status( (int) $page_id ), 'link' => get_permalink( (int) $page_id ), 'enabled' => (bool) Settings::get( 'rights_enabled', false ), 'url' => Rights::page_url(), 'caches' => Plugin::purge_caches(), ), 200 ); } // ── Utilidades ─────────────────────────────────────────────────────────── /** * Primera página que coincide por slug o por título. * * @param string[] $slugs Slugs candidatos, en orden. * @param string[] $titulos Títulos candidatos, en orden. * @return int ID de la página, 0 si no hay ninguna. */ private static function find_page( array $slugs, array $titulos ): int { foreach ( $slugs as $slug ) { $page = get_page_by_path( $slug ); if ( $page instanceof \WP_Post ) { return (int) $page->ID; } } foreach ( $titulos as $titulo ) { $ids = get_posts( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'draft', 'private', 'pending' ), 'title' => $titulo, 'numberposts' => 1, 'fields' => 'ids', ) ); if ( ! empty( $ids ) ) { return (int) $ids[0]; } } return 0; } /** Primera página que ya contiene el shortcode del formulario de derechos. */ private static function find_page_with_shortcode(): int { $ids = get_posts( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'draft', 'private', 'pending' ), 's' => 'cumpleconsent_derechos', 'numberposts' => 20, 'fields' => 'ids', ) ); foreach ( (array) $ids as $id ) { if ( false !== strpos( (string) get_post_field( 'post_content', (int) $id ), '[cumpleconsent_derechos]' ) ) { return (int) $id; } } return 0; } /** Literales del art. 14 ter que faltan, con su etiqueta. */ private static function missing_labels( array $score ): array { $out = array(); foreach ( (array) $score['coverage'] as $key => $row ) { if ( 'ok' === $row['status'] ) { continue; } $out[ $key ] = $row['label']; } return $out; } /** Contenido base de la página del canal de derechos. */ public static function rights_page_content(): string { $owner = (string) Settings::get( 'owner_name', '' ); if ( '' === trim( $owner ) ) { $owner = get_bloginfo( 'name' ); } $email = Rights::channel_email(); $correo = '' !== $email ? '' . esc_html( $email ) . '' : esc_html__( 'el correo de contacto publicado en este sitio', 'cumpleconsent' ); $html = array(); $html[] = '

' . esc_html__( 'Tus derechos sobre tus datos personales', 'cumpleconsent' ) . '

'; $html[] = '

' . esc_html__( 'Como titular de tus datos puedes ejercer los derechos de acceso (saber qué datos tratamos, para qué y con quién los compartimos), rectificación (corregir datos inexactos o desactualizados), supresión o cancelación, oposición a un tratamiento, portabilidad de tus datos y bloqueo temporal del tratamiento mientras se resuelve tu solicitud.', 'cumpleconsent' ) . '

'; $html[] = '

' . sprintf( /* translators: %s: correo de contacto */ esc_html__( 'La solicitud se presenta por escrito: usa el formulario de aquí abajo o escríbenos a %s. Recibirás el acuse de recibo y te responderemos dentro de 30 días corridos, plazo prorrogable por una sola vez por otros 30 días corridos. Si pides el bloqueo temporal, responderemos dentro de 2 días hábiles y mientras no resolvamos no trataremos los datos comprendidos en tu solicitud. Es gratuito y no necesitas patrocinio.', 'cumpleconsent' ), $correo ) . '

'; $html[] = '

' . sprintf( /* translators: %s: responsable */ esc_html__( 'El responsable del tratamiento es %s. Si rechazamos o no respondemos oportunamente, puedes reclamar ante la Agencia de Protección de Datos Personales dentro de 30 días hábiles (artículo 41 de la Ley 21.719).', 'cumpleconsent' ), '' . esc_html( $owner ) . '' ) . '

'; $html[] = ''; $html[] = '[cumpleconsent_derechos]'; $html[] = ''; $url = Policy::url(); if ( '' !== $url ) { $html[] = '

' . sprintf( /* translators: %s: enlace a la política */ esc_html__( 'El detalle de finalidades, destinatarios, plazos y transferencias está en nuestra %s.', 'cumpleconsent' ), '' . esc_html__( 'política de privacidad', 'cumpleconsent' ) . '' ) . '

'; } return implode( "\n", $html ); } }