PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
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 2.0.10 All 83 releases
yatra / app / Helpers / FormatHelper.php

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

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