['error','observed','A fatal error was recorded. Reproduce on staging using the component and line; check server logs locally.'], 'memory'=>['error','observed','Memory exhaustion was recorded. Check the reported PHP limit and document size on staging.'], 'rest'=>['error','observed','Builder requests returned a server error. Correlate event times with the local PHP and database logs.'], 'revision'=>['notice','observed','Concurrent editing was detected. Reload the latest draft before publishing; do not overwrite a stale revision.'], 'extension'=>['error','observed','A module renderer or cache adapter failed. Use the recorded component and line to reproduce on staging; consult the local server log for the original message.'], 'script'=>['warning','observed','An editor script failed. Reproduce in a private browser window, then test script optimization exclusions on staging.'], 'external'=>['warning','possible','An error in an external component was observed during a Builder request. This is correlation, not proof of a conflict. Isolate that component on staging.'], 'optimizer'=>['notice','possible','A script or cache optimizer is active. If the editor fails, test its exclusion settings on staging. Its presence alone is not a conflict.'], 'cron'=>['notice','observed','WordPress traffic-triggered cron is disabled. Confirm that a server cron runs the WordPress queue.'], 'uploads'=>['warning','observed','The uploads directory is not writable. Check permissions; CSS may use inline fallback and asset installation may fail.'], 'theme'=>['notice','observed','The theme has no dedicated region adapter. Verify header and footer behavior; content editing remains available.'], 'no_events'=>['notice','observed','No recent captured errors. This does not certify compatibility or exclude errors that occurred before logging was available.'], ]; public static function slug($value, int $max=100): string { return is_string($value) && strlen($value)<=$max && preg_match('~^[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)*$~D',$value) && !str_contains($value,'..') ? $value : 'unknown'; } public static function version($value): string { return is_scalar($value)&&preg_match('/^[0-9][a-zA-Z0-9.+_-]{0,39}$/D',(string)$value)?(string)$value:'unknown'; } public static function site($value): string { if(!is_string($value)||strlen($value)>250)return ''; $p=wp_parse_url($value); if(!$p||!in_array($p['scheme']??'', ['http','https'],true)||empty($p['host'])||isset($p['user'])||isset($p['pass'])||isset($p['query'])||isset($p['fragment'])||!preg_match('/^[a-z0-9.-]+$/iD',$p['host']))return ''; $path=rtrim($p['path']??'','/'); if(!preg_match('#^(?:/[a-zA-Z0-9._~-]+)*$#D',$path)||str_contains($path,'..'))return ''; return strtolower($p['scheme'].'://'.$p['host']).(isset($p['port'])?':'.(int)$p['port']:'').$path; } public static function event($raw): ?array { if(!is_array($raw)||!in_array($raw['code']??'',self::EVENT_CODES,true))return null; $kind=in_array($raw['kind']??'',['php','javascript','rest','integration'],true)?$raw['kind']:'php'; $context=in_array($raw['context']??'',['editor','public','rest','unknown'],true)?$raw['context']:'unknown'; return ['kind'=>$kind,'code'=>$raw['code'],'component'=>self::slug($raw['component']??'unknown',180),'line'=>max(0,min(1000000,(int)($raw['line']??0))), 'context'=>$context,'status'=>max(0,min(599,(int)($raw['status']??0))),'count'=>max(1,min(1000000,(int)($raw['count']??1))), 'first_seen'=>max(0,min(time()+300,(int)($raw['first_seen']??time()))),'last_seen'=>max(0,min(time()+300,(int)($raw['last_seen']??time())))]; } public static function clean($raw) { if(!is_array($raw)||($raw['schema']??null)!==1||($raw['product']??'')!=='digiter-builder-lite'||!is_string($raw['report_id']??null)||!preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/D',$raw['report_id'])||!($site=self::site($raw['site']??'')))return new \WP_Error('dgr_schema','Invalid diagnostic report.',['status'=>400]); if(!is_array($raw['environment']??null)||!is_array($raw['events']??null)||count($raw['events'])>100)return new \WP_Error('dgr_schema','Invalid diagnostic fields.',['status'=>400]); $e=$raw['environment'];$env=[];foreach(['builder','wordpress','php','db_version'] as $k)$env[$k]=self::version($e[$k]??''); $env['db_engine']=in_array($e['db_engine']??'',['mysql','sqlite','unknown'],true)?$e['db_engine']:'unknown'; $env['memory_limit_mb']=max(-1,min(65536,(int)($e['memory_limit_mb']??0))); $env['theme']=['slug'=>self::slug($e['theme']['slug']??''),'version'=>self::version($e['theme']['version']??'')]; $env['plugins']=[];foreach(array_slice(is_array($e['plugins']??null)?$e['plugins']:[],0,150) as $plugin){if(is_array($plugin))$env['plugins'][]=['slug'=>self::slug($plugin['slug']??''),'version'=>self::version($plugin['version']??'')];} $env['flags']=[];foreach(['multisite','object_cache','cron_disabled','uploads_writable','theme_regions_supported'] as $k)$env['flags'][$k]=($e['flags'][$k]??false)===true; $env['extensions']=[];foreach(['curl','mbstring','zip','fileinfo','gd','imagick'] as $k)$env['extensions'][$k]=($e['extensions'][$k]??false)===true; $events=[];foreach($raw['events'] as $event){$v=self::event($event);if(!$v)return new \WP_Error('dgr_event','Invalid diagnostic event.',['status'=>400]);$events[]=$v;} return ['schema'=>1,'product'=>'digiter-builder-lite','report_id'=>$raw['report_id'],'created_at'=>max(0,min(time()+300,(int)($raw['created_at']??time()))),'site'=>$site,'environment'=>$env,'events'=>$events,'findings'=>self::analyze($env,$events)]; } public static function analyze(array $env,array $events): array { $codes=[]; foreach($events as $e){ if($e['code']==='php_fatal')$codes['fatal']=true; if($e['code']==='memory_exhausted')$codes['memory']=true; if($e['code']==='rest_server_error')$codes['rest']=true; if($e['code']==='revision_conflict')$codes['revision']=true; if(in_array($e['code'],['cache_adapter_error','module_render_error'],true))$codes['extension']=true; if($e['kind']==='javascript')$codes['script']=true; if(str_starts_with($e['component'],'plugins/')&&!str_starts_with($e['component'],'plugins/digiter-builder-lite/'))$codes['external']=true; if(str_starts_with($e['component'],'themes/'))$codes['external']=true; } foreach($env['plugins'] as $p)if(preg_match('~^(autoptimize|wp-rocket|litespeed-cache|w3-total-cache|breeze)/~',$p['slug']))$codes['optimizer']=true; if($env['flags']['cron_disabled'])$codes['cron']=true; if(!$env['flags']['uploads_writable'])$codes['uploads']=true; if(!$env['flags']['theme_regions_supported'])$codes['theme']=true; if(!$events)$codes['no_events']=true; $out=[];foreach(array_keys($codes) as $code){$f=self::FINDINGS[$code];$out[]=['code'=>$code,'severity'=>$f[0],'confidence'=>$f[1],'recommendation'=>$f[2]];}return $out; } /** Compare-and-delete stale locks, with token-aware release. Fail closed when busy. */ public static function lock(string $name,int $ttl=60): ?array { $value=['time'=>time(),'token'=>wp_generate_uuid4()]; if(add_option($name,$value,'',false))return $value; $old=get_option($name);if(!is_array($old)||($old['time']??0)>=time()-$ttl)return null; self::unlock($name,$old); return add_option($name,$value,'',false)?$value:null; } public static function unlock(string $name,array $value): void { global $wpdb;$wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name=%s AND option_value=%s",$name,maybe_serialize($value)));wp_cache_delete($name,'options'); } } }