__( 'SMS activity', 'abientot' ), 'sms_state' => __( 'SMS booking state', 'abientot' ), 'staff' => __( 'Staff profiles', 'abientot' ), 'staff_rules' => __( 'Staff schedules', 'abientot' ), 'staff_leave' => __( 'Staff leave', 'abientot' ), 'historical_review' => __( 'Historical identity review', 'abientot' ), 'calendar_connections' => __( 'External calendar connections', 'abientot' ), 'selection_answers' => __( 'Intake answers', 'abientot' ), 'dependants' => __( 'Dependants', 'abientot' ), 'attendees' => __( 'Attendees', 'abientot' ), 'credit_grants' => __( 'Session packages', 'abientot' ), 'credit_applications' => __( 'Session credit applications', 'abientot' ), 'booking_instructions' => __( 'Saved service instructions', 'abientot' ), 'customers' => __( 'Customers', 'abientot' ), 'appointments' => __( 'Bookings', 'abientot' ), 'payment_records' => __( 'Payment records', 'abientot' ), 'activity_log' => __( 'Activity history', 'abientot' ), 'recurrence' => __( 'Recurring bookings', 'abientot' ), 'attendance' => __( 'Attendance', 'abientot' ), 'notification_attempts' => __( 'Email attempts', 'abientot' ), 'public_waitlist' => __( 'Waitlist preferences', 'abientot' ), 'pending_waitlist' => __( 'Unverified waitlist requests', 'abientot' ), 'groups' => __( 'Group enrollments', 'abientot' ), 'waitlist' => __( 'Waitlist', 'abientot' ), 'payment_plans' => __( 'Payment plans', 'abientot' ), 'payment_refunds' => __( 'Refund records', 'abientot' ) ); foreach ( $labels as $kind => $label ) { $exporters[ 'abientot-' . $kind ] = array( 'exporter_friendly_name' => 'Abientot — ' . $label, 'callback' => function ( $email, $page = 1 ) use ( $kind ) { return $this->export( $kind, $email, $page ); }, ); } return $exporters; } public function erasers( array $erasers ): array { $erasers['abientot'] = array( 'eraser_friendly_name' => 'Abientot', 'callback' => array( $this, 'erase' ) ); return $erasers; } public function export( string $kind, string $email, int $page = 1 ) { try { $repo = new PrivacyRepository(); if ( $kind === 'calendar_connections' ) { $user = get_user_by( 'email', $email ); $rows = $user ? ( new \Abientot\Repositories\CalendarConnectionsRepository() )->export_user( (int) $user->ID, max( 1, $page ) ) : array(); } elseif ( $kind === 'pending_waitlist' ) { $rows = ( new \Abientot\Repositories\PublicWaitlistRepository() )->pending( $email, 100, ( max( 1, $page ) - 1 ) * 100 ); } elseif ( $kind === 'sms' || $kind === 'sms_state' ) { $rows = ( new \Abientot\Repositories\RetainedSmsPrivacyRepository() )->export_rows( $repo->customer_ids( $email ), $page, $kind === 'sms_state' ? 'state' : 'jobs' ); } elseif ( $kind === 'staff' ) { $rows = ( new \Abientot\Repositories\StaffPrivacyRepository() )->export_rows( $email, $page ); } elseif ( $kind === 'staff_rules' || $kind === 'staff_leave' ) { $rows = ( new \Abientot\Repositories\StaffPrivacyRepository() )->export_subject_schedule( $kind === 'staff_rules' ? 'availability_rules' : 'availability_exceptions', $email, max( 1, $page ) ); } elseif ( $kind === 'historical_review' ) { $rows = $page === 1 && $repo->historical_review_needed( $email ) ? array( array( 'id' => 'review', 'notice' => __( 'Historical booking identity needs independent verification. Ask the business to review your request.', 'abientot' ) ) ) : array(); } else { $rows = $repo->export_rows( $kind, $repo->customer_ids( $email ), max( 1, $page ) ); } $data = array(); foreach ( $rows as $row ) { $data[] = array( 'group_id' => 'abientot-' . $kind, 'group_label' => __( 'Abientot personal data', 'abientot' ), 'item_id' => 'abientot-' . $kind . '-' . $row['id'], 'data' => array( array( 'name' => __( 'Stored data', 'abientot' ), 'value' => esc_html( wp_json_encode( $row, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) ) ) ), ); } return array( 'data' => $data, 'done' => count( $rows ) < 100 ); } catch ( \Throwable $exception ) { return new \WP_Error( 'abientot_privacy_failed', __( 'Personal data could not be processed. Please try again.', 'abientot' ) ); } } public function erase( string $email, int $page = 1 ) { $removed = false; try { $repo = new PrivacyRepository(); $calendarRemoved = false; $calendarUser = get_user_by( 'email', $email ); if ( $calendarUser ) { $calendarRemoved = ( new \Abientot\Repositories\CalendarConnectionsRepository() )->erase_user( (int) $calendarUser->ID ); } $removed = $calendarRemoved; // Always consume the first remaining matches: erasure removes the email index. // OFFSET pagination would skip customers as earlier pages disappear. $ids = $repo->customer_ids( $email, 21 ); $removed = $calendarRemoved; foreach ( array_slice( $ids, 0, 20 ) as $id ) { if ( ! $repo->erase_customer( $id ) ) { return array( 'items_removed' => $removed, 'items_retained' => true, 'messages' => array( __( 'Personal data could not be processed. Please try again.', 'abientot' ) ), 'done' => true ); } $removed = true; } $pending = $repo->erase_pending_waitlist( $email ); $removed = $removed || $pending['removed'] > 0; $staff = ( new \Abientot\Repositories\StaffPrivacyRepository() )->erase( $email ); $removed = $removed || $staff['removed']; $review = $repo->historical_review_needed( $email ); $messages = array(); if ( $review ) { $messages[] = __( 'Historical booking identity needs independent verification. Ask the business to review your request.', 'abientot' ); } $messages = array_merge( $messages, $staff['messages'] ); return array( 'items_removed' => $removed || $pending['removed'] > 0 || $staff['removed'], 'items_retained' => $review || $staff['retained'], 'messages' => $messages, 'done' => count( $ids ) <= 20 && $pending['done'] && $staff['done'] ); } catch ( \Throwable $exception ) { if ( $removed ) { return array( 'items_removed' => true, 'items_retained' => true, 'messages' => array( __( 'Personal data could not be processed. Please try again.', 'abientot' ) ), 'done' => true ); } return new \WP_Error( 'abientot_privacy_failed', __( 'Personal data could not be processed. Please try again.', 'abientot' ) ); } } }