Completed
Push — master ( ebabb0...a4acf5 )
by Mike
12:53
created

WC_Widget   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 376
Duplicated Lines 9.84 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 27.73%

Importance

Changes 0
Metric Value
dl 37
loc 376
ccs 33
cts 119
cp 0.2773
rs 4.5599
c 0
b 0
f 0
wmc 58
lcom 1
cbo 1

11 Methods

Rating   Name   Duplication   Size   Complexity  
A get_cached_widget() 0 14 3
A cache_widget() 0 13 2
A __construct() 0 13 1
A flush_widget_cache() 0 5 2
A get_instance_title() 0 11 3
A widget_start() 0 9 2
A widget_end() 0 3 1
C update() 6 48 13
C form() 19 72 12
F get_current_page_url() 12 66 16
A get_widget_id_for_cache() 0 9 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Widget 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_Widget, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Abstract widget class
4
 *
5
 * @class WC_Widget
6
 * @package  WooCommerce/Abstracts
7
 */
8
9
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
/**
14
 * WC_Widget
15
 *
16
 * @package  WooCommerce/Abstracts
17
 * @version  2.5.0
18
 * @extends  WP_Widget
19
 */
20
abstract class WC_Widget extends WP_Widget {
21
22
	/**
23
	 * CSS class.
24
	 *
25
	 * @var string
26
	 */
27
	public $widget_cssclass;
28
29
	/**
30
	 * Widget description.
31
	 *
32
	 * @var string
33
	 */
34
	public $widget_description;
35
36
	/**
37
	 * Widget ID.
38
	 *
39
	 * @var string
40
	 */
41
	public $widget_id;
42
43
	/**
44
	 * Widget name.
45
	 *
46
	 * @var string
47
	 */
48
	public $widget_name;
49
50
	/**
51
	 * Settings.
52
	 *
53
	 * @var array
54
	 */
55
	public $settings;
56
57
	/**
58
	 * Constructor.
59
	 */
60 3
	public function __construct() {
61
		$widget_ops = array(
62 3
			'classname'                   => $this->widget_cssclass,
63 3
			'description'                 => $this->widget_description,
64
			'customize_selective_refresh' => true,
65
		);
66
67 3
		parent::__construct( $this->widget_id, $this->widget_name, $widget_ops );
68
69 3
		add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
70 3
		add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
71 3
		add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
72
	}
73
74
	/**
75
	 * Get cached widget.
76
	 *
77
	 * @param  array $args Arguments.
78
	 * @return bool true if the widget is cached otherwise false
79
	 */
80 1
	public function get_cached_widget( $args ) {
81 1
		$cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
82
83 1
		if ( ! is_array( $cache ) ) {
84 1
			$cache = array();
85
		}
86
87 1
		if ( isset( $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] ) ) {
88 1
			echo $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ]; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
89 1
			return true;
90
		}
91
92 1
		return false;
93
	}
94
95
	/**
96
	 * Cache the widget.
97
	 *
98
	 * @param  array  $args Arguments.
99
	 * @param  string $content Content.
100
	 * @return string the content that was cached
101
	 */
102 1
	public function cache_widget( $args, $content ) {
103 1
		$cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
104
105 1
		if ( ! is_array( $cache ) ) {
106 1
			$cache = array();
107
		}
108
109 1
		$cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] = $content;
110
111 1
		wp_cache_set( $this->get_widget_id_for_cache( $this->widget_id ), $cache, 'widget' );
112
113 1
		return $content;
114
	}
115
116
	/**
117
	 * Flush the cache.
118
	 */
119 485
	public function flush_widget_cache() {
120 485
		foreach ( array( 'https', 'http' ) as $scheme ) {
121 485
			wp_cache_delete( $this->get_widget_id_for_cache( $this->widget_id, $scheme ), 'widget' );
122
		}
123
	}
124
125
	/**
126
	 * Get this widgets title.
127
	 *
128
	 * @param array $instance Array of instance options.
129
	 * @return string
130
	 */
131
	protected function get_instance_title( $instance ) {
132
		if ( isset( $instance['title'] ) ) {
133
			return $instance['title'];
134
		}
135
136
		if ( isset( $this->settings, $this->settings['title'], $this->settings['title']['std'] ) ) {
137
			return $this->settings['title']['std'];
138
		}
139
140
		return '';
141
	}
142
143
	/**
144
	 * Output the html at the start of a widget.
145
	 *
146
	 * @param array $args Arguments.
147
	 * @param array $instance Instance.
148
	 */
149
	public function widget_start( $args, $instance ) {
150
		echo $args['before_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
151
152
		$title = apply_filters( 'widget_title', $this->get_instance_title( $instance ), $instance, $this->id_base );
153
154
		if ( $title ) {
155
			echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
156
		}
157
	}
158
159
	/**
160
	 * Output the html at the end of a widget.
161
	 *
162
	 * @param  array $args Arguments.
163
	 */
164
	public function widget_end( $args ) {
165
		echo $args['after_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
166
	}
167
168
	/**
169
	 * Updates a particular instance of a widget.
170
	 *
171
	 * @see    WP_Widget->update
172
	 * @param  array $new_instance New instance.
173
	 * @param  array $old_instance Old instance.
174
	 * @return array
175
	 */
176
	public function update( $new_instance, $old_instance ) {
177
178
		$instance = $old_instance;
179
180
		if ( empty( $this->settings ) ) {
181
			return $instance;
182
		}
183
184
		// Loop settings and get values to save.
185
		foreach ( $this->settings as $key => $setting ) {
186
			if ( ! isset( $setting['type'] ) ) {
187
				continue;
188
			}
189
190
			// Format the value based on settings type.
191
			switch ( $setting['type'] ) {
192
				case 'number':
193
					$instance[ $key ] = absint( $new_instance[ $key ] );
194
195 View Code Duplication
					if ( isset( $setting['min'] ) && '' !== $setting['min'] ) {
196
						$instance[ $key ] = max( $instance[ $key ], $setting['min'] );
197
					}
198
199 View Code Duplication
					if ( isset( $setting['max'] ) && '' !== $setting['max'] ) {
200
						$instance[ $key ] = min( $instance[ $key ], $setting['max'] );
201
					}
202
					break;
203
				case 'textarea':
204
					$instance[ $key ] = wp_kses( trim( wp_unslash( $new_instance[ $key ] ) ), wp_kses_allowed_html( 'post' ) );
205
					break;
206
				case 'checkbox':
207
					$instance[ $key ] = empty( $new_instance[ $key ] ) ? 0 : 1;
208
					break;
209
				default:
210
					$instance[ $key ] = isset( $new_instance[ $key ] ) ? sanitize_text_field( $new_instance[ $key ] ) : $setting['std'];
211
					break;
212 1
			}
213
214 1
			/**
215 1
			 * Sanitize the value of a setting.
216
			 */
217
			$instance[ $key ] = apply_filters( 'woocommerce_widget_settings_sanitize_option', $instance[ $key ], $new_instance, $key, $setting );
218
		}
219
220
		$this->flush_widget_cache();
221
222
		return $instance;
223
	}
224
225
	/**
226
	 * Outputs the settings update form.
227
	 *
228
	 * @see   WP_Widget->form
229
	 *
230
	 * @param array $instance Instance.
231
	 */
232
	public function form( $instance ) {
233
234
		if ( empty( $this->settings ) ) {
235
			return;
236
		}
237
238
		foreach ( $this->settings as $key => $setting ) {
239
240
			$class = isset( $setting['class'] ) ? $setting['class'] : '';
241
			$value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std'];
242
243
			switch ( $setting['type'] ) {
244
245 View Code Duplication
				case 'text':
246
					?>
247
					<p>
248
						<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo wp_kses_post( $setting['label'] ); ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
249
						<input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" />
250
					</p>
251
					<?php
252
					break;
253
254
				case 'number':
255
					?>
256
					<p>
257
						<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
258
						<input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" />
259
					</p>
260
					<?php
261
					break;
262
263
				case 'select':
264
					?>
265
					<p>
266
						<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
267
						<select class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>">
268 View Code Duplication
							<?php foreach ( $setting['options'] as $option_key => $option_value ) : ?>
269
								<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $option_key, $value ); ?>><?php echo esc_html( $option_value ); ?></option>
270
							<?php endforeach; ?>
271
						</select>
272
					</p>
273
					<?php
274
					break;
275
276
				case 'textarea':
277
					?>
278
					<p>
279
						<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
280
						<textarea class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" cols="20" rows="3"><?php echo esc_textarea( $value ); ?></textarea>
281
						<?php if ( isset( $setting['desc'] ) ) : ?>
282
							<small><?php echo esc_html( $setting['desc'] ); ?></small>
283
						<?php endif; ?>
284
					</p>
285
					<?php
286
					break;
287
288 View Code Duplication
				case 'checkbox':
289
					?>
290
					<p>
291
						<input class="checkbox <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?> />
292
						<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
293
					</p>
294
					<?php
295
					break;
296
297
				// Default: run an action.
298
				default:
299
					do_action( 'woocommerce_widget_field_' . $setting['type'], $key, $value, $setting, $instance );
300
					break;
301
			}
302
		}
303
	}
304
305
	/**
306
	 * Get current page URL with various filtering props supported by WC.
307
	 *
308
	 * @return string
309
	 * @since  3.3.0
310
	 */
311
	protected function get_current_page_url() {
312
		if ( defined( 'SHOP_IS_ON_FRONT' ) ) {
313
			$link = home_url();
314
		} elseif ( is_shop() ) {
315
			$link = get_permalink( wc_get_page_id( 'shop' ) );
316
		} elseif ( is_product_category() ) {
317
			$link = get_term_link( get_query_var( 'product_cat' ), 'product_cat' );
318
		} elseif ( is_product_tag() ) {
319
			$link = get_term_link( get_query_var( 'product_tag' ), 'product_tag' );
320
		} else {
321
			$queried_object = get_queried_object();
322
			$link           = get_term_link( $queried_object->slug, $queried_object->taxonomy );
323
		}
324
325
		// Min/Max.
326 View Code Duplication
		if ( isset( $_GET['min_price'] ) ) {
327
			$link = add_query_arg( 'min_price', wc_clean( wp_unslash( $_GET['min_price'] ) ), $link );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
328
		}
329
330 View Code Duplication
		if ( isset( $_GET['max_price'] ) ) {
331
			$link = add_query_arg( 'max_price', wc_clean( wp_unslash( $_GET['max_price'] ) ), $link );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
332
		}
333
334
		// Order by.
335 View Code Duplication
		if ( isset( $_GET['orderby'] ) ) {
336
			$link = add_query_arg( 'orderby', wc_clean( wp_unslash( $_GET['orderby'] ) ), $link );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
337
		}
338
339
		/**
340
		 * Search Arg.
341
		 * To support quote characters, first they are decoded from &quot; entities, then URL encoded.
342
		 */
343
		if ( get_search_query() ) {
344
			$link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
345
		}
346
347
		// Post Type Arg.
348
		if ( isset( $_GET['post_type'] ) ) {
349
			$link = add_query_arg( 'post_type', wc_clean( wp_unslash( $_GET['post_type'] ) ), $link );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
350
351
			// Prevent post type and page id when pretty permalinks are disabled.
352
			if ( is_shop() ) {
353
				$link = remove_query_arg( 'page_id', $link );
354
			}
355
		}
356
357
		// Min Rating Arg.
358 View Code Duplication
		if ( isset( $_GET['rating_filter'] ) ) {
359
			$link = add_query_arg( 'rating_filter', wc_clean( wp_unslash( $_GET['rating_filter'] ) ), $link );
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
360
		}
361 486
362 486
		// All current filters.
363 485
		if ( $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes() ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.CodeAnalysis.AssignmentInCondition.Found
364
			foreach ( $_chosen_attributes as $name => $data ) {
365 1
				$filter_name = wc_attribute_taxonomy_slug( $name );
366
				if ( ! empty( $data['terms'] ) ) {
367
					$link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
368 486
				}
369
				if ( 'or' === $data['query_type'] ) {
370
					$link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
371
				}
372
			}
373
		}
374
375
		return apply_filters( 'woocommerce_widget_get_current_page_url', $link, $this );
376
	}
377
378
	/**
379
	 * Get widget id plus scheme/protocol to prevent serving mixed content from (persistently) cached widgets.
380
	 *
381
	 * @since  3.4.0
382
	 * @param  string $widget_id Id of the cached widget.
383
	 * @param  string $scheme    Scheme for the widget id.
384
	 * @return string            Widget id including scheme/protocol.
385
	 */
386
	protected function get_widget_id_for_cache( $widget_id, $scheme = '' ) {
387
		if ( $scheme ) {
388
			$widget_id_for_cache = $widget_id . '-' . $scheme;
389
		} else {
390
			$widget_id_for_cache = $widget_id . '-' . ( is_ssl() ? 'https' : 'http' );
391
		}
392
393
		return apply_filters( 'woocommerce_cached_widget_id', $widget_id_for_cache );
394
	}
395
}
396