PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / app / Helpers / FormatHelper.php

FormatHelper.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Helpers/FormatHelper.php

575 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Helpers;
6
7 /**
8 * Format Helper
9 *
10 * Utility functions for formatting data.
11 * All methods are static for easy access throughout the application.
12 *
13 * Usage: FormatHelper::formatPrice(100, 'USD')
14 *
15 * @package Yatra\Helpers
16 */
17 class FormatHelper
18 {
19 // Currency symbols are now managed by CurrencyHelper
20
21 /**
22 * Country codes to names mapping
23 */
24 private const COUNTRY_NAMES = [
25 'AF' => 'Afghanistan', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AD' => 'Andorra',
26 'AO' => 'Angola', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia',
27 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas',
28 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus',
29 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BT' => 'Bhutan',
30 'BO' => 'Bolivia', 'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BR' => 'Brazil',
31 'BN' => 'Brunei', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi',
32 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde',
33 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China',
34 'CO' => 'Colombia', 'KM' => 'Comoros', 'CG' => 'Congo', 'CD' => 'DR Congo',
35 'CR' => 'Costa Rica', 'CI' => 'Ivory Coast', 'HR' => 'Croatia', 'CU' => 'Cuba',
36 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'DJ' => 'Djibouti',
37 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'EC' => 'Ecuador', 'EG' => 'Egypt',
38 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia',
39 'SZ' => 'Eswatini', 'ET' => 'Ethiopia', 'FJ' => 'Fiji', 'FI' => 'Finland',
40 'FR' => 'France', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia',
41 'DE' => 'Germany', 'GH' => 'Ghana', 'GR' => 'Greece', 'GD' => 'Grenada',
42 'GT' => 'Guatemala', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana',
43 'HT' => 'Haiti', 'HN' => 'Honduras', 'HU' => 'Hungary', 'IS' => 'Iceland',
44 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran', 'IQ' => 'Iraq',
45 'IE' => 'Ireland', 'IL' => 'Israel', 'IT' => 'Italy', 'JM' => 'Jamaica',
46 'JP' => 'Japan', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya',
47 'KI' => 'Kiribati', 'KP' => 'North Korea', 'KR' => 'South Korea', 'KW' => 'Kuwait',
48 'KG' => 'Kyrgyzstan', 'LA' => 'Laos', 'LV' => 'Latvia', 'LB' => 'Lebanon',
49 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein',
50 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MG' => 'Madagascar', 'MW' => 'Malawi',
51 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta',
52 'MH' => 'Marshall Islands', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'MX' => 'Mexico',
53 'FM' => 'Micronesia', 'MD' => 'Moldova', 'MC' => 'Monaco', 'MN' => 'Mongolia',
54 'ME' => 'Montenegro', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar',
55 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands',
56 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria',
57 'MK' => 'North Macedonia', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan',
58 'PW' => 'Palau', 'PS' => 'Palestine', 'PA' => 'Panama', 'PG' => 'Papua New Guinea',
59 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PL' => 'Poland',
60 'PT' => 'Portugal', 'QA' => 'Qatar', 'RO' => 'Romania', 'RU' => 'Russia',
61 'RW' => 'Rwanda', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia',
62 'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino',
63 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal',
64 'RS' => 'Serbia', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore',
65 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia',
66 'ZA' => 'South Africa', 'SS' => 'South Sudan', 'ES' => 'Spain', 'LK' => 'Sri Lanka',
67 'SD' => 'Sudan', 'SR' => 'Suriname', 'SE' => 'Sweden', 'CH' => 'Switzerland',
68 'SY' => 'Syria', 'TW' => 'Taiwan', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania',
69 'TH' => 'Thailand', 'TL' => 'Timor-Leste', 'TG' => 'Togo', 'TO' => 'Tonga',
70 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan',
71 'TV' => 'Tuvalu', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates',
72 'GB' => 'United Kingdom', 'US' => 'United States', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan',
73 'VU' => 'Vanuatu', 'VA' => 'Vatican City', 'VE' => 'Venezuela', 'VN' => 'Vietnam',
74 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
75 ];
76
77 /**
78 * Format price with currency
79 *
80 * @param float $amount Amount to format
81 * @param string $currency Currency code (default: USD)
82 * @param bool $showCode Show currency code alongside symbol
83 * @return string Formatted price
84 */
85 public static function formatPrice(float $amount, string $currency = 'USD', bool $showCode = false): string
86 {
87 return CurrencyHelper::format($amount, $currency, $showCode);
88 }
89
90 /**
91 * Get currency symbol
92 *
93 * @param string $currency Currency code
94 * @return string Currency symbol
95 */
96 public static function getCurrencySymbol(string $currency): string
97 {
98 return CurrencyHelper::getSymbol($currency);
99 }
100
101 /**
102 * Format date for display
103 *
104 * @param string $date Date string
105 * @param string $format PHP date format (default: from WordPress settings)
106 * @return string Formatted date
107 */
108 public static function formatDate(string $date, string $format = ''): string
109 {
110 if (empty($date)) {
111 return '';
112 }
113
114 $format = $format ?: get_option('date_format', 'F j, Y');
115
116 $timestamp = strtotime($date);
117 if ($timestamp === false) {
118 return $date;
119 }
120
121 return date_i18n($format, $timestamp);
122 }
123
124 /**
125 * Format datetime for display
126 *
127 * @param string $datetime Datetime string
128 * @param string $format PHP date format
129 * @return string Formatted datetime
130 */
131 public static function formatDateTime(string $datetime, string $format = ''): string
132 {
133 if (empty($datetime)) {
134 return '';
135 }
136
137 $dateFormat = get_option('date_format', 'F j, Y');
138 $timeFormat = get_option('time_format', 'g:i a');
139 $format = $format ?: $dateFormat . ' ' . $timeFormat;
140
141 $timestamp = strtotime($datetime);
142 if ($timestamp === false) {
143 return $datetime;
144 }
145
146 return date_i18n($format, $timestamp);
147 }
148
149 /**
150 * Format time for display (24h to 12h) with uppercase AM/PM
151 *
152 * @param string $time Time string (e.g., "14:30")
153 * @return string Formatted time (e.g., "2:30 PM")
154 */
155 public static function formatTime(string $time): string
156 {
157 if (empty($time)) {
158 return '';
159 }
160
161 $timestamp = strtotime($time);
162 if ($timestamp === false) {
163 return $time;
164 }
165
166 // Always use uppercase AM/PM format (g:i A)
167 return date_i18n('g:i A', $timestamp);
168 }
169
170 /**
171 * Format time for display with uppercase AM/PM
172 *
173 * @param string $time Time string (e.g., "14:30")
174 * @return string Formatted time (e.g., "2:30 PM")
175 */
176 public static function formatTimeForDisplay(string $time): string
177 {
178 if (empty($time)) {
179 return '';
180 }
181
182 $timestamp = strtotime($time);
183 if ($timestamp === false) {
184 return $time;
185 }
186
187 // Always use uppercase AM/PM format (g:i A)
188 return date_i18n('g:i A', $timestamp);
189 }
190
191 /**
192 * Get relative time (e.g., "2 hours ago")
193 *
194 * @param string $datetime Datetime string
195 * @return string Relative time
196 */
197 public static function timeAgo(string $datetime): string
198 {
199 if (empty($datetime)) {
200 return '';
201 }
202
203 $timestamp = strtotime($datetime);
204 if ($timestamp === false) {
205 return $datetime;
206 }
207
208 return human_time_diff($timestamp, current_time('timestamp')) . ' ' . __('ago', 'yatra');
209 }
210
211 /**
212 * Get country name from code
213 *
214 * @param string $code Country code (e.g., "US")
215 * @return string Country name
216 */
217 public static function getCountryName(string $code): string
218 {
219 $code = strtoupper($code);
220 return self::COUNTRY_NAMES[$code] ?? $code;
221 }
222
223 /**
224 * Get all countries as options array
225 *
226 * @return array [code => name]
227 */
228 public static function getCountries(): array
229 {
230 return self::COUNTRY_NAMES;
231 }
232
233 /**
234 * Format phone number for display
235 *
236 * @param string $phone Phone number
237 * @return string Formatted phone
238 */
239 public static function formatPhone(string $phone): string
240 {
241 // Remove non-numeric characters except + and spaces
242 $phone = preg_replace('/[^\d\+\s\-\(\)]/', '', $phone);
243 return trim($phone);
244 }
245
246 /**
247 * Format duration (days/nights)
248 *
249 * @param int $days Number of days
250 * @param int|null $nights Number of nights (optional)
251 * @return string Formatted duration
252 */
253 public static function formatDuration(int $days, ?int $nights = null): string
254 {
255 if ($nights !== null) {
256 return sprintf(
257 _n('%d Day', '%d Days', $days, 'yatra'),
258 $days
259 ) . ' / ' . sprintf(
260 _n('%d Night', '%d Nights', $nights, 'yatra'),
261 $nights
262 );
263 }
264
265 return sprintf(_n('%d Day', '%d Days', $days, 'yatra'), $days);
266 }
267
268 /**
269 * Format file size
270 *
271 * @param int $bytes File size in bytes
272 * @return string Formatted size
273 */
274 public static function formatFileSize(int $bytes): string
275 {
276 $units = ['B', 'KB', 'MB', 'GB', 'TB'];
277 $unitIndex = 0;
278
279 while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
280 $bytes /= 1024;
281 $unitIndex++;
282 }
283
284 return round($bytes, 2) . ' ' . $units[$unitIndex];
285 }
286
287 /**
288 * Truncate text with ellipsis
289 *
290 * @param string $text Text to truncate
291 * @param int $length Maximum length
292 * @param string $suffix Suffix to add (default: ...)
293 * @return string Truncated text
294 */
295 public static function truncate(string $text, int $length = 100, string $suffix = '...'): string
296 {
297 if (mb_strlen($text) <= $length) {
298 return $text;
299 }
300
301 return mb_substr($text, 0, $length - mb_strlen($suffix)) . $suffix;
302 }
303
304 /**
305 * Generate excerpt from HTML content
306 *
307 * @param string $html HTML content
308 * @param int $length Maximum length
309 * @return string Plain text excerpt
310 */
311 public static function excerpt(string $html, int $length = 150): string
312 {
313 $text = wp_strip_all_tags($html);
314 $text = preg_replace('/\s+/', ' ', $text);
315 $text = trim($text);
316
317 return self::truncate($text, $length);
318 }
319
320 /**
321 * Format rating as stars HTML
322 *
323 * @param float $rating Rating (0-5)
324 * @param bool $showNumber Show rating number
325 * @return string HTML stars
326 */
327 public static function formatRatingStars(float $rating, bool $showNumber = false): string
328 {
329 $rating = max(0, min(5, $rating));
330 $fullStars = (int) floor($rating);
331 $hasHalfStar = ($rating - $fullStars) >= 0.5;
332
333 $html = '<span class="yatra-rating-stars">';
334
335 for ($i = 1; $i <= 5; $i++) {
336 if ($i <= $fullStars) {
337 $html .= '<span class="yatra-star filled">�
338 </span>';
339 } elseif ($i === $fullStars + 1 && $hasHalfStar) {
340 $html .= '<span class="yatra-star half">�
341 </span>';
342 } else {
343 $html .= '<span class="yatra-star">�
344 </span>';
345 }
346 }
347
348 $html .= '</span>';
349
350 if ($showNumber) {
351 $html .= '<span class="yatra-rating-number">' . number_format($rating, 1) . '</span>';
352 }
353
354 return $html;
355 }
356
357 /**
358 * Format booking status as badge
359 *
360 * @param string $status Booking status
361 * @return string HTML badge
362 */
363 public static function formatStatusBadge(string $status): string
364 {
365 $statusClasses = [
366 'pending' => 'yatra-badge yatra-badge-warning',
367 'confirmed' => 'yatra-badge yatra-badge-success',
368 'processing' => 'yatra-badge yatra-badge-info',
369 'completed' => 'yatra-badge yatra-badge-success',
370 'cancelled' => 'yatra-badge yatra-badge-danger',
371 'refunded' => 'yatra-badge yatra-badge-secondary',
372 'failed' => 'yatra-badge yatra-badge-danger',
373 'on_hold' => 'yatra-badge yatra-badge-warning',
374 ];
375
376 $class = $statusClasses[$status] ?? 'yatra-badge';
377 $label = ucfirst(str_replace('_', ' ', $status));
378
379 return '<span class="' . esc_attr($class) . '">' . esc_html($label) . '</span>';
380 }
381
382 /**
383 * Sanitize and format slug
384 *
385 * @param string $text Text to slugify
386 * @return string URL-safe slug
387 */
388 public static function slugify(string $text): string
389 {
390 return sanitize_title($text);
391 }
392
393 /**
394 * Convert array to HTML attributes string
395 *
396 * @param array $attributes Key-value pairs
397 * @return string HTML attributes
398 */
399 public static function arrayToAttributes(array $attributes): string
400 {
401 $html = [];
402
403 foreach ($attributes as $key => $value) {
404 if ($value === true) {
405 $html[] = esc_attr($key);
406 } elseif ($value !== false && $value !== null) {
407 $html[] = esc_attr($key) . '="' . esc_attr($value) . '"';
408 }
409 }
410
411 return implode(' ', $html);
412 }
413
414 /**
415 * Sanitize Quill editor HTML output
416 *
417 * This function sanitizes HTML content from the Quill rich text editor,
418 * allowing only safe HTML tags and attributes that match the Quill toolbar configuration.
419 *
420 * Allowed features based on Quill toolbar:
421 * - Headers: h1, h2, h3
422 * - Text formatting: bold, italic, underline, strike
423 * - Lists: ordered (ol, li), unordered (ul, li)
424 * - Alignment: text-align attribute on p tags
425 * - Links: a tags with href attribute
426 * - Paragraphs: p tags
427 *
428 * @param string $html Raw HTML from Quill editor
429 * @return string Sanitized HTML safe for database storage
430 */
431 public static function sanitizeQuillHtml(string $html): string
432 {
433 // Return empty string if input is empty or just whitespace
434 if (empty(trim($html)) || $html === '<p><br></p>') {
435 return '';
436 }
437
438 // Define allowed HTML tags and attributes based on Quill configuration
439 $allowed_tags = [
440 // Headers (from Quill header dropdown: 1, 2, 3)
441 'h1' => [],
442 'h2' => [],
443 'h3' => [],
444
445 // Paragraphs with alignment support
446 'p' => [
447 'style' => true, // For text-align
448 'class' => true, // Quill may add alignment classes
449 ],
450
451 // Text formatting (bold, italic, underline, strike)
452 'strong' => [],
453 'b' => [],
454 'em' => [],
455 'i' => [],
456 'u' => [],
457 's' => [],
458 'strike' => [],
459
460 // Lists (ordered and unordered)
461 'ol' => [],
462 'ul' => [],
463 'li' => [],
464
465 // Links
466 'a' => [
467 'href' => true,
468 'title' => true,
469 'target' => true,
470 'rel' => true,
471 ],
472
473 // Line breaks
474 'br' => [],
475 ];
476
477 // Use wp_kses to sanitize with allowed tags
478 $sanitized = wp_kses($html, $allowed_tags);
479
480 // Additional cleanup for alignment styles
481 // Only allow text-align in style attribute
482 $sanitized = preg_replace_callback(
483 '/style="([^"]*)"/i',
484 function ($matches) {
485 $styles = $matches[1];
486 // Extract only text-align property
487 if (preg_match('/text-align:\s*(left|center|right|justify)/i', $styles, $align)) {
488 return 'style="text-align: ' . esc_attr($align[1]) . '"';
489 }
490 return ''; // Remove style attribute if no valid text-align
491 },
492 $sanitized
493 );
494
495 // Ensure links have proper rel attribute for security
496 $sanitized = preg_replace_callback(
497 '/<a\s+([^>]*?)>/i',
498 function ($matches) {
499 $attrs = $matches[1];
500 // If target="_blank" exists, ensure rel="noopener noreferrer"
501 if (stripos($attrs, 'target="_blank"') !== false) {
502 if (stripos($attrs, 'rel=') === false) {
503 $attrs .= ' rel="noopener noreferrer"';
504 } elseif (stripos($attrs, 'noopener') === false || stripos($attrs, 'noreferrer') === false) {
505 $attrs = preg_replace(
506 '/rel="([^"]*)"/i',
507 'rel="$1 noopener noreferrer"',
508 $attrs
509 );
510 }
511 }
512 return '<a ' . $attrs . '>';
513 },
514 $sanitized
515 );
516
517 // Remove empty paragraphs and normalize whitespace
518 $sanitized = preg_replace('/<p[^>]*>(\s|&nbsp;|<br\s*\/?>)*<\/p>/i', '', $sanitized);
519
520 // Trim whitespace
521 $sanitized = trim($sanitized);
522
523 return $sanitized;
524 }
525
526 /**
527 * Prepare Quill HTML for display (output escaping)
528 *
529 * Use this when outputting sanitized Quill content to the frontend.
530 * This assumes the content was already sanitized with sanitizeQuillHtml() before storage.
531 *
532 * @param string $html Sanitized HTML from database
533 * @return string HTML safe for display
534 */
535 public static function displayQuillHtml(string $html): string
536 {
537 if (empty($html)) {
538 return '';
539 }
540
541 // Apply WordPress content filters (auto-paragraphs, shortcodes, etc.)
542 // But skip wpautop since Quill already handles paragraphs
543 remove_filter('the_content', 'wpautop');
544 $content = apply_filters('the_content', $html);
545 add_filter('the_content', 'wpautop');
546
547 return $content;
548 }
549
550 /**
551 * Strip all HTML tags from Quill content (for excerpts, meta descriptions, etc.)
552 *
553 * @param string $html Quill HTML content
554 * @return string Plain text
555 */
556 public static function quillToPlainText(string $html): string
557 {
558 if (empty($html)) {
559 return '';
560 }
561
562 // Remove all HTML tags
563 $text = wp_strip_all_tags($html);
564
565 // Normalize whitespace
566 $text = preg_replace('/\s+/', ' ', $text);
567
568 // Trim
569 $text = trim($text);
570
571 return $text;
572 }
573 }
574
575