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-abstract-field.php

class-merchant-abstract-field.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-abstract-field.php

350 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Merchant Abstract Field.
4 *
5 * Provides shared functionality for all field types including
6 * wrapper rendering, value retrieval, and default sanitize/preprocess.
7 *
8 * @package Merchant
9 * @since 2.2.5
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly
14 }
15
16 /**
17 * Merchant_Abstract_Field
18 *
19 * @since 2.2.5
20 */
21 abstract class Merchant_Abstract_Field implements Merchant_Field_Interface {
22
23 /**
24 * Field configuration array.
25 *
26 * @since 2.2.5
27 * @var array<string, mixed>
28 */
29 protected $field;
30
31 /**
32 * The current saved value.
33 *
34 * @since 2.2.5
35 * @var mixed
36 */
37 protected $value;
38
39 /**
40 * The module ID.
41 *
42 * @since 2.2.5
43 * @var string
44 */
45 protected $module_id;
46
47 /**
48 * Field ID.
49 *
50 * @since 2.2.5
51 * @var string
52 */
53 protected $id;
54
55 /**
56 * Field type.
57 *
58 * @since 2.2.5
59 * @var string
60 */
61 protected $type;
62
63
64
65 /**
66 * Constructor.
67 *
68 * @since 2.2.5
69 *
70 * @param array<string, mixed> $field The field configuration array.
71 * @param mixed $value The current saved value.
72 * @param string $module_id The module ID.
73 */
74 public function __construct( $field, $value = null, $module_id = '' ) {
75 $this->field = $field;
76 $this->value = $value;
77 $this->module_id = $module_id;
78 $this->id = isset( $field['id'] ) ? $field['id'] : '';
79 $this->type = isset( $field['type'] ) ? $field['type'] : '';
80 }
81
82 /**
83 * Sanitize the submitted field value.
84 *
85 * Checks for a custom sanitize callback first ($field['sanitize']),
86 * then falls back to the type-specific implementation.
87 * Subclasses should override sanitize_value() for type-specific logic.
88 *
89 * @since 2.2.5
90 *
91 * @param mixed $value The raw submitted value.
92 *
93 * @return mixed The sanitized value.
94 */
95 public function sanitize( $value ) {
96 // Custom sanitize callback takes priority.
97 if ( ! empty( $this->field['sanitize'] ) && is_callable( $this->field['sanitize'] ) ) {
98 return call_user_func( $this->field['sanitize'], $value );
99 }
100
101 return $this->sanitize_value( $value );
102 }
103
104 /**
105 * Type-specific sanitization.
106 *
107 * Subclasses should override this method for their type-specific sanitization logic.
108 * Default implementation uses sanitize_text_field().
109 *
110 * @since 2.2.5
111 *
112 * @param mixed $value The raw submitted value.
113 *
114 * @return mixed The sanitized value.
115 */
116 protected function sanitize_value( $value ) {
117 return sanitize_text_field( $value );
118 }
119
120 /**
121 * Preprocess the field value before saving.
122 *
123 * Default implementation is a passthrough.
124 * Subclasses can override for complex preprocessing (e.g., flexible_content, sortable_repeater).
125 *
126 * @since 2.2.5
127 *
128 * @param mixed $value The sanitized value.
129 *
130 * @return mixed The preprocessed value.
131 */
132 public function preprocess( $value ) {
133 return $value;
134 }
135
136 /**
137 * Get the field configuration array.
138 *
139 * @since 2.2.5
140 *
141 * @return array<string, mixed>
142 */
143 public function get_field() {
144 return $this->field;
145 }
146
147 /**
148 * Get the field value.
149 *
150 * @since 2.2.5
151 *
152 * @return mixed
153 */
154 public function get_value() {
155 return $this->value;
156 }
157
158 /**
159 * Get the field ID.
160 *
161 * @since 2.2.5
162 *
163 * @return string
164 */
165 public function get_id() {
166 return $this->id;
167 }
168
169 /**
170 * Get the field type.
171 *
172 * @since 2.2.5
173 *
174 * @return string
175 */
176 public function get_type() {
177 return $this->type;
178 }
179
180 /**
181 * Return the base directory that contains this field's template files.
182 *
183 * Override this method (not a property) in a subclass to point to your own
184 * plugin directory. Using a method allows dynamic expressions such as
185 * plugin_dir_path( __FILE__ ), which are illegal in property initializers.
186 *
187 * Example override:
188 * protected function get_template_dir(): string {
189 * return plugin_dir_path( __FILE__ ) . 'fields/';
190 * }
191 *
192 * @since 2.2.5
193 *
194 * @return string Absolute path with trailing slash.
195 */
196 protected function get_template_dir(): string {
197 return MERCHANT_DIR . 'admin/classes/admin-options/fields/';
198 }
199
200 /**
201 * Return the subfolder name for this field's templates.
202 *
203 * Defaults to the field type with underscores replaced by hyphens.
204 * Override this method in a subclass if your folder name doesn't
205 * match the convention (e.g., the type is 'my_custom' but the folder
206 * is 'custom-v2').
207 *
208 * @since 2.2.5
209 *
210 * @return string Folder name (no slashes).
211 */
212 protected function get_template_folder(): string {
213 return str_replace( '_', '-', $this->type );
214 }
215
216 /**
217 * Load a field template from the field's own folder.
218 *
219 * Templates are located beside the class file:
220 * admin/classes/admin-options/fields/{type-slug}/{name}.php
221 *
222 * Available variables inside the template:
223 * $settings — Field configuration array.
224 * $value — Current saved value.
225 * $module_id — Module ID string.
226 * $field — The field instance.
227 * + any keys passed via $extra_args.
228 *
229 * @since 2.2.5
230 *
231 * @param string $name Template name (without .php). Default: 'template'.
232 * @param array<string, mixed> $extra_args Additional variables to pass to the template.
233 *
234 * @return void
235 */
236 public function get_template_part( $name = 'template', $extra_args = array() ) {
237 $dir = trailingslashit( $this->get_template_dir() );
238 $folder = trim( $this->get_template_folder(), '/' );
239 $template_path = $dir . $folder . '/' . $name . '.php';
240
241 if ( empty( $folder ) ) {
242 _doing_it_wrong(
243 __METHOD__,
244 esc_html( sprintf( 'get_template_folder() returned an empty string for field type "%s".', $this->type ) ),
245 '2.2.5'
246 );
247
248 return;
249 }
250
251 if ( ! file_exists( $template_path ) ) {
252 printf(
253 '<div class="merchant-field-error"><strong>%s</strong> %s</div>',
254 esc_html__( 'Merchant field error:', 'merchant' ),
255 esc_html(
256 sprintf(
257 /* translators: 1: template name, 2: field type */
258 __( 'Template "%1$s" not found for field type "%2$s".', 'merchant' ),
259 $name . '.php',
260 $this->type
261 )
262 )
263 );
264
265 return;
266 }
267
268 // Variables available in the template scope.
269 $settings = $this->field;
270 $value = $this->value;
271 $module_id = $this->module_id;
272 $field = $this;
273
274 // Extract extra args into the template scope (e.g. layout_type, has_accordion).
275 if ( ! empty( $extra_args ) ) {
276 extract( $extra_args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- Intentional, matches merchant_get_template_part pattern.
277 }
278
279 include $template_path;
280 }
281
282 /**
283 * Render a sub-field with name attribute replacement.
284 *
285 * Captures the output of Merchant_Admin_Options::field(), then replaces
286 * the name attribute string for proper nesting (e.g., inside flexible_content or fields_group).
287 *
288 * @since 2.2.5
289 *
290 * @param array<string, mixed> $settings The sub-field configuration array.
291 * @param mixed $value The sub-field value.
292 * @param string|array<int, string> $search The string(s) to search for in the rendered HTML.
293 * @param string|array<int, string> $replace The replacement string(s).
294 * @param string $module_id The module ID.
295 *
296 * @return void
297 */
298 public static function render_sub_field( $settings, $value, $search, $replace, $module_id = '' ) {
299 ob_start();
300 Merchant_Settings_Renderer::field( $settings, $value, $module_id );
301 $field_html = (string) ob_get_clean();
302
303 // Replace attributes in the field.
304 $field = str_replace( $search, $replace, $field_html );
305
306 // Process specific field types.
307 if ( isset( $settings['type'] ) && $settings['type'] === 'hook_select' ) {
308 $field = self::update_field_attributes( $field, array(
309 'select' => '[hook_name]',
310 'input' => '[hook_priority]',
311 ) );
312 }
313
314 echo wp_kses( $field, merchant_kses_allowed_tags( array( 'all' ) ) );
315 }
316
317 /**
318 * Update attributes for specific tags in the field HTML.
319 *
320 * @since 2.2.5
321 *
322 * @param string $field The field's HTML.
323 * @param array<string, string> $updates An associative array where keys are tag names
324 * and values are strings to append to the `name` attribute.
325 *
326 * @return string Updated field HTML.
327 */
328 protected static function update_field_attributes( $field, $updates ) {
329 if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
330 return $field;
331 }
332
333 foreach ( $updates as $tag => $append ) {
334 $processor = new WP_HTML_Tag_Processor( $field );
335
336 while ( $processor->next_tag( array( 'tag_name' => $tag ) ) ) {
337 $current_name = (string) $processor->get_attribute( 'name' );
338
339 if ( $current_name && substr( $current_name, -strlen( $append ) ) !== $append ) {
340 $processor->set_attribute( 'name', $current_name . $append );
341 }
342 }
343
344 $field = $processor->get_updated_html();
345 }
346
347 return $field;
348 }
349 }
350