type_manager = new MetaTypeManager(); } public function sanitize(mixed $value, array $field_config):mixed { $callback = $this->getCallback($field_config); error_log('Callback: '.print_r($callback, true)); if (is_array($callback)) { return call_user_func([$this, $callback[1]], $value, $field_config); } if (method_exists($this, $callback)) { error_log('Method exists'); return $this->$callback($value, $field_config); } else { return call_user_func($callback, $value); } } public function getCallback(array $field_config):mixed { return $field_config['sanitize'] ?? $this->type_manager->getSanitizeCallback($field_config['type']); } protected function sanitizeTaxonomy(array|string $values, array $field_config):string { if (!is_array($values)) { $values = explode(',', $values); } // Ensure taxonomy starts with BASE $taxonomy = (str_starts_with($field_config['taxonomy'], BASE)) ? $field_config['taxonomy'] : BASE . $field_config['taxonomy']; $values = array_filter($values, fn($value) => term_exists((int)$value, $taxonomy)); return implode(',', $values); } protected function sanitizeUser(array|string $values, array $field_config):string { if (!is_array($values)) { $values = explode(',', $values); } $values = array_filter($values, fn($value) => (bool)get_userdata((int)$value)); return implode(',', $values); } protected function sanitizeTagList(array $values, array $field_config): array { if (!is_array($values)) { return []; } 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] = $this->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 function sanitizeRepeater(array $values, array $field_config):array { if (!is_array($values)) { return []; } 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] = $this->sanitize($row[$key], $subfield_config); } $sanitized[] = $clean_row; } return $sanitized; } protected 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 = $this->type_manager->getType($subfield_config['type'])['default'] ?? ''; $sanitized[$key] = $default; continue; } $subfield_config['name'] = $key; // For backwards compatibility $sanitized[$key] = $this->sanitize($clean_values[$key], $subfield_config); } return $sanitized; } protected 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 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 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 function sanitizeDate(string $value, array $field_config):string { $timestamp = strtotime($value); return $timestamp ? date('Y-m-d', $timestamp) : ''; } protected 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 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 function sanitizeFloat(string $value, array $config):float { if (is_numeric($value)) { return (float) $value; } return 0.0; } }