PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.2.7
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.2.7
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.2.7, at admin/classes/admin-options/class-merchant-select2-choices.php

311 lines 8.8 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, and customers. Extracted from
7 * {@see Merchant_Admin_Options} to isolate data-provider concerns.
8 *
9 * @package Merchant
10 * @since 1.9.3
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Merchant_Select2_Choices
19 *
20 * Static data providers that return `{id, text}` arrays
21 * formatted for Select2 dropdowns in module settings.
22 *
23 * @since 1.9.3
24 */
25 class Merchant_Select2_Choices {
26
27 /**
28 * Get product category choices formatted for Select2.
29 *
30 * Retrieves all WooCommerce product categories and formats them
31 * as a flat array of `{id, text}` objects suitable for Select2,
32 * preserving hierarchical indentation via non-breaking spaces.
33 *
34 * @since 1.9.3
35 *
36 * @return array<int, array{id: string, text: string}> Formatted category choices.
37 */
38 public static function get_category_select2_choices() {
39 $choices = array();
40
41 // Bypass language filtering so every term is visible, including
42 // categories that exist only in a non-default language.
43 // Deduplication happens in build_category_select2_choices().
44 Merchant_Translator::switch_to_all_languages();
45 $categories = merchant_get_product_categories();
46 Merchant_Translator::restore_language();
47
48 if ( is_array( $categories ) && ! empty( $categories ) ) {
49 $choices = self::build_category_select2_choices( $categories );
50 }
51
52 return $choices;
53 }
54
55 /**
56 * Recursively build Select2 choices from hierarchical categories.
57 *
58 * @since 1.9.3
59 *
60 * @param array<int, array{id?: int, slug?: string, name?: string, children?: array<int, mixed>}> $categories Category data.
61 * @param int $level Current nesting depth (used for indentation).
62 *
63 * @return array<int, array{id: string, text: string}> Formatted choices.
64 */
65 private static function build_category_select2_choices( $categories, $level = 0 ) {
66 $choices = array();
67
68 foreach ( $categories as $cat ) {
69 $slug = $cat['slug'] ?? '';
70 $term_id = $cat['id'] ?? 0;
71
72 // Deduplicate: skip non-default language versions that have a default counterpart.
73 // Keeps the default-language term (ASCII slug) and orphans (no default version).
74 $default_term_id = Merchant_Translator::translate_term_to_default( $term_id, 'product_cat' );
75 if ( $default_term_id !== $term_id ) {
76 continue;
77 }
78
79 $indent = str_repeat( '&nbsp;', $level * 4 );
80 $label = self::resolve_term_label( $term_id, 'product_cat', $cat['name'] ?? '' );
81
82 $choices[] = array(
83 'id' => esc_attr( $slug ),
84 'text' => esc_html( $indent . $label ),
85 );
86
87 if ( ! empty( $cat['children'] ) ) {
88 $choices = array_merge( $choices, self::build_category_select2_choices( $cat['children'], $level + 1 ) );
89 }
90 }
91
92 return $choices;
93 }
94
95 /**
96 * Get product tag choices formatted for Select2.
97 *
98 * Retrieves all WooCommerce product tags and formats them
99 * as a flat array of `{id, text}` objects for Select2.
100 *
101 * @since 1.9.3
102 *
103 * @return array<int, array{id: string, text: string}> Formatted tag choices.
104 */
105 public static function get_tag_select2_choices() {
106 $choices = array();
107
108 // Bypass language filtering so every term is visible.
109 Merchant_Translator::switch_to_all_languages();
110 $tags = get_terms(
111 array(
112 'taxonomy' => 'product_tag',
113 'hide_empty' => false,
114 )
115 );
116 Merchant_Translator::restore_language();
117
118 if ( is_array( $tags ) && ! is_wp_error( $tags ) && ! empty( $tags ) ) { // @phpstan-ignore function.impossibleType
119 foreach ( $tags as $tag ) {
120 // Deduplicate: skip non-default language versions that have a default counterpart.
121 $default_tag_id = Merchant_Translator::translate_term_to_default( $tag->term_id, 'product_tag' );
122 if ( $default_tag_id !== $tag->term_id ) {
123 continue;
124 }
125
126 $label = self::resolve_term_label( $tag->term_id, 'product_tag', $tag->name );
127
128 $choices[] = array(
129 'id' => esc_attr( $tag->slug ),
130 'text' => esc_html( $label ),
131 );
132 }
133 }
134
135 return $choices;
136 }
137
138 /**
139 * Get product brand choices formatted for Select2.
140 *
141 * Retrieves all terms from the `product_brand` taxonomy
142 * (WooCommerce Brands) and formats them for Select2.
143 * Returns an empty array if the taxonomy does not exist.
144 *
145 * @since 1.9.3
146 *
147 * @return array<int, array{id: string, text: string}> Formatted brand choices.
148 */
149 public static function get_brand_select2_choices() {
150 $choices = array();
151
152 if ( ! taxonomy_exists( 'product_brand' ) ) {
153 return $choices;
154 }
155
156 // Bypass language filtering so every term is visible.
157 Merchant_Translator::switch_to_all_languages();
158 $brands = get_terms(
159 array(
160 'taxonomy' => 'product_brand',
161 'hide_empty' => false,
162 )
163 );
164 Merchant_Translator::restore_language();
165
166 if ( is_array( $brands ) && ! is_wp_error( $brands ) && ! empty( $brands ) ) { // @phpstan-ignore function.impossibleType
167 foreach ( $brands as $brand ) {
168 // Deduplicate: skip non-default language versions that have a default counterpart.
169 $default_brand_id = Merchant_Translator::translate_term_to_default( $brand->term_id, 'product_brand' );
170 if ( $default_brand_id !== $brand->term_id ) {
171 continue;
172 }
173
174 $label = self::resolve_term_label( $brand->term_id, 'product_brand', $brand->name );
175
176 $choices[] = array(
177 'id' => esc_attr( $brand->slug ),
178 'text' => esc_html( $label ),
179 );
180 }
181 }
182
183 return $choices;
184 }
185
186 /**
187 * Resolve the current-language label for a term using its ID.
188 *
189 * Translates the default-language term_id to the current language,
190 * then fetches the translated term's name. Falls back to the provided
191 * default name when no translation exists.
192 *
193 * @since 2.1.9
194 *
195 * @param int $term_id Default-language term ID.
196 * @param string $taxonomy Taxonomy name.
197 * @param string $default_name Fallback name if no translation exists.
198 *
199 * @return string The current-language term name, or $default_name.
200 */
201 private static function resolve_term_label( $term_id, $taxonomy, $default_name ) {
202 if ( empty( $term_id ) ) {
203 return $default_name;
204 }
205
206 // translate_term( int ) returns the translated term_id via pll_get_term / wpml_object_id.
207 $translated_id = Merchant_Translator::translate_term( $term_id, $taxonomy );
208
209 if ( $translated_id === $term_id ) {
210 // No translation exists, keep the default name.
211 return $default_name;
212 }
213
214 $translated_term = get_term( (int) $translated_id, $taxonomy );
215
216 if ( $translated_term instanceof \WP_Term ) {
217 return $translated_term->name;
218 }
219
220 return $default_name;
221 }
222
223 /**
224 * Get user role choices formatted for Select2.
225 *
226 * Retrieves all editable WordPress user roles and formats
227 * them as `{id, text}` objects for Select2 dropdowns.
228 *
229 * @since 1.9.3
230 *
231 * @return array<int, array{id: string, text: string}> Formatted role choices.
232 */
233 public static function get_user_roles_select2_choices() {
234 $choices = array();
235 $user_roles = get_editable_roles();
236
237 if ( ! empty( $user_roles ) ) {
238 foreach ( $user_roles as $role_id => $role_data ) {
239 $choices[] = array(
240 'id' => $role_id,
241 'text' => $role_data['name'],
242 );
243 }
244 }
245
246 return $choices;
247 }
248
249 /**
250 * Get customer user choices formatted for Select2.
251 *
252 * Retrieves all users with the `customer` role and returns them
253 * as `{id, text}` objects. Results are cached in a transient
254 * with no expiration; the cache is cleared via
255 * {@see Merchant_Select2_Choices::clear_customer_choices_cache()}.
256 *
257 * @since 1.9.3
258 *
259 * @return array<int, array{id: int, text: string}> Formatted customer choices.
260 */
261 public static function get_customers_select2_choices() {
262 $cache_key = 'customers_select2_choices';
263 $choices = get_transient( $cache_key );
264
265 if ( ! empty( $choices ) && is_array( $choices ) ) {
266 return $choices;
267 }
268
269 $customer_users = get_users(
270 array(
271 'role' => 'customer',
272 'fields' => array( 'ID', 'display_name' ),
273 )
274 );
275
276 $choices = array();
277 if ( ! empty( $customer_users ) ) {
278 foreach ( $customer_users as $user ) {
279 $choices[] = array(
280 'id' => $user->ID,
281 'text' => $user->display_name,
282 );
283 }
284 }
285
286 set_transient( $cache_key, $choices );
287
288 return $choices;
289 }
290
291 /**
292 * Clear the customers Select2 choices transient cache.
293 *
294 * Hooked to `clean_user_cache` to invalidate the customer
295 * list whenever a customer user is created, updated, or deleted.
296 *
297 * @since 1.9.3
298 *
299 * @param int $user_id The user ID being cleaned.
300 * @param WP_User $user The user object.
301 *
302 * @return void
303 */
304 public function clear_customer_choices_cache( $user_id, $user ) {
305 $user_roles = ! empty( $user->roles ) ? $user->roles : array();
306 if ( in_array( 'customer', $user_roles, true ) ) {
307 delete_transient( 'customers_select2_choices' );
308 }
309 }
310 }
311