<?php
|
namespace JVBase\utility;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
/**
|
* Validates configuration arrays for content types, taxonomies, and user roles
|
* Catches errors early in the registration process
|
*/
|
class Validator
|
{
|
private array $errors = [];
|
private array $warnings = [];
|
|
public function validateAll():array
|
{
|
$success = [];
|
$success['content'] = $this->validateContentConfig(JVB_CONTENT);
|
$success['terms'] = $this->validateTaxonomyConfig(JVB_TAXONOMY);
|
$success['user'] = $this->validateUserConfig(JVB_USER);
|
$success['crossReference'] = $this->validateCrossReferences(JVB_CONTENT, JVB_TAXONOMY, JVB_USER);
|
return $success;
|
}
|
/**
|
* Validate JVB_CONTENT configuration
|
*/
|
public function validateContentConfig(array $config): bool
|
{
|
$this->errors = [];
|
$this->warnings = [];
|
|
foreach ($config as $slug => $settings) {
|
$this->validateSlug($slug, 'content');
|
$this->validateContentSettings($slug, $settings);
|
}
|
|
if (!empty($this->errors)) {
|
error_log('Validation result: '.print_r($this->errors, true));
|
}
|
if (!empty($this->warnings)) {
|
error_log('Warnings: '.print_r($this->warnings, true));
|
}
|
|
return empty($this->errors);
|
}
|
|
/**
|
* Validate JVB_TAXONOMY configuration
|
*/
|
public function validateTaxonomyConfig(array $config): bool
|
{
|
$this->errors = [];
|
$this->warnings = [];
|
|
foreach ($config as $slug => $settings) {
|
$this->validateSlug($slug, 'taxonomy');
|
$this->validateTaxonomySettings($slug, $settings);
|
}
|
|
if (!empty($this->errors)) {
|
error_log('Validation result: '.print_r($this->errors, true));
|
}
|
if (!empty($this->warnings)) {
|
error_log('Warnings: '.print_r($this->warnings, true));
|
}
|
return empty($this->errors);
|
}
|
|
/**
|
* Validate JVB_USER configuration
|
*/
|
public function validateUserConfig(array $config): bool
|
{
|
$this->errors = [];
|
$this->warnings = [];
|
|
foreach ($config as $slug => $settings) {
|
$this->validateSlug($slug, 'user_role');
|
$this->validateUserSettings($slug, $settings);
|
}
|
|
if (!empty($this->errors)) {
|
error_log('Validation result: '.print_r($this->errors, true));
|
}
|
if (!empty($this->warnings)) {
|
error_log('Warnings: '.print_r($this->warnings, true));
|
}
|
return empty($this->errors);
|
}
|
|
/**
|
* Validate cross-references between configs
|
*/
|
public function validateCrossReferences(array $content, array $taxonomy, array $user): bool
|
{
|
$this->errors = [];
|
$this->warnings = [];
|
|
// Check taxonomy -> content references
|
foreach ($taxonomy as $taxSlug => $taxConfig) {
|
foreach ($taxConfig['for_content'] ?? [] as $contentType) {
|
if (!isset($content[$contentType])) {
|
$this->addError(
|
"taxonomy.{$taxSlug}.for_content",
|
"References non-existent content type '{$contentType}'"
|
);
|
}
|
}
|
|
// Check is_owned_by references
|
foreach ($taxConfig['is_owned_by'] ?? [] as $role) {
|
if (!isset($user[$role])) {
|
$this->addError(
|
"taxonomy.{$taxSlug}.is_owned_by",
|
"References non-existent user role '{$role}'"
|
);
|
}
|
}
|
}
|
|
// Check user -> content references
|
foreach ($user as $userSlug => $userConfig) {
|
// Check profile reference
|
if (isset($userConfig['profile']) && !isset($content[$userConfig['profile']])) {
|
$this->addError(
|
"user.{$userSlug}.profile",
|
"References non-existent content type '{$userConfig['profile']}'"
|
);
|
}
|
|
// Check can_create references
|
$this->validateCreatableContent($userSlug, $userConfig['can_create'] ?? [], $content, $taxonomy);
|
}
|
|
// Check field section references
|
foreach ($content as $contentSlug => $contentConfig) {
|
$this->validateFieldSections($contentSlug, $contentConfig, 'content');
|
}
|
|
if (!empty($this->errors)) {
|
error_log('Validation result: '.print_r($this->errors, true));
|
}
|
if (!empty($this->warnings)) {
|
error_log('Warnings: '.print_r($this->warnings, true));
|
}
|
|
return empty($this->errors);
|
}
|
|
/**
|
* Validate slug format
|
*/
|
private function validateSlug(string $slug, string $type): void
|
{
|
// Check for valid characters
|
if (!preg_match('/^[a-z0-9_-]+$/', $slug)) {
|
$this->addError(
|
"{$type}.{$slug}",
|
"Slug must contain only lowercase letters, numbers, hyphens, and underscores"
|
);
|
}
|
|
// Check length
|
if (strlen($slug) > 20) {
|
$this->addWarning(
|
"{$type}.{$slug}",
|
"Slug is longer than 20 characters, which may cause issues"
|
);
|
}
|
|
// Check for reserved WordPress terms
|
$reserved = ['post', 'page', 'attachment', 'revision', 'nav_menu_item', 'author', 'category', 'tag'];
|
if (in_array($slug, $reserved)) {
|
$this->addError(
|
"{$type}.{$slug}",
|
"Slug '{$slug}' is a reserved WordPress term"
|
);
|
}
|
}
|
|
/**
|
* Validate content type settings
|
*/
|
private function validateContentSettings(string $slug, array $settings): void
|
{
|
// Required fields
|
if (empty($settings['singular'])) {
|
$this->addError("content.{$slug}", "Missing required 'singular' label");
|
}
|
|
if (empty($settings['plural'])) {
|
$this->addError("content.{$slug}", "Missing required 'plural' label");
|
}
|
|
// Validate boolean flags
|
$booleanFields = [
|
'hide_single', 'show_feed', 'show_directory', 'karma',
|
'favouritable', 'responses', 'is_calendar', 'single_image'
|
];
|
|
foreach ($booleanFields as $field) {
|
if (isset($settings[$field]) && !is_bool($settings[$field])) {
|
$this->addError(
|
"content.{$slug}.{$field}",
|
"Field '{$field}' must be a boolean value"
|
);
|
}
|
}
|
|
// Validate fields configuration
|
if (isset($settings['fields'])) {
|
$this->validateFieldsConfig($slug, $settings['fields'], 'content');
|
}
|
|
// Validate sections if fields exist
|
if (!empty($settings['fields']) && !empty($settings['sections'])) {
|
$this->validateSectionsConfig($slug, $settings['sections']);
|
}
|
|
// Check for conflicting settings
|
if (($settings['hide_single'] ?? false) && ($settings['show_directory'] ?? false)) {
|
$this->addWarning(
|
"content.{$slug}",
|
"Content type has both 'hide_single' and 'show_directory' enabled"
|
);
|
}
|
}
|
|
/**
|
* Validate taxonomy settings
|
*/
|
private function validateTaxonomySettings(string $slug, array $settings): void
|
{
|
// Required fields
|
if (empty($settings['singular'])) {
|
$this->addError("taxonomy.{$slug}", "Missing required 'singular' label");
|
}
|
|
if (empty($settings['plural'])) {
|
$this->addError("taxonomy.{$slug}", "Missing required 'plural' label");
|
}
|
|
// Validate for_content
|
if (empty($settings['for_content']) || !is_array($settings['for_content'])) {
|
$this->addError(
|
"taxonomy.{$slug}",
|
"Missing or invalid 'for_content' array"
|
);
|
}
|
|
// Validate content taxonomy specific settings
|
if ($settings['is_content'] ?? false) {
|
if (empty($settings['content_table'])) {
|
$this->addWarning(
|
"taxonomy.{$slug}",
|
"Content taxonomy missing 'content_table' configuration"
|
);
|
}
|
}
|
|
// Validate ownership settings
|
if ($settings['is_ownable'] ?? false) {
|
if (empty($settings['is_owned_by'])) {
|
$this->addError(
|
"taxonomy.{$slug}",
|
"Ownable taxonomy missing 'is_owned_by' configuration"
|
);
|
}
|
}
|
}
|
|
/**
|
* Validate user role settings
|
*/
|
private function validateUserSettings(string $slug, array $settings): void
|
{
|
// Validate dashboard access
|
if ($settings['has_dashboard'] ?? false) {
|
if (empty($settings['can_create']) && empty($settings['profile'])) {
|
$this->addWarning(
|
"user.{$slug}",
|
"User has dashboard access but no content creation or profile"
|
);
|
}
|
}
|
|
// Validate registration fields
|
if ($settings['can_register'] ?? false) {
|
if (empty($settings['register_fields'])) {
|
$this->addWarning(
|
"user.{$slug}",
|
"User can register but has no registration fields defined"
|
);
|
}
|
}
|
|
// Validate profile consistency
|
if (!empty($settings['profile']) && empty($settings['has_dashboard'])) {
|
$this->addWarning(
|
"user.{$slug}",
|
"User has profile type but no dashboard access"
|
);
|
}
|
}
|
|
/**
|
* Validate fields configuration
|
*/
|
private function validateFieldsConfig(string $slug, array $fields, string $type): void
|
{
|
foreach ($fields as $fieldName => $fieldConfig) {
|
// Check for required field properties
|
if (empty($fieldConfig['type'])) {
|
$this->addError(
|
"{$type}.{$slug}.fields.{$fieldName}",
|
"Field missing required 'type' property"
|
);
|
}
|
|
// Validate field type
|
$validTypes = [
|
'text', 'textarea', 'number', 'email', 'url', 'select',
|
'radio', 'checkbox', 'true_false', 'date', 'time',
|
'datetime', 'color', 'image', 'file', 'gallery',
|
'repeater', 'location', 'user', 'taxonomy', 'set'
|
];
|
|
if (isset($fieldConfig['type']) && !in_array($fieldConfig['type'], $validTypes)) {
|
$this->addError(
|
"{$type}.{$slug}.fields.{$fieldName}",
|
"Invalid field type '{$fieldConfig['type']}'"
|
);
|
}
|
|
// Validate field-specific configurations
|
$this->validateFieldTypeConfig($fieldName, $fieldConfig, "{$type}.{$slug}");
|
}
|
}
|
|
/**
|
* Validate field type specific configuration
|
*/
|
private function validateFieldTypeConfig(string $fieldName, array $config, string $path): void
|
{
|
switch ($config['type'] ?? '') {
|
case 'select':
|
case 'radio':
|
case 'checkbox':
|
if (empty($config['options'])) {
|
$this->addError(
|
"{$path}.fields.{$fieldName}",
|
"Field type '{$config['type']}' requires 'options' array"
|
);
|
}
|
break;
|
|
case 'taxonomy':
|
if (empty($config['taxonomy'])) {
|
$this->addError(
|
"{$path}.fields.{$fieldName}",
|
"Taxonomy field requires 'taxonomy' property"
|
);
|
}
|
break;
|
|
case 'repeater':
|
if (empty($config['fields'])) {
|
$this->addError(
|
"{$path}.fields.{$fieldName}",
|
"Repeater field requires 'fields' definition array"
|
);
|
}
|
break;
|
|
case 'number':
|
if (isset($config['min']) && isset($config['max']) && $config['min'] > $config['max']) {
|
$this->addError(
|
"{$path}.fields.{$fieldName}",
|
"Number field 'min' value cannot be greater than 'max'"
|
);
|
}
|
break;
|
}
|
}
|
|
/**
|
* Validate sections configuration
|
*/
|
private function validateSectionsConfig(string $slug, array $sections): void
|
{
|
foreach ($sections as $sectionSlug => $sectionConfig) {
|
if (empty($sectionConfig['label'])) {
|
$this->addError(
|
"content.{$slug}.sections.{$sectionSlug}",
|
"Section missing required 'label'"
|
);
|
}
|
}
|
}
|
|
/**
|
* Validate field sections match defined sections
|
*/
|
private function validateFieldSections(string $slug, array $config, string $type): void
|
{
|
if (empty($config['fields']) || empty($config['sections'])) {
|
return;
|
}
|
|
$definedSections = array_keys($config['sections']);
|
|
foreach ($config['fields'] as $fieldName => $fieldConfig) {
|
if (isset($fieldConfig['section']) && !in_array($fieldConfig['section'], $definedSections)) {
|
$this->addError(
|
"{$type}.{$slug}.fields.{$fieldName}",
|
"Field references non-existent section '{$fieldConfig['section']}'"
|
);
|
}
|
}
|
}
|
|
/**
|
* Validate creatable content references
|
*/
|
private function validateCreatableContent(string $userSlug, array $canCreate, array $content, array $taxonomy): void
|
{
|
foreach ($canCreate as $item) {
|
if (is_array($item)) {
|
foreach ($item as $subType => $types) {
|
foreach ($types as $type) {
|
if (!isset($content[$type]) && !isset($taxonomy[$type])) {
|
$this->addError(
|
"user.{$userSlug}.can_create",
|
"References non-existent type '{$type}'"
|
);
|
}
|
}
|
}
|
} else {
|
if (!isset($content[$item]) && !isset($taxonomy[$item])) {
|
$this->addError(
|
"user.{$userSlug}.can_create",
|
"References non-existent type '{$item}'"
|
);
|
}
|
}
|
}
|
}
|
|
/**
|
* Add error message
|
*/
|
private function addError(string $path, string $message): void
|
{
|
$this->errors[] = "[{$path}] {$message}";
|
}
|
|
/**
|
* Add warning message
|
*/
|
private function addWarning(string $path, string $message): void
|
{
|
$this->warnings[] = "[{$path}] {$message}";
|
}
|
|
/**
|
* Get validation errors
|
*/
|
public function getErrors(): array
|
{
|
return $this->errors;
|
}
|
|
/**
|
* Get validation warnings
|
*/
|
public function getWarnings(): array
|
{
|
return $this->warnings;
|
}
|
|
/**
|
* Output validation results to error log
|
*/
|
public function logResults(): void
|
{
|
if (!empty($this->errors)) {
|
error_log('[ConfigValidator] Validation Errors:');
|
foreach ($this->errors as $error) {
|
error_log(" - {$error}");
|
}
|
}
|
|
if (!empty($this->warnings) && WP_DEBUG) {
|
error_log('[ConfigValidator] Validation Warnings:');
|
foreach ($this->warnings as $warning) {
|
error_log(" - {$warning}");
|
}
|
}
|
}
|
}
|