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