PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.18
ووسلام – همگام سازی ووکامرس و باسلام v1.10.18
1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 1.8.5 1.8.6 1.8.7 1.8.4 All 51 releases
sync-basalam / includes / Utilities / GetProvincesData.php

GetProvincesData.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.18, at includes/Utilities/GetProvincesData.php

316 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SyncBasalam\Utilities;
4
5 class GetProvincesData
6 {
7 public static array $provinces = [
8 'KHZ' => 'خوزستان',
9 'THR' => 'تهران',
10 'ILM' => 'ایلا�
11 ',
12 'BHR' => 'بوشهر',
13 'ADL' => 'اردبیل',
14 'ESF' => 'اصفهان',
15 'YZD' => 'یزد',
16 'KRH' => 'کر�
17 انشاه',
18 'KRN' => 'کر�
19 ان',
20 'HDN' => 'ه�
21 دان',
22 'GZN' => 'قزوین',
23 'ZJN' => 'زنجان',
24 'LRS' => 'لرستان',
25 'ABZ' => 'البرز',
26 'EAZ' => 'آذربایجان شرقی',
27 'WAZ' => 'آذربایجان غربی',
28 'CHB' => 'چهار�
29 حال و بختیاری',
30 'SKH' => 'خراسان جنوبی',
31 'RKH' => 'خراسان رضوی',
32 'NKH' => 'خراسان ش�
33 الی',
34 'SMN' => 'س�
35 نان',
36 'FRS' => 'فارس',
37 'QHM' => 'ق�
38 ',
39 'KRD' => 'کردستان',
40 'KBD' => 'کهگیلویه و بویراح�
41 د',
42 'GLS' => 'گلستان',
43 'GIL' => 'گیلان',
44 'MZN' => '�
45 ازندران',
46 'MKZ' => '�
47 رکزی',
48 'HRZ' => 'هر�
49 زگان',
50 'SBN' => 'سیستان و بلوچستان',
51 ];
52
53 public static function getCodeByName(string $persianName): ?string
54 {
55 $needle = self::normalizePersian($persianName);
56 foreach (self::$provinces as $code => $name) {
57 if (self::normalizePersian($name) === $needle) return $code;
58 }
59
60 return null;
61 }
62
63 public static function getNameByCode(string $code): ?string
64 {
65 return self::$provinces[$code] ?? null;
66 }
67
68 /**
69 * Check if Persian WooCommerce Shipping plugin is active.
70 */
71 public static function isPWSActive(): bool
72 {
73 return in_array(
74 'persian-woocommerce-shipping/woocommerce-shipping.php',
75 (array) get_option('active_plugins', [])
76 );
77 }
78
79 /**
80 * Register IR states with WooCommerce when no other plugin has done so.
81 * WooCommerce won't persist `billing_state` for a country that has registered
82 * states unless the value matches a registered key — and it ships zero states
83 * for IR. Without this filter, set_billing_state('یزد') is silently dropped
84 * on stores that have no Persian shipping/state plugin active.
85 *
86 * Safe to call multiple times; if another plugin already provides IR states
87 * we leave them alone.
88 */
89 public static function registerIranStatesFilter(): void
90 {
91 static $registered = false;
92 if ($registered) return;
93 $registered = true;
94
95 add_filter('woocommerce_states', function ($states) {
96 if (!empty($states['IR'])) return $states;
97
98 $states['IR'] = self::$provinces;
99
100 return $states;
101 }, 5);
102 }
103
104 /**
105 * Whether PWS is running in Tapin mode (state IDs come from tapin.json, 1..31).
106 * Otherwise PWS falls back to the `state_city` taxonomy term IDs.
107 */
108 private static function isTapinEnabled(): bool
109 {
110 if (!class_exists('\PWS_Tapin')) return false;
111 if (!method_exists('\PWS_Tapin', 'is_enable')) return false;
112
113 return (bool) call_user_func(['\PWS_Tapin', 'is_enable']);
114 }
115
116 /**
117 * Normalize Persian/Arabic text so name comparisons survive small variations
118 * (Arabic Yeh/Kaf, ZWNJ / half-space, repeated whitespace, BOM, etc.).
119 */
120 private static function normalizePersian(?string $value): string
121 {
122 if ($value === null) return '';
123
124 $value = str_replace(
125 ["\xEF\xBB\xBF", "\xE2\x80\x8C", "\xC2\xA0", 'ي', 'ك', '', '', '', ''],
126 ['', ' ', ' ', 'ی', 'ک', 'ک', 'ک', 'ی', 'ی'],
127 $value
128 );
129
130 $value = preg_replace('/\s+/u', ' ', $value);
131
132 return trim((string) $value);
133 }
134
135 /**
136 * Set order address with PWS compatibility.
137 *
138 * - When PWS+Tapin is active: store Tapin numeric state/city IDs in both the
139 * WC billing_state/billing_city fields (so WC's state lookup resolves the
140 * Persian name correctly) AND in PWS's `_billing_state_id`/`_billing_city_id`
141 * meta (which Tapin label submission reads).
142 * - When PWS is active without Tapin: resolve the `state_city` taxonomy
143 * term IDs and use those.
144 * - Otherwise fall back to plain Persian names.
145 */
146 public static function setOrderAddress($order, $addressData, $type = 'billing')
147 {
148 if (!$order || !isset($addressData['province']) || !isset($addressData['city'])) {
149 return;
150 }
151
152 $province = trim((string) $addressData['province']);
153 $city = trim((string) $addressData['city']);
154
155 if ($province === '' && $city === '') {
156 return;
157 }
158
159 // WooCommerce validates billing_state against the registered keys of
160 // states['IR'], so the state value must be a valid KEY (numeric ID in
161 // PWS, 3-letter code without PWS). The city field has no such
162 // validation, so we always keep the human-readable Persian name there —
163 // otherwise the admin order screen would render the raw term ID.
164 $stateValue = $province;
165 $cityValue = $city;
166 $stateMetaId = null;
167 $cityMetaId = null;
168
169 if (self::isPWSActive()) {
170 if (self::isTapinEnabled()) {
171 $stateMetaId = self::getTapinStateIdByName($province);
172 $cityMetaId = self::getTapinCityIdByName($city, $stateMetaId);
173 } else {
174 $stateMetaId = self::getPwsStateTermIdByName($province);
175 $cityMetaId = self::getPwsCityTermIdByName($city, $stateMetaId);
176 }
177
178 if ($stateMetaId) $stateValue = (string) $stateMetaId;
179 } else {
180 // No PWS — make sure WC has IR states registered so it doesn't drop
181 // the billing_state on save, then store the code instead of the name.
182 self::registerIranStatesFilter();
183 $code = self::getCodeByName($province);
184 if ($code) {
185 $stateValue = $code;
186 }
187 }
188
189 if ($type === 'billing') {
190 $order->set_billing_state($stateValue);
191 $order->set_billing_city($cityValue);
192 } else {
193 $order->set_shipping_state($stateValue);
194 $order->set_shipping_city($cityValue);
195 }
196
197 if ($stateMetaId) {
198 $order->update_meta_data("_{$type}_state_id", $stateMetaId);
199 }
200 if ($cityMetaId) {
201 $order->update_meta_data("_{$type}_city_id", $cityMetaId);
202 }
203 }
204
205 /**
206 * Get Tapin state ID by Persian name.
207 */
208 private static function getTapinStateIdByName($stateName): ?int
209 {
210 if (!class_exists('\PWS_Tapin')) return null;
211
212 $states = call_user_func(['\PWS_Tapin', 'states']);
213 if (!$states) return null;
214
215 $needle = self::normalizePersian($stateName);
216 if ($needle === '') return null;
217
218 foreach ($states as $state_id => $state_title) {
219 if (self::normalizePersian((string) $state_title) === $needle) {
220 return (int) $state_id;
221 }
222 }
223
224 return null;
225 }
226
227 /**
228 * Get Tapin city ID by Persian name.
229 * If $stateId is provided the search is constrained to that state — this
230 * avoids the failure mode where a null state_id lets PWS_Tapin::cities()
231 * search across ALL cities and return the wrong match.
232 */
233 private static function getTapinCityIdByName($cityName, $stateId = null): ?int
234 {
235 if (!class_exists('\PWS_Tapin')) return null;
236
237 $needle = self::normalizePersian($cityName);
238 if ($needle === '') return null;
239
240 if ($stateId) {
241 $cities = call_user_func(['\PWS_Tapin', 'cities'], $stateId);
242 if (!$cities) return null;
243
244 foreach ($cities as $city_id => $city_title) {
245 if (self::normalizePersian((string) $city_title) === $needle) {
246 return (int) $city_id;
247 }
248 }
249
250 return null;
251 }
252
253 return null;
254 }
255
256 /**
257 * Resolve a `state_city` taxonomy term ID for the given province name
258 * (used when PWS is active but Tapin is not).
259 */
260 private static function getPwsStateTermIdByName($stateName): ?int
261 {
262 if (!taxonomy_exists('state_city')) return null;
263
264 $needle = self::normalizePersian($stateName);
265 if ($needle === '') return null;
266
267 $terms = get_terms([
268 'taxonomy' => 'state_city',
269 'hide_empty' => false,
270 'parent' => 0,
271 ]);
272
273 if (is_wp_error($terms) || empty($terms)) return null;
274
275 foreach ($terms as $term) {
276 if (self::normalizePersian((string) $term->name) === $needle) {
277 return (int) $term->term_id;
278 }
279 }
280
281 return null;
282 }
283
284 /**
285 * Resolve a `state_city` taxonomy term ID for the given city name.
286 * Scoped to the province term when available.
287 */
288 private static function getPwsCityTermIdByName($cityName, $stateTermId = null): ?int
289 {
290 if (!taxonomy_exists('state_city')) return null;
291
292 $needle = self::normalizePersian($cityName);
293 if ($needle === '') return null;
294
295 $args = [
296 'taxonomy' => 'state_city',
297 'hide_empty' => false,
298 ];
299
300 if ($stateTermId) {
301 $args['parent'] = $stateTermId;
302 }
303
304 $terms = get_terms($args);
305 if (is_wp_error($terms) || empty($terms)) return null;
306
307 foreach ($terms as $term) {
308 if (self::normalizePersian((string) $term->name) === $needle) {
309 return (int) $term->term_id;
310 }
311 }
312
313 return null;
314 }
315 }
316