8) { throw new \InvalidArgumentException('Display rules need AND/OR groups, at most eight groups.'); } $groups=[]; foreach ($raw['groups'] as $group) { if (!is_array($group) || !in_array($group['relation'] ?? '', ['AND','OR'], true) || !is_array($group['rules'] ?? null) || !array_is_list($group['rules']) || !$group['rules'] || count($group['rules']) > 8) { throw new \InvalidArgumentException('Each display group needs one to eight conditions.'); } $rules=[]; foreach ($group['rules'] as $rule) { if (!is_array($rule) || !in_array($rule['kind'] ?? '', ['role','term','field'], true) || !in_array($rule['operator'] ?? '', ['eq','ne','contains','gt','gte','lt','lte','empty','not-empty'], true)) { throw new \InvalidArgumentException('Unsupported display condition.'); } if (!is_string($rule['key'] ?? '') || !is_scalar($rule['value'] ?? '')) { throw new \InvalidArgumentException('Invalid display condition value.'); } $rules[]=['kind'=>$rule['kind'],'key'=>Document::text($rule['key'] ?? '',80),'operator'=>$rule['operator'],'value'=>Document::text((string)($rule['value'] ?? ''),300)]; } $groups[]=['relation'=>$group['relation'],'rules'=>$rules]; } return $groups ? ['relation'=>$raw['relation'],'groups'=>$groups] : []; } public static function compare($actual,string $operator,string $expected): bool { if (!is_scalar($actual) && $actual!==null) { return false; } $actual=is_bool($actual)?($actual?'1':'0'):(string)$actual; switch ($operator) { case 'eq': return $actual===$expected; case 'ne': return $actual!==$expected; case 'contains': return $expected!=='' && str_contains($actual,$expected); case 'empty': return $actual===''; case 'not-empty': return $actual!==''; case 'gt': case 'gte': case 'lt': case 'lte': if (!is_numeric($actual)||!is_numeric($expected)) { return false; } return match($operator) {'gt'=>(float)$actual>(float)$expected,'gte'=>(float)$actual>=(float)$expected,'lt'=>(float)$actual<(float)$expected,'lte'=>(float)$actual<=(float)$expected}; } return false; } private static function condition(array $rule,int $context): bool { $key=$rule['key'];$op=$rule['operator'];$value=$rule['value']; if ($rule['kind']==='role') { if (!in_array($op,['eq','ne'],true) || !isset(wp_roles()->roles[$value])) { return false; } $match=in_array($value,wp_get_current_user()->roles,true); return $op==='eq'?$match:!$match; } if ($rule['kind']==='term') { $tax=get_taxonomy($key); if (!$context || !$tax || !$tax->public || !in_array(get_post_type($context),$tax->object_type,true) || !ctype_digit($value) || !term_exists((int)$value,$key) || !in_array($op,['eq','ne'],true)) { return false; } $match=has_term((int)$value,$key,$context); return $op==='eq'?$match:!$match; } // Missing, deleted, protected or no-longer-public definitions fail closed, // including a "not equal" condition. Their value is never returned to UI. $field=$context?(QueryFields::catalog((string)get_post_type($context))[$key]??null):null; if (!$field) { return false; } $actual=DynamicData::public_meta($context,$key); if(in_array($field['type'],['number','integer'],true)&&in_array($op,['eq','ne'],true)){ if(!is_numeric($actual)||!is_numeric($value)){return false;} return $op==='eq'?(float)$actual===(float)$value:(float)$actual!==(float)$value; } return self::compare($actual,$op,$value); } public static function explain(array $style,int $context=0): array { $reasons=[];$audience=$style['audience']??'all'; foreach(['start','end'] as $key){ $value=$style[$key]??''; if($value!==''&&Document::date($value)===''){return ['visible'=>false,'reasons'=>['Display rules are invalid.'],'groups'=>[]];} } if (($audience==='members'&&!is_user_logged_in())||($audience==='guests'&&is_user_logged_in())) { $reasons[]='The audience condition is not met.'; } if (!empty($style['start'])&&time()=strtotime($style['end'])) { $reasons[]='The scheduled end has been reached.'; } try { $rules=self::clean($style['visibility']??[]); } catch (\InvalidArgumentException $e) { return ['visible'=>false,'reasons'=>['Display rules are invalid.'],'groups'=>[]]; } $groups=[]; foreach ($rules['groups']??[] as $group) { $results=array_map(static fn($rule)=>self::condition($rule,$context),$group['rules']); $groups[]=['matches'=>$group['relation']==='AND'?!in_array(false,$results,true):in_array(true,$results,true),'conditions'=>$results]; } if ($groups) { $matches=array_column($groups,'matches'); if (!(($rules['relation']==='AND')?!in_array(false,$matches,true):in_array(true,$matches,true))) { $reasons[]='The display conditions are not met.'; } } return ['visible'=>!$reasons,'reasons'=>$reasons,'groups'=>$groups]; } public static function visible(array $style,int $context=0): bool { return self::explain($style,$context)['visible']; } }