term_exists((int)$value, $taxonomy)); return implode(',', $values); } protected static function sanitizeUser(string $values, array $field_config):string { $values = array_map('absint', explode(',', $values)); $values = array_filter($values, fn($value) => (bool)get_userdata((int)$value)); return implode(',', $values); } protected static function sanitizePost(string $values, array $config):string { $values = array_map('absint', explode(',', $values)); return implode(',', array_filter($values, fn($value) => (bool)get_post((int)$value))); } protected static function sanitizeTagList(array $values, array $field_config): array { if (empty(array_filter($values, fn($value) => !empty($value)))) { return []; } if (!isset($field_config['fields']) || !is_array($field_config['fields'])) { return []; } $sanitized = []; foreach ($values as $row) { if (!is_array($row)) { continue; } // Clean up field names (remove prefixes like "fieldname:0:email") $temp = []; foreach ($row as $key => $value) { $key_parts = explode(':', $key); $clean_key = $key_parts[array_key_last($key_parts)]; $temp[$clean_key] = $value; } $row = $temp; // Sanitize each field $clean_row = []; foreach ($field_config['fields'] as $key => $subfield_config) { if (!array_key_exists($key, $row)) { continue; } $subfield_config['name'] = $key; // For backwards compatibility $clean_row[$key] = static::sanitize($row[$key], $subfield_config); } // Only add row if it has at least one non-empty value if (!empty(array_filter($clean_row))) { $sanitized[] = $clean_row; } } return $sanitized; } protected static function sanitizeRepeater(array $values, array $field_config):array { if (empty(array_filter($values, fn($value) => !empty($value)))) { return []; } $sanitized = []; foreach ($values as $row) { if (!is_array($row)) { continue; } $temp = []; foreach ($row as $key => $value) { $key = explode(':', $key); $temp[$key[array_key_last($key)]] = $value; } $row = $temp; $clean_row = []; foreach ($field_config['fields'] as $key => $subfield_config) { if (!array_key_exists($key, $row)) { continue; } $subfield_config['name'] = $key;//For backwards compatability $clean_row[$key] = static::sanitize($row[$key], $subfield_config); } $sanitized[] = $clean_row; } return $sanitized; } protected static function sanitizeGroup(array|string $values, array $field_config):array { if (!is_array($values)) { return []; } if (!isset($field_config['fields']) || !is_array($field_config['fields'])) { return []; } // Remove group: prefix from keys if present $clean_values = []; foreach ($values as $key => $value) { $key_parts = explode(':', $key); $clean_key = $key_parts[array_key_last($key_parts)]; $clean_values[$clean_key] = $value; } $sanitized = []; foreach ($field_config['fields'] as $key => $subfield_config) { if (!array_key_exists($key, $clean_values)) { // Use default value if not provided $default = MetaTypeManager::getType($subfield_config['type'])['default'] ?? ''; $sanitized[$key] = $default; continue; } $subfield_config['name'] = $key; // For backwards compatibility $sanitized[$key] = static::sanitize($clean_values[$key], $subfield_config); } return $sanitized; } protected static function sanitizeSelector(string $value, array $config):string { if (array_key_exists('type', $config)) { return match ($config['type']) { 'user' => self::sanitizeUser($value, $config), 'taxonomy'=> self::sanitizeTaxonomy($value, $config), 'post' => self::sanitizePost($value, $config), }; } return implode(',',array_map('absint', explode(',',$value))); } protected static function sanitizeUpload(array|string $value):string { if (empty($value)) { return ''; } // Split value into array if it's a string $ids = is_array($value) ? $value : explode(',', $value); // Filter and validate each ID $valid_ids = array_filter($ids, function ($id) { $id = absint($id); // Verify this is a valid attachment return $id > 0 && wp_attachment_is_image($id); }); return implode(',', $valid_ids); } protected static function sanitizeLocation(array $value, array $field_config):array { error_log('Location field to sanitize: '.print_r($value, true)); return [ 'address' => sanitize_text_field($value['address'] ?? ''), 'lat' => (float)($value['lat'] ?? 0), 'lng' => (float)($value['lng'] ?? 0), // Address components 'street' => sanitize_text_field($value['street'] ?? ''), 'city' => sanitize_text_field($value['city'] ?? ''), 'province' => sanitize_text_field($value['province'] ?? ''), 'postal_code' => sanitize_text_field($value['postal_code'] ?? ''), 'country' => sanitize_text_field($value['country'] ?? '') ]; } protected static function sanitizeOptions(array|string $value, array $field_config):string { error_log('Sanitizing options: '.print_r($value, true)); if (!isset($field_config['options'])) { return ''; } if (!is_array($value)) { $value = array_map('trim', explode(',', $value)); } return implode(',', array_intersect($value, array_keys($field_config['options']))); } protected static function sanitizeDate(string $value, array $field_config):string { $timestamp = strtotime($value); return $timestamp ? date('Y-m-d', $timestamp) : ''; } protected static function sanitizeDateTime(string $value, array $field_config): string { if (empty($value)) { return ''; } $timestamp = strtotime($value); if (!$timestamp) { return ''; } // Return in MySQL datetime format return date('Y-m-d H:i:s', $timestamp); } protected static function sanitizeTime(string $value, array $field_config):string { // Remove any whitespace $value = trim($value); // Convert to lowercase for consistency $value = strtolower($value); // Pattern to match various time formats $patterns = [ // 10am, 2pm, etc. '/^(\d{1,2})(am|pm)$/' => function ($matches) { $hour = (int)$matches[1]; if ($matches[2] === 'pm' && $hour < 12) { $hour += 12; } elseif ($matches[2] === 'am' && $hour === 12) { $hour = 0; } return sprintf('%02d:00', $hour); }, // 10:30am, 2:15pm, etc. '/^(\d{1,2}):(\d{2})(am|pm)$/' => function ($matches) { $hour = (int)$matches[1]; $minute = (int)$matches[2]; if ($matches[3] === 'pm' && $hour < 12) { $hour += 12; } elseif ($matches[3] === 'am' && $hour === 12) { $hour = 0; } return sprintf('%02d:%02d', $hour, $minute); }, // 14:30, 09:45, etc. (24-hour format) '/^(\d{1,2}):(\d{2})$/' => function ($matches) { $hour = (int)$matches[1]; $minute = (int)$matches[2]; return sprintf('%02d:%02d', $hour, $minute); }, // Just hours like "14", "9" (assuming 24-hour and whole hours) '/^(\d{1,2})$/' => function ($matches) { $hour = (int)$matches[1]; return sprintf('%02d:00', $hour); } ]; // Try each pattern foreach ($patterns as $pattern => $callback) { if (preg_match($pattern, $value, $matches)) { $time = $callback($matches); // Validate the resulting time $hour = (int)substr($time, 0, 2); $minute = (int)substr($time, 3, 2); if ($hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59) { return $time; // Valid time in HH:MM format } } } // If no pattern matched or time was invalid return ''; } public static function sanitizeFloat(string $value, array $config):float { if (is_numeric($value)) { return (float) $value; } return 0.0; } public static function sanitizePhone(string|int $value, array $config = []):string { $digits = preg_replace('/\D/', '', (string) $value); $length = strlen($digits); if ($length < 10 || $length > 13) { // 13 = 3-digit country code + 10 return ''; } $countryCode = $length > 10 ? substr($digits, 0, $length - 10) : null; $number = substr($digits, -10); $formatted = preg_replace('/(\d{3})(\d{3})(\d{4})/', '$1-$2-$3', $number); return $countryCode ? '+' . $countryCode . '-' . $formatted : $formatted; } }