| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
namespace Yatra\Validators; |
| 6 |
|
| 7 |
use Yatra\Exceptions\ValidationException; |
| 8 |
|
| 9 |
/** |
| 10 |
* Activity Validator |
| 11 |
* |
| 12 |
* Comprehensive validation for activity data |
| 13 |
*/ |
| 14 |
class ActivityValidator |
| 15 |
{ |
| 16 |
/** |
| 17 |
* Validate activity creation data |
| 18 |
*/ |
| 19 |
public static function validateCreate(array $data): void |
| 20 |
{ |
| 21 |
$errors = []; |
| 22 |
|
| 23 |
// Required fields |
| 24 |
if (empty($data['name'])) { |
| 25 |
$errors['name'][] = __('Activity name is required', 'yatra'); |
| 26 |
} elseif (strlen($data['name']) < 2) { |
| 27 |
$errors['name'][] = __('Activity name must be at least 2 characters', 'yatra'); |
| 28 |
} |
| 29 |
|
| 30 |
if (empty($data['status'])) { |
| 31 |
$errors['status'][] = __('Status is required', 'yatra'); |
| 32 |
} elseif (!in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) { |
| 33 |
$errors['status'][] = __('Invalid status value', 'yatra'); |
| 34 |
} |
| 35 |
|
| 36 |
// Optional field validation |
| 37 |
if (isset($data['description']) && strlen($data['description']) > 5000) { |
| 38 |
$errors['description'][] = __('Description cannot exceed 5000 characters', 'yatra'); |
| 39 |
} |
| 40 |
|
| 41 |
if (isset($data['short_description']) && strlen($data['short_description']) > 500) { |
| 42 |
$errors['short_description'][] = __('Short description cannot exceed 500 characters', 'yatra'); |
| 43 |
} |
| 44 |
|
| 45 |
if (isset($data['slug']) && !empty($data['slug'])) { |
| 46 |
if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) { |
| 47 |
$errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra'); |
| 48 |
} |
| 49 |
} |
| 50 |
|
| 51 |
if (isset($data['difficulty_level']) && !empty($data['difficulty_level'])) { |
| 52 |
if (!in_array($data['difficulty_level'], ['easy', 'moderate', 'challenging', 'extreme'])) { |
| 53 |
$errors['difficulty_level'][] = __('Invalid difficulty level', 'yatra'); |
| 54 |
} |
| 55 |
} |
| 56 |
|
| 57 |
if (isset($data['duration_hours']) && !empty($data['duration_hours'])) { |
| 58 |
if (!is_numeric($data['duration_hours']) || (float)$data['duration_hours'] <= 0) { |
| 59 |
$errors['duration_hours'][] = __('Duration must be a positive number', 'yatra'); |
| 60 |
} |
| 61 |
} |
| 62 |
|
| 63 |
if (isset($data['min_age']) && !empty($data['min_age'])) { |
| 64 |
if (!is_numeric($data['min_age']) || (int)$data['min_age'] < 0 || (int)$data['min_age'] > 100) { |
| 65 |
$errors['min_age'][] = __('Minimum age must be between 0 and 100', 'yatra'); |
| 66 |
} |
| 67 |
} |
| 68 |
|
| 69 |
if (isset($data['max_participants']) && !empty($data['max_participants'])) { |
| 70 |
if (!is_numeric($data['max_participants']) || (int)$data['max_participants'] <= 0) { |
| 71 |
$errors['max_participants'][] = __('Maximum participants must be a positive number', 'yatra'); |
| 72 |
} |
| 73 |
} |
| 74 |
|
| 75 |
if (!empty($errors)) { |
| 76 |
throw new ValidationException('Activity validation failed', $errors); |
| 77 |
} |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Validate activity update data |
| 82 |
*/ |
| 83 |
public static function validateUpdate(array $data, int $activityId): void |
| 84 |
{ |
| 85 |
$errors = []; |
| 86 |
|
| 87 |
// ID validation |
| 88 |
if ($activityId <= 0) { |
| 89 |
$errors['id'][] = __('Invalid activity ID', 'yatra'); |
| 90 |
} |
| 91 |
|
| 92 |
// Optional field validation |
| 93 |
if (isset($data['name'])) { |
| 94 |
if (empty($data['name'])) { |
| 95 |
$errors['name'][] = __('Activity name cannot be empty', 'yatra'); |
| 96 |
} elseif (strlen($data['name']) < 2) { |
| 97 |
$errors['name'][] = __('Activity name must be at least 2 characters', 'yatra'); |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
if (isset($data['status']) && !in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) { |
| 102 |
$errors['status'][] = __('Invalid status value', 'yatra'); |
| 103 |
} |
| 104 |
|
| 105 |
if (isset($data['description']) && strlen($data['description']) > 5000) { |
| 106 |
$errors['description'][] = __('Description cannot exceed 5000 characters', 'yatra'); |
| 107 |
} |
| 108 |
|
| 109 |
if (isset($data['short_description']) && strlen($data['short_description']) > 500) { |
| 110 |
$errors['short_description'][] = __('Short description cannot exceed 500 characters', 'yatra'); |
| 111 |
} |
| 112 |
|
| 113 |
if (isset($data['slug']) && !empty($data['slug']) && !preg_match('/^[\pL\pN-]+$/u', $data['slug'])) { |
| 114 |
$errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra'); |
| 115 |
} |
| 116 |
|
| 117 |
if (isset($data['difficulty_level']) && !empty($data['difficulty_level'])) { |
| 118 |
if (!in_array($data['difficulty_level'], ['easy', 'moderate', 'challenging', 'extreme'])) { |
| 119 |
$errors['difficulty_level'][] = __('Invalid difficulty level', 'yatra'); |
| 120 |
} |
| 121 |
} |
| 122 |
|
| 123 |
if (isset($data['duration_hours']) && !empty($data['duration_hours'])) { |
| 124 |
if (!is_numeric($data['duration_hours']) || (float)$data['duration_hours'] <= 0) { |
| 125 |
$errors['duration_hours'][] = __('Duration must be a positive number', 'yatra'); |
| 126 |
} |
| 127 |
} |
| 128 |
|
| 129 |
if (isset($data['min_age']) && !empty($data['min_age'])) { |
| 130 |
if (!is_numeric($data['min_age']) || (int)$data['min_age'] < 0 || (int)$data['min_age'] > 100) { |
| 131 |
$errors['min_age'][] = __('Minimum age must be between 0 and 100', 'yatra'); |
| 132 |
} |
| 133 |
} |
| 134 |
|
| 135 |
if (isset($data['max_participants']) && !empty($data['max_participants'])) { |
| 136 |
if (!is_numeric($data['max_participants']) || (int)$data['max_participants'] <= 0) { |
| 137 |
$errors['max_participants'][] = __('Maximum participants must be a positive number', 'yatra'); |
| 138 |
} |
| 139 |
} |
| 140 |
|
| 141 |
if (!empty($errors)) { |
| 142 |
throw new ValidationException('Activity validation failed', $errors); |
| 143 |
} |
| 144 |
} |
| 145 |
|
| 146 |
/** |
| 147 |
* Sanitize activity data |
| 148 |
*/ |
| 149 |
public static function sanitize(array $data): array |
| 150 |
{ |
| 151 |
$sanitized = []; |
| 152 |
|
| 153 |
// Text fields |
| 154 |
if (isset($data['name'])) { |
| 155 |
$sanitized['name'] = sanitize_text_field($data['name']); |
| 156 |
} |
| 157 |
|
| 158 |
if (isset($data['slug'])) { |
| 159 |
$sanitized['slug'] = \Yatra\Helpers\SlugHelper::generate($data['slug']); |
| 160 |
} |
| 161 |
|
| 162 |
if (isset($data['description'])) { |
| 163 |
$sanitized['description'] = wp_kses_post($data['description']); |
| 164 |
} |
| 165 |
|
| 166 |
if (isset($data['short_description'])) { |
| 167 |
$sanitized['short_description'] = wp_kses_post($data['short_description']); |
| 168 |
} |
| 169 |
|
| 170 |
if (isset($data['location'])) { |
| 171 |
$sanitized['location'] = sanitize_text_field($data['location']); |
| 172 |
} |
| 173 |
|
| 174 |
if (isset($data['equipment_needed'])) { |
| 175 |
$sanitized['equipment_needed'] = wp_kses_post($data['equipment_needed']); |
| 176 |
} |
| 177 |
|
| 178 |
if (isset($data['safety_requirements'])) { |
| 179 |
$sanitized['safety_requirements'] = wp_kses_post($data['safety_requirements']); |
| 180 |
} |
| 181 |
|
| 182 |
// Numeric fields |
| 183 |
if (isset($data['duration_hours'])) { |
| 184 |
$sanitized['duration_hours'] = (float)$data['duration_hours']; |
| 185 |
} |
| 186 |
|
| 187 |
if (isset($data['min_age'])) { |
| 188 |
$sanitized['min_age'] = (int)$data['min_age']; |
| 189 |
} |
| 190 |
|
| 191 |
if (isset($data['max_participants'])) { |
| 192 |
$sanitized['max_participants'] = (int)$data['max_participants']; |
| 193 |
} |
| 194 |
|
| 195 |
if (isset($data['sort_order'])) { |
| 196 |
$sanitized['sort_order'] = (int)$data['sort_order']; |
| 197 |
} |
| 198 |
|
| 199 |
// Enum fields |
| 200 |
if (isset($data['status'])) { |
| 201 |
$validStatuses = ['draft', 'publish', 'private', 'trash']; |
| 202 |
$sanitized['status'] = in_array($data['status'], $validStatuses) ? $data['status'] : 'draft'; |
| 203 |
} |
| 204 |
|
| 205 |
if (isset($data['difficulty_level'])) { |
| 206 |
$validLevels = ['easy', 'moderate', 'challenging', 'extreme']; |
| 207 |
$sanitized['difficulty_level'] = in_array($data['difficulty_level'], $validLevels) ? $data['difficulty_level'] : null; |
| 208 |
} |
| 209 |
|
| 210 |
// Boolean fields |
| 211 |
if (isset($data['is_featured'])) { |
| 212 |
$sanitized['is_featured'] = (bool)$data['is_featured']; |
| 213 |
} |
| 214 |
|
| 215 |
if (isset($data['requires_guide'])) { |
| 216 |
$sanitized['requires_guide'] = (bool)$data['requires_guide']; |
| 217 |
} |
| 218 |
|
| 219 |
// JSON fields |
| 220 |
if (isset($data['features'])) { |
| 221 |
$sanitized['features'] = is_array($data['features']) ? json_encode($data['features']) : $data['features']; |
| 222 |
} |
| 223 |
|
| 224 |
if (isset($data['included_items'])) { |
| 225 |
$sanitized['included_items'] = is_array($data['included_items']) ? json_encode($data['included_items']) : $data['included_items']; |
| 226 |
} |
| 227 |
|
| 228 |
if (isset($data['excluded_items'])) { |
| 229 |
$sanitized['excluded_items'] = is_array($data['excluded_items']) ? json_encode($data['excluded_items']) : $data['excluded_items']; |
| 230 |
} |
| 231 |
|
| 232 |
// Icon (Yatra SVG / Font Awesome / image) — preserve full picker shape; Service layer serializes. |
| 233 |
if (array_key_exists('icon', $data)) { |
| 234 |
if (is_array($data['icon'])) { |
| 235 |
$sanitized['icon'] = function_exists('yatra_normalize_icon_picker_for_storage') |
| 236 |
? yatra_normalize_icon_picker_for_storage($data['icon']) |
| 237 |
: $data['icon']; |
| 238 |
} elseif (is_string($data['icon'])) { |
| 239 |
$sanitized['icon'] = sanitize_text_field($data['icon']); |
| 240 |
} elseif ($data['icon'] === null) { |
| 241 |
$sanitized['icon'] = null; |
| 242 |
} |
| 243 |
} |
| 244 |
|
| 245 |
return $sanitized; |
| 246 |
} |
| 247 |
} |
| 248 |
|