Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like WC_Settings_API often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WC_Settings_API, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | abstract class WC_Settings_API { |
||
14 | |||
15 | /** |
||
16 | * The plugin ID. Used for option names. |
||
17 | * @var string |
||
18 | */ |
||
19 | public $plugin_id = 'woocommerce_'; |
||
20 | |||
21 | /** |
||
22 | * ID of the class extending the settings API. Used in option names. |
||
23 | * @var string |
||
24 | */ |
||
25 | public $id = ''; |
||
26 | |||
27 | /** |
||
28 | * Validation errors. |
||
29 | * @var array of strings |
||
30 | */ |
||
31 | public $errors = array(); |
||
32 | |||
33 | /** |
||
34 | * Setting values. |
||
35 | * @var array |
||
36 | */ |
||
37 | public $settings = array(); |
||
38 | |||
39 | /** |
||
40 | * Form option fields. |
||
41 | * @var array |
||
42 | */ |
||
43 | public $form_fields = array(); |
||
44 | |||
45 | /** |
||
46 | * Get the form fields after they are initialized. |
||
47 | * @return array of options |
||
48 | */ |
||
49 | public function get_form_fields() { |
||
52 | |||
53 | /** |
||
54 | * Set default required properties for each field. |
||
55 | * @param array |
||
56 | */ |
||
57 | private function set_defaults( $field ) { |
||
58 | if ( ! isset( $field['default'] ) ) { |
||
59 | $field['default'] = ''; |
||
60 | } |
||
61 | return $field; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * Output the admin options table. |
||
66 | */ |
||
67 | public function admin_options() { |
||
70 | |||
71 | /** |
||
72 | * Return the name of the option in the WP DB. |
||
73 | * @since 2.6.0 |
||
74 | * @return string |
||
75 | */ |
||
76 | public function get_option_key() { |
||
79 | |||
80 | /** |
||
81 | * Get a fields type. Defaults to "text" if not set. |
||
82 | * @param array $field |
||
83 | * @return string |
||
84 | */ |
||
85 | public function get_field_type( $field ) { |
||
88 | |||
89 | /** |
||
90 | * Get a fields default value. Defaults to "" if not set. |
||
91 | * @param array $field |
||
92 | * @return string |
||
93 | */ |
||
94 | public function get_field_default( $field ) { |
||
97 | |||
98 | /** |
||
99 | * Get a field's posted and validated value. |
||
100 | * @return string |
||
101 | */ |
||
102 | public function get_field_value( $key, $field ) { |
||
103 | $type = $this->get_field_type( $field ); |
||
104 | |||
105 | // Look for a validate_FIELDID_field method for special handling |
||
106 | View Code Duplication | if ( method_exists( $this, 'validate_' . $key . '_field' ) ) { |
|
|
|||
107 | return $this->{'validate_' . $key . '_field'}( $key ); |
||
108 | } |
||
109 | |||
110 | // Look for a validate_FIELDTYPE_field method |
||
111 | View Code Duplication | if ( method_exists( $this, 'validate_' . $type . '_field' ) ) { |
|
112 | return $this->{'validate_' . $type . '_field'}( $key ); |
||
113 | } |
||
114 | |||
115 | // Fallback to text |
||
116 | return $this->validate_text_field( $key ); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Processes and saves options. |
||
121 | * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out. |
||
122 | * @return bool was anything saved? |
||
123 | */ |
||
124 | public function process_admin_options() { |
||
139 | |||
140 | /** |
||
141 | * Add an error message for display in admin on save. |
||
142 | * @param string $error |
||
143 | */ |
||
144 | public function add_error( $error ) { |
||
147 | |||
148 | /** |
||
149 | * Display admin error messages. |
||
150 | */ |
||
151 | public function display_errors() { |
||
152 | if ( count( $this->errors ) > 0 ) { |
||
153 | echo '<div id="woocommerce_errors" class="error notice is-dismissible">'; |
||
154 | foreach ( $this->errors as $error ) { |
||
155 | echo '<p>' . wp_kses_post( $error ) . '</p>'; |
||
156 | } |
||
157 | echo '</div>'; |
||
158 | } |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * Initialise Settings. |
||
163 | * |
||
164 | * Store all settings in a single database entry |
||
165 | * and make sure the $settings array is either the default |
||
166 | * or the settings stored in the database. |
||
167 | * |
||
168 | * @since 1.0.0 |
||
169 | * @uses get_option(), add_option() |
||
170 | */ |
||
171 | View Code Duplication | public function init_settings() { |
|
172 | $this->settings = get_option( $this->get_option_key(), null ); |
||
173 | |||
174 | // If there are no settings defined, use defaults. |
||
175 | if ( ! is_array( $this->settings ) ) { |
||
176 | $form_fields = $this->get_form_fields(); |
||
177 | $this->settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) ); |
||
178 | } |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * get_option function. |
||
183 | * |
||
184 | * Gets an option from the settings API, using defaults if necessary to prevent undefined notices. |
||
185 | * |
||
186 | * @param string $key |
||
187 | * @param mixed $empty_value |
||
188 | * @return mixed The value specified for the option or a default value for the option. |
||
189 | */ |
||
190 | View Code Duplication | public function get_option( $key, $empty_value = null ) { |
|
191 | if ( empty( $this->settings ) ) { |
||
192 | $this->init_settings(); |
||
193 | } |
||
194 | |||
195 | // Get option default if unset. |
||
196 | if ( ! isset( $this->settings[ $key ] ) ) { |
||
197 | $form_fields = $this->get_form_fields(); |
||
198 | $this->settings[ $key ] = isset( $form_fields[ $key ] ) ? $this->get_field_default( $form_fields[ $key ] ) : ''; |
||
199 | } |
||
200 | |||
201 | if ( ! is_null( $empty_value ) && '' === $this->settings[ $key ] ) { |
||
202 | $this->settings[ $key ] = $empty_value; |
||
203 | } |
||
204 | |||
205 | return $this->settings[ $key ]; |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Prefix key for settings. |
||
210 | * |
||
211 | * @param mixed $key |
||
212 | * @return string |
||
213 | */ |
||
214 | public function get_field_key( $key ) { |
||
217 | |||
218 | /** |
||
219 | * Generate Settings HTML. |
||
220 | * |
||
221 | * Generate the HTML for the fields on the "settings" screen. |
||
222 | * |
||
223 | * @param array $form_fields (default: array()) |
||
224 | * @since 1.0.0 |
||
225 | * @uses method_exists() |
||
226 | * @return string the html for the settings |
||
227 | */ |
||
228 | public function generate_settings_html( $form_fields = array(), $echo = true ) { |
||
229 | if ( empty( $form_fields ) ) { |
||
230 | $form_fields = $this->get_form_fields(); |
||
231 | } |
||
232 | |||
233 | $html = ''; |
||
234 | foreach ( $form_fields as $k => $v ) { |
||
235 | $type = $this->get_field_type( $v ); |
||
236 | |||
237 | if ( method_exists( $this, 'generate_' . $type . '_html' ) ) { |
||
238 | $html .= $this->{'generate_' . $type . '_html'}( $k, $v ); |
||
239 | } else { |
||
240 | $html .= $this->generate_text_html( $k, $v ); |
||
241 | } |
||
242 | } |
||
243 | |||
244 | if ( $echo ) { |
||
245 | echo $html; |
||
246 | } else { |
||
247 | return $html; |
||
248 | } |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Get HTML for tooltips. |
||
253 | * |
||
254 | * @param array $data |
||
255 | * @return string |
||
256 | */ |
||
257 | public function get_tooltip_html( $data ) { |
||
268 | |||
269 | /** |
||
270 | * Get HTML for descriptions. |
||
271 | * |
||
272 | * @param array $data |
||
273 | * @return string |
||
274 | */ |
||
275 | public function get_description_html( $data ) { |
||
276 | View Code Duplication | if ( $data['desc_tip'] === true ) { |
|
277 | $description = ''; |
||
278 | } elseif ( ! empty( $data['desc_tip'] ) ) { |
||
279 | $description = $data['description']; |
||
280 | } elseif ( ! empty( $data['description'] ) ) { |
||
281 | $description = $data['description']; |
||
282 | } else { |
||
283 | $description = ''; |
||
284 | } |
||
285 | |||
286 | return $description ? '<p class="description">' . wp_kses_post( $description ) . '</p>' . "\n" : ''; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Get custom attributes. |
||
291 | * |
||
292 | * @param array $data |
||
293 | * @return string |
||
294 | */ |
||
295 | public function get_custom_attribute_html( $data ) { |
||
306 | |||
307 | /** |
||
308 | * Generate Text Input HTML. |
||
309 | * |
||
310 | * @param mixed $key |
||
311 | * @param mixed $data |
||
312 | * @since 1.0.0 |
||
313 | * @return string |
||
314 | */ |
||
315 | View Code Duplication | public function generate_text_html( $key, $data ) { |
|
316 | $field_key = $this->get_field_key( $key ); |
||
317 | $defaults = array( |
||
318 | 'title' => '', |
||
319 | 'disabled' => false, |
||
320 | 'class' => '', |
||
321 | 'css' => '', |
||
322 | 'placeholder' => '', |
||
323 | 'type' => 'text', |
||
324 | 'desc_tip' => false, |
||
325 | 'description' => '', |
||
326 | 'custom_attributes' => array() |
||
327 | ); |
||
328 | |||
329 | $data = wp_parse_args( $data, $defaults ); |
||
330 | |||
331 | ob_start(); |
||
332 | ?> |
||
333 | <tr valign="top"> |
||
334 | <th scope="row" class="titledesc"> |
||
335 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> |
||
336 | <?php echo $this->get_tooltip_html( $data ); ?> |
||
337 | </th> |
||
338 | <td class="forminp"> |
||
339 | <fieldset> |
||
340 | <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> |
||
341 | <input class="input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="<?php echo esc_attr( $data['type'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( $this->get_option( $key ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> |
||
342 | <?php echo $this->get_description_html( $data ); ?> |
||
343 | </fieldset> |
||
344 | </td> |
||
345 | </tr> |
||
346 | <?php |
||
347 | |||
348 | return ob_get_clean(); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Generate Price Input HTML. |
||
353 | * |
||
354 | * @param mixed $key |
||
355 | * @param mixed $data |
||
356 | * @since 1.0.0 |
||
357 | * @return string |
||
358 | */ |
||
359 | View Code Duplication | public function generate_price_html( $key, $data ) { |
|
360 | $field_key = $this->get_field_key( $key ); |
||
361 | $defaults = array( |
||
362 | 'title' => '', |
||
363 | 'disabled' => false, |
||
364 | 'class' => '', |
||
365 | 'css' => '', |
||
366 | 'placeholder' => '', |
||
367 | 'type' => 'text', |
||
368 | 'desc_tip' => false, |
||
369 | 'description' => '', |
||
370 | 'custom_attributes' => array() |
||
371 | ); |
||
372 | |||
373 | $data = wp_parse_args( $data, $defaults ); |
||
374 | |||
375 | ob_start(); |
||
376 | ?> |
||
377 | <tr valign="top"> |
||
378 | <th scope="row" class="titledesc"> |
||
379 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> |
||
380 | <?php echo $this->get_tooltip_html( $data ); ?> |
||
381 | </th> |
||
382 | <td class="forminp"> |
||
383 | <fieldset> |
||
384 | <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> |
||
385 | <input class="wc_input_price input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> |
||
386 | <?php echo $this->get_description_html( $data ); ?> |
||
387 | </fieldset> |
||
388 | </td> |
||
389 | </tr> |
||
390 | <?php |
||
391 | |||
392 | return ob_get_clean(); |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Generate Decimal Input HTML. |
||
397 | * |
||
398 | * @param mixed $key |
||
399 | * @param mixed $data |
||
400 | * @since 1.0.0 |
||
401 | * @return string |
||
402 | */ |
||
403 | View Code Duplication | public function generate_decimal_html( $key, $data ) { |
|
404 | $field_key = $this->get_field_key( $key ); |
||
405 | $defaults = array( |
||
406 | 'title' => '', |
||
407 | 'disabled' => false, |
||
408 | 'class' => '', |
||
409 | 'css' => '', |
||
410 | 'placeholder' => '', |
||
411 | 'type' => 'text', |
||
412 | 'desc_tip' => false, |
||
413 | 'description' => '', |
||
414 | 'custom_attributes' => array() |
||
415 | ); |
||
416 | |||
417 | $data = wp_parse_args( $data, $defaults ); |
||
418 | |||
419 | ob_start(); |
||
420 | ?> |
||
421 | <tr valign="top"> |
||
422 | <th scope="row" class="titledesc"> |
||
423 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></label> |
||
424 | <?php echo $this->get_tooltip_html( $data ); ?> |
||
425 | </th> |
||
426 | <td class="forminp"> |
||
427 | <fieldset> |
||
428 | <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend> |
||
429 | <input class="wc_input_decimal input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_decimal( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); ?> /> |
||
430 | <?php echo $this->get_description_html( $data ); ?> |
||
431 | </fieldset> |
||
432 | </td> |
||
433 | </tr> |
||
434 | <?php |
||
435 | |||
436 | return ob_get_clean(); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Generate Password Input HTML. |
||
441 | * |
||
442 | * @param mixed $key |
||
443 | * @param mixed $data |
||
444 | * @since 1.0.0 |
||
445 | * @return string |
||
446 | */ |
||
447 | public function generate_password_html( $key, $data ) { |
||
451 | |||
452 | /** |
||
453 | * Generate Color Picker Input HTML. |
||
454 | * |
||
455 | * @param mixed $key |
||
456 | * @param mixed $data |
||
457 | * @since 1.0.0 |
||
458 | * @return string |
||
459 | */ |
||
460 | public function generate_color_html( $key, $data ) { |
||
461 | $field_key = $this->get_field_key( $key ); |
||
496 | |||
497 | /** |
||
498 | * Generate Textarea HTML. |
||
499 | * |
||
500 | * @param mixed $key |
||
501 | * @param mixed $data |
||
502 | * @since 1.0.0 |
||
503 | * @return string |
||
504 | */ |
||
505 | View Code Duplication | public function generate_textarea_html( $key, $data ) { |
|
540 | |||
541 | /** |
||
542 | * Generate Checkbox HTML. |
||
543 | * |
||
544 | * @param mixed $key |
||
545 | * @param mixed $data |
||
546 | * @since 1.0.0 |
||
547 | * @return string |
||
548 | */ |
||
549 | public function generate_checkbox_html( $key, $data ) { |
||
589 | |||
590 | /** |
||
591 | * Generate Select HTML. |
||
592 | * |
||
593 | * @param mixed $key |
||
594 | * @param mixed $data |
||
595 | * @since 1.0.0 |
||
596 | * @return string |
||
597 | */ |
||
598 | public function generate_select_html( $key, $data ) { |
||
638 | |||
639 | /** |
||
640 | * Generate Multiselect HTML. |
||
641 | * |
||
642 | * @param mixed $key |
||
643 | * @param mixed $data |
||
644 | * @since 1.0.0 |
||
645 | * @return string |
||
646 | */ |
||
647 | public function generate_multiselect_html( $key, $data ) { |
||
692 | |||
693 | /** |
||
694 | * Generate Title HTML. |
||
695 | * |
||
696 | * @param mixed $key |
||
697 | * @param mixed $data |
||
698 | * @since 1.0.0 |
||
699 | * @return string |
||
700 | */ |
||
701 | public function generate_title_html( $key, $data ) { |
||
722 | |||
723 | /** |
||
724 | * Validate Text Field. |
||
725 | * |
||
726 | * Make sure the data is escaped correctly, etc. |
||
727 | * |
||
728 | * @param string $key |
||
729 | * @return string |
||
730 | */ |
||
731 | public function validate_text_field( $key ) { |
||
741 | |||
742 | /** |
||
743 | * Validate Price Field. |
||
744 | * |
||
745 | * Make sure the data is escaped correctly, etc. |
||
746 | * |
||
747 | * @param string $key |
||
748 | * @return string |
||
749 | */ |
||
750 | View Code Duplication | public function validate_price_field( $key ) { |
|
765 | |||
766 | /** |
||
767 | * Validate Decimal Field. |
||
768 | * |
||
769 | * Make sure the data is escaped correctly, etc. |
||
770 | * |
||
771 | * @param string $key |
||
772 | * @return string |
||
773 | */ |
||
774 | View Code Duplication | public function validate_decimal_field( $key ) { |
|
789 | |||
790 | /** |
||
791 | * Validate Password Field. |
||
792 | * |
||
793 | * Make sure the data is escaped correctly, etc. |
||
794 | * |
||
795 | * @param string $key |
||
796 | * @return string |
||
797 | */ |
||
798 | public function validate_password_field( $key ) { |
||
803 | |||
804 | /** |
||
805 | * Validate Textarea Field. |
||
806 | * |
||
807 | * @param string $key |
||
808 | * @return string |
||
809 | */ |
||
810 | public function validate_textarea_field( $key ) { |
||
828 | |||
829 | /** |
||
830 | * Validate Checkbox Field. |
||
831 | * |
||
832 | * If not set, return "no", otherwise return "yes". |
||
833 | * |
||
834 | * @param string $key |
||
835 | * @return string |
||
836 | */ |
||
837 | public function validate_checkbox_field( $key ) { |
||
841 | |||
842 | /** |
||
843 | * Validate Select Field. |
||
844 | * |
||
845 | * @param string $key |
||
846 | * @return string |
||
847 | */ |
||
848 | public function validate_select_field( $key ) { |
||
852 | |||
853 | /** |
||
854 | * Validate Multiselect Field. |
||
855 | * |
||
856 | * @param string $key |
||
857 | * @return string |
||
858 | */ |
||
859 | public function validate_multiselect_field( $key ) { |
||
863 | |||
864 | /** |
||
865 | * Validate the data on the "Settings" form. |
||
866 | * @deprecated 2.6.0 No longer used |
||
867 | */ |
||
868 | public function validate_settings_fields( $form_fields = array() ) {} |
||
869 | |||
870 | /** |
||
871 | * Format settings if needed. |
||
872 | * @deprecated 2.6.0 Unused |
||
873 | * @param array $value |
||
874 | * @return array |
||
875 | */ |
||
876 | public function format_settings( $value ) { |
||
879 | } |
||
880 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.