1,'title'=>'','excerpt'=>'','settings'=>['contentLanguage'=>'','metaDescription'=>'','tone'=>'sky','font'=>'editorial','width'=>1120,'canvas'=>true,'pattern'=>'','skin'=>'classic','palette'=>'sky','accent'=>'','paper'=>'','bodySize'=>0,'lineHeight'=>0,'headingFont'=>'inherit','bodyFont'=>0,'titleFont'=>0,'mobileBreakpoint'=>600,'tabletBreakpoint'=>900,'appearance'=>Appearance::defaults(),'buttonStyle'=>Appearance::button_defaults(),'audio'=>self::audio([]),'component'=>null], 'nodes'=>[]]; } public static function clean($input) { if (!is_array($input) || 1 !== ($input['schema'] ?? null)) { return new \WP_Error('digibl_schema', 'Unsupported document format.', ['status'=>400]); } if (strlen(wp_json_encode($input)) > 750000) { return new \WP_Error('digibl_size', 'This document is larger than the 750 KB limit.', ['status'=>413]); } $d = self::blank(); $d['title'] = self::text($input['title'] ?? '', 300); $d['excerpt'] = self::text($input['excerpt'] ?? '', 3000); $s = is_array($input['settings'] ?? null) ? $input['settings'] : []; $d['settings'] = [ 'contentLanguage'=>StudioTools::language($s['contentLanguage']??''),'metaDescription'=>self::text($s['metaDescription']??'',300), 'bodyFont'=>absint($s['bodyFont']??0),'titleFont'=>absint($s['titleFont']??0), 'mobileBreakpoint'=>self::number($s['mobileBreakpoint']??600,320,900),'tabletBreakpoint'=>max(self::number($s['mobileBreakpoint']??600,320,900)+100,self::number($s['tabletBreakpoint']??900,480,1600)), 'tone'=>self::choice($s['tone'] ?? '', ['sky','lavender','mint','rose','sand','ink'], 'sky'), 'font'=>self::choice($s['font'] ?? '', ['editorial','modern'], 'editorial'), 'width'=>self::number($s['width'] ?? 1120, 640, 1440), 'canvas'=>!empty($s['canvas']), 'pattern'=>sanitize_key(is_scalar($s['pattern']??null)?substr((string)$s['pattern'],0,80):''), 'skin'=>self::choice($s['skin']??'', ['classic','studio','press'],'classic'), // Old documents retain their exact legacy appearance until a palette is chosen. 'palette'=>self::choice($s['palette']??'', ['legacy','sky','mint','lavender','peach','sunshine'],'legacy'), 'accent'=>self::color($s['accent']??''), 'paper'=>self::color($s['paper']??''), 'bodySize'=>self::number($s['bodySize']??0,0,32), 'lineHeight'=>self::number($s['lineHeight']??0,0,3), 'headingFont'=>self::choice($s['headingFont']??'', ['inherit','serif','sans-serif','monospace'],'inherit'), 'appearance'=>Appearance::clean($s['appearance']??[]), 'buttonStyle'=>Appearance::clean_button($s['buttonStyle']??[]), 'audio'=>self::audio($s['audio']??[]), ]; if (!isset($input['nodes']) || !is_array($input['nodes'])) { return new \WP_Error('digibl_nodes', 'The document needs a module list.', ['status'=>400]); } $count = 0; $ids = []; try { $d['nodes'] = self::nodes($input['nodes'], 0, $count, $ids); $component=Components::definition($s['component']??null,$d['nodes']);if($component!==null){$d['settings']['component']=$component;} } catch (\InvalidArgumentException $e) { return new \WP_Error('digibl_nodes', $e->getMessage(), ['status'=>400]); } if(strlen((string)wp_json_encode($d))>750000){return new \WP_Error('digibl_size','The migrated document is larger than the 750 KB limit.',['status'=>413]);} return $d; } /** Scope a rendered instance without breaking its internal fragment links. */ public static function scope_nodes(array $nodes,string $prefix,int $length=20,bool $source=true): array { $map=[]; $rename=static function(&$list)use(&$rename,&$map,$prefix,$length,$source){ foreach($list as &$node){ $old=$node['id'];if($source){$node['_sourceId']=$old;} $node['id']=$prefix.substr(hash('sha256',$old),0,$length);$map[$old]=$node['id']; $rename($node['children']); } }; $rename($nodes); $links=static function(&$value)use(&$links,$map){ if(!is_array($value)){return;} foreach($value as &$item){ if(is_string($item)&&preg_match('/^#dbs-([A-Za-z0-9_-]+)$/D',$item,$match)&&isset($map[$match[1]])){$item='#dbs-'.$map[$match[1]];} elseif(is_array($item)){$links($item);} } }; $scan=static function(&$list)use(&$scan,$links){foreach($list as &$node){$links($node['props']);$scan($node['children']);}}; $scan($nodes);return $nodes; } private static function nodes(array $nodes, int $depth, int &$count, array &$ids): array { if ($depth > 8) { throw new \InvalidArgumentException('Use no more than eight nested sections.'); } $result=[]; $catalog=self::modules(); foreach ($nodes as $n) { if (++$count > 300) { throw new \InvalidArgumentException('The document supports up to 300 modules.'); } if (!is_array($n) || !is_string($n['type'] ?? null) || !isset($catalog[$n['type']])) { throw new \InvalidArgumentException('An unsupported module was found. Import was stopped without saving.'); } $type=$n['type'];if(class_exists(Modules::class)&&Modules::has($type)){$n=Modules::migrate($n);} $id=self::text($n['id'] ?? '', 80); if (!preg_match('/^[a-zA-Z0-9_-]{1,80}$/D', $id) || isset($ids[$id])) { throw new \InvalidArgumentException('Module identifiers must be unique.'); } $ids[$id]=true; $props=[]; $raw=is_array($n['props'] ?? null) ? $n['props'] : []; foreach ($catalog[$type]['fields'] as $field) { $props[$field['key']]=self::field($raw[$field['key']] ?? $field['default'], $field); } if(class_exists(Modules::class)&&Modules::has($type)){Modules::validate($type,$props);} // 1.2.8: preserve a real CTA from older documents while removing URL-less placeholder buttons. if(in_array($type,['callout','pricing'],true) && !array_key_exists('action',$raw) && !empty($props['url'])){$props['action']='yes';} if($type==='form'){$step=1;$files=0;foreach($props['fields'] as $i=>&$field){$field['step']=$step=max($step,(int)$field['step']);if((int)$field['conditionField']>$i){throw new \InvalidArgumentException('A form condition must refer to an earlier field number.');}if($field['type']==='file'&&++$files>3){throw new \InvalidArgumentException('A form can contain at most three file fields.');}}unset($field);} if($type==='quiz'){ $props['questions']=array_slice($props['questions'],0,20); foreach($props['questions'] as &$question){ $options=array_values(array_filter(array_map('trim',preg_split('/\r?\n/',(string)$question['options'])),static fn($v)=>$v!=='')); $question['options']=implode("\n",array_slice($options,0,12)); $question['correct']=(int)self::number($question['correct']??1,1,max(1,min(12,count($options)))); $question['points']=(int)self::number($question['points']??1,1,20); }unset($question); } if($type==='poll'){ $props['options']=array_slice(array_values(array_filter($props['options'],static fn($option)=>trim((string)($option['label']??''))!=='')),0,12); $optionIds=[]; foreach($props['options'] as &$option){ $optionId=(string)($option['id']??''); if(!preg_match('/^[a-z0-9_-]{8,64}$/D',$optionId)||isset($optionIds[$optionId])){ do{$optionId='o_'.str_replace('-','',wp_generate_uuid4());}while(isset($optionIds[$optionId])); } $option['id']=$optionId;$optionIds[$optionId]=true; }unset($option); $props['closes']=self::date($props['closes']??''); $props['resultSet']=(int)self::number($props['resultSet']??1,1,999999); } $s=is_array($n['style'] ?? null) ? $n['style'] : []; $style=['globalStyle'=>is_string($s['globalStyle']??null)&&preg_match('/^[a-f0-9-]{36}$/D',$s['globalStyle'])?$s['globalStyle']:'','globalOverrides'=>class_exists(GlobalStyles::class)?array_values(array_intersect(GlobalStyles::keys(),is_array($s['globalOverrides']??null)?array_filter($s['globalOverrides'],'is_string'):[])):[],'align'=>self::choice($s['align'] ?? '', ['left','center','right','justify'], 'left'), 'space'=>self::number($s['space'] ?? 24,0,240), 'hideMobile'=>!empty($s['hideMobile']), 'hideDesktop'=>!empty($s['hideDesktop']), 'customSpacing'=>!empty($s['customSpacing']), 'background'=>self::color($s['background']??''), 'textColor'=>self::color($s['textColor']??''), 'borderColor'=>self::color($s['borderColor']??''), 'backgroundToken'=>self::choice($s['backgroundToken']??'', ['','accent','ink','surface','soft','border','highlight'],''), 'textColorToken'=>self::choice($s['textColorToken']??'', ['','accent','ink','surface','soft','border','highlight'],''), 'borderColorToken'=>self::choice($s['borderColorToken']??'', ['','accent','ink','surface','soft','border','highlight'],''), 'borderWidth'=>self::number($s['borderWidth']??0,0,12), 'radius'=>self::number($s['radius']??0,0,100), 'shadow'=>self::choice($s['shadow']??'', ['none','soft','strong'],'none'), 'paddingTop'=>self::number($s['paddingTop']??0,0,200), 'paddingRight'=>self::number($s['paddingRight']??0,0,200), 'paddingBottom'=>self::number($s['paddingBottom']??0,0,200), 'paddingLeft'=>self::number($s['paddingLeft']??0,0,200), 'layoutOverrides'=>array_values(array_intersect(['paddingTop','paddingRight','paddingBottom','paddingLeft'],is_array($s['layoutOverrides']??null)?array_filter($s['layoutOverrides'],'is_string'):[])), 'hideTablet'=>!empty($s['hideTablet']), 'alignTablet'=>self::choice($s['alignTablet']??'', ['inherit','left','center','right','justify'],'inherit'), 'alignMobile'=>self::choice($s['alignMobile']??'', ['inherit','left','center','right','justify'],'inherit'), 'marginTop'=>self::number($s['marginTop']??0,-200,400), 'maxWidth'=>self::number($s['maxWidth']??0,0,2000), 'minHeight'=>self::number($s['minHeight']??0,0,1500), 'opacity'=>self::number($s['opacity']??100,0,100), 'rotate'=>self::number($s['rotate']??0,-180,180), 'hover'=>self::choice($s['hover']??'', ['none','lift','grow','shadow'],'none'), 'animation'=>self::choice($s['animation']??'', ['none','fade','slide-up','slide-left','zoom'],'none'), 'duration'=>self::number($s['duration']??600,100,3000), 'delay'=>self::number($s['delay']??0,0,3000), 'sticky'=>!empty($s['sticky']), 'stickyTop'=>self::number($s['stickyTop']??20,0,300), 'zIndex'=>self::number($s['zIndex']??0,0,100), 'visibility'=>DisplayRules::clean($s['visibility']??[]), 'audience'=>self::choice($s['audience']??'', ['all','guests','members'],'all'), 'start'=>self::date($s['start']??''), 'end'=>self::date($s['end']??''), 'label'=>self::text($s['label']??'',120), 'cssClass'=>implode(' ',array_slice(array_filter(array_map('sanitize_html_class',preg_split('/\s+/',self::text($s['cssClass']??'',300)))),0,8)), 'customCSS'=>safecss_filter_attr(preg_replace('/[<>]/','',self::text($s['customCSS']??'',3000))), 'fontDesktop'=>self::number($s['fontDesktop']??0,0,120), 'fontTablet'=>self::number($s['fontTablet']??0,0,120), 'fontMobile'=>self::number($s['fontMobile']??0,0,120), 'typography'=>NodeTypography::clean($s['typography']??[]), 'fieldTypography'=>FieldTypography::clean($s['fieldTypography']??[],$type,$props), 'alignExplicit'=>!empty($s['alignExplicit']), 'cardSurface'=>!empty($s['cardSurface']), 'appearance'=>Appearance::clean($s['appearance']??[]), 'buttonStyle'=>Appearance::clean_button($s['buttonStyle']??[]), 'audio'=>self::audio($s['audio']??[]), ]; foreach(['Tablet','Mobile'] as $device){foreach(['Top','Right','Bottom','Left'] as $side){$key='padding'.$side.$device;$style[$key]=self::number($s[$key]??-1,-1,200);}$key='space'.$device;$style[$key]=self::number($s[$key]??-1,-1,240);} $style=array_merge($style,Grid::clean_style($s));if(class_exists(Flex::class)){$style=array_merge($style,Flex::clean_style($s));} $children=[]; if (!empty($catalog[$type]['container'])) { $children=self::nodes(is_array($n['children'] ?? null) ? $n['children'] : [], $depth+1, $count, $ids); } if('section'===$type && 'columns'===$props['structure']){ if(count($children)!==(int)$props['columns']){throw new \InvalidArgumentException('A row needs one column container per configured column.');} foreach($children as $child){if('column'!==$child['type']){throw new \InvalidArgumentException('Place row contents inside its columns.');}} $props['widths']=self::widths($props['widths'],(int)$props['columns']); } $slot=is_array($n['slot']??null)?$n['slot']:[]; $required=array_values(array_intersect(array_filter(is_array($slot['required']??null)?$slot['required']:[], 'is_string'),array_keys($props))); $result[]=['id'=>$id,'type'=>$type,'bindings'=>DynamicData::clean($n['bindings']??[],$type),'props'=>$props,'style'=>$style,'children'=>$children,'slot'=>['label'=>self::text($slot['label']??'',120),'required'=>array_values(array_unique($required))]]; if(isset($catalog[$type]['schemaVersion'])){$result[array_key_last($result)]['moduleVersion']=$catalog[$type]['schemaVersion'];} } return $result; } public static function clean_field($v,array $f) { return self::field($v,$f); } private static function field($v, array $f) { if (!empty($f['iconPicker'])) { return Icons::clean($v,(string)$f['default'],$f['type']==='text'); } switch ($f['type']) { case 'query-filters': return QueryFields::clean($v); case 'grid-tracks': return Grid::tracks($v); case 'component-values': return Components::values($v); case 'animation': return self::animation($v); case 'rich': return wp_kses_post(is_scalar($v) ? self::cut((string)$v,100000) : ''); case 'image': return self::image($v); case 'images': return array_map([self::class,'image'], array_slice(is_array($v)?$v:[],0,40)); case 'audio': return self::audio($v); case 'url': return self::link($v); case 'number': return self::number($v,$f['min'],$f['max']); case 'category': return absint($v); case 'color': return self::color($v); case 'select': return self::choice($v,$f['options'],$f['default']); case 'widths': return self::text($v,180); case 'rows': $out=[]; foreach (array_slice(is_array($v)?$v:[],0,60) as $row) { if (!is_array($row)) { continue; } $item=[]; foreach ($f['columns'] as $c) { $item[$c['key']]=self::field($row[$c['key']] ?? $c['default'],$c); } $out[]=$item; } return $out; case 'textarea': $text=is_scalar($v)?self::cut((string)$v,100000):''; if ('html'===$f['key']) { return wp_kses_post($text); } if ('code'===$f['key']) { return wp_check_invalid_utf8($text); } return sanitize_textarea_field($text); default: return self::text($v,5000); } } public static function animation($value): string { if(!is_string($value)||strlen($value)>200000){return '';}$data=json_decode($value,true,48); if(!is_array($data)||!isset($data['layers'],$data['w'],$data['h'],$data['ip'],$data['op'],$data['fr'])||!is_array($data['layers'])){return '';} foreach(['w','h','ip','op','fr'] as $key){if(!is_numeric($data[$key])||!is_finite((float)$data[$key])){return '';}} if($data['w']<1||$data['w']>4000||$data['h']<1||$data['h']>4000||$data['fr']<1||$data['fr']>120||$data['op']<=$data['ip']||$data['op']-$data['ip']>36000){return '';} foreach($data['assets']??[] as $asset){if(!is_array($asset)||isset($asset['p'])||isset($asset['u'])){return '';}} if(!empty($data['fonts'])){return '';} $count=0;$clean=function(&$node,$depth=0)use(&$clean,&$count){if(++$count>10000||$depth>40){throw new \InvalidArgumentException('Animation is too complex.');}if(!is_array($node)){return;}foreach($node as $key=>&$v){if($key==='x'&&is_string($v)){unset($node[$key]);continue;}if(is_array($v)){$clean($v,$depth+1);}}}; try{$clean($data);}catch(\InvalidArgumentException $e){return '';} return wp_json_encode($data,JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT); } public static function image($v): array { $v=is_array($v)?$v:[]; $id=absint($v['id'] ?? 0); if ($id && !wp_attachment_is_image($id)) { $id=0; } $url=self::url(self::asset_url(is_string($v['url']??null)?$v['url']:'')); if ($id) { $url=wp_get_attachment_url($id) ?: ''; } return ['id'=>$id,'url'=>$url,'alt'=>self::text($v['alt'] ?? '',1000),'caption'=>self::text($v['caption'] ?? '',2000),'ratio'=>self::choice($v['ratio'] ?? '',['21/9','16/9','3/2','4/3','1/1','3/4','2/3','9/16','auto'],'16/9'),'fit'=>self::choice($v['fit'] ?? '',['cover','contain'],'cover'),'x'=>self::number($v['x'] ?? 50,0,100),'y'=>self::number($v['y'] ?? 50,0,100),'sizeMode'=>self::choice($v['sizeMode'] ?? '',['inherit','card','original','custom'],'inherit'),'width'=>self::number($v['width'] ?? 100,10,100),'maxWidth'=>self::number($v['maxWidth'] ?? 0,0,2400),'frameHeight'=>self::number($v['frameHeight'] ?? 0,0,1600),'align'=>self::choice($v['align'] ?? '',['left','center','right'],'center'),'zoom'=>self::number($v['zoom'] ?? 100,50,250)]; } public static function filled($v): bool { if(is_array($v)){ if(array_key_exists('url',$v) && array_key_exists('ratio',$v)){return !empty($v['url']);} foreach($v as $value){if(self::filled($value)){return true;}}return false; } return is_scalar($v) && ''!==trim(html_entity_decode(wp_strip_all_tags((string)$v),ENT_QUOTES,'UTF-8')," \t\n\r\0\x0B\xC2\xA0"); } public static function missing(array $doc): array { $missing=[];$catalog=self::modules(); $scan=function($nodes)use(&$scan,&$missing,$catalog){foreach($nodes as $n){foreach(($n['slot']['required']??[]) as $key){if(!self::filled($n['props'][$key]??'')){ $field=array_values(array_filter($catalog[$n['type']]['fields'],static fn($f)=>$f['key']===$key)); $missing[]=['id'=>$n['id'],'field'=>$key,'label'=>($n['slot']['label']?:$catalog[$n['type']]['label']).' / '.($field[0]['label']??$key)]; }}$scan($n['children']);}};$scan($doc['nodes']);return $missing; } private static function cut(string $s,int $max): string { return function_exists('mb_substr')?mb_substr($s,0,$max):substr($s,0,$max); } public static function text($v,int $max): string { return sanitize_text_field(is_scalar($v)?self::cut((string)$v,$max):''); } public static function number($v,$min,$max) { $n=is_numeric($v)?(float)$v:$min;return max($min,min($max,is_finite($n)?$n:$min)); } public static function color($v): string { return is_string($v) && preg_match('/^#(?:[a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/iD',$v)?strtolower($v):''; } /** * Local Web Audio settings. The recipe is deliberately allowlisted and * bounded so an imported document can never inject executable code or an * unbounded audio payload. A URL is optional: generated audio can remain * procedural, while an administrator may save a rendered WAV in Media. */ public static function audio_defaults(): array { return ['version'=>1,'enabled'=>'no','preset'=>'click','url'=>'','id'=>0,'name'=>'','volume'=>55,'cooldown'=>180,'repeat'=>'always','recipe'=>[ 'version'=>1,'preset'=>'click','duration'=>0.12,'waveform'=>'square','waveform2'=>'none','frequency'=>1150,'frequencyEnd'=>780, 'attack'=>0.002,'decay'=>0.08,'sustain'=>0.2,'release'=>0.04,'noise'=>0,'noiseType'=>'white','noiseFrequency'=>1800, 'filter'=>'none','filterFrequency'=>4000,'resonance'=>1,'reverb'=>0,'delay'=>0,'pan'=>0,'volume'=>55,'loop'=>false, ]]; } public static function audio($value): array { $base=self::audio_defaults();$raw=is_array($value)?$value:[];$out=$base; $out['version']=1;$out['override']=self::choice($raw['override']??'no',['no','yes'],'no'); $out['enabled']=self::choice($raw['enabled']??'no',['no','yes'],'no'); $out['preset']=self::choice($raw['preset']??'click',['custom','click','pop','chime','whoosh','glass-break','explosion','weapon','whistle','bird','scream','cry','yes','no'],'click'); $out['url']=self::url($raw['url']??'');$out['id']=absint($raw['id']??0);$out['name']=self::text($raw['name']??'',120); $out['volume']=(int)self::number($raw['volume']??55,0,100);$out['cooldown']=(int)self::number($raw['cooldown']??180,0,2000); $out['repeat']=self::choice($raw['repeat']??'always',['always','once'],'always'); $clip=is_array($raw['clip']??null)?$raw['clip']:[]; $out['clip']=['start'=>self::number($clip['start']??0,0,30),'end'=>self::number($clip['end']??0,0,30),'rate'=>self::number($clip['rate']??1,.5,2),'fadeIn'=>self::number($clip['fadeIn']??0,0,3),'fadeOut'=>self::number($clip['fadeOut']??.02,0,3)]; $out['sample']=self::text($raw['sample']??'',120); $recipe=is_array($raw['recipe']??null)?$raw['recipe']:[];$r=$base['recipe']; $r['version']=1;$r['preset']=self::choice($recipe['preset']??$out['preset'],['custom','click','pop','chime','whoosh','glass-break','explosion','weapon','whistle','bird','scream','cry','yes','no'],'custom'); $r['duration']=self::number($recipe['duration']??0.12,0.02,4);$r['waveform']=self::choice($recipe['waveform']??'square',['sine','triangle','sawtooth','square'],'square');$r['waveform2']=self::choice($recipe['waveform2']??'none',['none','sine','triangle','sawtooth','square'],'none'); $r['frequency']=self::number($recipe['frequency']??1150,20,12000);$r['frequencyEnd']=self::number($recipe['frequencyEnd']??780,20,12000); $r['attack']=self::number($recipe['attack']??0.002,0,1);$r['decay']=self::number($recipe['decay']??0.08,0,4);$r['sustain']=self::number($recipe['sustain']??0.2,0,1);$r['release']=self::number($recipe['release']??0.04,0,2); $r['noise']=self::number($recipe['noise']??0,0,1);$r['noiseType']=self::choice($recipe['noiseType']??'white',['white','pink','brown'],'white');$r['noiseFrequency']=self::number($recipe['noiseFrequency']??1800,20,18000); $r['filter']=self::choice($recipe['filter']??'none',['none','lowpass','highpass','bandpass','notch'],'none');$r['filterFrequency']=self::number($recipe['filterFrequency']??4000,20,20000);$r['resonance']=self::number($recipe['resonance']??1,0,30); $r['reverb']=self::number($recipe['reverb']??0,0,1);$r['delay']=self::number($recipe['delay']??0,0,1);$r['pan']=self::number($recipe['pan']??0,-1,1);$r['volume']=self::number($recipe['volume']??$out['volume'],0,100);$r['loop']=!empty($recipe['loop']); $out['recipe']=$r;return $out; } public static function widths($v,int $count): string { $count=max(1,min(6,$count));$parts=is_string($v)?explode(',',$v):[]; if(count($parts)!==$count || count(array_filter($parts,static fn($x)=>is_numeric($x) && (float)$x>0))!==$count){$parts=array_fill(0,$count,1);} $parts=array_map(static fn($x)=>max(1,min(100,(float)$x)),$parts);$total=array_sum($parts); return implode(',',array_map(static fn($x)=>(string)round($x*100/$total,4),$parts)); } public static function choice($v,array $options,$default) { return is_scalar($v) && in_array((string)$v,$options,true)?(string)$v:$default; } public static function date($v): string { if(!is_string($v)||!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?Z$/D',$v)){return '';} $format=strlen($v)===17?'!Y-m-d\TH:i\Z':'!Y-m-d\TH:i:s\Z';$date=\DateTimeImmutable::createFromFormat($format,$v,new \DateTimeZone('UTC'));$errors=\DateTimeImmutable::getLastErrors(); return $date&&(!$errors||(!$errors['warning_count']&&!$errors['error_count']))?$date->format('Y-m-d\TH:i:s\Z'):''; } public static function link($v): string { return is_string($v)?esc_url_raw(self::cut($v,3000),['http','https','mailto','tel']):''; } public static function url($v): string { return is_string($v)?esc_url_raw(self::cut($v,3000),['http','https']):''; } }