type_manager = new MetaTypeManager(); } public function validate(mixed $value, array $field_config):bool { $this->errors = []; // Reset errors $type = $field_config['type']; // Required field check if (!empty($field_config['required']) && empty($value)) { $this->addError($field_config['name'], __('This field is required', 'jvb')); return false; } // Type-specific validation $method = "validate_{$type}"; if (method_exists($this, $method)) { return $this->$method($value, $field_config); } return true; } protected function validateNumber(float $value, array $field_config):bool|WP_Error { if (empty($value)) { if (!empty($field_config['required'])) { // ✅ Correct variable return new \WP_Error('required_field', 'This field is required'); } return true; } if (!is_numeric($value)) { $this->addError($field_config['name'], __('Must be a number', 'jvb')); return false; } if (array_key_exists('min', $field_config) && $value < $field_config['min']) { $this->addError( $field_config['name'], sprintf(__('Must be at least %s', 'jvb'), $field_config['min']) ); return false; } if (array_key_exists('max', $field_config) && $value > $field_config['max']) { $this->addError( $field_config['name'], sprintf(__('Must not exceed %s', 'jvb'), $field_config['max']) ); return false; } return true; } protected function validateEmail(string $value, array $config):bool|WP_Error { if (empty($value)) { if (!empty($config['required'])) { // ✅ Correct variable return new \WP_Error('required_field', 'This field is required'); } return true; } // Validate email format if (!is_email($value)) { $this->addError($config['name'], __('Invalid email address', 'jvb')); return false; } return true; } protected function validateGroup(array $value, array $config):bool { if (empty($value) || !is_array($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } // Validate each sub-field if (!empty($config['fields']) && is_array($config['fields'])) { foreach ($config['fields'] as $subFieldName => $subFieldConfig) { if (isset($value[$subFieldName])) { $subFieldConfig['name'] = $subFieldName; $isValid = $this->validate($value[$subFieldName], $subFieldConfig); if (!$isValid) { return false; } } } } return true; } protected function validateGallery(array|string $value, array $field):bool|WP_Error { if (empty($value)) { if (!empty($field['required'])) { return new WP_Error('required_field', 'This field is required'); } return true; } $ids = is_array($value) ? $value : explode(',', $value); // Check maximum images if specified if (!empty($field['max_images']) && count($ids) > $field['max_images']) { return new WP_Error( 'max_images_exceeded', sprintf('Maximum of %d images allowed', $field['max_images']) ); } // Validate each ID is an actual image attachment foreach ($ids as $id) { $id = absint($id); if ($id <= 0 || !wp_attachment_is_image($id)) { return new WP_Error('invalid_image', 'One or more invalid images'); } } return true; } protected function validateUrl(string $value, array $config):bool|WP_Error { if (empty($value)) { if (!empty($field['required'])) { return new WP_Error('required_field', 'This field is required'); } return true; } $check = filter_var($value, FILTER_VALIDATE_URL) !== false; if (!$check) { $this->addError( $config['name'], __('Must be a valid URL', 'jvb') ); } return $check; } protected function validateDate(string $value, array $config):bool|WP_Error { if (empty($value)) { if (!empty($field['required'])) { return new WP_Error('required_field', 'This field is required'); } return true; } $timestamp = strtotime($value); if ($timestamp === false) { $this->addError( $config['name'], __('It\'s gotta be a time, bro', 'jvb') ); return false; } if (isset($config['min_date']) && $timestamp < strtotime($config['min_date'])) { return false; } if (isset($config['max_date']) && $timestamp > strtotime($config['max_date'])) { return false; } return true; } protected function validateDatetime(string $value, array $config):bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } // Try to parse the datetime $date = DateTime::createFromFormat('Y-m-d H:i:s', $value); if (!$date) { // Try alternative formats $formats = ['Y-m-d\TH:i:s', 'Y-m-d\TH:i', 'Y-m-d H:i']; foreach ($formats as $format) { $date = DateTime::createFromFormat($format, $value); if ($date) break; } } if (!$date) { $this->addError($config['name'], __('Invalid datetime format', 'jvb')); return false; } $timestamp = $date->getTimestamp(); // Validate datetime range if specified if (isset($config['min_datetime'])) { $min_timestamp = strtotime($config['min_datetime']); if ($timestamp < $min_timestamp) { $min_date = new DateTime($config['min_datetime']); $this->addError( $config['name'], sprintf(__('DateTime must be after %s', 'jvb'), $min_date->format('F j, Y g:i A')) ); return false; } } if (isset($config['max_datetime'])) { $max_timestamp = strtotime($config['max_datetime']); if ($timestamp > $max_timestamp) { $max_date = new DateTime($config['max_datetime']); $this->addError( $config['name'], sprintf(__('DateTime must be before %s', 'jvb'), $max_date->format('F j, Y g:i A')) ); return false; } } return true; } protected function validateTime(string $value, array $config):bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } // Check if time is in valid format (HH:MM) if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $value)) { $this->addError($config['name'], __('Time must be in HH:MM format', 'jvb')); return false; } // Validate time range if specified if (isset($config['min_time'])) { if (strtotime($value) < strtotime($config['min_time'])) { $this->addError( $config['name'], sprintf(__('Time must be after %s', 'jvb'), $config['min_time']) ); return false; } } if (isset($config['max_time'])) { if (strtotime($value) > strtotime($config['max_time'])) { $this->addError( $config['name'], sprintf(__('Time must be before %s', 'jvb'), $config['max_time']) ); return false; } } return true; } protected function validateTagList(array $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!is_array($value)) { $this->addError($config['name'], __('Invalid data format', 'jvb')); return false; } // Check min/max items if (isset($config['min_items']) && count($value) < $config['min_items']) { $this->addError( $config['name'], sprintf(__('Minimum of %d items required', 'jvb'), $config['min_items']) ); return false; } if (isset($config['max_items']) && count($value) > $config['max_items']) { $this->addError( $config['name'], sprintf(__('Maximum of %d items allowed', 'jvb'), $config['max_items']) ); return false; } // Validate each item's fields if (!isset($config['fields']) || !is_array($config['fields'])) { return true; } foreach ($value as $index => $row) { if (!is_array($row)) { continue; } foreach ($config['fields'] as $field_name => $field_config) { if (!isset($row[$field_name])) { continue; } $field_config['name'] = "{$config['name']}[{$index}][{$field_name}]"; if (!$this->validate($row[$field_name], $field_config)) { return false; } } } return true; } protected function validateRepeater(array $value, array $config):bool|WP_Error { if (empty($value)) { if (!empty($field['required'])) { return new WP_Error('required_field', 'This field is required'); } return true; } if (!is_array($value)) { return false; } if (isset($config['min_rows']) && count($value) < $config['min_rows']) { return false; } if (isset($config['max_rows']) && count($value) > $config['max_rows']) { return false; } foreach ($value as $row) { foreach ($config['fields'] as $field_name => $field_config) { if (!isset($row[$field_name])) { continue; } if (!$this->validate($row[$field_name], $field_config)) { return false; } } } return true; } public function getErrors():array { return $this->errors; } public function hasErrors():bool { return !empty($this->errors); } protected function addError(string $field, string $message):void { if (!isset($this->errors[$field])) { $this->errors[$field] = []; } $this->errors[$field][] = $message; } protected function validateText(string $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } // Check character limit if (isset($config['limit']) && strlen($value) > $config['limit']) { $this->addError( $config['name'], sprintf(__('Must not exceed %d characters', 'jvb'), $config['limit']) ); return false; } // Pattern validation if specified if (isset($config['pattern']) && !preg_match($config['pattern'], $value)) { $this->addError($config['name'], __('Invalid format', 'jvb')); return false; } return true; } protected function validateTextarea(string $value, array $config): bool { return $this->validateText($value, $config); // Reuse text validation } protected function validateSelect(string $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!isset($config['options']) || !array_key_exists($value, $config['options'])) { $this->addError($config['name'], __('Invalid selection', 'jvb')); return false; } return true; } protected function validateRadio(string $value, array $config): bool { return $this->validateSelect($value, $config); // Same logic } protected function validateSet(array|string $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!is_array($value)) { $value = explode(',', $value); } if (!isset($config['options'])) { return true; } $invalid_values = array_diff($value, array_keys($config['options'])); if (!empty($invalid_values)) { $this->addError($config['name'], __('Invalid selections', 'jvb')); return false; } return true; } protected function validateCheckbox(array|string $value, array $config): bool { return $this->validateSet($value, $config); // Same logic } protected function validateImage(int $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!wp_attachment_is_image($value)) { $this->addError($config['name'], __('Invalid image', 'jvb')); return false; } return true; } protected function validateTaxonomy(array|string $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!is_array($value)) { $value = explode(',', $value); } $taxonomy = (str_starts_with($config['taxonomy'], BASE)) ? $config['taxonomy'] : BASE . $config['taxonomy']; foreach ($value as $term_id) { if (!term_exists((int)$term_id, $taxonomy)) { $this->addError($config['name'], __('Invalid term selected', 'jvb')); return false; } } return true; } protected function validateUser(array|string $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!is_array($value)) { $value = explode(',', $value); } foreach ($value as $user_id) { if (!get_userdata((int)$user_id)) { $this->addError($config['name'], __('Invalid user selected', 'jvb')); return false; } } return true; } protected function validateLocation(array $value, array $config): bool { if (empty($value)) { if (!empty($config['required'])) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; } if (!is_array($value)) { $this->addError($config['name'], __('Location must be an array', 'jvb')); return false; } // Validate required location fields $required_fields = ['lat', 'lng']; foreach ($required_fields as $field) { if (!isset($value[$field]) || !is_numeric($value[$field])) { $this->addError($config['name'], __('Invalid location coordinates', 'jvb')); return false; } } return true; } protected function validateTrueFalse(mixed $value, array $config): bool { if (!empty($config['required']) && empty($value)) { $this->addError($config['name'], __('This field is required', 'jvb')); return false; } return true; // Boolean values are always valid after sanitization } }