user_email))throw new CatalogException('abece_booking_login_required',__('Log in to manage your personal data.','abece-booking'),401); if(!$this->requests->uniqueAccount($user->user_email,$actor))throw new CatalogException('abece_booking_privacy_identity_review',__('Your identity requires individual verification. Please contact the operator.','abece-booking'),409); return $user; } public function nativeIdentityValid(int $request):bool { $owner=(int)get_post_meta($request,'abece_booking_privacy_request_owner',true);if(!$owner)return true; $native=wp_get_user_request($request);$u=get_userdata($owner); return $native&&$u&&(int)$native->user_id===$owner&&strcasecmp($native->email,$u->user_email)===0&&$this->requests->uniqueAccount($u->user_email,$owner); } public function listing(int $actor):array { $u=$this->account($actor);return ['policy_url'=>get_privacy_policy_url(),'requests'=>array_map(fn($r)=>$this->view($r),$this->requests->own($actor,$u->user_email))]; } public function submit(array $input,int $actor):array { BookingValidator::fields($input,['type','confirmed']);$type=CatalogValidator::choice($input['type']??null,'type',['export','erase']); if($type==='erase'&&($input['confirmed']??false)!==true)throw new CatalogException('abece_booking_privacy_confirmation',__('Confirm that you understand how the erasure request will be reviewed.','abece-booking'),400); $u=$this->account($actor);return $this->requests->exclusive($actor,function()use($u,$actor,$type){ $action=$type==='export'?'export_personal_data':'remove_personal_data'; foreach($this->requests->own($actor,$u->user_email) as $r)if($r->action_name===$action&&in_array($r->status,['request-pending','request-confirmed'],true))return ['request'=>$this->view($r),'duplicate'=>true]; $this->requests->consumeRate($actor,$type); $id=wp_create_user_request($u->user_email,$action,['abece_booking_self_service'=>true,'abece_booking_account_closure_requested'=>$type==='erase']); if(is_wp_error($id))throw new CatalogException('abece_booking_privacy_request_failed',__('The request could not be created. Check your existing requests or contact the operator.','abece-booking'),409); $ambiguous=$this->identities->resolve($u->user_email,(int)$id)['ambiguous']; update_post_meta($id,'abece_booking_privacy_request_owner',$actor); if($ambiguous)update_post_meta($id,'abece_booking_privacy_review_required',true); $sent=wp_send_user_request($id); if(is_wp_error($sent)||!$sent){update_post_meta($id,'abece_booking_privacy_mail_failed',true);wp_update_post(['ID'=>$id,'post_status'=>'request-failed']);} // Never confirm, export, erase, cancel a booking or delete an account here. return ['request'=>$this->view(wp_get_user_request($id)),'duplicate'=>false]; }); } private function view(\WP_User_Request $r):array { $state=match($r->status){'request-pending'=>'awaiting_email','request-confirmed'=>'awaiting_processing','request-completed'=>'completed',default=>'review_required'}; if(get_post_meta($r->ID,'abece_booking_privacy_mail_failed',true)||get_post_meta($r->ID,'abece_booking_privacy_review_required',true)||get_post_meta($r->ID,'abece_booking_privacy_retained',true))$state='review_required'; if($r->action_name==='remove_personal_data'&&$r->status==='request-completed')$state='review_required'; // Native erasure completion does not close the WordPress account. return ['account_closure_requested'=>(bool)($r->request_data['abece_booking_account_closure_requested']??false),'requires_email'=>$r->status==='request-pending'&&!get_post_meta($r->ID,'abece_booking_privacy_mail_failed',true),'id'=>(int)$r->ID,'type'=>$r->action_name==='export_personal_data'?'export':'erase','state'=>$state,'created_at'=>gmdate('c',(int)$r->created_timestamp)]; } }