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

391 lines 15.8 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 if ( $is_pro_module || $is_pro_field ) {
115 self::disabled_field( $field, $value );
116 } else {
117 self::field( $field, $value, $module_id );
118 }
119 }
120 }
121
122 /**
123 * Render a single field.
124 *
125 * Outputs the full field markup including the wrapper div,
126 * title, description, and the field's inner HTML delegated
127 * to {@see Merchant_Field_Registry}.
128 *
129 * @since 1.0
130 *
131 * @param array<string, mixed> $settings The field configuration array.
132 * @param mixed $value The current saved value for this field.
133 * @param string $module_id The module ID (used for nested field contexts).
134 *
135 * @return void
136 */
137 public static function field( $settings, $value, $module_id = '' ) {
138 if ( empty( $settings['type'] ) ) {
139 return;
140 }
141
142 $type = $settings['type'];
143 $id = $settings['id'] ?? '';
144 $is_upsell = ! merchant_is_pro_active() && isset( $settings['pro'] ) && $settings['pro'] === true;
145 $value = self::resolve_field_value( $settings, $value, $module_id );
146
147 self::render_field_wrapper_open( $settings, $value, $module_id, $type, $id );
148 self::render_field_title( $settings, $id, $is_upsell );
149
150 echo '<div class="merchant-module-page-setting-field-inner merchant-field-' . esc_attr( $id ) . '">';
151 self::render_field_inner( $type, $settings, $value, $module_id );
152 echo '</div>';
153
154 self::render_field_description( $settings, $value, $module_id );
155 echo '</div>';
156 }
157
158 /**
159 * Resolve the default value for a field based on its type.
160 *
161 * Handles type-specific fallback logic for checkbox_multiple,
162 * text, and url field types.
163 *
164 * @since 1.9.3
165 *
166 * @param array<string, mixed> $settings The field configuration array.
167 * @param mixed $value The current raw value.
168 * @param string $module_id The module ID.
169 *
170 * @return mixed The resolved value.
171 */
172 private static function resolve_field_value( $settings, $value, $module_id ) {
173 $type = $settings['type'];
174 $id = $settings['id'] ?? '';
175 $default = $settings['default'] ?? null;
176
177 if ( $value || 0 === $value || '0' === $value ) {
178 return $value;
179 }
180
181 if ( $type === 'checkbox_multiple' ) {
182 return (array) $default;
183 }
184
185 if ( in_array( $type, array( 'text', 'url' ), true ) && ! empty( $module_id ) ) {
186 return Merchant_Option::get( $module_id, $id );
187 }
188
189 return $default;
190 }
191
192 /**
193 * Render the opening wrapper div for a field.
194 *
195 * Builds CSS classes and data attributes, then outputs the
196 * opening div tag.
197 *
198 * @since 1.9.3
199 *
200 * @param array<string, mixed> $settings The field configuration array.
201 * @param mixed $value The field value.
202 * @param string $module_id The module ID.
203 * @param string $type The field type.
204 * @param string $id The field ID.
205 *
206 * @return void
207 */
208 private static function render_field_wrapper_open( $settings, $value, $module_id, $type, $id ) {
209 $class = ! empty( $settings['class'] ) ? ' ' . $settings['class'] : '';
210 $condition = $settings['condition'] ?? array();
211 $conditions = $settings['conditions'] ?? '';
212
213 $wrapper_classes = array( 'merchant-module-page-setting-field' );
214 $wrapper_classes[] = 'merchant-module-page-setting-field-' . $type;
215
216 if ( ! empty( $class ) ) {
217 $wrapper_classes[] = $class;
218 }
219
220 /**
221 * Hook 'merchant_admin_module_field_wrapper_classes'
222 *
223 * @since 1.9.3
224 */
225 $wrapper_classes = apply_filters( 'merchant_admin_module_field_wrapper_classes', $wrapper_classes, $settings, $value, $module_id );
226
227 echo '<div class="' . esc_attr( implode( ' ', $wrapper_classes ) ) . '" data-id="'
228 . esc_attr( $id ) . '" data-type="' . esc_attr( $type ) . '" data-condition="' . esc_attr( (string) wp_json_encode( $condition ) )
229 . '" data-conditions="' . ( $conditions ? esc_attr( (string) wp_json_encode( $conditions ) ) : "" ) . '">';
230 }
231
232 /**
233 * Render the title bar for a field, including Pro upsell badge.
234 *
235 * @since 1.9.3
236 *
237 * @param array<string, mixed> $settings The field configuration array.
238 * @param string $id The field ID.
239 * @param bool $is_upsell Whether to show the Pro upsell badge.
240 *
241 * @return void
242 */
243 private static function render_field_title( $settings, $id, $is_upsell ) {
244 if ( empty( $settings['title'] ) ) {
245 return;
246 }
247 ?>
248 <div class="merchant-module-page-setting-field-title<?php echo esc_attr( $is_upsell ? ' merchant-module-page-setting-field-title__has-upsell' : '' ); ?>">
249 <?php echo esc_html( $settings['title'] ); ?>
250
251 <?php if ( $is_upsell ) : ?>
252 <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">
253 <span class="merchant-pro-badge merchant-pro-tooltip" data-tooltip-message="<?php echo esc_attr__( 'This option is only available on Merchant Pro', 'merchant' ); ?>">
254 <svg width="28" height="16" viewBox="0 0 28 16" fill="none" xmlns="http://www.w3.org/2000/svg">
255 <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"/>
256 <rect x="0.5" y="1" width="27" height="14" rx="1.5" stroke="#3858E9"/>
257 </svg>
258 </span>
259 </a>
260 <?php endif; ?>
261 </div>
262 <?php
263 }
264
265 /**
266 * Render the inner field content via the field registry.
267 *
268 * Delegates rendering to the registered field class. Logs an
269 * error if the field type is unknown.
270 *
271 * @since 1.9.3
272 *
273 * @param string $type The field type.
274 * @param array<string, mixed> $settings The field configuration array.
275 * @param mixed $value The field value.
276 * @param string $module_id The module ID.
277 *
278 * @return void
279 */
280 private static function render_field_inner( $type, $settings, $value, $module_id ) {
281 $registry = Merchant_Field_Registry::instance();
282
283 if ( $registry->has( $type ) ) {
284 try {
285 $field_instance = $registry->create( $type, $settings, $value, $module_id );
286 if ( $field_instance !== null ) {
287 $field_instance->render();
288 }
289 } catch ( \Exception $e ) {
290 wp_trigger_error( __METHOD__, 'Merchant field render error (' . $type . '): ' . $e->getMessage() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
291 }
292 } else {
293 printf(
294 '<div class="merchant-field-error"><strong>%s</strong> %s</div>',
295 esc_html__( 'Merchant field error:', 'merchant' ),
296 esc_html(
297 sprintf(
298 /* translators: %s: the unsupported field type slug */
299 __( 'Unknown field type "%s". Register it via the merchant_field_types filter.', 'merchant' ),
300 $type
301 )
302 )
303 );
304 _doing_it_wrong(
305 __METHOD__,
306 sprintf( 'Unknown field type "%s". Register it via the merchant_field_types filter.', esc_html( $type ) ),
307 '2.2.5'
308 );
309 }
310 }
311
312 /**
313 * Render the description and hidden description for a field.
314 *
315 * @since 1.9.3
316 *
317 * @param array<string, mixed> $settings The field configuration array.
318 * @param mixed $value The field value.
319 * @param string $module_id The module ID.
320 *
321 * @return void
322 */
323 private static function render_field_description( $settings, $value, $module_id ) {
324 $hidden_desc = $settings['hidden_desc'] ?? '';
325
326 /**
327 * Hook 'merchant_admin_module_field_hidden_description'
328 *
329 * @since 1.9.3
330 */
331 $hidden_desc = apply_filters( 'merchant_admin_module_field_hidden_description', $hidden_desc, $settings, $value, $module_id );
332
333 $desc = $settings['desc'] ?? '';
334
335 /**
336 * Hook 'merchant_admin_module_field_description'
337 *
338 * @since 1.9.3
339 */
340 $desc = apply_filters( 'merchant_admin_module_field_description', $desc, $settings, $value, $module_id );
341
342 if ( ! empty( $desc ) ) {
343 $hidden_desc_html = '';
344 if ( ! empty( $hidden_desc ) ) {
345 $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>';
346 $hidden_desc_html .= '<img src="' . esc_url( MERCHANT_URI . '/assets/images/arrow-down.svg' ) . '" alt="Merchant" />';
347 $hidden_desc_html .= '</div>';
348 }
349
350 $desc_class = 'merchant-module-page-setting-field-desc'
351 . ( $hidden_desc ? ' merchant-module-page-setting-field-desc-has-hidden-desc' : '' );
352
353 printf( '<div class="%s">%s%s</div>', esc_attr( $desc_class ), wp_kses_post( $desc ), wp_kses_post( $hidden_desc_html ) );
354 }
355
356 if ( ! empty( $hidden_desc ) ) {
357 printf( '<div class="merchant-module-page-setting-field-hidden-desc">%s</div>', wp_kses_post( nl2br( $hidden_desc ) ) );
358 }
359 }
360
361 /**
362 * Render a disabled (pro-gated) field.
363 *
364 * Renders the field via the normal registry path, then disables all
365 * interactive elements so the user can see the control but not interact.
366 *
367 * @since 1.0
368 *
369 * @param array<string, mixed> $settings Field settings.
370 * @param mixed $value Field value.
371 * @param string $module_id Module ID.
372 *
373 * @return void
374 */
375 public static function disabled_field( $settings, $value, $module_id = '' ) {
376 ob_start();
377 self::field( $settings, $value, $module_id );
378 $field_html = (string) ob_get_clean();
379
380 $field_html = str_replace(
381 array( '<input ', '<select ', '<textarea ', '<button ', 'merchant-module-page-setting-field-inner' ),
382 array( '<input disabled ', '<select disabled ', '<textarea disabled ', '<button disabled ', 'merchant-module-page-setting-field-inner disabled' ),
383 $field_html
384 );
385
386 // The HTML is generated by self::field() — trusted admin output.
387 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All values already escaped in field().
388 echo $field_html;
389 }
390 }
391