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}"); } } } }