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
← All changes | admin/classes/class-merchant-admin-options.php +262 -2489 1.10.42.3.2 View file →
@@ -1,7 +1,15 @@
1 1 <?php
2 2 /**
3 - * Merchant_Admin_Options Class.
3 + * Merchant Admin Options.
4 + *
5 + * Thin orchestrator that manages module settings. Delegates rendering
6 + * to {@see Merchant_Settings_Renderer}, saving to {@see Merchant_Settings_Saver},
7 + * asset enqueueing to {@see Merchant_Admin_Assets}, and Select2 data
8 + * to {@see Merchant_Select2_Choices}.
9 + *
10 + * @package Merchant
11 + * @since 1.0
4 12 */
5 13
6 14 if ( ! defined( 'ABSPATH' ) ) {
7 15 exit; // Exit if accessed directly
@@ -7,17 +15,36 @@
7 15 exit; // Exit if accessed directly
8 16 }
9 17
10 18 if ( ! class_exists( 'Merchant_Admin_Options' ) ) {
19 + /**
20 + * Merchant_Admin_Options
21 + *
22 + * Orchestrates the Merchant admin settings panel. Core responsibilities:
23 + * - CRUD operations for module settings (`get`, `set`, `delete`, `get_all`)
24 + * - Wiring admin hooks (assets, cache, cleanup, Pro activation)
25 + * - Backward-compatible facades that delegate to extracted classes
26 + *
27 + * This class is a singleton; use {@see Merchant_Admin_Options::instance()} to retrieve it.
28 + *
29 + * @since 1.0
30 + */
11 31 class Merchant_Admin_Options {
12 32
13 33 /**
14 34 * The single class instance.
35 + *
36 + * @since 1.0
37 + * @var Merchant_Admin_Options|null
15 38 */
16 39 private static $instance = null;
17 40
18 41 /**
19 - * Instance.
42 + * Get the singleton instance.
43 + *
44 + * @since 1.0
45 + *
46 + * @return Merchant_Admin_Options
20 47 */
21 48 public static function instance() {
22 49 if ( is_null( self::$instance ) ) {
23 50 self::$instance = new self();
@@ -27,221 +54,44 @@
27 54 }
28 55
29 56 /**
30 57 * Constructor.
58 + *
59 + * Registers admin hooks for script enqueueing, cache clearance,
60 + * legacy data cleanup, and Pro plugin activation.
61 + *
62 + * @since 1.0
31 63 */
32 64 public function __construct() {
33 - // Enqueue hooks.
34 - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
65 + $assets = new Merchant_Admin_Assets();
66 + $choices = new Merchant_Select2_Choices();
35 67
36 - // Ajax callbacks.
37 - add_action( 'wp_ajax_merchant_create_page_control', array( $this, 'create_page_control_ajax_callback' ) );
38 - add_action( 'wp_ajax_merchant_admin_options_select_ajax', array( $this, 'select_content_ajax' ) );
39 - add_action( 'wp_ajax_merchant_admin_products_search', array( $this, 'products_search' ) );
68 + add_action( 'admin_enqueue_scripts', array( $assets, 'enqueue_scripts' ) );
69 + add_action( 'clean_user_cache', array( $choices, 'clear_customer_choices_cache' ), 10, 2 );
40 70
41 - add_action( 'clean_user_cache', array( $this, 'clear_customer_choices_cache' ), 10, 2 );
71 + add_action( 'admin_init', array( $this, 'delete_module_data' ) );
72 + add_action( 'admin_init', array( $this, 'activate_pro_plugin' ) );
42 73 }
43 74
44 - /**
45 - * Enqueue scripts.
46 - */
47 - public function enqueue_scripts() {
48 - if (
49 - isset( $_GET['page'] ) && 'merchant' === $_GET['page'] // phpcs:ignore WordPress.Security.NonceVerification.Recommended
50 - && isset( $_GET['module'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
51 - ) {
52 - wp_enqueue_script( 'merchant-select2', MERCHANT_URI . 'assets/vendor/select2/select2.full.min.js', array( 'jquery' ), '4.0.13', true );
53 - wp_enqueue_style( 'merchant-select2', MERCHANT_URI . 'assets/vendor/select2/select2.min.css', array(), '4.0.13', 'all' );
75 + // ──────────────────────────────────────────────────────
76 + // CRUD — core orchestrator responsibility.
77 + // ──────────────────────────────────────────────────────
54 78
55 - wp_localize_script( 'merchant-select2', 'merchant_admin_options', array(
56 - 'ajaxurl' => admin_url( 'admin-ajax.php' ),
57 - 'ajaxnonce' => wp_create_nonce( 'merchant_admin_options' ),
58 - 'product_delete_confirmation_message' => esc_html__( 'Are you sure you want to remove this product?', 'merchant' ),
59 - ) );
60 -
61 - wp_enqueue_style('date-picker', MERCHANT_URI . 'assets/vendor/air-datepicker/air-datepicker.css', array(), MERCHANT_VERSION, 'all' );
62 - wp_enqueue_script('date-picker', MERCHANT_URI . 'assets/vendor/air-datepicker/air-datepicker.js', array( 'jquery' ), MERCHANT_VERSION, true );
63 - wp_localize_script( 'date-picker', 'merchant_datepicker_locale', array(
64 - wp_json_encode(
65 - array(
66 - 'days' => array(
67 - esc_html__( 'Sunday', 'merchant' ),
68 - esc_html__( 'Monday', 'merchant' ),
69 - esc_html__( 'Tuesday', 'merchant' ),
70 - esc_html__( 'Wednesday', 'merchant' ),
71 - esc_html__( 'Thursday', 'merchant' ),
72 - esc_html__( 'Friday', 'merchant' ),
73 - esc_html__( 'Saturday', 'merchant' ),
74 - ),
75 - 'daysShort' => array(
76 - esc_html__( 'Sun', 'merchant' ),
77 - esc_html__( 'Mon', 'merchant' ),
78 - esc_html__( 'Tue', 'merchant' ),
79 - esc_html__( 'Wed', 'merchant' ),
80 - esc_html__( 'Thu', 'merchant' ),
81 - esc_html__( 'Fri', 'merchant' ),
82 - esc_html__( 'Sat', 'merchant' ),
83 - ),
84 - 'daysMin' => array(
85 - esc_html__( 'Su', 'merchant' ),
86 - esc_html__( 'Mo', 'merchant' ),
87 - esc_html__( 'Tu', 'merchant' ),
88 - esc_html__( 'We', 'merchant' ),
89 - esc_html__( 'Th', 'merchant' ),
90 - esc_html__( 'Fr', 'merchant' ),
91 - esc_html__( 'Sa', 'merchant' ),
92 - ),
93 - 'months' => array(
94 - esc_html__( 'January', 'merchant' ),
95 - esc_html__( 'February', 'merchant' ),
96 - esc_html__( 'March', 'merchant' ),
97 - esc_html__( 'April', 'merchant' ),
98 - esc_html__( 'May', 'merchant' ),
99 - esc_html__( 'June', 'merchant' ),
100 - esc_html__( 'July', 'merchant' ),
101 - esc_html__( 'August', 'merchant' ),
102 - esc_html__( 'September', 'merchant' ),
103 - esc_html__( 'October', 'merchant' ),
104 - esc_html__( 'November', 'merchant' ),
105 - esc_html__( 'December', 'merchant' ),
106 - ),
107 - 'monthsShort' => array(
108 - esc_html__( 'Jan', 'merchant' ),
109 - esc_html__( 'Feb', 'merchant' ),
110 - esc_html__( 'Mar', 'merchant' ),
111 - esc_html__( 'Apr', 'merchant' ),
112 - esc_html__( 'May', 'merchant' ),
113 - esc_html__( 'Jun', 'merchant' ),
114 - esc_html__( 'Jul', 'merchant' ),
115 - esc_html__( 'Aug', 'merchant' ),
116 - esc_html__( 'Sep', 'merchant' ),
117 - esc_html__( 'Oct', 'merchant' ),
118 - esc_html__( 'Nov', 'merchant' ),
119 - esc_html__( 'Dec', 'merchant' ),
120 - ),
121 - 'clear' => esc_html__( 'Clear', 'merchant' ),
122 - )
123 - ),
124 - ) );
125 - }
126 - }
127 -
128 79 /**
129 - * Ajax callbacks.
130 - */
131 - public function create_page_control_ajax_callback() {
132 - check_ajax_referer( 'customize-create-page-control-nonce', 'nonce' );
133 -
134 - // Check current user capabilities
135 - if ( ! current_user_can( 'manage_options' ) ) {
136 - wp_send_json_error( 'You are not allowed to do this.' );
137 - }
138 -
139 - $page_title = isset( $_POST['page_title'] ) ? sanitize_text_field( $_POST['page_title'] ) : '';
140 - $page_meta_key = isset( $_POST['page_meta_key'] ) ? sanitize_text_field( $_POST['page_meta_key'] ) : '';
141 - $page_meta_value = isset( $_POST['page_meta_value'] ) ? sanitize_text_field( $_POST['page_meta_value'] ) : '';
142 - $option_name = isset( $_POST['option_name'] ) ? sanitize_text_field( $_POST['option_name'] ) : '';
143 -
144 - $meta_input = array();
145 - if ( $page_meta_key && $page_meta_value ) {
146 - $meta_input = array(
147 - $page_meta_key => $page_meta_value,
148 - );
149 - }
150 -
151 - $postarr = array(
152 - 'post_type' => 'page',
153 - 'post_status' => 'publish',
154 - 'post_title' => $page_title,
155 - 'post_content' => '',
156 - 'meta_input' => $meta_input,
157 - );
158 -
159 - $page_id = wp_insert_post( $postarr );
160 -
161 - if ( ! is_wp_error( $page_id ) ) {
162 - if ( $option_name ) {
163 - update_option( $option_name, $page_id );
164 - }
165 -
166 - wp_send_json( array(
167 - 'status' => 'success',
168 - 'page_id' => $page_id,
169 - ) );
170 - } else {
171 - wp_send_json( array(
172 - 'status' => 'error',
173 - ) );
174 - }
175 - }
176 -
177 - /**
178 - * Select content ajax callback.
80 + * Get a single module setting value.
179 81 *
82 + * Retrieves the value of a specific setting for a given module
83 + * from the `merchant` option. Falls back to `$default_val` if
84 + * the setting does not exist.
85 + *
86 + * @since 1.0
87 + *
88 + * @param string $module The module ID (e.g. 'buy-x-get-y').
89 + * @param string $setting The setting key within the module.
90 + * @param mixed $default_val The default value if the setting is not found.
91 + *
92 + * @return mixed The setting value, filtered via `merchant_get_option`.
180 93 */
181 - public function select_content_ajax() {
182 - $term = ( isset( $_GET['term'] ) ) ? sanitize_text_field( wp_unslash( $_GET['term'] ) ) : '';
183 - $nonce = ( isset( $_GET['nonce'] ) ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : '';
184 - $source = ( isset( $_GET['source'] ) ) ? sanitize_text_field( wp_unslash( $_GET['source'] ) ) : '';
185 -
186 - // Check current user capabilities
187 - if ( ! current_user_can( 'manage_options' ) ) {
188 - wp_send_json_error( 'You are not allowed to do this.' );
189 - }
190 -
191 - if ( ! empty( $term ) && ! empty( $source ) && ! empty( $nonce ) && wp_verify_nonce( $nonce, 'merchant_admin_options' ) ) {
192 - $options = array();
193 -
194 - switch ( $source ) {
195 - case 'post':
196 - case 'product':
197 - $query = new WP_Query( array(
198 - 's' => $term,
199 - 'post_type' => $source,
200 - 'post_status' => 'publish',
201 - 'posts_per_page' => 25,
202 - 'order' => 'DESC',
203 - ) );
204 -
205 - if ( ! empty( $query->posts ) ) {
206 - foreach ( $query->posts as $post ) {
207 - $options[] = array(
208 - 'id' => $post->ID,
209 - 'text' => $post->post_title,
210 - );
211 - }
212 - }
213 -
214 - break;
215 -
216 - case 'user':
217 - $query = new WP_User_Query( array(
218 - 'search' => '*' . $term . '*',
219 - 'search_columns' => array( 'user_login', 'user_nicename', 'user_email', 'user_url' ),
220 - 'number' => 25,
221 - ) );
222 -
223 - if ( ! empty( $query->results ) ) {
224 - foreach ( $query->results as $user ) {
225 - $options[] = array(
226 - 'id' => $user->ID,
227 - 'text' => $user->display_name,
228 - );
229 - }
230 - }
231 -
232 - break;
233 - }
234 -
235 - wp_send_json_success( $options );
236 - } else {
237 - wp_send_json_error();
238 - }
239 - }
240 -
241 - /**
242 - * Get option.
243 - */
244 94 public static function get( $module, $setting, $default_val ) {
245 95 $options = get_option( 'merchant', array() );
246 96
247 97 $value = $default_val;
@@ -257,17 +107,25 @@
257 107 * @param mixed $value Option value.
258 108 * @param string $module Module ID.
259 109 * @param string $setting Setting ID.
260 110 * @param mixed $default_val Default value.
261 - *
262 - * @since 1.9.3
111 + *
112 + * @since 1.9.3
263 113 */
264 114 return apply_filters( 'merchant_get_option', $value, $module, $setting, $default_val );
265 115 }
266 116
267 - /**
268 - * Set option.
269 - */
117 + /**
118 + * Set a single module setting value.
119 + *
120 + * @since 1.0
121 + *
122 + * @param string $module The module ID.
123 + * @param string $setting The setting key.
124 + * @param mixed $value The value to store.
125 + *
126 + * @return void
127 + */
270 128 public static function set( $module, $setting, $value ) {
271 129 $options = get_option( 'merchant', array() );
272 130 $options[ $module ][ $setting ] = $value;
273 131 update_option( 'merchant', $options );
@@ -272,19 +130,32 @@
272 130 $options[ $module ][ $setting ] = $value;
273 131 update_option( 'merchant', $options );
274 132 }
275 133
276 - /**
277 - * Delete option.
278 - */
279 - public static function delete( $module, $setting ) {
280 - $options = get_option( 'merchant', array() );
281 - unset( $options[ $module ][ $setting ] );
282 - update_option( 'merchant', $options );
283 - }
134 + /**
135 + * Delete a single module setting.
136 + *
137 + * @since 1.0
138 + *
139 + * @param string $module The module ID.
140 + * @param string $setting The setting key to remove.
141 + *
142 + * @return void
143 + */
144 + public static function delete( $module, $setting ) {
145 + $options = get_option( 'merchant', array() );
146 + unset( $options[ $module ][ $setting ] );
147 + update_option( 'merchant', $options );
148 + }
284 149
285 150 /**
286 - * Get all options.
151 + * Get all settings for a module.
152 + *
153 + * @since 1.0
154 + *
155 + * @param string $module The module ID.
156 + *
157 + * @return array All settings for the module.
287 158 */
288 159 public static function get_all( $module ) {
289 160 $options = get_option( 'merchant', array() );
290 161 $value = array();
@@ -295,2386 +166,288 @@
295 166
296 167 return $value;
297 168 }
298 169
299 - /**
300 - * Create options.
301 - */
302 - public static function create( $settings ) {
303 - $module_id = ( isset( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
170 + // ──────────────────────────────────────────────────────
171 + // Delegation facades — backward compatibility.
172 + // ──────────────────────────────────────────────────────
304 173
305 - /**
306 - * Hook: merchant_module_settings
307 - *
308 - * @param array $settings Module settings.
309 - * @param string $module_id Module ID.
310 - *
311 - * @since 1.0
312 - */
313 - $settings = apply_filters( 'merchant_module_settings', $settings, $module_id );
314 -
315 - self::save_options( $settings );
316 -
317 - $options = get_option( 'merchant', array() );
318 - ?>
319 - <div class="merchant-module-page-settings">
320 - <div class="merchant-module-page-setting-box">
321 - <?php
322 - if ( ! empty( $settings['title'] ) ) : ?>
323 - <div class="merchant-module-page-setting-title">
324 - <?php
325 - echo esc_html( $settings['title'] ); ?>
326 - <?php
327 - if ( ! empty( $settings['subtitle'] ) ) : ?>
328 - <div class="merchant-module-page-setting-subtitle"><?php
329 - echo esc_html( $settings['subtitle'] ); ?></div>
330 - <?php
331 - endif; ?>
332 - </div>
333 - <?php
334 - endif; ?>
335 - <div class="merchant-module-page-setting-fields">
336 - <?php
337 -
338 - if ( ! empty( $settings['fields'] ) ) {
339 - foreach ( $settings['fields'] as $field ) {
340 - $value = null;
341 -
342 - if ( isset( $field['default'] ) ) {
343 - $value = $field['default'];
344 - }
345 -
346 - if ( isset( $field['id'] ) && isset( $options[ $settings['module'] ] ) && isset( $options[ $settings['module'] ][ $field['id'] ] ) ) {
347 - $value = $options[ $settings['module'] ][ $field['id'] ];
348 - }
349 -
350 - $current_module = Merchant_Admin_Modules::get_module_info( $settings['module'] );
351 - $is_pro_module = ! merchant_is_pro_active() && isset( $current_module['pro'] ) && $current_module['pro'] === true;
352 - $is_pro_field = ! merchant_is_pro_active() && isset( $field['pro'] ) && $field['pro'] === true;
353 -
354 - if ( $is_pro_module || $is_pro_field ) {
355 - self::disabled_field( $field, $value );
356 - } else {
357 - self::field( $field, $value, $module_id );
358 - }
359 - }
360 - }
361 - ?>
362 - </div>
363 - </div>
364 - </div>
365 - <?php
366 - }
367 -
368 174 /**
369 - * Save options.
370 - */
371 - public static function save_options( $settings ) {
372 - $save = ( isset( $_POST['merchant_save'] ) ) ? sanitize_text_field( wp_unslash( $_POST['merchant_save'] ) ) : '';
373 - $reset = ( isset( $_POST['merchant_reset'] ) ) ? sanitize_text_field( wp_unslash( $_POST['merchant_reset'] ) ) : '';
374 - $nonce = ( isset( $_POST['merchant_nonce'] ) ) ? sanitize_text_field( wp_unslash( $_POST['merchant_nonce'] ) ) : '';
375 -
376 - if ( ! wp_verify_nonce( $nonce, 'merchant_nonce' ) ) {
377 - return;
378 - }
379 -
380 - if ( ! current_user_can( 'manage_options' ) ) {
381 - return;
382 - }
383 -
384 - $options = get_option( 'merchant', array() );
385 -
386 - if ( ! empty( $save ) ) {
387 - if ( ! empty( $settings['fields'] ) ) {
388 - foreach ( $settings['fields'] as $field ) {
389 - if ( ! isset( $field['id'] ) ) {
390 - continue;
391 - }
392 -
393 - if ( ! merchant_is_pro_active() && isset( $field['pro'] ) && $field['pro'] === true ) {
394 - continue;
395 - }
396 -
397 - $value = null;
398 -
399 - if ( isset( $_POST['merchant'] ) && isset( $_POST['merchant'][ $field['id'] ] ) ) {
400 - if ( 'textarea_code' === $field['type'] ) {
401 - $value = wp_kses( $_POST['merchant'][ $field['id'] ], merchant_kses_allowed_tags_for_code_snippets() );
402 - } elseif ( 'textarea_multiline' === $field['type'] ) {
403 - $value = sanitize_textarea_field( $_POST['merchant'][ $field['id'] ] );
404 - } elseif ( is_array( $_POST['merchant'][ $field['id'] ] ) ) {
405 - $value = array_filter( map_deep( wp_unslash( $_POST['merchant'][ $field['id'] ] ), 'sanitize_textarea_field' ) );
406 - } else {
407 - $value = sanitize_text_field( wp_unslash( $_POST['merchant'][ $field['id'] ] ) );
408 - }
409 - }
410 -
411 - $options[ $settings['module'] ][ $field['id'] ] = self::sanitize( $field, $value );
412 - }
413 - }
414 - } elseif ( ! empty( $reset ) ) {
415 - $options[ $settings['module'] ] = array();
416 - }
417 -
418 - update_option( 'merchant', $options );
419 -
420 - /**
421 - * Hook: merchant_options_saved, fired after saving module options.
422 - *
423 - * @param string $module module ID.
424 - * @param array $options module options.
425 - *
426 - * @since 1.9.3
427 - */
428 - do_action( 'merchant_options_saved', $settings['module'], $options[ $settings['module'] ] );
429 -
430 - /**
431 - * Hook: merchant_options_saved, fired after saving specific module options.
432 - *
433 - * @param array $options module options.
434 - *
435 - * @since 1.9.3
436 - */
437 - do_action( "merchant_options_saved_{$settings['module']}", $options[ $settings['module'] ] );
438 - }
439 -
440 - /**
441 - * Sanitize options.
442 - */
443 - public static function sanitize( $field, $value ) {
444 - // Callback for custom sanitize methods.
445 - if ( ! empty( $field['sanitize'] ) && is_callable( $field['sanitize'] ) ) {
446 - return call_user_func( $field['sanitize'], $value );
447 - }
448 -
449 - switch ( $field['type'] ) {
450 - case 'text':
451 - case 'color':
452 - case 'number':
453 - case 'select_size_chart':
454 - $value = sanitize_text_field( $value );
455 - break;
456 -
457 - case 'textarea_multiline':
458 - case 'textarea':
459 - $value = sanitize_textarea_field( $value );
460 - break;
461 -
462 - case 'textarea_code':
463 - $value = $value;
464 - break;
465 -
466 - case 'checkbox_multiple':
467 - if ( is_array( $value ) && ! empty( $value ) ) {
468 - $value = array_filter( array_map( 'sanitize_text_field', $value ) );
469 - } else {
470 - $value = array();
471 - }
472 - break;
473 -
474 - case 'checkbox':
475 - case 'switcher':
476 - $value = ( '1' === $value ) ? 1 : 0;
477 - break;
478 -
479 - case 'range':
480 - case 'number':
481 - $value = absint( $value );
482 - break;
483 - case 'select_ajax':
484 - if ( is_array( $value ) && ! empty( $value ) ) {
485 - $value = array_filter( array_map( 'sanitize_text_field', $value ) );
486 - } elseif ( is_string( $value ) ) {
487 - $value = sanitize_text_field( $value );
488 - } else {
489 - $value = isset( $field['multiple'] ) && $field['multiple'] === false ? '' : array();
490 - }
491 - break;
492 - case 'radio':
493 - case 'radio_alt':
494 - case 'select':
495 - case 'choices':
496 - case 'buttons':
497 - case 'buttons_alt':
498 - case 'buttons_content':
499 - $value = ( in_array( $value, array_keys( $field['options'] ), true ) ) ? sanitize_key( $value ) : '';
500 - break;
501 -
502 - case 'hook_select':
503 - $value = is_array( $value ) ? $value : array();
504 -
505 - foreach ( $value as $key => $val ) {
506 - if ( $key === 'hook_name' ) {
507 - $value[ $key ] = in_array( $val, array_keys( $field['options'] ), true ) ? sanitize_key( $val ) : '';
508 - } else {
509 - $value[ $key ] = (int) $val;
510 - }
511 - }
512 -
513 - break;
514 -
515 - case 'code-editor':
516 - $value = wp_kses_post( $value );
517 - break;
518 -
519 - case 'sortable':
520 - $values = json_decode( $value );
521 - $value = array();
522 -
523 - foreach ( $values as $val ) {
524 - if ( in_array( $val, array_keys( $field['options'] ), true ) ) {
525 - $value[] = sanitize_key( $val );
526 - }
527 - }
528 - break;
529 - case 'dimensions':
530 - $values = is_array( $value ) ? $value : array();
531 -
532 - foreach ( $values as $key => $val ) {
533 - if ( $key === 'unit' ) {
534 - $value[ $key ] = sanitize_key( $value[ $key ] );
535 - } else {
536 - $value[ $key ] = absint( $value[ $key ] );
537 - }
538 - }
539 - break;
540 - case 'responsive_dimensions':
541 - $values = is_array( $value ) ? $value : array();
542 -
543 - foreach ( $values as $device => $device_fields ) {
544 - foreach ( $device_fields as $device_field => $device_field_val ) {
545 - if ( $device_field === 'unit' ) {
546 - $value[ $device ][ $device_field ] = sanitize_key( $device_field_val );
547 - } else {
548 - $value[ $device ][ $device_field ] = absint( $device_field_val );
549 - }
550 - }
551 - }
552 - break;
553 -
554 - case 'sortable_repeater':
555 - $values = json_decode( $value );
556 - $value = array_map( 'sanitize_text_field', $values );
557 - break;
558 - case 'flexible_content':
559 - $value = ! is_array( $value ) || empty( $value ) ? array() : $value;
560 -
561 - $value = array_map( function ( $sub_fields ) use ( $field ) {
562 - foreach ( $sub_fields as $sub_field => $value ) {
563 - if ( $sub_field === 'layout' ) {
564 - $sub_fields[ $sub_field ] = sanitize_text_field( $value );
565 - } else {
566 - $layout_field = array_filter( $field['layouts'][ $sub_fields['layout'] ]['fields'], function ( $layout_field ) use ( $sub_field ) {
567 - return $layout_field['id'] === $sub_field;
568 - } );
569 -
570 -
571 - $sub_fields[ $sub_field ] = self::sanitize( reset( $layout_field ), $value );
572 - }
573 - }
574 -
575 - return $sub_fields;
576 - }, $value );
577 -
578 - break;
579 - }
580 -
581 - return $value;
582 - }
583 -
584 - /**
585 - * Field
586 - */
587 - public static function field( $settings, $value, $module_id = '') {
588 -
589 - if ( ! empty( $settings['type'] ) ) {
590 - $type = $settings['type'];
591 -
592 - $id = ( ! empty( $settings['id'] ) ) ? $settings['id'] : '';
593 - $class = ( ! empty( $settings['class'] ) ) ? ' ' . $settings['class'] : '';
594 - $condition = ( ! empty( $settings['condition'] ) ) ? $settings['condition'] : array();
595 - $conditions = ( ! empty( $settings['conditions'] ) ) ? $settings['conditions'] : ''; //Docs here: https://github.com/athemes/Merchant/pull/133
596 - $default = ( ! empty( $settings['default'] ) ) ? $settings['default'] : null;
597 - $is_upsell = ! merchant_is_pro_active() && isset( $settings['pro'] ) && $settings['pro'] === true;
598 -
599 - if ( ! $value && ( 0 !== $value && '0' !== $value ) ) {
600 - if ( $type === 'checkbox_multiple' ) {
601 - $value = is_array( $value ) ? $value : (array) $default;
602 - } elseif ( $type === 'text' && ! empty( $module_id ) ) {
603 - $value = Merchant_Option::get( $module_id, $id );
604 - } else {
605 - $value = $default;
606 - }
607 - }
608 -
609 - $wrapper_classes = array( 'merchant-module-page-setting-field' );
610 - $wrapper_classes[] = 'merchant-module-page-setting-field-' . $type;
611 -
612 - if ( ! empty( $class ) ) {
613 - $wrapper_classes[] = $class;
614 - }
615 -
616 - /**
617 - * Hook 'merchant_admin_module_field_wrapper_classes'
618 - *
619 - * @since 1.9.3
620 - */
621 - $wrapper_classes = apply_filters( 'merchant_admin_module_field_wrapper_classes', $wrapper_classes, $settings, $value, $module_id );
622 -
623 - echo '<div class="'. esc_attr( implode( ' ', $wrapper_classes ) ) .'" data-id="'
624 - . esc_attr( $id ) . '" data-type="' . esc_attr( $type ) . '" data-condition="' . esc_attr( wp_json_encode( $condition ) ) .
625 - '" data-conditions="' . ( $conditions ? esc_attr( wp_json_encode( $conditions ) ) : "" ) . '">';
626 - if ( ! empty( $settings['title'] ) ) {
627 - ?>
628 - <div class="merchant-module-page-setting-field-title<?php echo esc_attr( $is_upsell ? ' merchant-module-page-setting-field-title__has-upsell' : '' ); ?>">
629 - <?php echo esc_html( $settings['title'] ); ?>
630 -
631 - <?php if ( $is_upsell ) : ?>
632 - <a href="<?php echo esc_url( 'https://athemes.com/merchant-upgrade?utm_source=plugin_dashboard&utm_medium=merchant_dashboard&utm_campaign=Merchant' ); ?>" class="merchant-module-pro-upsell" target="_blank">
633 - <span class="merchant-pro-badge merchant-pro-tooltip" data-tooltip-message="<?php echo esc_attr__( 'This option is only available on Merchant Pro', 'merchant' ); ?>">
634 - <svg width="28" height="16" viewBox="0 0 28 16" fill="none" xmlns="http://www.w3.org/2000/svg">
635 - <path d="M7.41309 8.90723H5.58203V7.85254H7.41309C7.71257 7.85254 7.95508 7.80371 8.14062 7.70605C8.32943 7.60514 8.46777 7.46842 8.55566 7.2959C8.64355 7.12012 8.6875 6.91992 8.6875 6.69531C8.6875 6.47721 8.64355 6.27376 8.55566 6.08496C8.46777 5.89616 8.32943 5.74316 8.14062 5.62598C7.95508 5.50879 7.71257 5.4502 7.41309 5.4502H6.02148V11.5H4.67871V4.39062H7.41309C7.96647 4.39062 8.43848 4.48991 8.8291 4.68848C9.22298 4.88379 9.52246 5.1556 9.72754 5.50391C9.93587 5.84896 10.04 6.24284 10.04 6.68555C10.04 7.14453 9.93587 7.54004 9.72754 7.87207C9.52246 8.2041 9.22298 8.45964 8.8291 8.63867C8.43848 8.81771 7.96647 8.90723 7.41309 8.90723ZM11.0947 4.39062H13.6777C14.2181 4.39062 14.682 4.47201 15.0693 4.63477C15.4567 4.79753 15.7546 5.03841 15.9629 5.35742C16.1712 5.67643 16.2754 6.06868 16.2754 6.53418C16.2754 6.90202 16.2103 7.22103 16.0801 7.49121C15.9499 7.76139 15.766 7.98763 15.5283 8.16992C15.2939 8.35221 15.0173 8.49544 14.6982 8.59961L14.2783 8.81445H11.998L11.9883 7.75488H13.6924C13.9691 7.75488 14.1986 7.70605 14.3809 7.6084C14.5632 7.51074 14.6999 7.37565 14.791 7.20312C14.8854 7.0306 14.9326 6.83366 14.9326 6.6123C14.9326 6.37467 14.887 6.1696 14.7959 5.99707C14.7048 5.82129 14.5664 5.6862 14.3809 5.5918C14.1953 5.4974 13.9609 5.4502 13.6777 5.4502H12.4375V11.5H11.0947V4.39062ZM15.1084 11.5L13.4629 8.31641L14.8838 8.31152L16.5488 11.4316V11.5H15.1084ZM23.209 7.76465V8.13086C23.209 8.66797 23.1374 9.15137 22.9941 9.58105C22.8509 10.0075 22.6475 10.3704 22.3838 10.6699C22.1201 10.9694 21.806 11.1989 21.4414 11.3584C21.0768 11.5179 20.6715 11.5977 20.2256 11.5977C19.7861 11.5977 19.3825 11.5179 19.0146 11.3584C18.6501 11.1989 18.3343 10.9694 18.0674 10.6699C17.8005 10.3704 17.5938 10.0075 17.4473 9.58105C17.3008 9.15137 17.2275 8.66797 17.2275 8.13086V7.76465C17.2275 7.22428 17.3008 6.74089 17.4473 6.31445C17.5938 5.88802 17.7988 5.52507 18.0625 5.22559C18.3262 4.92285 18.6403 4.69173 19.0049 4.53223C19.3727 4.37272 19.7764 4.29297 20.2158 4.29297C20.6618 4.29297 21.0671 4.37272 21.4316 4.53223C21.7962 4.69173 22.1104 4.92285 22.374 5.22559C22.641 5.52507 22.846 5.88802 22.9893 6.31445C23.1357 6.74089 23.209 7.22428 23.209 7.76465ZM21.8516 8.13086V7.75488C21.8516 7.36751 21.8158 7.02734 21.7441 6.73438C21.6725 6.43815 21.5667 6.18913 21.4268 5.9873C21.2868 5.78548 21.1143 5.63411 20.9092 5.5332C20.7041 5.42904 20.473 5.37695 20.2158 5.37695C19.9554 5.37695 19.7243 5.42904 19.5225 5.5332C19.3239 5.63411 19.1546 5.78548 19.0146 5.9873C18.8747 6.18913 18.7673 6.43815 18.6924 6.73438C18.6208 7.02734 18.585 7.36751 18.585 7.75488V8.13086C18.585 8.51497 18.6208 8.85514 18.6924 9.15137C18.7673 9.44759 18.8747 9.69824 19.0146 9.90332C19.1579 10.1051 19.3304 10.2581 19.5322 10.3623C19.734 10.4665 19.9652 10.5186 20.2256 10.5186C20.486 10.5186 20.7171 10.4665 20.9189 10.3623C21.1208 10.2581 21.29 10.1051 21.4268 9.90332C21.5667 9.69824 21.6725 9.44759 21.7441 9.15137C21.8158 8.85514 21.8516 8.51497 21.8516 8.13086Z" fill="#3858E9"/>
636 - <rect x="0.5" y="1" width="27" height="14" rx="1.5" stroke="#3858E9"/>
637 - </svg>
638 - </span>
639 - </a>
640 - <?php endif; ?>
641 - </div>
642 - <?php
643 - }
644 -
645 - echo '<div class="merchant-module-page-setting-field-inner merchant-field-' . esc_attr( $id ) . '">';
646 - if ( method_exists( 'Merchant_Admin_Options', $type ) ) {
647 - call_user_func( array( 'Merchant_Admin_Options', $type ), $settings, $value, $module_id );
648 - } else {
649 - esc_html_e( 'Field not found!', 'merchant' );
650 - }
651 - echo '</div>';
652 -
653 - $hidden_desc = $settings['hidden_desc'] ?? '';
654 -
655 - /**
656 - * Hook 'merchant_admin_module_field_hidden_description'
657 - *
658 - * @since 1.9.3
659 - */
660 - $hidden_desc = apply_filters( 'merchant_admin_module_field_hidden_description', $hidden_desc, $settings, $value, $module_id );
661 -
662 - $desc = ( ! empty( $settings['desc'] ) ) ? $settings['desc'] : '';
663 -
664 - /**
665 - * Hook 'merchant_admin_module_field_description'
666 - *
667 - * @since 1.9.3
668 - */
669 - $desc = apply_filters( 'merchant_admin_module_field_description', $desc, $settings, $value, $module_id );
670 -
671 - if ( ! empty( $desc ) ) {
672 - $hidden_desc_html = '';
673 - if ( ! empty( $hidden_desc ) ) {
674 - $hidden_desc_html = '<div class="merchant-module-page-setting-field-hidden-desc-trigger" data-show-text="' . esc_html__( 'Show more', 'merchant' ) . '" data-hidden-text="' . esc_html__( 'Show less', 'merchant' ) . '"><span>' . esc_html__( 'Show more', 'merchant' ) . '</span>';
675 - $hidden_desc_html .= '<img src="' . esc_url( MERCHANT_URI . '/assets/images/arrow-down.svg' ) . '" alt="Merchant" />';
676 - $hidden_desc_html .= '</div>';
677 - }
678 -
679 - printf( '<div class="merchant-module-page-setting-field-desc' . esc_attr( $hidden_desc ? ' merchant-module-page-setting-field-desc-has-hidden-desc' : '' ) .'">%s%s</div>', wp_kses_post( $desc ), wp_kses_post( $hidden_desc_html ) );
680 - }
681 -
682 - if ( ! empty( $hidden_desc ) ) {
683 - printf( '<div class="merchant-module-page-setting-field-hidden-desc">%s</div>', wp_kses_post( nl2br( $hidden_desc ) ) );
684 - }
685 -
686 - echo '</div>';
687 - }
688 - }
689 -
690 - /**
691 - * Disabled field
175 + * Create and render a module settings panel.
692 176 *
693 - * @param array $settings
694 - * @param mixed $value
177 + * @since 1.0
178 + * @see Merchant_Settings_Renderer::create()
695 179 *
180 + * @param array $settings Module settings configuration.
181 + *
696 182 * @return void
697 183 */
698 - public static function disabled_field( $settings, $value, $module_id = '') {
699 - static::replace_field(
700 - $settings,
701 - $value,
702 - array(
703 - '<input ',
704 - '<select ',
705 - 'merchant-module-page-setting-field-inner',
706 - ),
707 - array(
708 - '<input disabled ',
709 - '<select disabled ',
710 - 'merchant-module-page-setting-field-inner disabled',
711 - ),
712 - $module_id
713 - );
184 + public static function create( $settings ) {
185 + Merchant_Settings_Renderer::create( $settings );
714 186 }
715 187
716 188 /**
717 - * Modified field.
189 + * Render a single field.
718 190 *
719 - * @param $settings
720 - * @param $value
721 - * @param $search
722 - * @param $replace
191 + * @since 1.0
192 + * @see Merchant_Settings_Renderer::field()
723 193 *
194 + * @param array $settings The field configuration array.
195 + * @param mixed $value The current saved value.
196 + * @param string $module_id The module ID.
197 + *
724 198 * @return void
725 199 */
726 - public static function replace_field( $settings, $value, $search, $replace, $module_id = '') {
727 - ob_start();
728 - self::field( $settings, $value, $module_id );
729 - $field = ob_get_clean();
730 -
731 - echo wp_kses( str_replace( $search, $replace, $field ), merchant_kses_allowed_tags( array( 'all' ) ) );
200 + public static function field( $settings, $value, $module_id = '' ) {
201 + Merchant_Settings_Renderer::field( $settings, $value, $module_id );
732 202 }
733 203
734 204 /**
735 - * Field: Text
736 - */
737 - public static function text( $settings, $value, $module_id = '' ) {
738 - ?>
739 - <input type="text" name="merchant[<?php
740 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
741 - echo esc_attr( $value ); ?>" placeholder="<?php
742 - echo esc_attr( $settings['placeholder'] ?? '' ); ?>"/>
743 - <?php
744 - }
745 -
746 - /**
747 - * Field: Text (readonly)
748 - */
749 - public static function text_readonly( $settings, $value, $module_id = '' ) {
750 - ?>
751 - <input type="text" value="<?php
752 - echo esc_attr( $value ); ?>" readonly/>
753 - <?php
754 - }
755 -
756 - /**
757 - * Field: Number
758 - */
759 - public static function number( $settings, $value, $module_id = '' ) {
760 - ?>
761 - <input type="number" name="merchant[<?php
762 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
763 - echo esc_attr( $value ); ?>"<?php
764 - echo isset( $settings['step'] ) ? ' step="' . esc_attr( $settings['step'] ) . '"' : '';
765 - echo isset( $settings['max'] ) ? ' max="' . esc_attr( $settings['max'] ) . '"' : '';
766 - echo isset( $settings['min'] ) ? ' min="' . esc_attr( $settings['min'] ) . '"' : '' ?>
767 - placeholder="<?php echo esc_attr( $settings['placeholder'] ?? '' ); ?>"/>
768 - <?php
769 - }
770 -
771 - /**
772 - * Field: Textarea
773 - */
774 - public static function textarea( $settings, $value, $module_id = '' ) {
775 - $value = ( $value ) ? $value : '';
776 - ?>
777 - <textarea name="merchant[<?php
778 - echo esc_attr( $settings['id'] ); ?>]"><?php
779 - echo wp_kses_post( $value ); ?></textarea>
780 - <?php
781 - }
782 -
783 - /**
784 - * Field: Textarea
785 - */
786 - public static function textarea_multiline( $settings, $value, $module_id = '' ) {
787 - $value = ( $value ) ? $value : '';
788 - ?>
789 - <textarea name="merchant[<?php
790 - echo esc_attr( $settings['id'] ); ?>]"><?php
791 - echo wp_kses_post( $value ); ?></textarea>
792 - <?php
793 - }
794 -
795 - /**
796 - * Field: Textarea Code Snippet.
797 - */
798 - public static function textarea_code( $settings, $value, $module_id = '' ) {
799 - $value = ( $value ) ? $value : '';
800 - ?>
801 - <textarea name="merchant[<?php
802 - echo esc_attr( $settings['id'] ); ?>]"><?php
803 - echo wp_kses( $value, merchant_kses_allowed_tags_for_code_snippets() ); ?></textarea>
804 - <?php
805 - }
806 -
807 - /**
808 - * Field: Checkbox
809 - */
810 - public static function checkbox( $settings, $value, $module_id = '' ) {
811 - ?>
812 - <div>
813 - <label>
814 - <input type="checkbox" name="merchant[<?php
815 - echo esc_attr( $settings['id'] ); ?>]" value="1" <?php
816 - checked( $value, 1, true ); ?> />
817 - <?php
818 - if ( ! empty( $settings['label'] ) ) : ?>
819 - <span><?php
820 - echo esc_html( $settings['label'] ); ?></span>
821 - <?php
822 - endif; ?>
823 - </label>
824 - </div>
825 - <?php
826 - }
827 -
828 - /**
829 - * Field: Checkbox multiple
830 - */
831 - public static function checkbox_multiple( $settings, $value ) {
832 - if ( ! empty( $settings['options'] ) ) : ?>
833 - <?php
834 - foreach ( $settings['options'] as $key => $option ) : ?>
835 - <label>
836 - <input
837 - type="checkbox" name="merchant[<?php echo esc_attr( $settings['id'] ); ?>][]"
838 - value="<?php echo esc_attr( $key ); ?>"
839 - <?php checked( in_array( $key, $value, true ), true ); ?>
840 - />
841 - <span><?php echo esc_html( $option ); ?></span>
842 - </label>
843 - <?php
844 - endforeach; ?>
845 - <?php
846 - endif;
847 - }
848 -
849 - /**
850 - * Field: Switcher
851 - */
852 - public static function switcher( $settings, $value, $module_id = '' ) {
853 - ?>
854 - <div class="merchant-toggle-switch">
855 - <input type="checkbox" id="<?php
856 - echo esc_attr( $settings['id'] ); ?>" name="merchant[<?php
857 - echo esc_attr( $settings['id'] ); ?>]" value="1" <?php
858 - checked( $value, 1, true ); ?>
859 - class="toggle-switch-checkbox"/>
860 - <label class="toggle-switch-label" for="<?php
861 - echo esc_attr( $settings['id'] ); ?>">
862 - <span class="toggle-switch-inner"></span>
863 - <span class="toggle-switch-switch"></span>
864 - </label>
865 - <?php
866 - if ( ! empty( $settings['label'] ) ) : ?>
867 - <span><?php
868 - echo esc_html( $settings['label'] ); ?></span>
869 - <?php
870 - endif; ?>
871 - </div>
872 - <?php
873 - }
874 -
875 - /**
876 - * Field: Radio
877 - */
878 - public static function radio( $settings, $value, $module_id = '' ) {
879 - ?>
880 - <?php
881 - if ( ! empty( $settings['options'] ) ) : ?>
882 - <?php
883 - foreach ( $settings['options'] as $key => $option ) : ?>
884 - <label>
885 - <input type="radio" name="merchant[<?php
886 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
887 - echo esc_attr( $key ); ?>" <?php
888 - checked( $value, $key, true ); ?>/>
889 - <span><?php
890 - echo esc_html( $option ); ?></span>
891 - </label>
892 - <?php
893 - endforeach; ?>
894 - <?php
895 - endif; ?>
896 - <?php
897 - }
898 -
899 - /**
900 - * Field: Image picker
901 - */
902 - public static function image_picker( $settings, $value, $module_id = '' ) {
903 - ?>
904 - <div class="merchant-image-picker">
905 - <?php
906 - if ( ! empty( $settings['options'] ) ) : ?>
907 - <?php
908 - foreach ( $settings['options'] as $key => $option ) : ?>
909 - <label>
910 - <input type="radio" name="merchant[<?php
911 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
912 - echo esc_attr( $key ); ?>" <?php
913 - checked( $value, $key, true ); ?>/>
914 - <?php
915 - if ( isset( $option['image'] ) ) { ?>
916 - <img src="<?php
917 - echo esc_url( $option['image'] ) ?>" alt="">
918 - <?php
919 - } ?>
920 - <?php
921 - if ( isset( $option['title'] ) ) { ?>
922 - <span class="tool-tip-text"><?php
923 - echo esc_html( $option['title'] ) ?></span>
924 - <?php
925 - } ?>
926 - </label>
927 - <?php
928 - endforeach; ?>
929 - <?php
930 - endif; ?>
931 - </div>
932 - <?php
933 - }
934 -
935 - /**
936 - * Field: Radio Alt
937 - */
938 - public static function radio_alt( $settings, $value, $module_id = '' ) {
939 - ?>
940 - <?php
941 - if ( ! empty( $settings['options'] ) ) : ?>
942 - <?php
943 - foreach ( $settings['options'] as $key => $option ) : ?>
944 - <div>
945 - <label>
946 - <input type="radio" name="merchant[<?php
947 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
948 - echo esc_attr( $key ); ?>" <?php
949 - checked( $value, $key, true ); ?>/>
950 - <span><?php
951 - echo esc_html( $option['title'] ); ?></span>
952 - </label>
953 - <p><?php
954 - echo esc_html( $option['desc'] ); ?></p>
955 - </div>
956 - <?php
957 - endforeach; ?>
958 - <?php
959 - endif; ?>
960 - <?php
961 - }
962 -
963 - /**
964 - * Field: Choices
965 - */
966 - public static function choices( $settings, $value, $module_id = '' ) {
967 - ?>
968 - <div class="merchant-choices merchant-choices-<?php
969 - echo esc_attr( $settings['id'] ) ?>">
970 - <?php
971 - if ( ! empty( $settings['options'] ) ) : ?>
972 - <?php
973 - foreach ( $settings['options'] as $key => $option ) : ?>
974 - <label>
975 - <input type="radio" name="merchant[<?php
976 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
977 - echo esc_attr( $key ); ?>" <?php
978 - checked( $value, $key, true ); ?>/>
979 - <?php
980 - if ( ! empty( $option['svg'] ) ) : ?>
981 - <span class="merchant-svg">
982 - <?php
983 - echo wp_kses( Merchant_SVG_Icons::get_svg_icon( $option['svg'] ), merchant_kses_allowed_tags( array(), false ) ); ?>
984 -
985 - <?php
986 - if ( ! empty( $option['label'] ) ) : ?>
987 - <span class="merchant-tooltip"><?php
988 - echo esc_html( $option['label'] ); ?></span>
989 - <?php
990 - endif; ?>
991 - </span>
992 - <?php
993 - else : ?>
994 - <figure>
995 - <?php if ( ! empty( $option['image'] ) ) :
996 - $title = $option['title'] ?? '';
997 - ?>
998 - <img src="<?php echo esc_url( sprintf( $option['image'], MERCHANT_URI . 'assets/images' ) ); ?>" alt="<?php echo esc_attr( $title ); ?>" title="<?php echo esc_attr( $title ); ?>"/>
999 - <?php
1000 - else : ?>
1001 - <img src="<?php
1002 - echo esc_url( sprintf( $option, MERCHANT_URI . 'assets/images' ) ); ?>"/>
1003 - <?php
1004 - endif; ?>
1005 - <?php
1006 - if ( ! empty( $option['label'] ) ) : ?>
1007 - <span class="merchant-tooltip"><?php
1008 - echo esc_html( $option['label'] ); ?></span>
1009 - <?php
1010 - endif; ?>
1011 - </figure>
1012 - <?php
1013 - endif; ?>
1014 - </label>
1015 - <?php
1016 - endforeach; ?>
1017 - <?php
1018 - endif; ?>
1019 - </div>
1020 - <?php
1021 - }
1022 -
1023 - /**
1024 - * Field: Select
1025 - */
1026 - public static function select( $settings, $value, $module_id = '' ) {
1027 - ?>
1028 - <?php
1029 - if ( ! empty( $settings['options'] ) ) : ?>
1030 - <select name="merchant[<?php
1031 - echo esc_attr( $settings['id'] ); ?>]">
1032 - <?php
1033 - foreach ( $settings['options'] as $key => $option ) : ?>
1034 - <option value="<?php
1035 - echo esc_attr( $key ); ?>" <?php
1036 - selected( $value, $key, true ); ?>><?php
1037 - echo esc_html( $option ); ?></option>
1038 - <?php
1039 - endforeach; ?>
1040 - </select>
1041 - <?php
1042 - endif; ?>
1043 - <?php
1044 - }
1045 -
1046 - /**
1047 - * Field: Hook Select
1048 - */
1049 - public static function hook_select( $settings, $value, $module_id = '' ) {
1050 - $hook_name = isset( $value['hook_name'] ) ? $value['hook_name'] : '';
1051 - $hook_priority = isset( $value['hook_priority'] ) ? $value['hook_priority'] : '';
1052 -
1053 - ?>
1054 - <div class="merchant-module-page-setting-field-hook_select-wrapper">
1055 - <div class="merchant-module-page-setting-field-select">
1056 - <select name="merchant[<?php
1057 - echo esc_attr( $settings['id'] ); ?>][hook_name]">
1058 - <?php
1059 - if ( ! empty( $settings['options'] ) ) : ?>
1060 - <?php
1061 - foreach ( $settings['options'] as $key => $option ) : ?>
1062 - <option value="<?php
1063 - echo esc_attr( $key ); ?>" <?php
1064 - selected( $hook_name, $key, true ); ?>><?php
1065 - echo esc_html( $option ); ?></option>
1066 - <?php
1067 - endforeach; ?>
1068 - <?php
1069 - endif; ?>
1070 - </select>
1071 - </div>
1072 - <?php
1073 -
1074 - if ( isset( $settings['order'] ) && true === $settings['order'] ) {
1075 - ?>
1076 - <div class="merchant-module-page-setting-field-number">
1077 - <input type="number" name="merchant[<?php
1078 - echo esc_attr( $settings['id'] ); ?>][hook_priority]" value="<?php
1079 - echo esc_attr( $hook_priority ); ?>"/>
1080 - </div>
1081 - <?php
1082 - } ?>
1083 - </div>
1084 -
1085 - <?php
1086 - }
1087 -
1088 - /**
1089 - * Field: Select ajax
205 + * Render a disabled (pro-gated) field.
1090 206 *
1091 - * @param $settings
1092 - * @param $value
207 + * @since 1.0
208 + * @see Merchant_Settings_Renderer::disabled_field()
1093 209 *
210 + * @param array $settings Field settings.
211 + * @param mixed $value Field value.
212 + * @param string $module_id Module ID.
213 + *
1094 214 * @return void
1095 215 */
1096 - public static function select_ajax( $settings, $value, $module_id = '' ) {
1097 - $settings = wp_parse_args( $settings, array(
1098 - 'source' => 'post',
1099 - ) );
1100 -
1101 - $ids = ( is_array( $value ) && ! empty( $value ) ) ? $value : (array) $value;
1102 -
1103 - if ( isset( $settings['multiple'] ) ) {
1104 - $multiple = $settings['multiple'] === true ? 'multiple' : '';
1105 - } else {
1106 - $multiple = 'multiple';
1107 - }
1108 -
1109 - $placeholder = $settings['placeholder'] ?? '';
1110 - ?>
1111 - <select
1112 - name="merchant[<?php echo esc_attr( $settings['id'] ); ?>]<?php echo esc_attr( $multiple ? '[]' : '' ); ?>"
1113 - <?php echo esc_attr( $multiple ); ?>
1114 - data-source="<?php echo esc_attr( $settings['source'] ); ?>"
1115 - data-placeholder="<?php echo esc_attr( $placeholder ); ?>">
1116 - <?php
1117 - if ( ! empty( $ids ) ) {
1118 - foreach ( $ids as $id ) {
1119 - switch ( $settings['source'] ) {
1120 - case 'post':
1121 - case 'product':
1122 - $post = get_post( $id );
1123 -
1124 - if ( ! empty( $post ) ) {
1125 - echo '<option value="' . esc_attr( $post->ID ) . '" selected>' . esc_html( $post->post_title ) . '</option>';
1126 - }
1127 - break;
1128 - case 'user':
1129 - $user = get_user_by( 'ID', $id );
1130 - if( $user ) {
1131 - echo '<option value="' . esc_attr( $user->ID ) . '" selected>' . esc_html( $user->display_name ) . '</option>';
1132 - }
1133 - break;
1134 - case 'options':
1135 - }
1136 - }
1137 - }
1138 - if ( $settings['source'] === 'options' ) {
1139 - $value = ! empty( $value ) ? $value : '';
1140 -
1141 - foreach ( $settings['options'] as $option ) {
1142 - if ( isset( $option['options'] ) ) {
1143 - echo '<optgroup label="' . esc_attr( $option['text'] ) . '">';
1144 -
1145 - foreach ( $option['options'] as $child_option ) {
1146 - $selected_value = is_array( $value ) ? in_array( $child_option['id'], $value, true ) : $child_option['id'];
1147 - echo '<option value="' . esc_attr( $child_option['id'] ) . '" ' . selected( $selected_value, is_array( $value ) ? true : $value, false ) . '>' . esc_html( $child_option['text'] ) . '</option>';
1148 - }
1149 -
1150 - echo '</optgroup>';
1151 - } else {
1152 - $selected_value = is_array( $value ) ? in_array( $option['id'], $value, true ) : $option['id'];
1153 - echo '<option value="' . esc_attr( $option['id'] ) . '" ' . selected( $selected_value, is_array( $value ) ? true : $value, false ) . '>' . esc_html( $option['text'] ) . '</option>';
1154 - }
1155 - }
1156 - } ?>
1157 - </select>
1158 - <?php
216 + public static function disabled_field( $settings, $value, $module_id = '' ) {
217 + Merchant_Settings_Renderer::disabled_field( $settings, $value, $module_id );
1159 218 }
1160 219
1161 220 /**
1162 - * Field: Info Block
221 + * Save module options from a form submission.
1163 222 *
223 + * @since 1.0
224 + * @see Merchant_Settings_Saver::save_options()
225 + *
226 + * @param array $settings Module settings configuration.
227 + *
1164 228 * @return void
1165 229 */
1166 - public static function info_block( $settings, $value, $module_id = '' ) {
1167 - ?>
1168 - <div class="merchant-info-block">
1169 - <i class="dashicons dashicons-info"></i>
1170 - <p><?php
1171 - echo ! empty( $settings['description'] ) ? esc_html( $settings['description'] ) : ''; ?><?php
1172 - if ( ! empty( $settings['button_text'] ) && ! empty( $settings['button_link'] ) ) {
1173 - printf( '<a href="%s" target="_blank">%s</a>', esc_url( $settings['button_link'] ), esc_html( $settings['button_text'] ) );
1174 - } ?></p>
1175 - </div>
1176 - <?php
230 + public static function save_options( $settings ) {
231 + Merchant_Settings_Saver::save_options( $settings );
1177 232 }
1178 233
1179 234 /**
1180 - * Field: Products Selector
235 + * Sanitize options.
1181 236 *
1182 - * @param $settings
1183 - * @param $value
237 + * @since 1.9.3
238 + * @see Merchant_Settings_Saver::sanitize()
1184 239 *
1185 - * @return void
240 + * @param array $field The field configuration.
241 + * @param mixed $value The raw submitted value.
242 + *
243 + * @return mixed The sanitized value.
1186 244 */
1187 - public static function products_selector( $settings, $value, $module_id = '' ) {
1188 - if ( ! class_exists( 'WooCommerce' ) ) {
1189 - echo '<p class="merchant-notice">' . esc_html__( 'WooCommerce is not installed or activated.', 'merchant' ) . '</p>';
1190 -
1191 - return;
1192 - }
1193 -
1194 - $ids = $value ? explode( ',', $value ) : array();
1195 -
1196 - if ( isset( $settings['multiple'] ) ) {
1197 - $multiple = $settings['multiple'] === true ? 'multiple' : 'false';
1198 - } else {
1199 - $multiple = 'multiple';
1200 - }
1201 -
1202 - if ( ! isset( $settings['allowed_types'] ) ) {
1203 - $settings['allowed_types'] = array( 'all' );
1204 - }
1205 - ?>
1206 -
1207 - <div class="merchant-products-search-container" data-multiple="<?php
1208 - echo esc_attr( $multiple ); ?>">
1209 - <div class="merchant-search-area">
1210 - <input type="text" name="merchant-search-field" data-allowed-types="<?php
1211 - echo esc_attr( implode( ',', $settings['allowed_types'] ) ) ?>" placeholder="<?php
1212 - esc_attr_e( 'Search products', 'merchant' ); ?>" class="merchant-search-field">
1213 - <span class="merchant-searching"><?php
1214 - esc_html_e( 'Searching...', 'merchant' ); ?></span>
1215 - <img src="<?php echo esc_url( MERCHANT_URI . 'assets/images/admin/products-search-icon.svg' ); ?>" class="merchant-search-icon"
1216 - alt="<?php esc_attr_e( 'Search icon', 'merchant' ); ?>">
1217 - <div class="merchant-selections-products-preview"></div>
1218 - </div>
1219 - <div class="merchant-selected-products-preview">
1220 - <ul>
1221 - <?php
1222 - if ( ! empty( $ids ) ) {
1223 - foreach ( $ids as $product_id ) {
1224 - $product = wc_get_product( $product_id );
1225 - if ( $product ) {
1226 - self::product_data_li( $product );
1227 - }
1228 - }
1229 - }
1230 - ?>
1231 - </ul>
1232 - </div>
1233 - <input type="hidden" name="merchant[<?php
1234 - echo esc_attr( $settings['id'] ); ?>]" class="merchant-selected-products" value="<?php
1235 - echo esc_attr( $value ) ?>">
1236 - </div>
1237 - <?php
245 + public static function sanitize( $field, $value ) {
246 + return Merchant_Settings_Saver::sanitize( $field, $value );
1238 247 }
1239 248
1240 - public static function product_data_li( $product, $search = false, $hierarchy = false ) {
1241 - $product_id = $product->get_id();
1242 - $product_sku = $product->get_sku();
1243 - $product_name = $product->get_name();
1244 - $edit_link = get_edit_post_link( $product_id );
1245 - if ( $product->is_type( 'variation' ) ) {
1246 - $edit_link = get_edit_post_link( $product->get_parent_id() );
1247 - }
1248 -
1249 - /**
1250 - * Filter product image.
1251 - *
1252 - * @since 1.9.1
1253 - */
1254 - $product_image = apply_filters(
1255 - 'merchant_product_item_product_image',
1256 - '<span class="img">' . $product->get_image( array( 30, 30 ) ) . '</span>',
1257 - $product
1258 - );
1259 -
1260 - $price = $product->get_price();
1261 - $key = $product_id . '_' . $product_sku;
1262 -
1263 - /**
1264 - * Filter the product bundle item product info.
1265 - *
1266 - * @param string $product_info The product bundle item product info.
1267 - * @param WC_Product $product The product object.
1268 - *
1269 - * @since 1.9.0
1270 - */
1271 - $product_info = apply_filters( 'merchant_pro_product_bundle_item_product_info', $product->get_type() . '<br/>#' . $product_id, $product );
1272 - if ( $search ) {
1273 - $remove_btn = '<span class="remove hint--left" aria-label="' . esc_html__( 'Add', 'merchant' ) . '">+</span>';
1274 - } else {
1275 - $remove_btn = '<span class="remove hint--left" aria-label="' . esc_html__( 'Remove', 'merchant' ) . '">×</span>';
1276 - }
1277 -
1278 - $item_class = 'product-item';
1279 - if( $hierarchy && $product->is_type( 'variation' ) ) {
1280 - $item_class .= ' hierarchy-style';
1281 - }
1282 -
1283 - echo '<li class="' . esc_attr( $item_class ) . '" data-key="' . esc_attr( $key ) . '" data-name="' . esc_attr( $product_name ) . '" data-sku="'
1284 - . esc_attr( $product_sku ) . '" data-id="' . esc_attr( $product_id ) . '" data-price="' . esc_attr( $price ) . '">'
1285 - . wp_kses( $product_image, array(
1286 - 'span' => array(
1287 - 'class' => true,
1288 - ),
1289 - 'img' => array(
1290 - 'src' => true,
1291 - 'alt' => true,
1292 - 'decoding' => true,
1293 - 'srcset' => true,
1294 - 'loading' => true,
1295 - 'sizes' => true,
1296 - 'class' => true,
1297 - 'width' => true,
1298 - 'height' => true,
1299 - ),
1300 - ) ) . '<span class="data">'
1301 - . '<span class="name">' . esc_html( $product_name ) . '</span><span class="info">' . wp_kses( $product->get_price_html(), array(
1302 - 'span' => array(
1303 - 'class' => true,
1304 - ),
1305 - 'del' => array(
1306 - 'aria-hidden' => true,
1307 - ),
1308 - 'ins' => array(),
1309 - 'bdi' => array(),
1310 -
1311 - ) ) . '</span> ' . ( $product->is_sold_individually()
1312 - ? '<span class="info">' . esc_html__( 'sold individually', 'merchant' ) . '</span> ' : '' ) . '</span>'
1313 - . '<span class="type"><a href="' . esc_url( $edit_link ) . '" target="_blank">' . wp_kses_post( $product_info ) . '</a></span> ' . wp_kses( $remove_btn, array(
1314 - 'span' => array(
1315 - 'class' => true,
1316 - 'aria-label' => true,
1317 - ),
1318 - ) );
1319 -
1320 - echo '</li>';
1321 - }
1322 -
1323 - public function products_search() {
1324 - check_ajax_referer( 'merchant_admin_options', 'nonce' );
1325 -
1326 - if ( ! isset( $_POST['keyword'] ) || empty( $_POST['keyword'] ) ) {
1327 - exit();
1328 - }
1329 -
1330 - $types = array( 'simple', 'variable' ); // limit search to product types
1331 -
1332 - if ( isset( $_POST['product_types'] ) || ! empty( $_POST['product_types'] ) ) {
1333 - $types = explode( ',', sanitize_text_field( $_POST['product_types'] ) );
1334 - }
1335 -
1336 - $hierarchy = false;
1337 - if (
1338 - in_array( 'all', $types, true )
1339 - || ( in_array( 'variation', $types, true ) && in_array( 'variable', $types, true ) )
1340 - ) {
1341 - $hierarchy = true;
1342 - }
1343 -
1344 - $added_ids = isset( $_POST['ids'] ) ? explode( ',', sanitize_text_field( $_POST['ids'] ) ) : array();
1345 - $keyword = sanitize_text_field( $_POST['keyword'] );
1346 - if ( is_numeric( $keyword ) ) {
1347 - // search by id
1348 - $query_args = array(
1349 - 'p' => absint( $keyword ),
1350 - 'post_type' => 'product',
1351 - );
1352 - } else {
1353 - $query_args = array(
1354 - 'post_type' => 'product',
1355 - 'post_status' => array( 'publish', 'private' ),
1356 - 's' => $keyword,
1357 - 'posts_per_page' => 10,
1358 - );
1359 -
1360 - if ( ! empty( $types ) && ! in_array( 'all', $types, true ) ) {
1361 - $product_types = $types;
1362 -
1363 - if ( in_array( 'variation', $types, true ) ) {
1364 - $product_types[] = 'variable';
1365 - }
1366 -
1367 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1368 - $query_args['tax_query'] = array(
1369 - array(
1370 - 'taxonomy' => 'product_type',
1371 - 'field' => 'slug',
1372 - 'terms' => $product_types,
1373 - ),
1374 - );
1375 - }
1376 -
1377 - $query_args['post__not_in'] = array_map( 'absint', $added_ids );
1378 - }
1379 -
1380 - // Filter by category slugs.
1381 - $categories = array_map( 'sanitize_text_field', $_POST['categories'] ?? array() );
1382 - if ( is_array( $categories ) && ! empty( $categories ) ) {
1383 - $query_args['tax_query'][] = array(
1384 - 'taxonomy' => 'product_cat',
1385 - 'field' => 'slug',
1386 - 'terms' => $categories,
1387 - 'operator' => 'IN',
1388 - );
1389 - }
1390 -
1391 - $query = new WP_Query( $query_args );
1392 -
1393 - if ( $query->have_posts() ) {
1394 - echo '<ul>';
1395 -
1396 - while ( $query->have_posts() ) {
1397 - $query->the_post();
1398 - $_product = wc_get_product( get_the_ID() );
1399 -
1400 - if ( ! $_product ) {
1401 - continue;
1402 - }
1403 -
1404 - if (
1405 - ! $_product->is_type( 'variable' )
1406 - || in_array( 'variable', $types, true )
1407 - || in_array( 'all', $types, true )
1408 - ) {
1409 - self::product_data_li( $_product, array( 'qty' => 1 ), true, $hierarchy );
1410 - }
1411 -
1412 - if (
1413 - $_product->is_type( 'variable' )
1414 - && (
1415 - empty( $types )
1416 - || in_array( 'all', $types, true )
1417 - || in_array( 'variation', $types, true )
1418 - )
1419 - ) {
1420 - // show all children
1421 - $children = $_product->get_children();
1422 -
1423 - if ( is_array( $children ) && count( $children ) > 0 ) {
1424 - foreach ( $children as $child ) {
1425 - $child_product = wc_get_product( $child );
1426 - /**
1427 - * @var WC_Product_Variation $child_product
1428 - */
1429 - if ( ! $this->are_variation_attributes_set( $child_product ) ) {
1430 - // Don't display variations that don't have all attributes set.
1431 - continue;
1432 - }
1433 - self::product_data_li( $child_product, true, $hierarchy );
1434 - }
1435 - }
1436 - }
1437 - }
1438 -
1439 - echo '</ul>';
1440 - wp_reset_postdata();
1441 - } else {
1442 - // translators: %s is the search keyword
1443 - echo wp_kses( '<ul><span>' . sprintf( esc_html__( 'No results found for "%s"', 'merchant' ), $keyword ) . '</span></ul>', array(
1444 - 'ul' => array(),
1445 - 'span' => array(),
1446 - ) );
1447 - }
1448 -
1449 - exit;
1450 - }
1451 -
1452 249 /**
1453 - * Check if all variation attributes are set.
250 + * Get product category choices for Select2.
1454 251 *
1455 - * @param $variation WC_Product_Variation variation product object
252 + * @since 1.9.3
253 + * @see Merchant_Select2_Choices::get_category_select2_choices()
1456 254 *
1457 - * @return bool
255 + * @return array Formatted category choices.
1458 256 */
1459 - public function are_variation_attributes_set( $variation ) {
1460 - // Check if the product is a variation and has attributes
1461 - if ( $variation && $variation->is_type( 'variation' ) ) {
1462 - // Get the variation attributes
1463 - $variation_attributes = $variation->get_variation_attributes();
1464 -
1465 - // Check if all variation attributes are set
1466 - foreach ( $variation_attributes as $attribute => $value ) {
1467 - if ( empty( $value ) ) {
1468 - return false; // At least one attribute is not set
1469 - }
1470 - }
1471 -
1472 - return true; // All variation attributes are set
1473 - }
1474 -
1475 - return false; // Not a valid variation product
257 + public static function get_category_select2_choices() {
258 + return Merchant_Select2_Choices::get_category_select2_choices();
1476 259 }
1477 260
1478 261 /**
1479 - * Field: Select Size Chart
262 + * Get product tag choices for Select2.
263 + *
264 + * @since 1.9.3
265 + * @see Merchant_Select2_Choices::get_tag_select2_choices()
266 + *
267 + * @return array Formatted tag choices.
1480 268 */
1481 - public static function select_size_chart( $settings, $value, $module_id = '' ) {
1482 - $options = array(
1483 - '' => esc_html__( 'Default', 'merchant' ),
1484 - );
1485 -
1486 - $posts = get_posts( array(
1487 - 'post_type' => 'merchant_size_chart',
1488 - 'posts_per_page' => - 1,
1489 - 'post_status' => 'publish',
1490 - ) );
1491 -
1492 - if ( ! is_wp_error( $posts ) && ! empty( $posts ) ) {
1493 - foreach ( $posts as $_post ) {
1494 - $options[ $_post->ID ] = $_post->post_title;
1495 - }
1496 - }
1497 -
1498 - ?>
1499 - <?php
1500 - if ( ! empty( $options ) ) : ?>
1501 - <select name="merchant[<?php
1502 - echo esc_attr( $settings['id'] ); ?>]">
1503 - <?php
1504 - foreach ( $options as $key => $option ) : ?>
1505 - <option value="<?php
1506 - echo esc_attr( $key ); ?>" <?php
1507 - selected( $value, $key, true ); ?>><?php
1508 - echo esc_html( $option ); ?></option>
1509 - <?php
1510 - endforeach; ?>
1511 - </select>
1512 - <?php
1513 - endif; ?>
1514 - <?php
269 + public static function get_tag_select2_choices() {
270 + return Merchant_Select2_Choices::get_tag_select2_choices();
1515 271 }
1516 272
1517 273 /**
1518 - * Field: Buttons
274 + * Get product brand choices for Select2.
275 + *
276 + * @since 1.9.3
277 + * @see Merchant_Select2_Choices::get_brand_select2_choices()
278 + *
279 + * @return array Formatted brand choices.
1519 280 */
1520 - public static function buttons( $settings, $value, $module_id = '' ) {
1521 - ?>
1522 - <div class="merchant-buttons">
1523 - <?php
1524 - if ( ! empty( $settings['options'] ) ) : ?>
1525 - <?php
1526 - foreach ( $settings['options'] as $key => $option ) : ?>
1527 - <label class="merchant-button-<?php
1528 - echo esc_attr( $key ); ?>"">
1529 - <input type="radio" name="merchant[<?php
1530 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1531 - echo esc_attr( $key ); ?>" <?php
1532 - checked( $value, $key, true ); ?>/>
1533 - <span><?php
1534 - echo esc_html( $option ); ?></span>
1535 - </label>
1536 - <?php
1537 - endforeach; ?>
1538 - <?php
1539 - endif; ?>
1540 - </div>
1541 - <?php
281 + public static function get_brand_select2_choices() {
282 + return Merchant_Select2_Choices::get_brand_select2_choices();
1542 283 }
1543 284
1544 285 /**
1545 - * Field: Buttons Alt
286 + * Get user role choices for Select2.
287 + *
288 + * @since 1.9.3
289 + * @see Merchant_Select2_Choices::get_user_roles_select2_choices()
290 + *
291 + * @return array Formatted role choices.
1546 292 */
1547 - public static function buttons_alt( $settings, $value, $module_id = '' ) {
1548 - ?>
1549 - <div class="merchant-buttons">
1550 - <?php
1551 - if ( ! empty( $settings['options'] ) ) : ?>
1552 - <?php
1553 - foreach ( $settings['options'] as $key => $option ) : ?>
1554 - <label class="merchant-button-<?php
1555 - echo esc_attr( $key ); ?>">
1556 - <input type="radio" name="merchant[<?php
1557 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1558 - echo esc_attr( $key ); ?>" <?php
1559 - checked( $value, $key, true ); ?>/>
1560 - <span><?php
1561 - echo esc_html( $option ); ?></span>
1562 - </label>
1563 - <?php
1564 - endforeach; ?>
1565 - <?php
1566 - endif; ?>
1567 - </div>
1568 - <?php
293 + public static function get_user_roles_select2_choices() {
294 + return Merchant_Select2_Choices::get_user_roles_select2_choices();
1569 295 }
1570 296
1571 297 /**
1572 - * Field: Buttons Content
298 + * Get weekday choices, Monday first, keyed by day name.
299 + *
300 + * @since 2.3.2
301 + * @see Merchant_Select2_Choices::get_weekday_choices()
302 + *
303 + * @return array Day name => translated label.
1573 304 */
1574 - public static function buttons_content( $settings, $value, $module_id = '' ) {
1575 - ?>
1576 - <div class="merchant-buttons-content">
1577 - <?php
1578 - if ( ! empty( $settings['options'] ) ) : ?>
1579 - <?php
1580 - foreach ( $settings['options'] as $key => $option ) : ?>
1581 - <label class="merchant-button-content-<?php echo esc_attr( $key ); ?>">
1582 - <input
1583 - type="radio"
1584 - name="merchant[<?php echo esc_attr( $settings['id'] ); ?>]"
1585 - value="<?php echo esc_attr( $key ); ?>"
1586 - <?php checked( $value, $key, true ); ?>
1587 - />
1588 -
1589 - <span class="merchant-buttons-inner-content">
1590 - <?php if ( $option['icon'] ) : ?>
1591 - <img src="<?php echo esc_url( $option['icon'] ); ?>" alt="">
1592 - <?php endif; ?>
1593 - <span>
1594 - <span><?php echo esc_html( $option['title'] ?? '' ); ?></span>
1595 - <span><?php echo esc_html( $option['desc'] ?? '' ); ?></span>
1596 - </span>
1597 - </span>
1598 - </label>
1599 - <?php
1600 - endforeach; ?>
1601 - <?php
1602 - endif; ?>
1603 - </div>
1604 - <?php
305 + public static function get_weekday_choices() {
306 + return Merchant_Select2_Choices::get_weekday_choices();
1605 307 }
1606 308
1607 309 /**
1608 - * Field: Range
310 + * Get shipping zone choices for Select2.
311 + *
312 + * @since 2.3.2
313 + * @see Merchant_Select2_Choices::get_shipping_zone_select2_choices()
314 + *
315 + * @return array Formatted zone choices.
1609 316 */
1610 - public static function range( $settings, $value, $module_id = '' ) {
1611 - $settings = wp_parse_args( $settings, array(
1612 - 'min' => '',
1613 - 'max' => '',
1614 - 'step' => '',
1615 - 'unit' => '',
1616 - ) );
1617 -
1618 - ?>
1619 - <div class="merchant-range">
1620 - <input type="range" class="merchant-range-input" name="" min="<?php
1621 - echo esc_attr( $settings['min'] ); ?>" max="<?php
1622 - echo esc_attr( $settings['max'] ); ?>"
1623 - step="<?php
1624 - echo esc_attr( $settings['step'] ); ?>" value="<?php
1625 - echo esc_attr( $value ); ?>"/>
1626 - <input type="number" class="merchant-range-number-input" name="merchant[<?php
1627 - echo esc_attr( $settings['id'] ); ?>]" min="<?php
1628 - echo esc_attr( $settings['min'] ); ?>"
1629 - max="<?php
1630 - echo esc_attr( $settings['max'] ); ?>" step="<?php
1631 - echo esc_attr( $settings['step'] ); ?>" value="<?php
1632 - echo esc_attr( $value ); ?>"/>
1633 - <?php
1634 - if ( ! empty( $settings['unit'] ) ) : ?>
1635 - <span class="merchant-range-unit"><?php
1636 - echo esc_html( $settings['unit'] ); ?></span>
1637 - <?php
1638 - endif; ?>
1639 - </div>
1640 - <?php
317 + public static function get_shipping_zone_select2_choices() {
318 + return Merchant_Select2_Choices::get_shipping_zone_select2_choices();
1641 319 }
1642 320
1643 321 /**
1644 - * Field: Color
322 + * Get registered shipping method choices for Select2.
323 + *
324 + * @since 2.3.2
325 + * @see Merchant_Select2_Choices::get_shipping_method_select2_choices()
326 + *
327 + * @return array Formatted method choices.
1645 328 */
1646 - public static function color( $settings, $value, $module_id = '' ) {
1647 - $settings = wp_parse_args( $settings, array(
1648 - 'default' => '#212121',
1649 - ) );
1650 -
1651 - ?>
1652 - <div class="merchant-color">
1653 - <div class="merchant-color-picker" data-default-color="<?php
1654 - echo esc_attr( $settings['default'] ); ?>" style="background-color: <?php
1655 - echo esc_attr( $value ); ?>;"></div>
1656 - <input type="text" class="merchant-color-input" name="merchant[<?php
1657 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1658 - echo esc_attr( $value ); ?>"/>
1659 - </div>
1660 - <?php
329 + public static function get_shipping_method_select2_choices() {
330 + return Merchant_Select2_Choices::get_shipping_method_select2_choices();
1661 331 }
1662 332
1663 333 /**
1664 - * Field: Gallery
334 + * Get enabled payment gateway choices for Select2.
335 + *
336 + * @since 2.3.2
337 + * @see Merchant_Select2_Choices::get_payment_gateway_select2_choices()
338 + *
339 + * @return array Formatted gateway choices.
1665 340 */
1666 - public static function gallery( $settings, $value, $module_id = '' ) {
1667 - $settings = wp_parse_args( $settings, array(
1668 - 'label' => esc_html__( 'Select Images', 'merchant' ),
1669 - ) );
1670 -
1671 - $images = ( ! empty( $value ) ) ? explode( ',', $value ) : array();
1672 -
1673 -
1674 - echo '<div class="merchant-gallery-images">';
1675 -
1676 - if ( ! empty( $images ) ) :
1677 -
1678 - foreach ( $images as $image_id ) :
1679 -
1680 - $image = wp_get_attachment_image_src( $image_id, 'thumbnail' );
1681 -
1682 - if ( ! empty( $image ) && ! empty( $image[0] ) ) :
1683 -
1684 - printf( '<div class="merchant-gallery-image" data-item-id="%s">', esc_attr( $image_id ) );
1685 -
1686 - echo '<i class="merchant-gallery-remove dashicons dashicons-no-alt"></i>';
1687 -
1688 - printf( '<img src="%s" />', esc_url( $image[0] ) );
1689 -
1690 - echo '</div>';
1691 -
1692 - endif;
1693 -
1694 - endforeach;
1695 -
1696 - endif;
1697 -
1698 - echo '</div>';
1699 -
1700 - ?>
1701 - <a href="#" class="merchant-gallery-button"><?php
1702 - echo esc_html( $settings['label'] ); ?></a>
1703 - <input type="hidden" class="merchant-gallery-input" name="merchant[<?php
1704 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1705 - echo esc_attr( $value ); ?>"/>
1706 - <?php
341 + public static function get_payment_gateway_select2_choices() {
342 + return Merchant_Select2_Choices::get_payment_gateway_select2_choices();
1707 343 }
1708 344
1709 345 /**
1710 - * Field: Upload
346 + * Get customer user choices for Select2.
347 + *
348 + * @since 1.9.3
349 + * @see Merchant_Select2_Choices::get_customers_select2_choices()
350 + *
351 + * @return array Formatted customer choices.
1711 352 */
1712 - public static function upload( $settings, $value, $module_id = '' ) {
1713 - $settings = wp_parse_args( $settings, array(
1714 - 'label' => esc_html__( 'Select Image', 'merchant' ),
1715 - ) );
1716 -
1717 - echo '<div class="merchant-upload-wrapper">';
1718 - if ( ! empty( $value ) ) :
1719 - $image = wp_get_attachment_image_src( $value, 'thumbnail' );
1720 - if ( ! empty( $image ) && ! empty( $image[0] ) ) :
1721 - echo '<div class="merchant-upload-image">';
1722 - echo '<i class="merchant-upload-remove dashicons dashicons-no-alt"></i>';
1723 - printf( '<img src="%s" />', esc_url( $image[0] ) );
1724 - echo '</div>';
1725 - endif;
1726 - endif;
1727 - echo '</div>';
1728 -
1729 - $drag_drop = $settings['drag_drop'] ?? false;
1730 - ?>
1731 - <div class="merchant-upload-button-wrapper<?php echo esc_attr( $drag_drop ? ' merchant-upload-button-drag-drop' : '' ); ?>">
1732 - <?php if ( $drag_drop ) : ?>
1733 - <img src="<?php echo esc_url( esc_url( MERCHANT_URI . '/assets/images/upload-icon.svg' ) ); ?>" alt="<?php echo esc_attr__( 'Upload image', 'merchant' ); ?>"/>
1734 - <?php endif; ?>
1735 - <a href="#" class="merchant-upload-button"><?php echo esc_html( $settings['label'] ?? '' ); ?></a>
1736 - </div>
1737 - <input type="hidden" class="merchant-upload-input" name="merchant[<?php echo esc_attr( $settings['id'] ); ?>]" value="<?php echo esc_attr( $value ); ?>"/>
1738 - <?php
353 + public static function get_customers_select2_choices() {
354 + return Merchant_Select2_Choices::get_customers_select2_choices();
1739 355 }
1740 356
1741 - /**
1742 - * Field: Warning
1743 - */
1744 - public static function warning( $settings ) {
1745 - echo wp_kses_post( $settings['content'] );
1746 - }
357 + // ──────────────────────────────────────────────────────
358 + // Admin hooks — small one-off actions.
359 + // ──────────────────────────────────────────────────────
1747 360
1748 361 /**
1749 - * Field: Warning
1750 - */
1751 - public static function info( $settings ) {
1752 - echo wp_kses_post( $settings['content'] );
1753 - }
1754 -
1755 - /**
1756 - * Field: Divider
1757 - */
1758 - public static function divider() {
1759 - }
1760 -
1761 - /**
1762 - * Field: Content
1763 - */
1764 - public static function content( $settings ) {
1765 - echo wp_kses_post( $settings['content'] );
1766 - }
1767 -
1768 - /**
1769 - * Field: Sortable
1770 - */
1771 - public static function sortable( $settings, $value, $module_id = '' ) {
1772 - ?>
1773 - <div class="merchant-sortable">
1774 - <ul class="merchant-sortable-list ui-sortable">
1775 - <?php
1776 - foreach ( $value as $option_key ) :
1777 - $option_val = $settings['options'][ $option_key ];
1778 -
1779 - if ( in_array( $option_key, $value, true ) ) :
1780 - ?>
1781 - <li class="merchant-sortable-item" data-value="<?php
1782 - echo esc_attr( $option_key ); ?>">
1783 - <i class='dashicons dashicons-menu'></i>
1784 - <i class="dashicons dashicons-visibility visibility"></i>
1785 - <?php
1786 - echo esc_html( $option_val ); ?>
1787 - </li>
1788 - <?php
1789 - endif; ?>
1790 - <?php
1791 - endforeach; ?>
1792 -
1793 - <?php
1794 - foreach ( $settings['options'] as $option_key => $option_val ) :
1795 - if ( ! in_array( $option_key, $value, true ) ) :
1796 - $invisible = ! in_array( $option_key, $value, true ) ? ' invisible' : '';
1797 -
1798 - ?>
1799 - <li class="merchant-sortable-item<?php
1800 - echo esc_attr( $invisible ); ?>" data-value="<?php
1801 - echo esc_attr( $option_key ); ?>">
1802 - <i class='dashicons dashicons-menu'></i>
1803 - <i class="dashicons dashicons-visibility visibility"></i>
1804 - <?php
1805 - echo esc_html( $option_val ); ?>
1806 - </li>
1807 - <?php
1808 - endif; ?>
1809 - <?php
1810 - endforeach; ?>
1811 - </ul>
1812 -
1813 - <input class="merchant-sortable-input" type="hidden" name="merchant[<?php
1814 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1815 - echo esc_attr( wp_json_encode( $value ) ); ?>"/>
1816 - </div>
1817 - <?php
1818 - }
1819 -
1820 - /**
1821 - * Field: Sortable Repeater.
1822 - */
1823 - public static function sortable_repeater( $settings, $value, $module_id = '' ) {
1824 - ?>
1825 - <div class="merchant-sortable-repeater-control<?php
1826 - echo isset( $settings['sorting'] ) && false === $settings['sorting'] ? ' disable-sorting' : ''; ?>">
1827 - <div class="merchant-sortable-repeater sortable regular-field">
1828 - <div class="repeater">
1829 - <input type="text" value="" class="repeater-input"/><span class="dashicons dashicons-menu"></span><a class="customize-control-sortable-repeater-delete"
1830 - href="#"><span
1831 - class="dashicons dashicons-no-alt"></span></a>
1832 - </div>
1833 - </div>
1834 - <button class="button customize-control-sortable-repeater-add" type="button"><?php
1835 - echo esc_html( $settings['button_label'] ); ?></button>
1836 - <input class="merchant-sortable-repeater-input" type="hidden" name="merchant[<?php
1837 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1838 - echo esc_attr( wp_json_encode( $value ) ); ?>"/>
1839 - </div>
1840 - <?php
1841 - }
1842 -
1843 - public static function wc_coupons( $settings, $value ) {
1844 - $coupons = get_posts( array(
1845 - 'posts_per_page' => - 1,
1846 - 'orderby' => 'title',
1847 - 'order' => 'asc',
1848 - 'post_type' => 'shop_coupon',
1849 - 'post_status' => 'publish',
1850 - ) );
1851 - if ( $coupons ) {
1852 - ?>
1853 - <div class="merchant-wc-coupon-selector merchant-<?php
1854 - echo esc_attr( $settings['id'] ) ?>">
1855 - <?php
1856 - echo '<select name="merchant[' . esc_attr( $settings['id'] ) . ']">';
1857 - echo '<option value="">' . esc_html__( 'Select a coupon', 'merchant' ) . '</option>';
1858 -
1859 - foreach ( $coupons as $coupon ) {
1860 - $coupon_code = strtolower( $coupon->post_title );
1861 -
1862 - echo '<option value="' . esc_attr( $coupon_code ) . '"' . selected( esc_attr( $coupon_code ), $value, false ) . '>';
1863 - echo esc_html( $coupon_code );
1864 - echo '</option>';
1865 - }
1866 -
1867 - echo '</select>';
1868 - echo '<a href="' . esc_url( admin_url( 'edit.php?post_type=shop_coupon' ) ) . '" target="_blank" style="margin-left: 10px;">' . esc_html__( 'Manage coupons',
1869 - 'merchant' ) . '</a>';
1870 - ?>
1871 - </div>
1872 - <?php
1873 - } else {
1874 - echo '<div style="color: red;">';
1875 - echo wp_kses_post(
1876 - sprintf(
1877 - /* Translators: 1. Coupon admin url 2. Link target attribute value */
1878 - __( 'No coupons found! <a href="%1$s" target="%2$s">Create a new coupon</a>', 'merchant' ),
1879 - admin_url( 'post-new.php?post_type=shop_coupon' ),
1880 - '_blank'
1881 - )
1882 - );
1883 - echo '</div>';
1884 - echo '<input type="hidden" name="merchant[' . esc_attr( $settings['id'] ) . ']" value="" />';
1885 - }
1886 - }
1887 -
1888 - /**
1889 - * Field: Date Time.
1890 - *
1891 - * @param $settings array field settings.
1892 - * @param $value string field value.
1893 - * @param $module_id string module id.
362 + * Delete stale module data from the database.
1894 363 *
1895 - * @return void
1896 - */
1897 - public static function date_time( $settings, $value, $module_id = '' ) {
1898 - // All options are documented here: https://air-datepicker.com/docs
1899 - $options = array(
1900 - 'dateFormat' => 'MM-dd-yyyy',
1901 - 'timepicker' => true,
1902 - 'timeFormat' => 'hh:mm AA',
1903 - 'minDate' => 'today',
1904 - 'timeZone' => wp_timezone_string(),
1905 - );
1906 - if ( isset( $settings['options'] ) ) {
1907 - $settings['options'] = wp_parse_args( $settings['options'], $options );
1908 - } else {
1909 - $settings['options'] = $options;
1910 - }
1911 - ?>
1912 - <div class="merchant-datetime-field" data-options="<?php echo esc_attr( wp_json_encode( $settings['options'] ) ); ?>">
1913 - <input type="text" name="merchant[<?php
1914 - echo esc_attr( $settings['id'] ); ?>]" value="<?php
1915 - echo esc_attr( $value ); ?>" placeholder="<?php
1916 - echo isset( $settings['placeholder'] ) ? esc_attr( $settings['placeholder'] ) : ''; ?>"/>
1917 - </div>
1918 - <?php
1919 - }
1920 -
1921 - /**
1922 - * Field: Flexible Content.
364 + * Cleans up data for removed modules (e.g., the deprecated
365 + * `code-snippets` module). Hooked to `admin_init`.
1923 366 *
1924 - * @param array $settings
1925 - * @param mixed $value
367 + * @since 1.9.3
1926 368 *
1927 369 * @return void
1928 370 */
1929 - public static function flexible_content( $settings, $value, $module_id = '' ) {
1930 - $values = ( is_array( $value ) && ! empty( $value ) ) ? $value : array();
1931 - $empty = empty( $values ) ? 'empty' : '';
371 + public function delete_module_data() {
372 + $options = get_option( 'merchant', array() );
1932 373
1933 - $settings = wp_parse_args( $settings, array(
1934 - 'sorting' => false,
1935 - 'style' => 'default',
1936 - 'accordion' => false,
1937 - ) );
1938 -
1939 - $classes = array(
1940 - 'merchant-flexible-content-control',
1941 - "{$settings['style']}-style",
1942 - );
1943 -
1944 - $has_sorting = (bool) ( $settings['sorting'] ?? true );
1945 - if ( ! $has_sorting ) {
1946 - $classes[] = 'disable-sorting';
374 + if ( isset( $options['code-snippets'] ) ) {
375 + unset( $options['code-snippets'] );
376 + update_option( 'merchant', $options );
1947 377 }
1948 -
1949 - $has_accordion = (bool) ( $settings['accordion'] ?? false );
1950 - if ( $has_accordion ) {
1951 - $classes[] = 'has-accordion';
1952 - }
1953 -
1954 - $has_duplicate = (bool) ( $settings['duplicate'] ?? false );
1955 - if ( $has_duplicate ) {
1956 - $classes[] = 'has-duplicate';
1957 - }
1958 - ?>
1959 - <div class="<?php
1960 - echo esc_attr( implode( ' ', $classes ) ); ?>"
1961 - data-id="<?php
1962 - echo esc_attr( $settings['id'] ) ?>">
1963 -
1964 - <div class="layouts" data-id="<?php echo esc_attr( $settings['id'] ) ?>">
1965 - <?php foreach ( $settings['layouts'] as $layout_type => $layout ) : ?>
1966 - <div class="layout" data-type="<?php echo esc_attr( $layout_type ) ?>">
1967 - <div class="layout__inner">
1968 - <?php if ( $has_sorting ) : ?>
1969 - <span class="customize-control-flexible-content-move">
1970 - <svg xmlns="http://www.w3.org/2000/svg" width="10" height="14" viewBox="0 0 10 14" fill="none">
1971 - <path d="M1.75 0.5C1.19772 0.5 0.75 0.947715 0.75 1.5V2.5C0.75 3.05228 1.19772 3.5 1.75 3.5H2.75C3.30228 3.5 3.75 3.05228 3.75 2.5V1.5C3.75 0.947715 3.30228 0.5 2.75 0.5H1.75Z" fill="#4A4A4A"/>
1972 - <path d="M1.75 5.5C1.19772 5.5 0.75 5.94772 0.75 6.5V7.5C0.75 8.05228 1.19772 8.5 1.75 8.5H2.75C3.30228 8.5 3.75 8.05228 3.75 7.5V6.5C3.75 5.94772 3.30228 5.5 2.75 5.5H1.75Z" fill="#4A4A4A"/>
1973 - <path d="M0.75 11.5C0.75 10.9477 1.19772 10.5 1.75 10.5H2.75C3.30228 10.5 3.75 10.9477 3.75 11.5V12.5C3.75 13.0523 3.30228 13.5 2.75 13.5H1.75C1.19772 13.5 0.75 13.0523 0.75 12.5V11.5Z" fill="#4A4A4A"/>
1974 - <path d="M7.25 0.5C6.69772 0.5 6.25 0.947715 6.25 1.5V2.5C6.25 3.05228 6.69772 3.5 7.25 3.5H8.25C8.80228 3.5 9.25 3.05228 9.25 2.5V1.5C9.25 0.947715 8.80228 0.5 8.25 0.5H7.25Z" fill="#4A4A4A"/>
1975 - <path d="M6.25 6.5C6.25 5.94772 6.69772 5.5 7.25 5.5H8.25C8.80228 5.5 9.25 5.94772 9.25 6.5V7.5C9.25 8.05228 8.80228 8.5 8.25 8.5H7.25C6.69772 8.5 6.25 8.05228 6.25 7.5V6.5Z" fill="#4A4A4A"/>
1976 - <path d="M7.25 10.5C6.69772 10.5 6.25 10.9477 6.25 11.5V12.5C6.25 13.0523 6.69772 13.5 7.25 13.5H8.25C8.80228 13.5 9.25 13.0523 9.25 12.5V11.5C9.25 10.9477 8.80228 10.5 8.25 10.5H7.25Z" fill="#4A4A4A"/>
1977 - </svg>
1978 - </span>
1979 - <?php endif; ?>
1980 - <div class="layout-header">
1981 - <div class="layout-count">1</div>
1982 - <div class="layout-title"<?php
1983 - if ( isset( $layout['title-field'] ) && ! empty( $layout['title-field'] ) ) {
1984 - echo ' data-title-field="' . esc_attr( $layout['title-field'] ) . '"';
1985 - } ?>>
1986 - <?php
1987 - echo esc_html( $layout['title'] ) ?>
1988 - </div>
1989 - <div class="layout-toggle">
1990 - <?php if ( $has_accordion ) : ?>
1991 - <span class="customize-control-flexible-content-accordion">
1992 - <svg xmlns="http://www.w3.org/2000/svg" width="10" height="7" viewBox="0 0 10 7" fill="none">
1993 - <path fill-rule="evenodd" clip-rule="evenodd" d="M0.71967 0.732854C1.01256 0.43996 1.48744 0.43996 1.78033 0.732854L5.25 4.20252L8.71967 0.732854C9.01256 0.43996 9.48744 0.43996 9.78033 0.732854C10.0732 1.02575 10.0732 1.50062 9.78033 1.79351L5.78033 5.79351C5.48744 6.08641 5.01256 6.08641 4.71967 5.79351L0.71967 1.79351C0.426777 1.50062 0.426777 1.02575 0.71967 0.732854Z" fill="#4A4A4A"/>
1994 - </svg>
1995 - </span>
1996 - <?php endif; ?>
1997 - </div>
1998 - </div>
1999 - <div class="layout-body">
2000 - <?php
2001 - foreach ( $layout['fields'] as $sub_field ) :
2002 - $classes = array( 'layout-field' );
2003 -
2004 - if ( isset( $sub_field['classes'] ) ) {
2005 - $classes = array_merge( $classes, $sub_field['classes'] );
2006 - } ?>
2007 - <div class="<?php
2008 - echo esc_attr( implode( ' ', $classes ) ); ?>">
2009 - <?php
2010 - if ( 'fields_group' === $sub_field['type'] ) {
2011 - static::fields_group( $sub_field, $value, $module_id, true, array(
2012 - 'id' => $settings['id'],
2013 - 'option_key' => 0,
2014 - 'value' => $value,
2015 - ) );
2016 - } else {
2017 - static::replace_field(
2018 - $sub_field,
2019 - $sub_field['default'] ?? '',
2020 - array(
2021 - "name=\"merchant[{$sub_field['id']}]",
2022 - 'merchant-module-page-setting-field-upload',
2023 - 'merchant-module-page-setting-field-select_ajax',
2024 - ),
2025 - array(
2026 - "data-name=\"merchant[{$settings['id']}][0][{$sub_field['id']}]",
2027 - 'merchant-module-page-setting-field-upload template',
2028 - 'merchant-module-page-setting-field-select_ajax template',
2029 - ),
2030 - $module_id
2031 - );
2032 - }
2033 - ?>
2034 - </div>
2035 - <?php
2036 - endforeach; ?>
2037 - <input type="hidden" data-name="merchant[<?php
2038 - echo esc_attr( $settings['id'] ) ?>][0][layout]" value="<?php
2039 - echo esc_attr( $layout_type ) ?>">
2040 - </div>
2041 - <?php self::print_flexible_layout_actions( $settings, $layout_type ); ?>
2042 - </div>
2043 - </div>
2044 - <?php
2045 - endforeach; ?>
2046 - </div>
2047 -
2048 - <div class="merchant-flexible-content <?php echo esc_attr( $empty ); ?> sortable">
2049 - <?php
2050 - foreach ( $values as $option_key => $option ) : ?>
2051 - <div class="layout" data-type="<?php echo esc_attr( $option['layout'] ) ?>">
2052 - <div class="layout__inner">
2053 - <?php if ( $has_sorting ) : ?>
2054 - <span class="customize-control-flexible-content-move">
2055 - <svg xmlns="http://www.w3.org/2000/svg" width="10" height="14" viewBox="0 0 10 14" fill="none">
2056 - <path d="M1.75 0.5C1.19772 0.5 0.75 0.947715 0.75 1.5V2.5C0.75 3.05228 1.19772 3.5 1.75 3.5H2.75C3.30228 3.5 3.75 3.05228 3.75 2.5V1.5C3.75 0.947715 3.30228 0.5 2.75 0.5H1.75Z" fill="#4A4A4A"/>
2057 - <path d="M1.75 5.5C1.19772 5.5 0.75 5.94772 0.75 6.5V7.5C0.75 8.05228 1.19772 8.5 1.75 8.5H2.75C3.30228 8.5 3.75 8.05228 3.75 7.5V6.5C3.75 5.94772 3.30228 5.5 2.75 5.5H1.75Z" fill="#4A4A4A"/>
2058 - <path d="M0.75 11.5C0.75 10.9477 1.19772 10.5 1.75 10.5H2.75C3.30228 10.5 3.75 10.9477 3.75 11.5V12.5C3.75 13.0523 3.30228 13.5 2.75 13.5H1.75C1.19772 13.5 0.75 13.0523 0.75 12.5V11.5Z" fill="#4A4A4A"/>
2059 - <path d="M7.25 0.5C6.69772 0.5 6.25 0.947715 6.25 1.5V2.5C6.25 3.05228 6.69772 3.5 7.25 3.5H8.25C8.80228 3.5 9.25 3.05228 9.25 2.5V1.5C9.25 0.947715 8.80228 0.5 8.25 0.5H7.25Z" fill="#4A4A4A"/>
2060 - <path d="M6.25 6.5C6.25 5.94772 6.69772 5.5 7.25 5.5H8.25C8.80228 5.5 9.25 5.94772 9.25 6.5V7.5C9.25 8.05228 8.80228 8.5 8.25 8.5H7.25C6.69772 8.5 6.25 8.05228 6.25 7.5V6.5Z" fill="#4A4A4A"/>
2061 - <path d="M7.25 10.5C6.69772 10.5 6.25 10.9477 6.25 11.5V12.5C6.25 13.0523 6.69772 13.5 7.25 13.5H8.25C8.80228 13.5 9.25 13.0523 9.25 12.5V11.5C9.25 10.9477 8.80228 10.5 8.25 10.5H7.25Z" fill="#4A4A4A"/>
2062 - </svg>
2063 - </span>
2064 - <?php endif; ?>
2065 - <div class="layout-header">
2066 - <div class="layout-count"><?php
2067 - echo absint( $option_key + 1 ) ?></div>
2068 - <div class="layout-title"<?php
2069 - if ( isset( $layout['title-field'] ) && ! empty( $layout['title-field'] ) ) {
2070 - echo ' data-title-field="' . esc_attr( $layout['title-field'] ) . '"';
2071 - } ?>>
2072 - <?php
2073 - echo isset( $settings['layouts'][ $option['layout'] ]['title'] ) ? esc_html( $settings['layouts'][ $option['layout'] ]['title'] ) : '' ?>
2074 - </div>
2075 - <div class="layout-toggle">
2076 - <?php if ( $has_accordion ) : ?>
2077 - <span class="customize-control-flexible-content-accordion">
2078 - <svg xmlns="http://www.w3.org/2000/svg" width="10" height="7" viewBox="0 0 10 7" fill="none">
2079 - <path fill-rule="evenodd" clip-rule="evenodd" d="M0.71967 0.732854C1.01256 0.43996 1.48744 0.43996 1.78033 0.732854L5.25 4.20252L8.71967 0.732854C9.01256 0.43996 9.48744 0.43996 9.78033 0.732854C10.0732 1.02575 10.0732 1.50062 9.78033 1.79351L5.78033 5.79351C5.48744 6.08641 5.01256 6.08641 4.71967 5.79351L0.71967 1.79351C0.426777 1.50062 0.426777 1.02575 0.71967 0.732854Z" fill="#4A4A4A"/>
2080 - </svg>
2081 - </span>
2082 - <?php endif; ?>
2083 - </div>
2084 - </div>
2085 - <div class="layout-body">
2086 - <?php
2087 - foreach ( $settings['layouts'][ $option['layout'] ]['fields'] as $sub_field ) :
2088 - $classes = array( 'layout-field' );
2089 - if ( isset( $sub_field['classes'] ) ) {
2090 - $classes = array_merge( $classes, $sub_field['classes'] );
2091 - } ?>
2092 - <div class="<?php
2093 - echo esc_attr( implode( ' ', $classes ) ) ?>">
2094 - <?php
2095 - $value = null;
2096 - if ( isset( $option[ $sub_field['id'] ] ) ) {
2097 - $value = $option[ $sub_field['id'] ];
2098 - } elseif ( isset( $sub_field['type'] ) && $sub_field['type'] === 'switcher' ) {
2099 - $value = is_null( $value ) ? 0 : 1;
2100 - } elseif ( isset( $sub_field['default'] ) ) {
2101 - $value = $sub_field['default'];
2102 - }
2103 -
2104 - if ( 'fields_group' === $sub_field['type'] ) {
2105 - static::fields_group( $sub_field, $value, $module_id, true, array(
2106 - 'id' => $settings['id'],
2107 - 'option_key' => $option_key,
2108 - 'value' => $option,
2109 - ) );
2110 - } else {
2111 - static::replace_field(
2112 - $sub_field,
2113 - $value,
2114 - "name=\"merchant[{$sub_field['id']}]",
2115 - "name=\"merchant[{$settings['id']}][{$option_key}][{$sub_field['id']}]",
2116 - $module_id
2117 - );
2118 - }
2119 - ?>
2120 - </div>
2121 - <?php
2122 - endforeach; ?>
2123 - <input type="hidden" name="merchant[<?php
2124 - echo esc_attr( $settings['id'] ) ?>][<?php
2125 - echo absint( $option_key ) ?>][layout]"
2126 - value="<?php
2127 - echo esc_attr( $option['layout'] ) ?>">
2128 - </div>
2129 - <?php self::print_flexible_layout_actions( $settings, $layout_type ); ?>
2130 - </div>
2131 - </div>
2132 - <?php
2133 - endforeach; ?>
2134 - </div>
2135 -
2136 - <div class="customize-control-flexible-content-add-wrapper">
2137 - <div class="customize-control-flexible-content-add-list">
2138 - <?php
2139 - foreach ( $settings['layouts'] as $layout_type => $layout ) : ?>
2140 - <a href="#"
2141 - class="customize-control-flexible-content-add"
2142 - data-id="<?php
2143 - echo esc_attr( $settings['id'] ) ?>"
2144 - data-layout="<?php
2145 - echo esc_attr( $layout_type ) ?>">
2146 - <?php
2147 - echo esc_attr( $layout['title'] ) ?>
2148 - </a>
2149 - <?php
2150 - endforeach; ?>
2151 - </div>
2152 - <button class="button customize-control-flexible-content-add-button" type="button"><?php
2153 - echo esc_html( $settings['button_label'] ); ?></button>
2154 - </div>
2155 - </div>
2156 - <?php
2157 378 }
2158 379
2159 380 /**
2160 - * Field: Group.
2161 - *
2162 - * @param $settings array field settings.
2163 - * @param $value mixed field value.
2164 - * @param $module_id string module id.
2165 - * @param $inside_flexible boolean is inside flexible content.
2166 - * @param $args array extra arguments to be passed if inside flexible content.
381 + * Activate the Merchant Pro plugin.
2167 382 *
383 + * Handles the `merchant_activate_pro` admin action: verifies
384 + * nonce and capabilities, activates the Pro plugin, and
385 + * redirects back to the originating Merchant page.
386 + *
387 + * @since 1.0
388 + *
2168 389 * @return void
2169 390 */
2170 - public static function fields_group( $settings, $value, $module_id = '', $inside_flexible = false, $args = array() ) {
2171 - $control_field_status = ! empty( $settings['display_status'] ) && $settings['display_status'] === true;
2172 - $accordion = ! empty( $settings['accordion'] ) && $settings['accordion'] === true;
2173 - $state = ! empty( $settings['state'] ) && $settings['state'] === 'open';
2174 - ?>
2175 - <div class="merchant-group-field<?php
2176 - echo $accordion ? ' has-accordion' : '';
2177 - echo $control_field_status ? ' has-flag' : '';
2178 - echo $state ? ' open' : '';
2179 - echo ' merchant-group-field-' . esc_attr( $settings['id'] ) ?>" data-id="<?php
2180 - echo esc_attr( $settings['id'] ) ?>">
2181 - <div class="title-area<?php
2182 - echo $accordion ? ' accordion-style' : '' ?>">
2183 - <?php
2184 - if ( ! empty( $settings['title'] ) ) {
2185 - printf( '<div class="merchant-module-page-setting-field-title">%s<span class="field-status hidden"></span></div>', esc_html( $settings['title'] ) );
2186 - }
2187 - if ( ! empty( $settings['sub-desc'] ) ) {
2188 - printf( '<div class="merchant-module-page-setting-field-desc">%s</div>', wp_kses_post( $settings['sub-desc'] ) );
2189 - }
2190 - if ( $accordion ) {
2191 - ?>
2192 - <span class="accordion-icon dashicons dashicons-arrow-down-alt2"></span>
2193 - <?php
2194 - }
2195 - ?>
2196 - </div>
2197 - <div class="merchant-group-fields-container">
2198 - <?php
2199 - if ( $control_field_status ) {
2200 - /**
2201 - * Field: Status.
2202 - *
2203 - * @since 1.9.12
2204 - */
2205 - $status = apply_filters(
2206 - 'merchant_group_status_field',
2207 - array(
2208 - 'id' => $settings['id'] . '_status',
2209 - 'type' => 'select',
2210 - 'title' => esc_html__( 'Status', 'merchant' ),
2211 - 'options' => array(
2212 - 'inactive' => esc_html__( 'Inactive', 'merchant' ),
2213 - 'active' => esc_html__( 'Active', 'merchant' ),
2214 - ),
2215 - 'default' => isset( $settings['default'] ) ? $settings['default'] : 'active',
2216 - ),
2217 - $settings,
2218 - $value,
2219 - $module_id
2220 - );
2221 - if ( $inside_flexible ) {
2222 - static::replace_field(
2223 - $status,
2224 - isset( $args['value'][$settings['id']][ $status['id'] ] ) ? $args['value'][$settings['id']][ $status['id'] ] : $status['default'] ?? '',
2225 - "name=\"merchant[{$status['id']}]",
2226 - "name=\"merchant[{$args['id']}][{$args['option_key']}][{$settings['id']}][{$status['id']}]\" data-name=\"merchant[{$args['id']}][0][{$settings['id']}][{$status['id']}]",
2227 - $module_id
2228 - );
2229 - } else {
2230 - static::replace_field(
2231 - $status,
2232 - isset( $value[ $status['id'] ] ) ? $value[ $status['id'] ] : '',
2233 - "name=\"merchant[{$status['id']}]",
2234 - "name=\"merchant[{$settings['id']}][{$status['id']}]",
2235 - $module_id
2236 - );
2237 - }
2238 - }
2239 -
2240 - foreach ( $settings['fields'] as $field ) {
2241 - if ( $inside_flexible ) {
2242 - static::replace_field(
2243 - $field,
2244 - isset( $args['value'][$settings['id']][ $field['id'] ] ) ? $args['value'][$settings['id']][ $field['id'] ] : ( $field['default'] ?? '' ),
2245 - "name=\"merchant[{$field['id']}]",
2246 - "name=\"merchant[{$args['id']}][{$args['option_key']}][{$settings['id']}][{$field['id']}]\" data-name=\"merchant[{$args['id']}][0][{$settings['id']}][{$field['id']}]",
2247 - $module_id
2248 - );
2249 - } else {
2250 - static::replace_field(
2251 - $field,
2252 - isset( $value[ $field['id'] ] ) ? $value[ $field['id'] ] : ( $field['default'] ?? '' ),
2253 - "name=\"merchant[{$field['id']}]",
2254 - "name=\"merchant[{$settings['id']}][{$field['id']}]",
2255 - $module_id
2256 - );
2257 - }
2258 - } ?>
2259 - </div>
2260 - </div>
2261 - <?php
2262 - }
2263 -
2264 - /**
2265 - * Field: Create Page.
2266 - */
2267 - public static function create_page( $settings, $value, $module_id = '' ) {
2268 - $page_id = get_option( $settings['option_name'] );
2269 -
2270 - echo '<div class="merchant-create-page-control">';
2271 -
2272 - if ( $page_id && post_exists( get_the_title( $page_id ) ) && 'publish' === get_post_status( $page_id ) ) {
2273 - echo wp_kses_post(
2274 - sprintf( /* translators: 1: link to edit page */
2275 - __( '<p class="merchant-module-page-setting-field-desc mrc-mt-0">Your page is created!</p><p class="merchant-module-page-setting-field-desc">Click <a href="%1$s" target="_blank">here</a> if you want to edit the page.</p><p class="merchant-module-page-setting-field-desc mrc-mb-0">To display the page in your theme header area, assign the page to the primary menu by clicking <a href="%2$s" target="_blank">here</a></p>',
2276 - 'merchant' ),
2277 - get_admin_url() . 'post.php?post=' . $page_id . '&action=edit',
2278 - get_admin_url() . 'nav-menus.php'
2279 - )
2280 - );
2281 - } else {
2282 - echo '<div class="merchant-create-page-control-create-message">';
2283 - echo wp_kses_post(
2284 - '<p class="merchant-module-page-setting-field-desc mrc-mt-0">'.
2285 - sprintf( /* translators: 1: page name */
2286 - __( 'It looks like you haven\'t created a <strong>%1$s</strong> page yet. Click the below button to create the page.',
2287 - 'merchant' ),
2288 - $settings['page_title']
2289 - ). '</p>'
2290 - );
2291 - echo '</div>';
2292 - echo '<div class="merchant-create-page-control-success-message" style="display: none;">';
2293 - echo wp_kses_post(
2294 - sprintf( /* translators: 1: link to edit page */
2295 - __( '<p class="merchant-module-page-setting-field-desc">Page created with success!</p><p class="merchant-module-page-setting-field-desc">Click <a href="%1$s" target="_blank">here</a> if you want to edit the page.</p><p class="merchant-module-page-setting-field-desc mrc-mb-0">To display the page in your theme header area, assign the page to the primary menu by clicking <a href="%2$s" target="_blank">here</a></p>',
2296 - 'merchant' ),
2297 - get_admin_url() . 'post.php?post=&action=edit',
2298 - get_admin_url() . 'nav-menus.php'
2299 - )
2300 - );
2301 - echo '</div>';
2302 - echo wp_kses_post(
2303 - sprintf( /* translators: 1: page title, 2: page meta key, 3: page meta value, 4: option name, 5: nonce, 6: loading text, 7: success text */
2304 - __( '<a href="#" class="merchant-create-page-control-button button-tertiary" data-page-title="%2$s" data-page-meta-key="%3$s" data-page-meta-value="%4$s" data-option-name="%5$s" data-nonce="%6$s" data-creating-text="%7$s" data-created-text="%8$s">%1$s</a>',
2305 - 'merchant' ),
2306 - __( 'Create Page', 'merchant' ),
2307 - $settings['page_title'],
2308 - $settings['page_meta_key'],
2309 - $settings['page_meta_value'],
2310 - $settings['option_name'],
2311 - wp_create_nonce( 'customize-create-page-control-nonce' ),
2312 - __( 'Creating...', 'merchant' ),
2313 - __( 'Created!', 'merchant' )
2314 - )
2315 - );
391 + public function activate_pro_plugin() {
392 + if ( ! isset( $_GET['action'] ) || 'merchant_activate_pro' !== $_GET['action'] ) {
393 + return;
2316 394 }
2317 395
2318 - echo '</div>';
2319 - }
2320 -
2321 - public static function dimensions( $settings, $value, $module_id = '' ) {
2322 - $settings = wp_parse_args( $settings, array(
2323 - 'units' => array(
2324 - 'px' => 'px',
2325 - 'rem' => 'rem',
2326 - 'em' => 'em',
2327 - 'vw' => 'vw',
2328 - 'vh' => 'vh',
2329 - '%' => '%',
2330 - ),
2331 - 'dimensions' => array(
2332 - 'top',
2333 - 'right',
2334 - 'bottom',
2335 - 'left',
2336 - ),
2337 - ) );
2338 - $default_value = array(
2339 - 'unit' => reset( $settings['units'] ),
2340 - );
2341 - foreach ( $settings['dimensions'] as $dimension ) {
2342 - $default_value[ $dimension ] = 0;
396 + if (
397 + ! isset( $_GET['nonce'] )
398 + || ! wp_verify_nonce(
399 + sanitize_text_field( wp_unslash( $_GET['nonce'] ) ),
400 + 'merchant_activate_pro'
401 + )
402 + ) {
403 + return;
2343 404 }
2344 - $value = wp_parse_args( $value, $default_value );
2345 - ?>
2346 - <div class="merchant-module-page-settings-unit">
2347 - <select name="merchant[<?php
2348 - echo esc_attr( $settings['id'] ); ?>][unit]">
2349 - <?php
2350 - foreach ( $settings['units'] as $key => $option ) : ?>
2351 - <option value="<?php
2352 - echo esc_attr( $key ); ?>" <?php
2353 - selected( $value['unit'], $key, true ); ?>><?php
2354 - echo esc_html( $option ); ?></option>
2355 - <?php
2356 - endforeach; ?>
2357 - </select>
2358 - </div>
2359 - <div class="merchant-module-page-settings-dimensions">
2360 - <?php
2361 - foreach ( $settings['dimensions'] as $dimension ) : ?>
2362 - <div>
2363 - <input id="merchant-<?php
2364 - echo esc_attr( $settings['id'] ); ?>-<?php
2365 - echo esc_attr( $dimension ) ?>"
2366 - type="number"
2367 - name="merchant[<?php
2368 - echo esc_attr( $settings['id'] ); ?>][<?php
2369 - echo esc_attr( $dimension ) ?>]"
2370 - value="<?php
2371 - echo esc_attr( $value[ $dimension ] ); ?>"/>
2372 - <label for="merchant-<?php
2373 - echo esc_attr( $settings['id'] ); ?>-<?php
2374 - echo esc_attr( $dimension ) ?>">
2375 - <?php
2376 - echo esc_html( ucfirst( $dimension ) ); ?>
2377 - </label>
2378 - </div>
2379 - <?php
2380 - endforeach; ?>
2381 - </div>
2382 - <?php
2383 - }
2384 405
2385 - public static function responsive_dimensions( $settings, $value, $module_id = '' ) {
2386 - $settings = wp_parse_args( $settings, array(
2387 - 'units' => array(
2388 - 'px' => 'px',
2389 - 'rem' => 'rem',
2390 - 'em' => 'em',
2391 - 'vw' => 'vw',
2392 - 'vh' => 'vh',
2393 - '%' => '%',
2394 - ),
2395 - 'dimensions' => array(
2396 - 'top',
2397 - 'right',
2398 - 'bottom',
2399 - 'left',
2400 - ),
2401 - 'devices' => array(
2402 - 'desktop' => 'dashicons-desktop',
2403 - 'tablet' => 'dashicons-tablet',
2404 - 'mobile' => 'dashicons-smartphone',
2405 - ),
2406 - ) );
2407 - $default_values = array();
2408 - foreach ( $settings['devices'] as $device => $icon ) {
2409 - $default_values[ $device ]['unit'] = reset( $settings['units'] );
2410 -
2411 - foreach ( $settings['dimensions'] as $dimension ) {
2412 - $default_values[ $device ][ $dimension ] = 0;
2413 - }
406 + if ( ! current_user_can( 'activate_plugins' ) ) {
407 + return;
2414 408 }
2415 - $value = wp_parse_args( $value, $default_values );
2416 - ?>
2417 - <div class="merchant-module-page-settings-responsive">
2418 - <ul class="merchant-module-page-settings-devices">
2419 - <?php
2420 - foreach ( $settings['devices'] as $device => $icon ) : ?>
2421 - <li class="<?php
2422 - echo esc_attr( $device ); ?>">
2423 - <button type="button" class="preview-<?php
2424 - echo esc_attr( $device ); ?> <?php
2425 - echo $device === key( $settings['devices'] ) ? 'active' : '' ?>"
2426 - data-device="<?php
2427 - echo esc_attr( $device ); ?>">
2428 - <i class="dashicons <?php
2429 - echo esc_attr( $icon ); ?>"></i>
2430 - </button>
2431 - </li>
2432 - <?php
2433 - endforeach; ?>
2434 - </ul>
2435 - <?php
2436 - foreach ( $settings['devices'] as $device => $icon ) : ?>
2437 - <div class="merchant-module-page-settings-device-container <?php
2438 - echo $device === key( $settings['devices'] ) ? 'active' : '' ?>" data-device="<?php
2439 - echo esc_attr( $device ); ?>">
2440 - <div class="merchant-module-page-settings-unit">
2441 - <select name="merchant[<?php
2442 - echo esc_attr( $settings['id'] ); ?>][<?php
2443 - echo esc_attr( $device ) ?>][unit]">
2444 - <?php
2445 - foreach ( $settings['units'] as $key => $option ) : ?>
2446 - <option value="<?php
2447 - echo esc_attr( $key ); ?>" <?php
2448 - selected( $value[ esc_attr( $device ) ]['unit'], $key, true ); ?>><?php
2449 - echo esc_html( $option ); ?></option>
2450 - <?php
2451 - endforeach; ?>
2452 - </select>
2453 - </div>
2454 - <div class="merchant-module-page-settings-dimensions">
2455 - <?php
2456 - foreach ( $settings['dimensions'] as $dimension ) : ?>
2457 - <div>
2458 - <input id="merchant-<?php
2459 - echo esc_attr( $settings['id'] ); ?>-<?php
2460 - echo esc_attr( $device ) ?>-<?php
2461 - echo esc_attr( $dimension ) ?>"
2462 - type="number"
2463 - name="merchant[<?php
2464 - echo esc_attr( $settings['id'] ); ?>][<?php
2465 - echo esc_attr( $device ) ?>][<?php
2466 - echo esc_attr( $dimension ) ?>]"
2467 - value="<?php
2468 - echo esc_attr( $value[ $device ][ $dimension ] ); ?>"/>
2469 - <label for="merchant-<?php
2470 - echo esc_attr( $settings['id'] ); ?>-<?php
2471 - echo esc_attr( $device ) ?>-<?php
2472 - echo esc_attr( $dimension ) ?>">
2473 - <?php
2474 - echo esc_html( ucfirst( $dimension ) ); ?>
2475 - </label>
2476 - </div>
2477 - <?php
2478 - endforeach; ?>
2479 - </div>
2480 - </div>
2481 - <?php
2482 - endforeach; ?>
2483 - </div>
2484 409
2485 - <?php
2486 - }
410 + $result = activate_plugin( 'merchant-pro/merchant-pro.php' );
2487 411
2488 - /**
2489 - * Field: Custom callback.
2490 - */
2491 - public static function custom_callback( $settings, $value, $module_id = '' ) {
2492 - if ( ! empty( $settings['class_name'] ) && ! empty( $settings['callback_name'] ) ) {
2493 - return call_user_func( array( $settings['class_name'], $settings['callback_name'] ), $settings, $value );
412 + if ( is_wp_error( $result ) ) {
413 + wp_die( esc_html( $result->get_error_message() ) );
2494 414 }
2495 415
2496 - if ( ! empty( $settings['callback_name'] ) ) {
2497 - return call_user_func( $settings['callback_name'], $settings, $value );
2498 - }
2499 -
2500 - return false;
416 + wp_safe_redirect( $this->get_safe_merchant_redirect_url() );
417 + exit;
2501 418 }
2502 419
2503 - private static function print_flexible_layout_actions( $settings, $layout_type ) {
2504 - $has_duplicate = ! empty( $settings['duplicate'] );
2505 - $has_accordion = ! empty( $settings['accordion'] );
2506 - ?>
2507 - <div class="layout-actions<?php echo $has_accordion ? esc_attr( ' layout-actions-has_accordion' ) : ' layout-actions-no_accordion'; ?>">
2508 - <a href="#" class="layout-actions__toggle">
2509 - <svg xmlns="http://www.w3.org/2000/svg" width="14" height="4" viewBox="0 0 14 4" fill="none">
2510 - <path d="M1.5 0.763184C2.32843 0.763183 3 1.43476 3 2.26318C3 3.09161 2.32843 3.76318 1.5 3.76318C0.671573 3.76318 -2.93554e-08 3.09161 -6.55671e-08 2.26318C-1.01779e-07 1.43476 0.671573 0.763184 1.5 0.763184Z" fill="#4A4A4A"/>
2511 - <path d="M7 0.763183C7.82843 0.763183 8.5 1.43476 8.5 2.26318C8.5 3.09161 7.82843 3.76318 7 3.76318C6.17157 3.76318 5.5 3.09161 5.5 2.26318C5.5 1.43476 6.17157 0.763183 7 0.763183Z" fill="#4A4A4A"/>
2512 - <path d="M12.5 0.763183C13.3284 0.763183 14 1.43476 14 2.26318C14 3.09161 13.3284 3.76318 12.5 3.76318C11.6716 3.76318 11 3.09161 11 2.26318C11 1.43476 11.6716 0.763183 12.5 0.763183Z" fill="#4A4A4A"/>
2513 - </svg>
2514 - </a>
2515 - <div class="layout-actions__inner" style="display: none;">
2516 - <?php if ( $has_duplicate ) : ?>
2517 - <a
2518 - href="#"
2519 - class="customize-control-flexible-content-duplicate"
2520 - data-id="<?php echo esc_attr( $settings['id'] ); ?>"
2521 - data-layout="<?php echo esc_attr( $layout_type ); ?>"
2522 - title="<?php echo esc_attr__( 'Duplicate', 'merchant' ); ?>">
2523 - <svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 22" fill="none">
2524 - <path fill-rule="evenodd" clip-rule="evenodd" d="M2 1.76318H13C13.2761 1.76318 13.5 1.98704 13.5 2.26318V13.2632C13.5 13.5393 13.2761 13.7632 13 13.7632H2C1.72386 13.7632 1.5 13.5393 1.5 13.2632V2.26318C1.5 1.98704 1.72386 1.76318 2 1.76318ZM0 2.26318C0 1.15861 0.895431 0.263184 2 0.263184H13C14.1046 0.263184 15 1.15861 15 2.26318V13.2632C15 14.3678 14.1046 15.2632 13 15.2632H2C0.89543 15.2632 0 14.3678 0 13.2632V2.26318ZM17 5.26318V16.0132C17 16.7035 16.4404 17.2632 15.75 17.2632H3V18.7632H15.75C17.2688 18.7632 18.5 17.532 18.5 16.0132V5.26318H17Z" fill="#757575"/>
2525 - </svg>
2526 - <span><?php echo esc_html__( 'Duplicate', 'merchant' ); ?></span>
2527 - </a>
2528 - <?php endif; ?>
2529 -
2530 - <a class="customize-control-flexible-content-delete" href="#">
2531 - <svg xmlns="http://www.w3.org/2000/svg" width="14" height="17" viewBox="0 0 14 17" fill="none">
2532 - <path fill-rule="evenodd" clip-rule="evenodd" d="M7 0.263184C5.18578 0.263184 3.67247 1.55151 3.32502 3.26318H0V4.76318H1.2699L2.08782 13.7602C2.21659 15.1767 3.40421 16.2613 4.82652 16.2613H9.17366C10.596 16.2613 11.7836 15.1767 11.9124 13.7602L12.7303 4.76318H14V3.26318H10.675C10.3275 1.5515 8.81422 0.263184 7 0.263184ZM7 1.76318C6.02034 1.76318 5.18691 2.38929 4.87803 3.26318H9.12197C8.81309 2.38929 7.97966 1.76318 7 1.76318ZM11.2241 4.76318H2.77609L3.58166 13.6244C3.64019 14.2683 4.18002 14.7613 4.82652 14.7613H9.17366C9.82017 14.7613 10.36 14.2683 10.4185 13.6244L11.2241 4.76318Z" fill="#757575"/>
2533 - </svg>
2534 - <span><?php echo esc_html__( 'Delete', 'merchant' ); ?></span>
2535 - </a>
2536 - </div>
2537 - </div>
2538 - <?php
2539 - }
2540 -
2541 420 /**
2542 - * Get category choices for select2
2543 - *
2544 - * @return array
2545 - */
2546 - public static function get_category_select2_choices() {
2547 - $choices = array();
2548 - $categories = merchant_get_product_categories();
2549 -
2550 - if ( is_array( $categories ) && ! empty( $categories ) ) {
2551 - $choices = self::build_category_select2_choices( $categories );
2552 - }
2553 -
2554 - return $choices;
2555 - }
2556 -
2557 - /**
2558 - * Build Select2 choices for hierarchical categories.
2559 - *
2560 - * @param $categories
2561 - * @param $level
421 + * Get a safe redirect URL pointing to a Merchant admin page.
2562 422 *
2563 - * @return array
423 + * Validates the HTTP referer is a Merchant page. Falls back
424 + * to the main Merchant dashboard if the referer is invalid
425 + * or points to a non-Merchant page.
426 + *
427 + * @since 1.9.3
428 + *
429 + * @return string Safe redirect URL.
2564 430 */
2565 - private static function build_category_select2_choices( $categories, $level = 0 ) {
2566 - $choices = array();
431 + private function get_safe_merchant_redirect_url() {
432 + $fallback_url = admin_url( 'admin.php?page=merchant' );
433 + $referer = wp_get_referer();
434 + $redirect_to = wp_validate_redirect( $referer, $fallback_url );
2567 435
2568 - foreach ( $categories as $cat ) {
2569 - $indent = str_repeat( '&nbsp;', $level * 4 ); // Use non-breaking spaces for indentation
2570 -
2571 - $choices[] = array(
2572 - 'id' => esc_attr( $cat['slug'] ?? '' ),
2573 - 'text' => esc_html( $indent . ( $cat['name'] ?? '' ) ),
2574 - );
2575 -
2576 - if ( ! empty( $cat['children'] ) ) {
2577 - $choices = array_merge( $choices, self::build_category_select2_choices( $cat['children'], $level + 1 ) );
2578 - }
436 + if ( $redirect_to === $fallback_url ) {
437 + return $fallback_url;
2579 438 }
2580 439
2581 - return $choices;
2582 - }
440 + $parsed_url = wp_parse_url( $redirect_to );
441 + $query_params = array();
442 + parse_str( $parsed_url['query'] ?? '', $query_params );
2583 443
2584 - /**
2585 - * Get Tag choices for select2
2586 - *
2587 - * @return array
2588 - */
2589 - public static function get_tag_select2_choices() {
2590 - $choices = array();
2591 - $tags = merchant_get_product_tags();
2592 -
2593 - if ( is_array( $tags ) && ! empty( $tags ) ) {
2594 - foreach ( $tags as $slug => $name ) {
2595 - $choices[] = array(
2596 - 'id' => esc_attr( $slug ),
2597 - 'text' => esc_html( $name ),
2598 - );
2599 - }
444 + if ( empty( $query_params['page'] ) || strpos( $query_params['page'], 'merchant' ) !== 0 ) {
445 + return $fallback_url;
2600 446 }
2601 447
2602 - return $choices;
448 + return $redirect_to;
2603 449 }
2604 -
2605 - /**
2606 - * Get User Roles choices for select2
2607 - *
2608 - * @return array
2609 - */
2610 - public static function get_user_roles_select2_choices() {
2611 - $choices = array();
2612 - $user_roles = get_editable_roles();
2613 -
2614 - if ( ! empty( $user_roles ) ) {
2615 - foreach ( $user_roles as $role_id => $role_data ) {
2616 - $choices[] = array(
2617 - 'id' => $role_id,
2618 - 'text' => $role_data['name'],
2619 - );
2620 - }
2621 - }
2622 -
2623 - return $choices;
2624 - }
2625 -
2626 - /**
2627 - * Get Customers choices for select2.
2628 - *
2629 - * @return array
2630 - */
2631 - public static function get_customers_select2_choices() {
2632 - $cache_key = 'customers_select2_choices';
2633 - $choices = get_transient( $cache_key );
2634 -
2635 - if ( ! empty( $choices ) && is_array( $choices ) ) {
2636 - return $choices;
2637 - }
2638 -
2639 - // Get users with the 'customer' role
2640 - $customer_users = get_users(
2641 - array(
2642 - 'role' => 'customer',
2643 - 'fields' => array( 'ID', 'display_name' ),
2644 - )
2645 - );
2646 -
2647 - $choices = array();
2648 - if ( ! empty( $customer_users ) ) {
2649 - foreach ( $customer_users as $user ) {
2650 - $choices[] = array(
2651 - 'id' => $user->ID,
2652 - 'text' => $user->display_name,
2653 - );
2654 - }
2655 - }
2656 -
2657 - // Cache the choices with no expiration. Will be cleared using `clear_customer_choices_cache`
2658 - set_transient( $cache_key, $choices );
2659 -
2660 - return $choices;
2661 - }
2662 -
2663 - /**
2664 - * Clear customers cache when a customer is created/deleted/updated.
2665 - *
2666 - * @param $user_id
2667 - * @param $user
2668 - *
2669 - * @return void
2670 - */
2671 - public function clear_customer_choices_cache( $user_id, $user ) {
2672 - $user_roles = $user->roles ?? array();
2673 - if ( in_array( 'customer', $user_roles, true ) ) {
2674 - delete_transient( 'customers_select2_choices' );
2675 - }
2676 - }
2677 450 }
2678 451
2679 452 Merchant_Admin_Options::instance();
2680 453 }