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() { |
||
50 | return apply_filters( 'woocommerce_settings_api_form_fields_' . $this->id, $this->form_fields ); |
||
51 | } |
||
52 | |||
53 | /** |
||
54 | * Output the admin options table. |
||
55 | */ |
||
56 | public function admin_options() { |
||
57 | echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>'; |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * Return the name of the option in the WP DB. |
||
62 | * @since 2.6.0 |
||
63 | * @return string |
||
64 | */ |
||
65 | public function get_option_key() { |
||
66 | return $this->plugin_id . $this->id . '_settings'; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Get a fields type. Defaults to "text" if not set. |
||
71 | * @param array $field |
||
72 | * @return string |
||
73 | */ |
||
74 | public function get_field_type( $field ) { |
||
75 | return empty( $field['type'] ) ? 'text' : $field['type']; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * Get a fields default value. Defaults to "" if not set. |
||
80 | * @param array $field |
||
81 | * @return string |
||
82 | */ |
||
83 | public function get_field_default( $field ) { |
||
84 | return empty( $field['default'] ) ? '' : $field['default']; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Get a field's posted and validated value. |
||
89 | * @return string |
||
90 | */ |
||
91 | public function get_field_value( $key, $field ) { |
||
92 | $type = $this->get_field_type( $field ); |
||
93 | |||
94 | // Look for a validate_FIELDID_field method for special handling |
||
95 | View Code Duplication | if ( method_exists( $this, 'validate_' . $key . '_field' ) ) { |
|
|
|||
96 | return $this->{'validate_' . $key . '_field'}( $key ); |
||
97 | } |
||
98 | |||
99 | // Look for a validate_FIELDTYPE_field method |
||
100 | View Code Duplication | if ( method_exists( $this, 'validate_' . $type . '_field' ) ) { |
|
101 | return $this->{'validate_' . $type . '_field'}( $key ); |
||
102 | } |
||
103 | |||
104 | // Fallback to text |
||
105 | return $this->validate_text_field( $key ); |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Processes and saves options. |
||
110 | * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out. |
||
111 | * @return bool was anything saved? |
||
112 | */ |
||
113 | public function process_admin_options() { |
||
114 | $this->init_settings(); |
||
115 | |||
116 | View Code Duplication | foreach ( $this->get_form_fields() as $key => $field ) { |
|
117 | if ( ! in_array( $this->get_field_type( $field ), array( 'title' ) ) ) { |
||
118 | try { |
||
119 | $this->settings[ $key ] = $this->get_field_value( $key, $field ); |
||
120 | } catch ( Exception $e ) { |
||
121 | $this->add_error( $e->getMessage() ); |
||
122 | } |
||
123 | } |
||
124 | } |
||
125 | |||
126 | return update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ) ); |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Add an error message for display in admin on save. |
||
131 | * @param string $error |
||
132 | */ |
||
133 | public function add_error( $error ) { |
||
134 | $this->errors[] = $error; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Display admin error messages. |
||
139 | */ |
||
140 | public function display_errors() { |
||
141 | if ( count( $this->errors ) > 0 ) { |
||
142 | echo '<div id="woocommerce_errors" class="error notice is-dismissible">'; |
||
143 | foreach ( $this->errors as $error ) { |
||
144 | echo '<p>' . wp_kses_post( $error ) . '</p>'; |
||
145 | } |
||
146 | echo '</div>'; |
||
147 | } |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Initialise Settings. |
||
152 | * |
||
153 | * Store all settings in a single database entry |
||
154 | * and make sure the $settings array is either the default |
||
155 | * or the settings stored in the database. |
||
156 | * |
||
157 | * @since 1.0.0 |
||
158 | * @uses get_option(), add_option() |
||
159 | */ |
||
160 | View Code Duplication | public function init_settings() { |
|
161 | $this->settings = get_option( $this->get_option_key(), null ); |
||
162 | |||
163 | // If there are no settings defined, use defaults. |
||
164 | if ( ! is_array( $this->settings ) ) { |
||
165 | $form_fields = $this->get_form_fields(); |
||
166 | $this->settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) ); |
||
167 | } |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * get_option function. |
||
172 | * |
||
173 | * Gets and option from the settings API, using defaults if necessary to prevent undefined notices. |
||
174 | * |
||
175 | * @param string $key |
||
176 | * @param mixed $empty_value |
||
177 | * @return mixed The value specified for the option or a default value for the option. |
||
178 | */ |
||
179 | View Code Duplication | public function get_option( $key, $empty_value = null ) { |
|
180 | if ( empty( $this->settings ) ) { |
||
181 | $this->init_settings(); |
||
182 | } |
||
183 | |||
184 | // Get option default if unset. |
||
185 | if ( ! isset( $this->settings[ $key ] ) ) { |
||
186 | $form_fields = $this->get_form_fields(); |
||
187 | $this->settings[ $key ] = isset( $form_fields[ $key ] ) ? $this->get_field_default( $form_fields[ $key ] ) : ''; |
||
188 | } |
||
189 | |||
190 | if ( ! is_null( $empty_value ) && '' === $this->settings[ $key ] ) { |
||
191 | $this->settings[ $key ] = $empty_value; |
||
192 | } |
||
193 | |||
194 | return $this->settings[ $key ]; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Prefix key for settings. |
||
199 | * |
||
200 | * @param mixed $key |
||
201 | * @return string |
||
202 | */ |
||
203 | public function get_field_key( $key ) { |
||
206 | |||
207 | /** |
||
208 | * Generate Settings HTML. |
||
209 | * |
||
210 | * Generate the HTML for the fields on the "settings" screen. |
||
211 | * |
||
212 | * @param array $form_fields (default: array()) |
||
213 | * @since 1.0.0 |
||
214 | * @uses method_exists() |
||
215 | * @return string the html for the settings |
||
216 | */ |
||
217 | public function generate_settings_html( $form_fields = array(), $echo = true ) { |
||
239 | |||
240 | /** |
||
241 | * Get HTML for tooltips. |
||
242 | * |
||
243 | * @param array $data |
||
244 | * @return string |
||
245 | */ |
||
246 | public function get_tooltip_html( $data ) { |
||
257 | |||
258 | /** |
||
259 | * Get HTML for descriptions. |
||
260 | * |
||
261 | * @param array $data |
||
262 | * @return string |
||
263 | */ |
||
264 | public function get_description_html( $data ) { |
||
277 | |||
278 | /** |
||
279 | * Get custom attributes. |
||
280 | * |
||
281 | * @param array $data |
||
282 | * @return string |
||
283 | */ |
||
284 | public function get_custom_attribute_html( $data ) { |
||
295 | |||
296 | /** |
||
297 | * Generate Text Input HTML. |
||
298 | * |
||
299 | * @param mixed $key |
||
300 | * @param mixed $data |
||
301 | * @since 1.0.0 |
||
302 | * @return string |
||
303 | */ |
||
304 | View Code Duplication | public function generate_text_html( $key, $data ) { |
|
339 | |||
340 | /** |
||
341 | * Generate Price Input HTML. |
||
342 | * |
||
343 | * @param mixed $key |
||
344 | * @param mixed $data |
||
345 | * @since 1.0.0 |
||
346 | * @return string |
||
347 | */ |
||
348 | View Code Duplication | public function generate_price_html( $key, $data ) { |
|
383 | |||
384 | /** |
||
385 | * Generate Decimal Input HTML. |
||
386 | * |
||
387 | * @param mixed $key |
||
388 | * @param mixed $data |
||
389 | * @since 1.0.0 |
||
390 | * @return string |
||
391 | */ |
||
392 | View Code Duplication | public function generate_decimal_html( $key, $data ) { |
|
427 | |||
428 | /** |
||
429 | * Generate Password Input HTML. |
||
430 | * |
||
431 | * @param mixed $key |
||
432 | * @param mixed $data |
||
433 | * @since 1.0.0 |
||
434 | * @return string |
||
435 | */ |
||
436 | public function generate_password_html( $key, $data ) { |
||
440 | |||
441 | /** |
||
442 | * Generate Color Picker Input HTML. |
||
443 | * |
||
444 | * @param mixed $key |
||
445 | * @param mixed $data |
||
446 | * @since 1.0.0 |
||
447 | * @return string |
||
448 | */ |
||
449 | public function generate_color_html( $key, $data ) { |
||
485 | |||
486 | /** |
||
487 | * Generate Textarea HTML. |
||
488 | * |
||
489 | * @param mixed $key |
||
490 | * @param mixed $data |
||
491 | * @since 1.0.0 |
||
492 | * @return string |
||
493 | */ |
||
494 | View Code Duplication | public function generate_textarea_html( $key, $data ) { |
|
529 | |||
530 | /** |
||
531 | * Generate Checkbox HTML. |
||
532 | * |
||
533 | * @param mixed $key |
||
534 | * @param mixed $data |
||
535 | * @since 1.0.0 |
||
536 | * @return string |
||
537 | */ |
||
538 | public function generate_checkbox_html( $key, $data ) { |
||
578 | |||
579 | /** |
||
580 | * Generate Select HTML. |
||
581 | * |
||
582 | * @param mixed $key |
||
583 | * @param mixed $data |
||
584 | * @since 1.0.0 |
||
585 | * @return string |
||
586 | */ |
||
587 | View Code Duplication | public function generate_select_html( $key, $data ) { |
|
627 | |||
628 | /** |
||
629 | * Generate Multiselect HTML. |
||
630 | * |
||
631 | * @param mixed $key |
||
632 | * @param mixed $data |
||
633 | * @since 1.0.0 |
||
634 | * @return string |
||
635 | */ |
||
636 | View Code Duplication | public function generate_multiselect_html( $key, $data ) { |
|
677 | |||
678 | /** |
||
679 | * Generate Title HTML. |
||
680 | * |
||
681 | * @param mixed $key |
||
682 | * @param mixed $data |
||
683 | * @since 1.0.0 |
||
684 | * @return string |
||
685 | */ |
||
686 | public function generate_title_html( $key, $data ) { |
||
707 | |||
708 | /** |
||
709 | * Validate Text Field. |
||
710 | * |
||
711 | * Make sure the data is escaped correctly, etc. |
||
712 | * |
||
713 | * @param mixed $key |
||
714 | * @return string |
||
715 | */ |
||
716 | public function validate_text_field( $key ) { |
||
726 | |||
727 | /** |
||
728 | * Validate Price Field. |
||
729 | * |
||
730 | * Make sure the data is escaped correctly, etc. |
||
731 | * |
||
732 | * @param mixed $key |
||
733 | * @return string |
||
734 | */ |
||
735 | View Code Duplication | public function validate_price_field( $key ) { |
|
750 | |||
751 | /** |
||
752 | * Validate Decimal Field. |
||
753 | * |
||
754 | * Make sure the data is escaped correctly, etc. |
||
755 | * |
||
756 | * @param mixed $key |
||
757 | * @return string |
||
758 | */ |
||
759 | View Code Duplication | public function validate_decimal_field( $key ) { |
|
775 | |||
776 | /** |
||
777 | * Validate Password Field. |
||
778 | * |
||
779 | * Make sure the data is escaped correctly, etc. |
||
780 | * |
||
781 | * @param mixed $key |
||
782 | * @since 1.0.0 |
||
783 | * @return string |
||
784 | */ |
||
785 | public function validate_password_field( $key ) { |
||
790 | |||
791 | /** |
||
792 | * Validate Textarea Field. |
||
793 | * |
||
794 | * @param string $key |
||
795 | * @return string |
||
796 | */ |
||
797 | public function validate_textarea_field( $key ) { |
||
815 | |||
816 | /** |
||
817 | * Validate Checkbox Field. |
||
818 | * |
||
819 | * If not set, return "no", otherwise return "yes". |
||
820 | * |
||
821 | * @param string $key |
||
822 | * @return string |
||
823 | */ |
||
824 | public function validate_checkbox_field( $key ) { |
||
828 | |||
829 | /** |
||
830 | * Validate Select Field. |
||
831 | * |
||
832 | * @param string $key |
||
833 | * @return string |
||
834 | */ |
||
835 | public function validate_select_field( $key ) { |
||
839 | |||
840 | /** |
||
841 | * Validate Multiselect Field. |
||
842 | * |
||
843 | * @param string $key |
||
844 | * @return string |
||
845 | */ |
||
846 | public function validate_multiselect_field( $key ) { |
||
850 | |||
851 | /** |
||
852 | * Validate the data on the "Settings" form. |
||
853 | * @deprecated 2.6.0 No longer used |
||
854 | */ |
||
855 | public function validate_settings_fields( $form_fields = array() ) {} |
||
856 | |||
857 | /** |
||
858 | * Format settings if needed. |
||
859 | * @deprecated 2.6.0 Unused |
||
860 | * @param array $value |
||
861 | * @return array |
||
862 | */ |
||
863 | public function format_settings( $value ) { |
||
866 | } |
||
867 |
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.