get_site(); $evidence = Availability::has( Availability::EVIDENCE ); $recent = $evidence ? $client->list_evidence( array( 'limit' => (string) self::RECENT_LIMIT, 'include_test' => 'true', ) ) : array( 'body' => array( 'data' => array() ) ); ?> self::FRESH_DAYS * DAY_IN_SECONDS ) { /* translators: %s: date and time in IST. */ echo '

' . esc_html( sprintf( __( 'Recording evidence since your first visitor decision on %s.', 'consiva-ai' ), $when ) ) . '

'; return; } ?>

$items Evidence summaries, newest first. * @return void */ private static function render_recent( array $items ) { if ( ! $items ) { return; } ?>

$job Stored job. * @return void */ private static function render_job( array $job ) { $status = ( new ConsivaClient() )->get_evidence_export( (string) $job['job_id'] ); if ( is_wp_error( $status ) ) { Screens::render_error( $status ); return; } $body = $status['body']; $state = isset( $body['status'] ) ? sanitize_key( (string) $body['status'] ) : ''; $count = isset( $body['record_count'] ) ? (int) $body['record_count'] : null; echo ''; } // ------------------------------------------------------------------ Handlers /** * "Send a test decision" (Runtime §5.2): stamped test = true, so it never counts as first evidence. * * @return void */ public function handle_test_decision() { self::guard( self::ACTION_TEST ); $map = CategoryMap::get(); if ( null === $map ) { self::finish( new WP_Error( 'consiva_consent_no_token', __( 'Consent controls are not fully set up on this site yet. Activate them first, then send a test decision.', 'consiva-ai' ) ) ); } $result = ( new ConsivaClient() )->send_consent_events( $map['token'], array( array( 'event_id' => 'wp_test_' . bin2hex( random_bytes( 8 ) ), 'action' => 'RejectAll', 'categories' => array( 'necessary' => true, 'preferences' => false, 'statistics' => false, 'marketing' => false, ), 'url' => home_url( '/' ), 'test' => true, ), ) ); if ( is_wp_error( $result ) ) { self::finish( $result ); } $item = isset( $result['body']['results'][0] ) && is_array( $result['body']['results'][0] ) ? $result['body']['results'][0] : array(); if ( ! isset( $item['status'] ) || 'validated' !== $item['status'] ) { $why = isset( $item['error'] ) && is_string( $item['error'] ) ? sanitize_text_field( $item['error'] ) : __( 'no reason given', 'consiva-ai' ); /* translators: %s: Consiva's reason. */ self::finish( new WP_Error( 'consiva_consent_test_rejected', sprintf( __( 'Consiva did not accept the test decision: %s', 'consiva-ai' ), $why ), array( 'request_id' => $result['request_id'] ) ) ); } self::finish( null, __( 'Test decision accepted. Consiva checked it and, because it is a test, did not store it.', 'consiva-ai' ), $result['request_id'] ); } /** * Starts an export job (Runtime §5.3). Consiva applies the plan's export window. * * @return void */ public function handle_export() { self::guard( self::ACTION_EXPORT ); $format = isset( $_POST['format'] ) && 'csv' === $_POST['format'] ? 'csv' : 'json'; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). // No "from": Consiva exports everything the plan's window allows and says if it clamped. $result = ( new ConsivaClient() )->start_evidence_export( array( 'format' => $format ) ); if ( is_wp_error( $result ) ) { self::finish( $result ); } $body = $result['body']; $id = isset( $body['job_id'] ) && is_string( $body['job_id'] ) ? $body['job_id'] : ''; if ( ! preg_match( ScanProgress::ID_PATTERN, $id ) ) { self::finish( new WP_Error( 'consiva_consent_invalid_payload', __( 'Consiva started the export but did not say which one. Try again.', 'consiva-ai' ) ) ); } State::update( array( self::JOB_STATE_KEY => array( 'job_id' => $id, 'format' => $format, 'window_clamped' => ! empty( $body['window_clamped'] ), 'from' => isset( $body['from'] ) ? sanitize_text_field( (string) $body['from'] ) : '', ), ) ); self::finish( null, __( 'Preparing your export.', 'consiva-ai' ), $result['request_id'] ); } /** * Streams the finished export to the admin, unchanged. * * @return void */ public function handle_download() { self::guard( self::ACTION_DOWNLOAD ); $job = State::get( self::JOB_STATE_KEY ); if ( ! is_array( $job ) || empty( $job['job_id'] ) ) { self::finish( new WP_Error( 'consiva_consent_no_export', __( 'There is no export to download. Prepare one first.', 'consiva-ai' ) ) ); } $file = ( new ConsivaClient() )->download_evidence_export( (string) $job['job_id'] ); if ( is_wp_error( $file ) ) { self::finish( $file ); } $is_csv = 'csv' === $job['format']; nocache_headers(); header( 'Content-Type: ' . ( $is_csv ? 'text/csv' : 'application/json' ) . '; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="consiva-evidence-' . sanitize_file_name( (string) $job['job_id'] ) . ( $is_csv ? '.csv' : '.json' ) . '"' ); header( 'X-Content-Type-Options: nosniff' ); header( 'Content-Length: ' . strlen( $file['raw'] ) ); echo $file['raw']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- a file download, byte for byte, so it still verifies. exit; } // ------------------------------------------------------------------ Helpers /** * Date and time in IST (Runtime §5.2 wording). * * @param int $timestamp Unix time. * @return string */ private static function ist( $timestamp ) { /* translators: %s: formatted date and time. */ return sprintf( __( '%s IST', 'consiva-ai' ), wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $timestamp, new DateTimeZone( 'Asia/Kolkata' ) ) ); } /** * @param string $type Evidence event type. * @return string */ private static function event_label( $type ) { $labels = array( 'consent_collected' => __( 'Visitor consent decision', 'consiva-ai' ), 'consent_withdrawn' => __( 'Visitor withdrew consent', 'consiva-ai' ), 'consent_config_published' => __( 'Consent settings published', 'consiva-ai' ), 'scan_completed' => __( 'Site scan completed', 'consiva-ai' ), 'enforcement_verified' => __( 'Verification passed', 'consiva-ai' ), 'enforcement_conflict' => __( 'Verification found a conflict', 'consiva-ai' ), 'recommendation_applied' => __( 'Recommendation applied', 'consiva-ai' ), 'form_consent_collected' => __( 'Form consent recorded', 'consiva-ai' ), ); return isset( $labels[ $type ] ) ? $labels[ $type ] : ucfirst( str_replace( '_', ' ', $type ) ); } /** * @param string $action Nonce action. * @return void */ private static function guard( $action ) { if ( ! Capabilities::can_manage() ) { // §9.1: consiva_manage_privacy (administrators hold it). wp_die( esc_html__( 'You do not have permission to manage Consiva evidence.', 'consiva-ai' ), '', array( 'response' => 403 ) ); } check_admin_referer( $action ); } /** * @param WP_Error|null $error Error, or null on success. * @param string $message Success message. * @param string $request_id X-Request-Id. * @return void */ private static function finish( $error, $message = '', $request_id = '' ) { if ( $error instanceof WP_Error ) { Notices::push_error( $error ); } else { Notices::push( 'success', $message, $request_id ); } wp_safe_redirect( Screens::url() . '#consiva-consent-export' ); exit; } }