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

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

404 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Merchant Settings Renderer.
4 *
5 * Handles all rendering of module settings panels, including
6 * field wrappers, titles, descriptions, and inner field content.
7 * Extracted from {@see Merchant_Admin_Options}.
8 *
9 * @package Merchant
10 * @since 1.9.3
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Merchant_Settings_Renderer
19 *
20 * Renders module settings panels and their individual fields.
21 * Delegates field-type rendering to the {@see Merchant_Field_Registry}.
22 *
23 * @since 1.9.3
24 */
25 class Merchant_Settings_Renderer {
26
27 /**
28 * Create and render a module settings panel.
29 *
30 * Processes the settings definition array, saves any submitted
31 * form data, and renders the full settings panel HTML including
32 * title, field wrappers, and individual fields.
33 *
34 * @since 1.0
35 *
36 * @param array<string, mixed> $settings {
37 * Module settings configuration.
38 *
39 * @type string $module Module ID.
40 * @type string $title Panel title.
41 * @type string $subtitle Panel subtitle.
42 * @type array $fields Array of field definition arrays.
43 * }
44 *
45 * @return void
46 */
47 public static function create( $settings ) {
48 $module_id = ( isset( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
49
50 /**
51 * Hook: merchant_module_settings
52 *
53 * @param array $settings Module settings.
54 * @param string $module_id Module ID.
55 *
56 * @since 1.0
57 */
58 $settings = apply_filters( 'merchant_module_settings', $settings, $module_id );
59
60 Merchant_Settings_Saver::save_options( $settings );
61
62 $options = get_option( 'merchant', array() );
63 ?>
64 <div class="merchant-module-page-settings">
65 <div class="merchant-module-page-setting-box">
66 <?php if ( ! empty( $settings['title'] ) ) : ?>
67 <div class="merchant-module-page-setting-title">
68 <?php echo esc_html( $settings['title'] ); ?>
69 <?php if ( ! empty( $settings['subtitle'] ) ) : ?>
70 <div class="merchant-module-page-setting-subtitle"><?php echo esc_html( $settings['subtitle'] ); ?></div>
71 <?php endif; ?>
72 </div>
73 <?php endif; ?>
74 <div class="merchant-module-page-setting-fields">
75 <?php self::render_fields( $settings, $options, $module_id ); ?>
76 </div>
77 </div>
78 </div>
79 <?php
80 }
81
82 /**
83 * Render all fields in a module settings panel.
84 *
85 * Iterates the field definitions, resolves each field's saved
86 * value, and delegates rendering to either {@see field()} or
87 * {@see disabled_field()} depending on Pro status.
88 *
89 * @since 1.9.3
90 *
91 * @param array<string, mixed> $settings Module settings with 'module' and 'fields' keys.
92 * @param array<string, mixed> $options All saved merchant options.
93 * @param string $module_id The current module ID.
94 *
95 * @return void
96 */
97 private static function render_fields( $settings, $options, $module_id ) {
98 if ( empty( $settings['fields'] ) ) {
99 return;
100 }
101
102 $current_module = Merchant_Admin_Modules::get_module_info( $settings['module'] );
103 $is_pro_module = ! merchant_is_pro_active() && isset( $current_module['pro'] ) && $current_module['pro'] === true;
104
105 foreach ( $settings['fields'] as $field ) {
106 $value = $field['default'] ?? null;
107
108 if ( isset( $field['id'] ) && isset( $options[ $settings['module'] ][ $field['id'] ] ) ) {
109 $value = $options[ $settings['module'] ][ $field['id'] ];
110 }
111
112 $is_pro_field = ! merchant_is_pro_active() && isset( $field['pro'] ) && $field['pro'] === true;
113
114 // A locked section or field is disabled without the Pro badge: the
115 // gate behind it is a plan tier, not the free-vs-pro split.
116 $is_locked = ! empty( $settings['locked'] ) || ! empty( $field['locked'] );
117
118 if ( $is_pro_module || $is_pro_field || $is_locked ) {
119 self::disabled_field( $field, $value );
120 } else {
121 self::field( $field, $value, $module_id );
122 }
123 }
124 }
125
126 /**
127 * Render a single field.
128 *
129 * Outputs the full field markup including the wrapper div,
130 * title, description, and the field's inner HTML delegated
131 * to {@see Merchant_Field_Registry}.
132 *
133 * @since 1.0
134 *
135 * @param array<string, mixed> $settings The field configuration array.
136 * @param mixed $value The current saved value for this field.
137 * @param string $module_id The module ID (used for nested field contexts).
138 *
139 * @return void
140 */
141 public static function field( $settings, $value, $module_id = '' ) {
142 if ( empty( $settings['type'] ) ) {
143 return;
144 }
145
146 $type = $settings['type'];
147 $id = $settings['id'] ?? '';
148 $is_upsell = ! merchant_is_pro_active() && isset( $settings['pro'] ) && $settings['pro'] === true;
149 $value = self::resolve_field_value( $settings, $value, $module_id );
150
151 self::render_field_wrapper_open( $settings, $value, $module_id, $type, $id );
152 self::render_field_title( $settings, $id, $is_upsell );
153
154 echo '<div class="merchant-module-page-setting-field-inner merchant-field-' . esc_attr( $id ) . '">';
155 self::render_field_inner( $type, $settings, $value, $module_id );
156 echo '</div>';
157
158 self::render_field_description( $settings, $value, $module_id );
159 echo '</div>';
160 }
161
162 /**
163 * Resolve the default value for a field based on its type.
164 *
165 * Handles type-specific fallback logic for checkbox_multiple,
166 * text, and url field types.
167 *
168 * @since 1.9.3
169 *
170 * @param array<string, mixed> $settings The field configuration array.
171 * @param mixed $value The current raw value.
172 * @param string $module_id The module ID.
173 *
174 * @return mixed The resolved value.
175 */
176 private static function resolve_field_value( $settings, $value, $module_id ) {
177 $type = $settings['type'];
178 $id = $settings['id'] ?? '';
179 $default = $settings['default'] ?? null;
180
181 if ( $value || 0 === $value || '0' === $value ) {
182 return $value;
183 }
184
185 if ( $type === 'checkbox_multiple' ) {
186 return (array) $default;
187 }
188
189 if ( in_array( $type, array( 'text', 'url' ), true ) && ! empty( $module_id ) ) {
190 return Merchant_Option::get( $module_id, $id );
191 }
192
193 return $default;
194 }
195
196 /**
197 * Render the opening wrapper div for a field.
198 *
199 * Builds CSS classes and data attributes, then outputs the
200 * opening div tag.
201 *
202 * @since 1.9.3
203 *
204 * @param array<string, mixed> $settings The field configuration array.
205 * @param mixed $value The field value.
206 * @param string $module_id The module ID.
207 * @param string $type The field type.
208 * @param string $id The field ID.
209 *
210 * @return void
211 */
212 private static function render_field_wrapper_open( $settings, $value, $module_id, $type, $id ) {
213 $class = ! empty( $settings['class'] ) ? ' ' . $settings['class'] : '';
214 $condition = $settings['condition'] ?? array();
215 $conditions = $settings['conditions'] ?? '';
216
217 $wrapper_classes = array( 'merchant-module-page-setting-field' );
218 $wrapper_classes[] = 'merchant-module-page-setting-field-' . $type;
219
220 if ( ! empty( $class ) ) {
221 $wrapper_classes[] = $class;
222 }
223
224 /**
225 * Hook 'merchant_admin_module_field_wrapper_classes'
226 *
227 * @since 1.9.3
228 */
229 $wrapper_classes = apply_filters( 'merchant_admin_module_field_wrapper_classes', $wrapper_classes, $settings, $value, $module_id );
230
231 echo '<div class="' . esc_attr( implode( ' ', $wrapper_classes ) ) . '" data-id="'
232 . esc_attr( $id ) . '" data-type="' . esc_attr( $type ) . '" data-condition="' . esc_attr( (string) wp_json_encode( $condition ) )
233 . '" data-conditions="' . ( $conditions ? esc_attr( (string) wp_json_encode( $conditions ) ) : "" ) . '">';
234 }
235
236 /**
237 * Render the title bar for a field, including Pro upsell badge.
238 *
239 * @since 1.9.3
240 *
241 * @param array<string, mixed> $settings The field configuration array.
242 * @param string $id The field ID.
243 * @param bool $is_upsell Whether to show the Pro upsell badge.
244 *
245 * @return void
246 */
247 private static function render_field_title( $settings, $id, $is_upsell ) {
248 if ( empty( $settings['title'] ) ) {
249 return;
250 }
251 ?>
252 <div class="merchant-module-page-setting-field-title<?php echo esc_attr( $is_upsell ? ' merchant-module-page-setting-field-title__has-upsell' : '' ); ?>">
253 <?php echo esc_html( $settings['title'] ); ?>
254
255 <?php if ( $is_upsell ) : ?>
256 <a href="https://athemes.com/merchant-upgrade?utm_source=inner_module_settings_field&utm_content=<?php echo esc_attr( $id ); ?>&utm_medium=merchant_dashboard&utm_campaign=Merchant" class="merchant-module-pro-upsell" target="_blank">
257 <span class="merchant-pro-badge merchant-pro-tooltip" data-tooltip-message="<?php echo esc_attr__( 'This option is only available on Merchant Pro', 'merchant' ); ?>">
258 <svg width="28" height="16" viewBox="0 0 28 16" fill="none" xmlns="http://www.w3.org/2000/svg">
259 <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"/>
260 <rect x="0.5" y="1" width="27" height="14" rx="1.5" stroke="#3858E9"/>
261 </svg>
262 </span>
263 </a>
264 <?php endif; ?>
265 </div>
266 <?php
267 }
268
269 /**
270 * Render the inner field content via the field registry.
271 *
272 * Delegates rendering to the registered field class. Logs an
273 * error if the field type is unknown.
274 *
275 * @since 1.9.3
276 *
277 * @param string $type The field type.
278 * @param array<string, mixed> $settings The field configuration array.
279 * @param mixed $value The field value.
280 * @param string $module_id The module ID.
281 *
282 * @return void
283 */
284 private static function render_field_inner( $type, $settings, $value, $module_id ) {
285 $registry = Merchant_Field_Registry::instance();
286
287 if ( $registry->has( $type ) ) {
288 try {
289 $field_instance = $registry->create( $type, $settings, $value, $module_id );
290 if ( $field_instance !== null ) {
291 $field_instance->render();
292 }
293 } catch ( \Exception $e ) {
294 wp_trigger_error( __METHOD__, 'Merchant field render error (' . $type . '): ' . $e->getMessage() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
295 }
296 } else {
297 printf(
298 '<div class="merchant-field-error"><strong>%s</strong> %s</div>',
299 esc_html__( 'Merchant field error:', 'merchant' ),
300 esc_html(
301 sprintf(
302 /* translators: %s: the unsupported field type slug */
303 __( 'Unknown field type "%s". Register it via the merchant_field_types filter.', 'merchant' ),
304 $type
305 )
306 )
307 );
308 _doing_it_wrong(
309 __METHOD__,
310 sprintf( 'Unknown field type "%s". Register it via the merchant_field_types filter.', esc_html( $type ) ),
311 '2.2.5'
312 );
313 }
314 }
315
316 /**
317 * Render the description and hidden description for a field.
318 *
319 * @since 1.9.3
320 *
321 * @param array<string, mixed> $settings The field configuration array.
322 * @param mixed $value The field value.
323 * @param string $module_id The module ID.
324 *
325 * @return void
326 */
327 private static function render_field_description( $settings, $value, $module_id ) {
328 $hidden_desc = $settings['hidden_desc'] ?? '';
329
330 /**
331 * Hook 'merchant_admin_module_field_hidden_description'
332 *
333 * @since 1.9.3
334 */
335 $hidden_desc = apply_filters( 'merchant_admin_module_field_hidden_description', $hidden_desc, $settings, $value, $module_id );
336
337 $desc = $settings['desc'] ?? '';
338
339 /**
340 * Hook 'merchant_admin_module_field_description'
341 *
342 * @since 1.9.3
343 */
344 $desc = apply_filters( 'merchant_admin_module_field_description', $desc, $settings, $value, $module_id );
345
346 if ( ! empty( $desc ) ) {
347 $hidden_desc_html = '';
348 if ( ! empty( $hidden_desc ) ) {
349 $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>';
350 $hidden_desc_html .= '<img src="' . esc_url( MERCHANT_URI . '/assets/images/arrow-down.svg' ) . '" alt="Merchant" />';
351 $hidden_desc_html .= '</div>';
352 }
353
354 $desc_class = 'merchant-module-page-setting-field-desc'
355 . ( $hidden_desc ? ' merchant-module-page-setting-field-desc-has-hidden-desc' : '' );
356
357 printf( '<div class="%s">%s%s</div>', esc_attr( $desc_class ), wp_kses_post( $desc ), wp_kses_post( $hidden_desc_html ) );
358 }
359
360 if ( ! empty( $hidden_desc ) ) {
361 printf( '<div class="merchant-module-page-setting-field-hidden-desc">%s</div>', wp_kses_post( nl2br( $hidden_desc ) ) );
362 }
363 }
364
365 /**
366 * Render a disabled (pro-gated) field.
367 *
368 * Renders the field via the normal registry path, then disables all
369 * interactive elements so the user can see the control but not interact.
370 *
371 * @since 1.0
372 *
373 * @param array<string, mixed> $settings Field settings.
374 * @param mixed $value Field value.
375 * @param string $module_id Module ID.
376 *
377 * @return void
378 */
379 public static function disabled_field( $settings, $value, $module_id = '' ) {
380 ob_start();
381 self::field( $settings, $value, $module_id );
382 $field_html = (string) ob_get_clean();
383
384 // Matched on the tag name, not on "<input " — templates break their
385 // attributes across lines however they like, and a newline right after the
386 // tag name used to slip past this and leave the control editable.
387 $field_html = (string) preg_replace(
388 '/<(input|select|textarea|button)(?=[\s>])/i',
389 '<$1 disabled',
390 $field_html
391 );
392
393 $field_html = str_replace(
394 'merchant-module-page-setting-field-inner',
395 'merchant-module-page-setting-field-inner disabled',
396 $field_html
397 );
398
399 // The HTML is generated by self::field() — trusted admin output.
400 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All values already escaped in field().
401 echo $field_html;
402 }
403 }
404