\d+)/notes',[ ['methods'=>'GET','permission_callback'=>[$this,'permission'],'callback'=>[$this,'read']], ['methods'=>'POST','permission_callback'=>[$this,'permission'],'callback'=>[$this,'write']], ]);} public function permission($r): bool {$id=absint($r->get_url_params()['id']??0);return $id&¤t_user_can('edit_post',$id)&&!in_array(get_post_status($id),['trash','auto-draft'],true)&&(bool)get_post_meta($id,'_digibl_draft',true);} private function envelope(int $id): array {$data=json_decode(get_post_meta($id,'_digibl_notes',true),true);return is_array($data)?$data:['revision'=>'','notes'=>[]];} private function response(array $data): \WP_REST_Response {$r=new \WP_REST_Response($data);$r->header('Cache-Control','no-store, private');return $r;} public function read($r){return $this->response($this->envelope(absint($r->get_url_params()['id']??0)));} public function write($r){ $id=absint($r->get_url_params()['id']??0);$key='digibl_notes_lock_'.$id;$token=wp_generate_uuid4(); if(!add_option($key,['token'=>$token,'time'=>time()],'',false)){global $wpdb;$old=get_option($key);if(is_array($old)&&($old['time']??0)query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name=%s AND option_value=%s",$key,maybe_serialize($old)));wp_cache_delete($key,'options');}if(!add_option($key,['token'=>$token,'time'=>time()],'',false)){return new \WP_Error('digibl_notes_busy','Notes are being updated. Reload and try again.',['status'=>409]);}} try{wp_cache_delete($id,'post_meta');$data=$this->envelope($id);if(!hash_equals($data['revision'],(string)$r->get_param('revision'))){return new \WP_Error('digibl_notes_conflict','Notes changed in another session. Reload before saving.',['status'=>409]);} $action=$r->get_param('action');$noteId=Document::text($r->get_param('note'),80); if(in_array($action,['resolve','reopen','delete'],true)){$found=false;foreach($data['notes'] as $i=>&$note){if($note['id']!==$noteId){continue;}$found=true;if($action==='delete'){if($note['user']!==get_current_user_id()&&!current_user_can('manage_options')){return new \WP_Error('digibl_note_owner','Only the author or an administrator can delete this note.',['status'=>403]);}unset($data['notes'][$i]);}else{$note['resolved']=$action==='resolve';}$note['updated']=gmdate('c');break;}unset($note);if(!$found){return new \WP_Error('digibl_note_missing','This note no longer exists.',['status'=>404]);}} else{if(count($data['notes'])>=100){return new \WP_Error('digibl_notes_limit','Delete old notes before adding more.',['status'=>400]);}$message=Document::text($r->get_param('message'),3000);if(!$message){return new \WP_Error('digibl_note_empty','Write a note first.',['status'=>400]);}$node=Document::text($r->get_param('node'),80);$draft=json_decode(get_post_meta($id,'_digibl_draft',true),true);$ids=[];$scan=function($nodes)use(&$scan,&$ids){foreach($nodes as $n){$ids[]=$n['id'];$scan($n['children']??[]);}};$scan($draft['doc']['nodes']??[]);if($node&&!in_array($node,$ids,true)){return new \WP_Error('digibl_note_node','Save the module before attaching a note.',['status'=>400]);}$data['notes'][]=['id'=>wp_generate_uuid4(),'node'=>$node,'message'=>$message,'user'=>get_current_user_id(),'author'=>wp_get_current_user()->display_name,'date'=>gmdate('c'),'updated'=>gmdate('c'),'resolved'=>false];} $data['notes']=array_values($data['notes']);$data['revision']=wp_generate_uuid4();if(!update_post_meta($id,'_digibl_notes',wp_slash(wp_json_encode($data)))){return new \WP_Error('digibl_note_storage','The note could not be saved.',['status'=>500]);}return $this->response($data); }finally{$lock=get_option($key);if(is_array($lock)&&($lock['token']??'')===$token){delete_option($key);}} } }