<?php
|
namespace JVBase\meta;
|
|
use DateTime;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
/**
|
* Handles meta value validation
|
*/
|
class Validator
|
{
|
protected array $errors = [];
|
|
public static function validate(mixed $value, array $config): bool|array
|
{
|
$errors = [];
|
$type = $config['type'];
|
$name = $config['name'] ?? 'field';
|
|
// Required field check
|
if (!empty($config['required']) && static::isEmpty($value)) {
|
return [$name => __('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
|
}
|
}
|