PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / admin / classes / admin-options / class-merchant-select2-choices.php

class-merchant-select2-choices.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at admin/classes/admin-options/class-merchant-select2-choices.php

426 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Merchant Select2 Choices.
4 *
5 * Provides formatted Select2 choice data for categories, tags,
6 * brands, user roles, customers, shipping zones, shipping methods,
7 * and payment gateways. Extracted from {@see Merchant_Admin_Options}
8 * to isolate data-provider concerns.
9 *
10 * @package Merchant
11 * @since 1.9.3
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Merchant_Select2_Choices
20 *
21 * Static data providers that return `{id, text}` arrays
22 * formatted for Select2 dropdowns in module settings.
23 *
24 * @since 1.9.3
25 */
26 class Merchant_Select2_Choices {
27
28 /**
29 * Get product category choices formatted for Select2.
30 *
31 * Retrieves all WooCommerce product categories and formats them
32 * as a flat array of `{id, text}` objects suitable for Select2,
33 * preserving hierarchical indentation via non-breaking spaces.
34 *
35 * @since 1.9.3
36 *
37 * @return array<int, array{id: string, text: string}> Formatted category choices.
38 */
39 public static function get_category_select2_choices() {
40 $choices = array();
41
42 // Bypass language filtering so every term is visible, including
43 // categories that exist only in a non-default language.
44 // Deduplication happens in build_category_select2_choices().
45 Merchant_Translator::switch_to_all_languages();
46 $categories = merchant_get_product_categories();
47 Merchant_Translator::restore_language();
48
49 if ( is_array( $categories ) && ! empty( $categories ) ) {
50 $choices = self::build_category_select2_choices( $categories );
51 }
52
53 return $choices;
54 }
55
56 /**
57 * Recursively build Select2 choices from hierarchical categories.
58 *
59 * @since 1.9.3
60 *
61 * @param array<int, array{id?: int, slug?: string, name?: string, children?: array<int, mixed>}> $categories Category data.
62 * @param int $level Current nesting depth (used for indentation).
63 *
64 * @return array<int, array{id: string, text: string}> Formatted choices.
65 */
66 private static function build_category_select2_choices( $categories, $level = 0 ) {
67 $choices = array();
68
69 foreach ( $categories as $cat ) {
70 $slug = $cat['slug'] ?? '';
71 $term_id = $cat['id'] ?? 0;
72
73 // Deduplicate: skip non-default language versions that have a default counterpart.
74 // Keeps the default-language term (ASCII slug) and orphans (no default version).
75 $default_term_id = Merchant_Translator::translate_term_to_default( $term_id, 'product_cat' );
76 if ( $default_term_id !== $term_id ) {
77 continue;
78 }
79
80 $indent = str_repeat( '&nbsp;', $level * 4 );
81 $label = self::resolve_term_label( $term_id, 'product_cat', $cat['name'] ?? '' );
82
83 $choices[] = array(
84 'id' => esc_attr( $slug ),
85 'text' => esc_html( $indent . $label ),
86 );
87
88 if ( ! empty( $cat['children'] ) ) {
89 $choices = array_merge( $choices, self::build_category_select2_choices( $cat['children'], $level + 1 ) );
90 }
91 }
92
93 return $choices;
94 }
95
96 /**
97 * Get product tag choices formatted for Select2.
98 *
99 * Retrieves all WooCommerce product tags and formats them
100 * as a flat array of `{id, text}` objects for Select2.
101 *
102 * @since 1.9.3
103 *
104 * @return array<int, array{id: string, text: string}> Formatted tag choices.
105 */
106 public static function get_tag_select2_choices() {
107 $choices = array();
108
109 // Bypass language filtering so every term is visible.
110 Merchant_Translator::switch_to_all_languages();
111 $tags = get_terms(
112 array(
113 'taxonomy' => 'product_tag',
114 'hide_empty' => false,
115 )
116 );
117 Merchant_Translator::restore_language();
118
119 if ( is_array( $tags ) && ! is_wp_error( $tags ) && ! empty( $tags ) ) { // @phpstan-ignore function.impossibleType
120 foreach ( $tags as $tag ) {
121 // Deduplicate: skip non-default language versions that have a default counterpart.
122 $default_tag_id = Merchant_Translator::translate_term_to_default( $tag->term_id, 'product_tag' );
123 if ( $default_tag_id !== $tag->term_id ) {
124 continue;
125 }
126
127 $label = self::resolve_term_label( $tag->term_id, 'product_tag', $tag->name );
128
129 $choices[] = array(
130 'id' => esc_attr( $tag->slug ),
131 'text' => esc_html( $label ),
132 );
133 }
134 }
135
136 return $choices;
137 }
138
139 /**
140 * Get product brand choices formatted for Select2.
141 *
142 * Retrieves all terms from the `product_brand` taxonomy
143 * (WooCommerce Brands) and formats them for Select2.
144 * Returns an empty array if the taxonomy does not exist.
145 *
146 * @since 1.9.3
147 *
148 * @return array<int, array{id: string, text: string}> Formatted brand choices.
149 */
150 public static function get_brand_select2_choices() {
151 $choices = array();
152
153 if ( ! taxonomy_exists( 'product_brand' ) ) {
154 return $choices;
155 }
156
157 // Bypass language filtering so every term is visible.
158 Merchant_Translator::switch_to_all_languages();
159 $brands = get_terms(
160 array(
161 'taxonomy' => 'product_brand',
162 'hide_empty' => false,
163 )
164 );
165 Merchant_Translator::restore_language();
166
167 if ( is_array( $brands ) && ! is_wp_error( $brands ) && ! empty( $brands ) ) { // @phpstan-ignore function.impossibleType
168 foreach ( $brands as $brand ) {
169 // Deduplicate: skip non-default language versions that have a default counterpart.
170 $default_brand_id = Merchant_Translator::translate_term_to_default( $brand->term_id, 'product_brand' );
171 if ( $default_brand_id !== $brand->term_id ) {
172 continue;
173 }
174
175 $label = self::resolve_term_label( $brand->term_id, 'product_brand', $brand->name );
176
177 $choices[] = array(
178 'id' => esc_attr( $brand->slug ),
179 'text' => esc_html( $label ),
180 );
181 }
182 }
183
184 return $choices;
185 }
186
187 /**
188 * Get weekday choices, Monday first, keyed by day name so a stored
189 * value reads for itself and Sunday is never a falsy zero.
190 *
191 * @since 2.3.2
192 *
193 * @return array<string, string> Day name => translated label.
194 */
195 public static function get_weekday_choices() {
196 /** @var WP_Locale $wp_locale */
197 $wp_locale = $GLOBALS['wp_locale'];
198
199 $names = array( 1 => 'mon', 2 => 'tue', 3 => 'wed', 4 => 'thu', 5 => 'fri', 6 => 'sat', 0 => 'sun' );
200 $labels = array();
201
202 foreach ( $names as $day => $name ) {
203 $labels[ $name ] = $wp_locale->get_weekday( $day );
204 }
205
206 return $labels;
207 }
208
209 /**
210 * Get shipping zone choices formatted for Select2, including the
211 * "rest of the world" zone (id 0).
212 *
213 * @since 2.3.2
214 *
215 * @return array<int, array{id: string, text: string}> Formatted zone choices.
216 */
217 public static function get_shipping_zone_select2_choices() {
218 if ( ! class_exists( 'WC_Shipping_Zones' ) ) {
219 return array();
220 }
221
222 $choices = array();
223
224 // get_zones() would also instantiate every zone's shipping methods, which this list never uses.
225 foreach ( WC_Shipping_Zones::get_shipping_zones() as $zone ) {
226 $choices[] = array(
227 'id' => (string) $zone->get_id(),
228 'text' => $zone->get_zone_name(),
229 );
230 }
231
232 $rest_of_world = new WC_Shipping_Zone( 0 );
233
234 $choices[] = array(
235 'id' => '0',
236 'text' => $rest_of_world->get_zone_name(),
237 );
238
239 return $choices;
240 }
241
242 /**
243 * Get registered shipping method choices formatted for Select2.
244 *
245 * @since 2.3.2
246 *
247 * @return array<int, array{id: string, text: string}> Formatted method choices.
248 */
249 public static function get_shipping_method_select2_choices() {
250 if ( ! function_exists( 'WC' ) ) {
251 return array();
252 }
253
254 $choices = array();
255
256 foreach ( WC()->shipping()->get_shipping_methods() as $method_id => $method ) {
257 $choices[] = array(
258 'id' => (string) $method_id,
259 'text' => $method->get_method_title(),
260 );
261 }
262
263 return $choices;
264 }
265
266 /**
267 * Get enabled payment gateway choices formatted for Select2.
268 *
269 * @since 2.3.2
270 *
271 * @return array<int, array{id: string, text: string}> Formatted gateway choices.
272 */
273 public static function get_payment_gateway_select2_choices() {
274 if ( ! function_exists( 'WC' ) ) {
275 return array();
276 }
277
278 $choices = array();
279
280 foreach ( WC()->payment_gateways()->payment_gateways() as $gateway_id => $gateway ) {
281 if ( 'yes' !== $gateway->enabled ) {
282 continue;
283 }
284
285 $choices[] = array(
286 'id' => (string) $gateway_id,
287 'text' => $gateway->get_method_title(),
288 );
289 }
290
291 return $choices;
292 }
293
294 /**
295 * Resolve the current-language label for a term using its ID.
296 *
297 * Translates the default-language term_id to the current language,
298 * then fetches the translated term's name. Falls back to the provided
299 * default name when no translation exists.
300 *
301 * @since 2.1.9
302 *
303 * @param int $term_id Default-language term ID.
304 * @param string $taxonomy Taxonomy name.
305 * @param string $default_name Fallback name if no translation exists.
306 *
307 * @return string The current-language term name, or $default_name.
308 */
309 private static function resolve_term_label( $term_id, $taxonomy, $default_name ) {
310 if ( empty( $term_id ) ) {
311 return $default_name;
312 }
313
314 // translate_term( int ) returns the translated term_id via pll_get_term / wpml_object_id.
315 $translated_id = Merchant_Translator::translate_term( $term_id, $taxonomy );
316
317 if ( $translated_id === $term_id ) {
318 // No translation exists, keep the default name.
319 return $default_name;
320 }
321
322 $translated_term = get_term( (int) $translated_id, $taxonomy );
323
324 if ( $translated_term instanceof \WP_Term ) {
325 return $translated_term->name;
326 }
327
328 return $default_name;
329 }
330
331 /**
332 * Get user role choices formatted for Select2.
333 *
334 * Retrieves all editable WordPress user roles and formats
335 * them as `{id, text}` objects for Select2 dropdowns.
336 *
337 * @since 1.9.3
338 *
339 * @return array<int, array{id: string, text: string}> Formatted role choices.
340 */
341 public static function get_user_roles_select2_choices() {
342 $choices = array();
343
344 // get_editable_roles() is in wp-admin/includes/user.php
345 // which is not loaded outside admin context (e.g. REST API).
346 if ( ! function_exists( 'get_editable_roles' ) ) {
347 require_once ABSPATH . 'wp-admin/includes/user.php';
348 }
349
350 $user_roles = get_editable_roles();
351
352 if ( ! empty( $user_roles ) ) {
353 foreach ( $user_roles as $role_id => $role_data ) {
354 $choices[] = array(
355 'id' => $role_id,
356 'text' => $role_data['name'],
357 );
358 }
359 }
360
361 return $choices;
362 }
363
364 /**
365 * Get customer user choices formatted for Select2.
366 *
367 * Retrieves all users with the `customer` role and returns them
368 * as `{id, text}` objects. Results are cached in a transient
369 * with no expiration; the cache is cleared via
370 * {@see Merchant_Select2_Choices::clear_customer_choices_cache()}.
371 *
372 * @since 1.9.3
373 *
374 * @return array<int, array{id: int, text: string}> Formatted customer choices.
375 */
376 public static function get_customers_select2_choices() {
377 $cache_key = 'customers_select2_choices';
378 $choices = get_transient( $cache_key );
379
380 if ( ! empty( $choices ) && is_array( $choices ) ) {
381 return $choices;
382 }
383
384 $customer_users = get_users(
385 array(
386 'role' => 'customer',
387 'fields' => array( 'ID', 'display_name' ),
388 )
389 );
390
391 $choices = array();
392 if ( ! empty( $customer_users ) ) {
393 foreach ( $customer_users as $user ) {
394 $choices[] = array(
395 'id' => $user->ID,
396 'text' => $user->display_name,
397 );
398 }
399 }
400
401 set_transient( $cache_key, $choices );
402
403 return $choices;
404 }
405
406 /**
407 * Clear the customers Select2 choices transient cache.
408 *
409 * Hooked to `clean_user_cache` to invalidate the customer
410 * list whenever a customer user is created, updated, or deleted.
411 *
412 * @since 1.9.3
413 *
414 * @param int $user_id The user ID being cleaned.
415 * @param WP_User $user The user object.
416 *
417 * @return void
418 */
419 public function clear_customer_choices_cache( $user_id, $user ) {
420 $user_roles = ! empty( $user->roles ) ? $user->roles : array();
421 if ( in_array( 'customer', $user_roles, true ) ) {
422 delete_transient( 'customers_select2_choices' );
423 }
424 }
425 }
426