$page ? 'privacy' : 'settings' ) ); } public static function assets( string $hook ): void { if ( 'toplevel_page_orderworth' !== $hook || ! self::allowed() ) { return; } wp_enqueue_style( 'owwc-consent', plugins_url( 'assets/consent.css', OWWC_FILE ), array( 'owwc-admin' ), OWWC_VERSION ); } private static function scalar( array $values, string $key ): string { return isset( $values[ $key ] ) && is_string( $values[ $key ] ) ? wp_unslash( $values[ $key ] ) : ''; } private static function state(): array { $value = OWWC_Consent_Store::freshOption( self::OPTION, array() ); if ( ! is_array( $value ) ) { throw new \RuntimeException( 'Saved privacy choices are unreadable.' ); } return $value; } /** Only completed quickstarts invite sharing; viewing the screen never grants it. */ public static function invitation(): bool { if ( ! self::allowed() || ! OWWC_Guide::completed() ) { return false; } $buffering = false; try { if ( 'completed' === OWWC_Consent_Store::freshOption( self::ONBOARDING, false ) || self::hasConnection() ) { return false; } ob_start(); $buffering = true; echo ''; ob_end_flush(); $buffering = false; return true; } catch ( \Throwable $error ) { if ( $buffering ) { ob_end_clean(); } } // Optional invitation cannot interrupt quickstart completion. return false; } /** Monotonic local completion, separate from the independent sharing permissions. */ private static function complete(): void { if ( 'completed' === OWWC_Consent_Store::freshOption( self::ONBOARDING, false ) ) { return; } update_option( self::ONBOARDING, 'completed', false ); if ( 'completed' !== OWWC_Consent_Store::freshOption( self::ONBOARDING, false ) ) { throw new \RuntimeException( 'Welcome completion could not be confirmed.' ); } } private static function locked( callable $callback ) { $store = new OWWC_Consent_Store(); $owner = $store->lock( 'welcome_connection', 30 ); if ( ! $owner ) { return new \WP_Error( 'welcome_busy', __( 'Your choices are already being saved. Wait a moment, then retry.', 'orderworth' ) ); } try { return $callback(); } finally { $store->unlock( 'welcome_connection', $owner ); } } private static function binding(): string { return hash( 'sha256', home_url( '/' ) ); } private static function emailHash( string $email ): string { return hash_hmac( 'sha256', strtolower( trim( $email ) ), wp_salt( 'auth' ) ); } private static function edition(): string { return defined( 'OWWC_PRO_VERSION' ) ? 'pro' : 'free'; } /** Bind new consent to its displayed notice and saved choices, including a completed withdrawal. */ private static function choiceAction( array $state ): string { $encoded = wp_json_encode( array( 'consent_version' => self::CONSENT, 'state' => $state ) ); if ( ! is_string( $encoded ) ) { throw new \RuntimeException( 'Saved privacy choices could not be confirmed.' ); } return 'owwc_consent_choice_' . hash( 'sha256', $encoded ); } private static function installation(): array { return array( 'domain' => strtolower( (string) wp_parse_url( home_url( '/' ), PHP_URL_HOST ) ) ); } /** No arbitrary endpoint, redirect, response body or diagnostic credentials. */ private static function request( array $state, string $operation, array $payload ): bool { if ( ! preg_match( '/^[a-f0-9]{32}$/D', $state['id'] ?? '' ) || ! preg_match( '/^[a-f0-9]{64}$/D', $state['token'] ?? '' ) ) { return false; } $body = wp_json_encode( $payload ); if ( ! is_string( $body ) || strlen( $body ) > 2048 ) { return false; } // A worker may pause after its last save until a newer owner withdraws. // Known stale intents must not send; a request already dispatched cannot be recalled. if ( ! OWWC_Consent_Store::currentOption( self::OPTION, $state ) ) { return false; } $response = wp_safe_remote_post( 'https://orderworth.com/newsletter-api/v1/plugin-welcome/' . $state['id'] . '/' . $operation, array( 'timeout' => 8, 'redirection' => 0, 'sslverify' => true, 'limit_response_size' => 2048, 'user-agent' => 'OrderWorth-Welcome/1', 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $state['token'] ), 'body' => $body, 'data_format' => 'body', 'cookies' => array(), ) ); return ! is_wp_error( $response ) && 202 === wp_remote_retrieve_response_code( $response ); } /** Validated explicit choices only; returns local data without making requests. */ private static function payload( array $input ) { foreach ( array( 'installation', 'newsletter' ) as $key ) { if ( isset( $input[ $key ] ) && ( ! is_string( $input[ $key ] ) || '1' !== self::scalar( $input, $key ) ) ) { return new \WP_Error( 'welcome_choice', __( 'Reload this screen and choose the options you want.', 'orderworth' ) ); } } $installation = '1' === self::scalar( $input, 'installation' ); $newsletter = '1' === self::scalar( $input, 'newsletter' ); $domain = self::installation()['domain']; if ( $installation && ( strlen( $domain ) > 253 || false === strpos( $domain, '.' ) || 1 !== preg_match( '/^[\p{L}\p{M}\p{N}.-]+$/uD', $domain ) || filter_var( $domain, FILTER_VALIDATE_IP ) || preg_match( '/\.(localhost|local|localdomain|internal|test|invalid|example|lan|home|home\.arpa|onion)\.?$/iD', $domain ) ) ) { return new \WP_Error( 'consent_domain', __( 'Use a public site domain, or leave domain sharing unchecked. Email signup works independently.', 'orderworth' ) ); } $email = trim( self::scalar( $input, 'email' ) ); if ( $newsletter && ( ! is_string( $input['email'] ?? null ) || strlen( $email ) > 254 || ! is_email( $email ) || sanitize_email( $email ) !== $email ) ) { return new \WP_Error( 'welcome_email', __( 'Enter a valid email address, or leave the email option unchecked.', 'orderworth' ) ); } return array( 'consent_version' => self::CONSENT, 'requested_at' => time(), 'installation' => $installation ? self::installation() : null, 'newsletter' => $newsletter ? array( 'email' => $email, 'edition' => self::edition() ) : null ); } private static function submit( array $input ) { $payload = self::payload( $input ); if ( is_wp_error( $payload ) ) { return $payload; } if ( null === $payload['installation'] && null === $payload['newsletter'] ) { return array( 'message' => __( 'Nothing shared. Your reports and WordPress updates work either way.', 'orderworth' ) ); } $intent = sanitize_text_field( self::scalar( $input, 'owwc_consent_choice_nonce' ) ); return self::locked( static function () use ( $payload, $intent ) { $state = self::state(); // Check after taking the lease: an older POST may have waited before its first state read. if ( ! wp_verify_nonce( $intent, self::choiceAction( $state ) ) ) { return new \WP_Error( 'owwc_consent_stale_choice', __( 'Your privacy choices changed. Refresh this page and review them before sharing again.', 'orderworth' ) ); } if ( ! empty( $state['installation'] ) || ! empty( $state['newsletter'] ) || ! empty( $state['withdraw'] ) ) { return new \WP_Error( 'welcome_existing', __( 'Review your current connection below. Stop its sharing before submitting different choices.', 'orderworth' ) ); } $state = array( 'id' => bin2hex( random_bytes( 16 ) ), 'token' => bin2hex( random_bytes( 32 ) ), 'binding' => self::binding(), 'consent_version' => self::CONSENT, 'installation' => null !== $payload['installation'], 'newsletter' => null !== $payload['newsletter'], 'payload' => $payload, 'status' => 'pending', 'consented_at' => gmdate( 'c' ), 'actor' => get_current_user_id(), 'edition' => self::edition(), 'last_attempt' => time(), 'last_refresh' => 0, 'last_installation_refresh' => 0, 'last_installation_attempt' => 0 ); // Persist the idempotent request before contacting the optional service. OWWC_Consent_Store::saveOption( self::OPTION, $state ); return self::enroll( $state ); } ); } private static function enroll( array $state ) { if ( time() > (int) ( $state['payload']['requested_at'] ?? 0 ) + DAY_IN_SECONDS ) { return new \WP_Error( 'consent_expired', __( 'This saved request expired. Cancel it below, then review and save fresh choices. Nothing new was sent.', 'orderworth' ) ); } $state['last_attempt'] = time(); if ( ! empty( $state['installation'] ) ) { $state['last_installation_attempt'] = time(); } OWWC_Consent_Store::saveOption( self::OPTION, $state ); if ( ! self::request( $state, 'enroll', $state['payload'] ) ) { return new \WP_Error( 'welcome_delivery', __( 'We could not confirm delivery. Your choices are saved locally; use Retry in Privacy choices. You can continue to the dashboard now.', 'orderworth' ) ); } $state['status'] = 'accepted'; $state['last_refresh'] = time(); $state['last_installation_refresh'] = $state['installation'] ? time() : 0; OWWC_Consent_Store::saveOption( self::OPTION, $state ); self::schedule(); self::complete(); return array( 'message' => $state['newsletter'] ? __( 'Request accepted. Check your email and confirm if asked. Existing subscriptions and preferences are unchanged. This screen does not verify an active subscription or inbox delivery.', 'orderworth' ) : __( 'Your selected installation details were accepted. No email signup was requested.', 'orderworth' ) ); } private static function retry() { return self::locked( static function () { $state = self::state(); if ( ! empty( $state['withdraw'] ) ) { return self::sendWithdrawal( $state ); } if ( 'pending' !== ( $state['status'] ?? '' ) || ( $state['binding'] ?? '' ) !== self::binding() ) { return new \WP_Error( 'welcome_retry', __( 'This request cannot be retried. Review or stop its saved sharing before making a new choice.', 'orderworth' ) ); } return self::enroll( $state ); } ); } /** A stop is durable locally before the authenticated remote withdrawal attempt. */ private static function withdraw( bool $installation, bool $newsletter ) { return self::locked( static function () use ( $installation, $newsletter ) { return self::withdrawState( self::state(), $installation, $newsletter ); } ); } /** Called only under the connection lock, including email-scoped privacy erasure. */ private static function withdrawState( array $state, bool $installation, bool $newsletter ) { // Keep a nonpersonal local revision even after removal, so old consent cannot see // the same empty state again. It is never sent and follows the existing uninstall cleanup. $state['choice_revision'] = bin2hex( random_bytes( 24 ) ); if ( empty( $state['id'] ) ) { OWWC_Consent_Store::saveOption( self::OPTION, array( 'choice_revision' => $state['choice_revision'] ) ); self::stop(); return array( 'message' => __( 'No connection is saved.', 'orderworth' ) ); } // Pending enrollment must be cancelled in full, so a delayed replay cannot restore removed scopes. if ( 'pending' === ( $state['status'] ?? '' ) ) { $installation = true; $newsletter = true; } if ( $newsletter && ! empty( $state['payload']['newsletter']['email'] ) && is_string( $state['payload']['newsletter']['email'] ) ) { // Retain no address after stopping locally. This keyed, local-only match allows privacy-eraser retries after a transport failure. $state['withdraw_email_hash'] = self::emailHash( $state['payload']['newsletter']['email'] ); } $state['withdraw'] = array( 'installation' => $installation || ! empty( $state['withdraw']['installation'] ), 'newsletter' => $newsletter || ! empty( $state['withdraw']['newsletter'] ) ); foreach ( array( 'installation', 'newsletter' ) as $scope ) { if ( $state['withdraw'][ $scope ] ) { $state[ $scope ] = false; $state['payload'][ $scope ] = null; } } $state['status'] = 'accepted'; OWWC_Consent_Store::saveOption( self::OPTION, $state ); if ( ! $state['installation'] && ! $state['newsletter'] ) { self::stop(); } return self::sendWithdrawal( $state ); } private static function sendWithdrawal( array $state ) { if ( ! self::request( $state, 'withdraw', $state['withdraw'] ) ) { return new \WP_Error( 'welcome_withdrawal', __( 'Sharing has stopped locally, but remote removal could not be confirmed. Use Retry to remove the saved details. To stop a confirmed email subscription, use the unsubscribe link in an email.', 'orderworth' ) ); } unset( $state['withdraw'], $state['withdraw_email_hash'] ); if ( ! $state['installation'] && ! $state['newsletter'] ) { $state = array( 'choice_revision' => $state['choice_revision'] ?? bin2hex( random_bytes( 24 ) ) ); } OWWC_Consent_Store::saveOption( self::OPTION, $state ); return array( 'message' => __( 'Selected sharing stopped and removal accepted. To stop a confirmed email subscription, use the unsubscribe link in an email.', 'orderworth' ) ); } public static function deactivate( bool $network_wide = false ): void { if ( ! is_multisite() || ! $network_wide ) { self::stop(); return; } $offset = 0; do { $ids = get_sites( array( 'fields' => 'ids', 'number' => 100, 'offset' => $offset ) ); foreach ( $ids as $id ) { switch_to_blog( $id ); try { self::stop(); } finally { restore_current_blog(); } } $offset += 100; } while ( 100 === count( $ids ) ); } public static function stop(): void { wp_clear_scheduled_hook( self::HOOK ); } /** Scheduling and local navigation send no request; only opted-in background jobs send. */ public static function schedule(): void { try { $state = self::state(); } catch ( \Throwable $error ) { return; } if ( 'accepted' !== ( $state['status'] ?? '' ) || ! empty( $state['withdraw'] ) || empty( $state['installation'] ) && empty( $state['newsletter'] ) || ( $state['binding'] ?? '' ) !== self::binding() ) { return; } $due = max( time() + 60, max( (int) ( $state['last_refresh'] ?? 0 ), (int) ( $state['last_attempt'] ?? 0 ) ) + WEEK_IN_SECONDS ); if ( ! empty( $state['newsletter'] ) && ( $state['edition'] ?? '' ) !== self::edition() ) { $due = max( time() + 60, (int) ( $state['last_attempt'] ?? 0 ) + HOUR_IN_SECONDS ); } $scheduled = wp_next_scheduled( self::HOOK ); if ( $scheduled && $scheduled <= $due ) { return; } if ( $scheduled ) { self::stop(); } wp_schedule_single_event( $due, self::HOOK ); } public static function refresh(): void { try { self::locked( static function () { $state = self::state(); if ( 'accepted' !== ( $state['status'] ?? '' ) || ! empty( $state['withdraw'] ) || empty( $state['installation'] ) && empty( $state['newsletter'] ) || ( $state['binding'] ?? '' ) !== self::binding() ) { return; } // An upgrade never broadens a saved permission or relabels an older report. $consent = $state['consent_version'] ?? ''; if ( ! in_array( $consent, array( self::CONSENT ), true ) ) { return; } $editionChanged = ! empty( $state['newsletter'] ) && ( $state['edition'] ?? '' ) !== self::edition(); if ( time() < (int) ( $state['last_attempt'] ?? 0 ) + ( $editionChanged ? HOUR_IN_SECONDS : WEEK_IN_SECONDS ) ) { return; } $payload = array( 'consent_version' => $consent, 'requested_at' => time(), 'installation' => $state['installation'] && time() >= max( (int) ( $state['last_installation_refresh'] ?? 0 ), (int) ( $state['last_installation_attempt'] ?? 0 ) ) + WEEK_IN_SECONDS ? self::installation() : null, 'newsletter' => $state['newsletter'] ? array( 'edition' => self::edition() ) : null ); $state['last_attempt'] = time(); if ( null !== $payload['installation'] ) { $state['last_installation_attempt'] = time(); } OWWC_Consent_Store::saveOption( self::OPTION, $state ); if ( self::request( $state, 'refresh', $payload ) ) { $state['last_refresh'] = time(); $state['edition'] = self::edition(); if ( null !== $payload['installation'] ) { $state['last_installation_refresh'] = time(); } unset( $state['refresh_failed'] ); } else { $state['refresh_failed'] = true; } OWWC_Consent_Store::saveOption( self::OPTION, $state ); } ); } catch ( \Throwable $error ) { /* Optional connection failures never affect reports. */ } try { self::schedule(); } catch ( \Throwable $error ) { /* Optional scheduling failure cannot interrupt reports. */ } } public static function handle(): void { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact method comparison only; server input is neither displayed nor stored. if ( 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) || ! self::allowed() ) { wp_die( esc_html__( 'Administrator authority and a POST request are required.', 'orderworth' ), '', array( 'response' => 403 ) ); } if ( ! is_string( $_POST['_owwc_consent_nonce'] ?? null ) ) { wp_die( esc_html__( 'Refresh this form and try again.', 'orderworth' ), '', array( 'response' => 403 ) ); } $nonce = sanitize_text_field( wp_unslash( $_POST['_owwc_consent_nonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'owwc_consent' ) ) { wp_die( esc_html__( 'Refresh this form and try again.', 'orderworth' ), '', array( 'response' => 403 ) ); } $operation = self::scalar( $_POST, 'consent_operation' ); $finish = 'quickstart' === self::scalar( $_POST, 'consent_context' ); if ( $finish && ( ! in_array( $operation, array( 'save', 'skip' ), true ) || ! OWWC_Guide::completed() || ! wp_verify_nonce( sanitize_text_field( self::scalar( $_POST, '_owwc_consent_finish_nonce' ) ), 'owwc_consent_quickstart' ) ) ) { wp_die( esc_html__( 'Your quickstart progress changed or this form expired. Reopen Quickstart to review the current step. No new optional choice was submitted.', 'orderworth' ), '', array( 'response' => 403 ) ); } $result = null; try { switch ( $operation ) { case 'skip': $result = array( 'message' => __( 'Nothing new shared. Your reports remain available. Existing saved choices, if any, are unchanged.', 'orderworth' ) ); break; case 'save': $result = self::submit( $_POST ); break; case 'retry': $result = self::retry(); break; case 'stop_installation': $result = self::withdraw( true, false ); break; case 'stop_newsletter': $result = self::withdraw( false, true ); break; case 'stop_all': $result = self::withdraw( true, true ); break; default: wp_die( esc_html__( 'Choose a valid privacy action.', 'orderworth' ), '', array( 'response' => 400 ) ); } if ( ! is_wp_error( $result ) && in_array( $operation, array( 'skip', 'save' ), true ) ) { self::complete(); } } catch ( \Throwable $error ) { $result = new \WP_Error( 'welcome_storage', __( 'Your choices could not be confirmed. Review the saved state before retrying. Reports and cost settings remain available.', 'orderworth' ) ); } $error = is_wp_error( $result ); set_transient( 'owwc_consent_notice_' . get_current_user_id(), array( 'kind' => $error ? 'error' : 'success', 'message' => $error ? $result->get_error_message() : $result['message'], 'quickstart_finished' => $finish ), 120 ); // Lesson completion is already saved. Optional failures cannot trap the merchant in setup. wp_safe_redirect( $finish ? OWWC_Admin::url() : self::url( $error || ! in_array( $operation, array( 'skip', 'save' ), true ) ) ); exit; } public static function notice(): void { if ( ! self::allowed() ) { return; } $key = 'owwc_consent_notice_' . get_current_user_id(); $notice = get_transient( $key ); if ( ! is_array( $notice ) || ! is_string( $notice['message'] ?? null ) ) { return; } delete_transient( $key ); echo '
'; if ( ! empty( $notice['quickstart_finished'] ) ) { echo ''; } } private static function formStart(): void { echo ''; } private static function hasConnection(): bool { $state = self::state(); return ! empty( $state['installation'] ) || ! empty( $state['newsletter'] ) || ! empty( $state['withdraw'] ); } private static function intro(): void { echo '' . esc_html__( 'Choose either, both, or neither. Every Free feature works without sharing.', 'orderworth' ) . '
'; } private static function choicesForm( bool $finish = false ): void { $data = self::installation(); $choice_action = self::choiceAction( self::state() ); self::formStart(); if ( $finish ) { echo ''; wp_nonce_field( 'owwc_consent_quickstart', '_owwc_consent_finish_nonce', false ); } wp_nonce_field( $choice_action, 'owwc_consent_choice_nonce', false ); echo ''; echo ''; } public static function render(): void { if ( ! self::allowed() ) { echo '' . esc_html__( 'A site administrator can manage optional sharing. All reporting tools remain available to your store role.', 'orderworth' ) . '
' . esc_html__( 'Return to Cost assumptions', 'orderworth' ) . '
'; } private static function renderConnection(): void { $state = self::state(); echo '' . esc_html__( 'These optional permissions never disable your reports.', 'orderworth' ) . '
'; if ( ! empty( $state['installation'] ) ) { echo '' . esc_html( $state['payload']['installation']['domain'] ?? '' ) . '
' . esc_html( $state['payload']['newsletter']['email'] ?? '' ) . ' — ' . esc_html__( 'Confirm your email if asked. Subscription status is not checked here.', 'orderworth' ) . '
'; } if ( ( $state['binding'] ?? '' ) !== self::binding() ) { echo '' . esc_html__( 'The site address changed. Automatic sharing is paused. Stop the old connection before making new choices for this site.', 'orderworth' ) . '
'; } if ( ! empty( $state['refresh_failed'] ) || ( ! empty( $state['last_refresh'] ) && time() > (int) $state['last_refresh'] + 2 * WEEK_IN_SECONDS ) ) { echo '' . esc_html__( 'The latest automatic update is unconfirmed or out of date. Your reports remain available. Stop sharing and make fresh choices to reconnect.', 'orderworth' ) . '
'; } echo '' . esc_html__( 'Stopping a choice removes its shared details and cancels this connection’s pending signup. An unconfirmed delivery request is cancelled in full. For a confirmed newsletter subscription, use Unsubscribe in an email to stop tips, updates and offers.', 'orderworth' ) . '
' . esc_html__( 'Privacy and retention details (opens in a new tab)', 'orderworth' ) . '
'; } /** Only an email-matched local record; credentials and connection IDs are never exported. */ public static function privacyExport( string $email ): array { $state = self::state(); if ( '' === $email || 0 !== strcasecmp( (string) ( $state['payload']['newsletter']['email'] ?? '' ), $email ) ) { return array(); } $delivery_labels = array( 'pending' => __( 'Delivery pending', 'orderworth' ), 'accepted' => __( 'Request accepted', 'orderworth' ) ); $delivery_state = is_string( $state['status'] ?? null ) ? $state['status'] : ''; $data = array( array( 'name' => __( 'Newsletter request email', 'orderworth' ), 'value' => $email ), array( 'name' => __( 'Delivery state (not subscription status)', 'orderworth' ), 'value' => $delivery_labels[ $delivery_state ] ?? __( 'Unknown', 'orderworth' ) ), array( 'name' => __( 'Consent version', 'orderworth' ), 'value' => $state['consent_version'] ?? '' ), array( 'name' => __( 'Consent time (UTC)', 'orderworth' ), 'value' => $state['consented_at'] ?? '' ), array( 'name' => __( 'Edition sharing', 'orderworth' ), 'value' => empty( $state['newsletter'] ) ? __( 'Off', 'orderworth' ) : __( 'Selected', 'orderworth' ) ), ); if ( ! empty( $state['installation'] ) && is_array( $state['payload']['installation'] ?? null ) ) { $shared_labels = array( 'domain' => __( 'Shared site domain', 'orderworth' ), ); foreach ( $shared_labels as $key => $label ) { $data[] = array( 'name' => $label, 'value' => (string) ( $state['payload']['installation'][ $key ] ?? '' ) ); } } return array( array( 'group_id' => 'orderworth-welcome', 'group_label' => __( 'OrderWorth privacy choices', 'orderworth' ), 'item_id' => 'orderworth-welcome-email', 'data' => $data ) ); } public static function privacyErase( string $email ) { return self::locked( static function () use ( $email ) { // Match the fresh state while holding the same lock used for enrollment and withdrawal. $state = self::state(); if ( '' === $email ) { return array( 'removed' => false ); } $matches = 0 === strcasecmp( (string) ( $state['payload']['newsletter']['email'] ?? '' ), $email ); $pending_match = ! empty( $state['withdraw']['newsletter'] ) && is_string( $state['withdraw_email_hash'] ?? null ) && hash_equals( $state['withdraw_email_hash'], self::emailHash( $email ) ); if ( ! $matches && ! $pending_match ) { return array( 'removed' => false ); } $result = self::withdrawState( $state, false, true ); if ( is_wp_error( $result ) ) { return $result; } return array( 'removed' => true ); } ); } public static function exporters( array $exporters ): array { $exporters['orderworth-consent'] = array( 'exporter_friendly_name' => __( 'OrderWorth optional sharing', 'orderworth' ), 'callback' => array( self::class, 'exportData' ) ); return $exporters; } public static function exportData( string $email, int $page = 1 ): array { return array( 'data' => $page > 1 ? array() : self::privacyExport( $email ), 'done' => true ); } public static function erasers( array $erasers ): array { $erasers['orderworth-consent'] = array( 'eraser_friendly_name' => __( 'OrderWorth optional sharing', 'orderworth' ), 'callback' => array( self::class, 'eraseData' ) ); return $erasers; } public static function eraseData( string $email, int $page = 1 ): array { $result = $page > 1 ? array( 'removed' => false ) : self::privacyErase( $email ); $error = is_wp_error( $result ); return array( 'items_removed' => ! $error && ! empty( $result['removed'] ), 'items_retained' => $error, 'messages' => $error ? array( $result->get_error_message() ) : ( ! empty( $result['removed'] ) ? array( __( 'This connection’s email sharing was removed. Confirmed newsletters are separate; use Unsubscribe in an email.', 'orderworth' ) ) : array() ), 'done' => true ); } /** Local-only cleanup; no network request is implied by deleting a plugin. */ public static function uninstall(): void { self::stop(); self::locked( static function () { OWWC_Consent_Store::saveOption( self::OPTION, array( 'choice_revision' => bin2hex( random_bytes( 24 ) ) ) ); } ); delete_option( self::ONBOARDING ); } }