__('This field is required', 'jvb')]; } // Skip validation for empty optional fields if (static::isEmpty($value)) { return true; } // Type-specific validation $method = 'validate' . str_replace('_', '', ucwords($type, '_')); if (method_exists(static::class, $method)) { $result = static::$method($value, $config); if ($result !== true) { return is_array($result) ? $result : [$name => $result]; } } return true; } protected static function isEmpty(mixed $value): bool { if ($value === null || $value === '' || $value === []) { return true; } if (is_array($value) && empty(array_filter($value))) { return true; } return false; } protected static function validateText(string $value, array $config): bool|string { if (isset($config['limit']) && strlen($value) > $config['limit']) { return sprintf(__('Must not exceed %d characters', 'jvb'), $config['limit']); } if (isset($config['pattern']) && !preg_match($config['pattern'], $value)) { return __('Invalid format', 'jvb'); } return true; } protected static function validateTextarea(string $value, array $config): bool|string { return static::validateText($value, $config); } protected static function validateNumber(mixed $value, array $config): bool|string { if (!is_numeric($value)) { return __('Must be a number', 'jvb'); } if (isset($config['min']) && $value < $config['min']) { return sprintf(__('Must be at least %s', 'jvb'), $config['min']); } if (isset($config['max']) && $value > $config['max']) { return sprintf(__('Must not exceed %s', 'jvb'), $config['max']); } return true; } protected static function validateEmail(string $value, array $config): bool|string { if (!is_email($value)) { return __('Invalid email address', 'jvb'); } return true; } protected static function validateUrl(string $value, array $config): bool|string { if (filter_var($value, FILTER_VALIDATE_URL) === false) { return __('Must be a valid URL', 'jvb'); } return true; } protected static function validateDate(string $value, array $config): bool|string { $timestamp = strtotime($value); if ($timestamp === false) { return __('Invalid date format', 'jvb'); } if (isset($config['min_date']) && $timestamp < strtotime($config['min_date'])) { return sprintf(__('Date must be after %s', 'jvb'), $config['min_date']); } if (isset($config['max_date']) && $timestamp > strtotime($config['max_date'])) { return sprintf(__('Date must be before %s', 'jvb'), $config['max_date']); } return true; } protected static function validateDatetime(string $value, array $config): bool|string { $date = DateTime::createFromFormat('Y-m-d H:i:s', $value); if (!$date) { $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) { return __('Invalid datetime format', 'jvb'); } $timestamp = $date->getTimestamp(); if (isset($config['min_datetime']) && $timestamp < strtotime($config['min_datetime'])) { return sprintf(__('DateTime must be after %s', 'jvb'), $config['min_datetime']); } if (isset($config['max_datetime']) && $timestamp > strtotime($config['max_datetime'])) { return sprintf(__('DateTime must be before %s', 'jvb'), $config['max_datetime']); } return true; } protected static function validateTime(string $value, array $config): bool|string { if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $value)) { return __('Time must be in HH:MM format', 'jvb'); } if (isset($config['min_time']) && strtotime($value) < strtotime($config['min_time'])) { return sprintf(__('Time must be after %s', 'jvb'), $config['min_time']); } if (isset($config['max_time']) && strtotime($value) > strtotime($config['max_time'])) { return sprintf(__('Time must be before %s', 'jvb'), $config['max_time']); } return true; } protected static function validateSelect(string $value, array $config): bool|string { if (!isset($config['options']) || !array_key_exists($value, $config['options'])) { return __('Invalid selection', 'jvb'); } return true; } protected static function validateRadio(string $value, array $config): bool|string { return static::validateSelect($value, $config); } protected static function validateSet(array|string $value, array $config): bool|string { if (!is_array($value)) { $value = explode(',', $value); } if (!isset($config['options'])) { return true; } $invalid = array_diff($value, array_keys($config['options'])); if (!empty($invalid)) { return __('Invalid selections', 'jvb'); } return true; } protected static function validateCheckbox(array|string $value, array $config): bool|string { return static::validateSet($value, $config); } protected static function validateImage(int $value, array $config): bool|string { if (!wp_attachment_is_image($value)) { return __('Invalid image', 'jvb'); } return true; } protected static function validateGallery(array|string $value, array $config): bool|string { $ids = is_array($value) ? $value : explode(',', $value); if (!empty($config['max_images']) && count($ids) > $config['max_images']) { return sprintf(__('Maximum of %d images allowed', 'jvb'), $config['max_images']); } foreach ($ids as $id) { if (absint($id) <= 0 || !wp_attachment_is_image(absint($id))) { return __('One or more invalid images', 'jvb'); } } return true; } protected static function validateTaxonomy(array|string $value, array $config): bool|string { 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)) { return __('Invalid term selected', 'jvb'); } } return true; } protected static function validateUser(array|string $value, array $config): bool|string { if (!is_array($value)) { $value = explode(',', $value); } foreach ($value as $user_id) { if (!get_userdata((int)$user_id)) { return __('Invalid user selected', 'jvb'); } } return true; } protected static function validateLocation(array $value, array $config): bool|string { if (!is_array($value)) { return __('Location must be an array', 'jvb'); } $required_fields = ['lat', 'lng']; foreach ($required_fields as $field) { if (!isset($value[$field]) || !is_numeric($value[$field])) { return __('Invalid location coordinates', 'jvb'); } } return true; } protected static function validateRepeater(array $value, array $config): bool|string { if (!is_array($value)) { return __('Invalid repeater data', 'jvb'); } if (isset($config['min_rows']) && count($value) < $config['min_rows']) { return sprintf(__('Minimum of %d rows required', 'jvb'), $config['min_rows']); } if (isset($config['max_rows']) && count($value) > $config['max_rows']) { return sprintf(__('Maximum of %d rows allowed', 'jvb'), $config['max_rows']); } foreach ($value as $row) { foreach ($config['fields'] as $field_name => $field_config) { if (!isset($row[$field_name])) { continue; } $field_config['name'] = $field_name; $result = static::validate($row[$field_name], $field_config); if ($result !== true) { return is_array($result) ? array_values($result)[0] : $result; } } } return true; } protected static function validateGroup(array $value, array $config): bool|string { if (!is_array($value)) { return __('Invalid group data', 'jvb'); } if (!empty($config['fields']) && is_array($config['fields'])) { foreach ($config['fields'] as $field_name => $field_config) { if (isset($value[$field_name])) { $field_config['name'] = $field_name; $result = static::validate($value[$field_name], $field_config); if ($result !== true) { return is_array($result) ? array_values($result)[0] : $result; } } } } return true; } protected static function validateTagList(array $value, array $config): bool|string { if (!is_array($value)) { return __('Invalid data format', 'jvb'); } if (isset($config['min_items']) && count($value) < $config['min_items']) { return sprintf(__('Minimum of %d items required', 'jvb'), $config['min_items']); } if (isset($config['max_items']) && count($value) > $config['max_items']) { return sprintf(__('Maximum of %d items allowed', 'jvb'), $config['max_items']); } if (!isset($config['fields']) || !is_array($config['fields'])) { return true; } foreach ($value as $row) { if (!is_array($row)) { continue; } foreach ($config['fields'] as $field_name => $field_config) { if (!isset($row[$field_name])) { continue; } $field_config['name'] = $field_name; $result = static::validate($row[$field_name], $field_config); if ($result !== true) { return is_array($result) ? array_values($result)[0] : $result; } } } return true; } protected static function validateTrueFalse(mixed $value, array $config): bool { return true; // Boolean values are always valid after sanitization } }