PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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 2.0.11 All 82 releases
yatra / app / Helpers / SlugHelper.php

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

191 lines 6.2 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 * Slug Helper
9 * Generates URL-friendly slugs from text
10 * Uses WordPress sanitize_title() for consistency
11 */
12 class SlugHelper
13 {
14 /**
15 * Generate a slug from a string.
16 *
17 * Unicode-aware so Cyrillic / CJK / Devanagari / accented Latin survive.
18 * We avoid WordPress's sanitize_title() because for non-Latin input it
19 * percent-encodes the result (e.g. "моя-семья" → "%d0%bc..."). That
20 * percent-encoded form then gets stripped to "-" by `sanitize_text_field()`
21 * further down the BaseRepository write path, so the DB stores just a
22 * dash. Doing the normalisation here in raw Unicode lets the slug pass
23 * through `sanitize_text_field()` unchanged.
24 *
25 * @param string $text The text to convert to a slug
26 * @return string A URL-friendly slug
27 */
28 public static function generate(string $text): string
29 {
30 if ($text === '') {
31 return '';
32 }
33
34 // Decode any percent-encoded UTF-8 first. This matches WordPress core
35 // behaviour (see WP::parse_request / get_page_by_path) — when our
36 // route matchers receive a slug captured from a rewrite rule, it
37 // arrives in its URL-encoded form (e.g. `%D0%BC%D0%BE%D1%8F-...`
38 // for "моя-..."). Without this, the regex below would treat the `%`
39 // as invalid, strip everything, and produce a meaningless byte
40 // string. rawurldecode (instead of urldecode) preserves a literal
41 // `+` — a `+` is not URL-encoding for space in path segments, so we
42 // shouldn't accidentally turn it into one.
43 if (strpos($text, '%') !== false) {
44 $decoded = rawurldecode($text);
45 if ($decoded !== false && $decoded !== '') {
46 $text = $decoded;
47 }
48 }
49
50 // Lowercase. mb_strtolower handles Cyrillic / Greek / accented Latin
51 // correctly (strtolower is byte-wise and would mangle multibyte).
52 if (function_exists('mb_strtolower')) {
53 $text = mb_strtolower($text, 'UTF-8');
54 } else {
55 $text = strtolower($text);
56 }
57
58 // Strip HTML and decode entities so &amp; etc. don't bleed through.
59 $text = wp_strip_all_tags($text);
60 $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
61
62 // Replace whitespace / underscore runs with a single hyphen.
63 $text = preg_replace('/[\s_]+/u', '-', $text) ?? '';
64
65 // Keep letters (any script), digits, and hyphens. \pL + \pN with the
66 // `u` flag matches Unicode letter and number categories.
67 $text = preg_replace('/[^\pL\pN-]+/u', '', $text) ?? '';
68
69 // Collapse multiple consecutive hyphens.
70 $text = preg_replace('/-+/u', '-', $text) ?? '';
71
72 // Trim leading / trailing hyphens.
73 return trim($text, '-');
74 }
75
76 /**
77 * Generate a unique slug by appending a number if needed
78 *
79 * @param string $baseSlug The base slug
80 * @param array $existingSlugs Array of existing slugs to check against
81 * @return string A unique slug
82 */
83 public static function generateUnique(string $baseSlug, array $existingSlugs): string
84 {
85 $slug = self::generate($baseSlug);
86
87 if (empty($slug)) {
88 $slug = 'untitled';
89 }
90
91 // If slug is already unique, return it
92 if (!in_array($slug, $existingSlugs, true)) {
93 return $slug;
94 }
95
96 // Try appending numbers until we find a unique slug
97 $counter = 1;
98 $uniqueSlug = $slug . '-' . $counter;
99
100 while (in_array($uniqueSlug, $existingSlugs, true)) {
101 $counter++;
102 $uniqueSlug = $slug . '-' . $counter;
103 }
104
105 return $uniqueSlug;
106 }
107
108 /**
109 * Generate a unique slug by checking against database
110 *
111 * @param string $baseSlug The base slug
112 * @param string $tableName The table name (without prefix)
113 * @param string $columnName The column name (default: 'slug')
114 * @param int|null $excludeId ID to exclude from check (for updates)
115 * @return string A unique slug
116 */
117 public static function generateUniqueFromDatabase(
118 string $baseSlug,
119 string $tableName,
120 string $columnName = 'slug',
121 ?int $excludeId = null
122 ): string {
123 global $wpdb;
124
125 $slug = self::generate($baseSlug);
126
127 if (empty($slug)) {
128 $slug = 'untitled';
129 }
130
131 // Check if table name already has prefix to avoid double prefix
132 if (strpos($tableName, $wpdb->prefix) === 0) {
133 $table = $tableName; // Already has prefix
134 } else {
135 $table = $wpdb->prefix . $tableName; // Add prefix
136 }
137 $table = esc_sql($table);
138 $columnName = esc_sql($columnName);
139
140 // Check if slug exists
141 $query = $wpdb->prepare(
142 "SELECT COUNT(*) FROM `{$table}` WHERE `{$columnName}` = %s",
143 $slug
144 );
145
146 // Exclude current ID if updating
147 if ($excludeId !== null) {
148 $query = $wpdb->prepare(
149 "SELECT COUNT(*) FROM `{$table}` WHERE `{$columnName}` = %s AND id != %d",
150 $slug,
151 $excludeId
152 );
153 }
154
155 $exists = (int) $wpdb->get_var($query) > 0;
156
157 if (!$exists) {
158 return $slug;
159 }
160
161 // Try appending numbers until we find a unique slug
162 $counter = 1;
163 $uniqueSlug = $slug . '-' . $counter;
164
165 while (true) {
166 $checkQuery = $wpdb->prepare(
167 "SELECT COUNT(*) FROM `{$table}` WHERE `{$columnName}` = %s",
168 $uniqueSlug
169 );
170
171 if ($excludeId !== null) {
172 $checkQuery = $wpdb->prepare(
173 "SELECT COUNT(*) FROM `{$table}` WHERE `{$columnName}` = %s AND id != %d",
174 $uniqueSlug,
175 $excludeId
176 );
177 }
178
179 $exists = (int) $wpdb->get_var($checkQuery) > 0;
180
181 if (!$exists) {
182 return $uniqueSlug;
183 }
184
185 $counter++;
186 $uniqueSlug = $slug . '-' . $counter;
187 }
188 }
189 }
190
191