Completed
Push — master ( b569bd...1dcf6c )
by Mike
09:13
created

WC_Widget_Layered_Nav::widget()   F

Complexity

Conditions 18
Paths 809

Size

Total Lines 71
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 18
eloc 47
c 1
b 1
f 0
nc 809
nop 2
dl 0
loc 71
rs 2.8169

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * Layered Navigation Widget.
9
 *
10
 * @author   WooThemes
11
 * @category Widgets
12
 * @package  WooCommerce/Widgets
13
 * @version  2.6.0
14
 * @extends  WC_Widget
15
 */
16
class WC_Widget_Layered_Nav extends WC_Widget {
17
18
	/**
19
	 * Constructor.
20
	 */
21
	public function __construct() {
22
		$this->widget_cssclass    = 'woocommerce widget_layered_nav';
23
		$this->widget_description = __( 'Shows a custom attribute in a widget which lets you narrow down the list of products when viewing product categories.', 'woocommerce' );
24
		$this->widget_id          = 'woocommerce_layered_nav';
25
		$this->widget_name        = __( 'WooCommerce Layered Nav', 'woocommerce' );
26
		parent::__construct();
27
	}
28
29
	/**
30
	 * Updates a particular instance of a widget.
31
	 *
32
	 * @see WP_Widget->update
33
	 *
34
	 * @param array $new_instance
35
	 * @param array $old_instance
36
	 *
37
	 * @return array
38
	 */
39
	public function update( $new_instance, $old_instance ) {
40
		$this->init_settings();
41
		return parent::update( $new_instance, $old_instance );
42
	}
43
44
	/**
45
	 * Outputs the settings update form.
46
	 *
47
	 * @see WP_Widget->form
48
	 *
49
	 * @param array $instance
50
	 */
51
	public function form( $instance ) {
52
		$this->init_settings();
53
		parent::form( $instance );
54
	}
55
56
	/**
57
	 * Init settings after post types are registered.
58
	 */
59
	public function init_settings() {
60
		$attribute_array      = array();
61
		$attribute_taxonomies = wc_get_attribute_taxonomies();
62
63
		if ( ! empty( $attribute_taxonomies ) ) {
64
			foreach ( $attribute_taxonomies as $tax ) {
65
				if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
66
					$attribute_array[ $tax->attribute_name ] = $tax->attribute_name;
67
				}
68
			}
69
		}
70
71
		$this->settings = array(
72
			'title' => array(
73
				'type'  => 'text',
74
				'std'   => __( 'Filter by', 'woocommerce' ),
75
				'label' => __( 'Title', 'woocommerce' )
76
			),
77
			'attribute' => array(
78
				'type'    => 'select',
79
				'std'     => '',
80
				'label'   => __( 'Attribute', 'woocommerce' ),
81
				'options' => $attribute_array
82
			),
83
			'display_type' => array(
84
				'type'    => 'select',
85
				'std'     => 'list',
86
				'label'   => __( 'Display type', 'woocommerce' ),
87
				'options' => array(
88
					'list'     => __( 'List', 'woocommerce' ),
89
					'dropdown' => __( 'Dropdown', 'woocommerce' )
90
				)
91
			),
92
			'query_type' => array(
93
				'type'    => 'select',
94
				'std'     => 'and',
95
				'label'   => __( 'Query type', 'woocommerce' ),
96
				'options' => array(
97
					'and' => __( 'AND', 'woocommerce' ),
98
					'or'  => __( 'OR', 'woocommerce' )
99
				)
100
			),
101
		);
102
	}
103
104
	/**
105
	 * Output widget.
106
	 *
107
	 * @see WP_Widget
108
	 *
109
	 * @param array $args
110
	 * @param array $instance
111
	 */
112
	public function widget( $args, $instance ) {
113
		if ( ! is_post_type_archive( 'product' ) && ! is_tax( get_object_taxonomies( 'product' ) ) ) {
114
			return;
115
		}
116
117
		$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
118
		$taxonomy           = isset( $instance['attribute'] ) ? wc_attribute_taxonomy_name( $instance['attribute'] ) : $this->settings['attribute']['std'];
119
		$query_type         = isset( $instance['query_type'] ) ? $instance['query_type'] : $this->settings['query_type']['std'];
120
		$display_type       = isset( $instance['display_type'] ) ? $instance['display_type'] : $this->settings['display_type']['std'];
121
122
		if ( ! taxonomy_exists( $taxonomy ) ) {
123
			return;
124
		}
125
126
		$get_terms_args = array( 'hide_empty' => '1' );
127
128
		$orderby = wc_attribute_orderby( $taxonomy );
129
130
		switch ( $orderby ) {
131
			case 'name' :
132
				$get_terms_args['orderby']    = 'name';
133
				$get_terms_args['menu_order'] = false;
134
			break;
135
			case 'id' :
136
				$get_terms_args['orderby']    = 'id';
137
				$get_terms_args['order']      = 'ASC';
138
				$get_terms_args['menu_order'] = false;
139
			break;
140
			case 'menu_order' :
141
				$get_terms_args['menu_order'] = 'ASC';
142
			break;
143
		}
144
145
		$terms = get_terms( $taxonomy, $get_terms_args );
146
147
		if ( 0 === sizeof( $terms ) ) {
148
			return;
149
		}
150
151
		switch ( $orderby ) {
152
			case 'name_num' :
153
				usort( $terms, '_wc_get_product_terms_name_num_usort_callback' );
154
			break;
155
			case 'parent' :
156
				usort( $terms, '_wc_get_product_terms_parent_usort_callback' );
157
			break;
158
		}
159
160
		ob_start();
161
162
		$this->widget_start( $args, $instance );
163
164
		if ( 'dropdown' === $display_type ) {
165
			$found = $this->layered_nav_dropdown( $terms, $taxonomy, $query_type );
166
		} else {
167
			$found = $this->layered_nav_list( $terms, $taxonomy, $query_type );
168
		}
169
170
		$this->widget_end( $args );
171
172
		// Force found when option is selected - do not force found on taxonomy attributes
173
		if ( ! is_tax() && is_array( $_chosen_attributes ) && array_key_exists( $taxonomy, $_chosen_attributes ) ) {
174
			$found = true;
175
		}
176
177
		if ( ! $found ) {
178
			ob_end_clean();
179
		} else {
180
			echo ob_get_clean();
181
		}
182
	}
183
184
	/**
185
	 * Return the currently viewed taxonomy name.
186
	 * @return string
187
	 */
188
	protected function get_current_taxonomy() {
189
		return is_tax() ? get_queried_object()->taxonomy : '';
190
	}
191
192
	/**
193
	 * Return the currently viewed term ID.
194
	 * @return int
195
	 */
196
	protected function get_current_term_id() {
197
		return absint( is_tax() ? get_queried_object()->term_id : 0 );
198
	}
199
200
	/**
201
	 * Return the currently viewed term slug.
202
	 * @return int
203
	 */
204
	protected function get_current_term_slug() {
205
		return absint( is_tax() ? get_queried_object()->slug : 0 );
206
	}
207
208
	/**
209
	 * Show dropdown layered nav.
210
	 * @param  array $terms
211
	 * @param  string $taxonomy
212
	 * @param  string $query_type
213
	 * @return bool Will nav display?
214
	 */
215
	protected function layered_nav_dropdown( $terms, $taxonomy, $query_type ) {
216
		$found = false;
217
218
		if ( $taxonomy !== $this->get_current_taxonomy() ) {
219
			$term_counts          = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, $query_type );
220
			$_chosen_attributes   = WC_Query::get_layered_nav_chosen_attributes();
221
			$taxonomy_filter_name = str_replace( 'pa_', '', $taxonomy );
222
223
			echo '<select class="dropdown_layered_nav_' . esc_attr( $taxonomy_filter_name ) . '">';
224
			echo '<option value="">' . sprintf( __( 'Any %s', 'woocommerce' ), wc_attribute_label( $taxonomy ) ) . '</option>';
225
226
			foreach ( $terms as $term ) {
227
228
				// If on a term page, skip that term in widget list
229
				if ( $term->term_id === $this->get_current_term_id() ) {
230
					continue;
231
				}
232
233
				// Get count based on current view
234
				$current_values    = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array();
235
				$option_is_set     = in_array( $term->slug, $current_values );
236
				$count             = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
237
238
				// Only show options with count > 0
239 View Code Duplication
				if ( 0 < $count ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
240
					$found = true;
241
				} elseif ( 'and' === $query_type && 0 === $count && ! $option_is_set ) {
242
					continue;
243
				}
244
245
				echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( $option_is_set, true, false ) . '>' . esc_html( $term->name ) . '</option>';
246
			}
247
248
			echo '</select>';
249
250
			wc_enqueue_js( "
251
				jQuery( '.dropdown_layered_nav_". esc_js( $taxonomy_filter_name ) . "' ).change( function() {
252
					var slug = jQuery( this ).val();
253
					location.href = '" . preg_replace( '%\/page\/[0-9]+%', '', str_replace( array( '&amp;', '%2C' ), array( '&', ',' ), esc_js( add_query_arg( 'filtering', '1', remove_query_arg( array( 'page', 'filter_' . $taxonomy_filter_name ) ) ) ) ) ) . "&filter_". esc_js( $taxonomy_filter_name ) . "=' + slug;
254
				});
255
			" );
256
		}
257
258
		return $found;
259
	}
260
261
	/**
262
	 * Get current page URL for layered nav items.
263
	 * @return string
264
	 */
265
	protected function get_page_base_url( $taxonomy ) {
266 View Code Duplication
		if ( defined( 'SHOP_IS_ON_FRONT' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
267
			$link = home_url();
268
		} elseif ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id('shop') ) ) {
269
			$link = get_post_type_archive_link( 'product' );
270
		} else {
271
			$link = get_term_link( get_query_var('term'), get_query_var('taxonomy') );
272
		}
273
274
		// Min/Max
275
		if ( isset( $_GET['min_price'] ) ) {
276
			$link = add_query_arg( 'min_price', wc_clean( $_GET['min_price'] ), $link );
277
		}
278
279
		if ( isset( $_GET['max_price'] ) ) {
280
			$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link );
281
		}
282
283
		// Orderby
284 View Code Duplication
		if ( isset( $_GET['orderby'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
285
			$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link );
286
		}
287
288
		/**
289
		 * Search Arg.
290
		 * To support quote characters, first they are decoded from &quot; entities, then URL encoded.
291
		 */
292
		if ( get_search_query() ) {
293
			$link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
294
		}
295
296
		// Post Type Arg
297 View Code Duplication
		if ( isset( $_GET['post_type'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
298
			$link = add_query_arg( 'post_type', wc_clean( $_GET['post_type'] ), $link );
299
		}
300
301
		// Min Rating Arg
302
		if ( isset( $_GET['min_rating'] ) ) {
303
			$link = add_query_arg( 'min_rating', wc_clean( $_GET['min_rating'] ), $link );
304
		}
305
306
		// All current filters
307 View Code Duplication
		if ( $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
308
			foreach ( $_chosen_attributes as $name => $data ) {
309
				if ( $name === $taxonomy ) {
310
					continue;
311
				}
312
				$filter_name = sanitize_title( str_replace( 'pa_', '', $name ) );
313
				if ( ! empty( $data['terms'] ) ) {
314
					$link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
315
				}
316
				if ( 'or' == $data['query_type'] ) {
317
					$link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
318
				}
319
			}
320
		}
321
322
		return $link;
323
	}
324
325
	/**
326
	 * Count products within certain terms, taking the main WP query into consideration.
327
	 * @param  array $term_ids
328
	 * @param  string $taxonomy
329
	 * @param  string $query_type
330
	 * @return array
331
	 */
332
	protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
333
		global $wpdb;
334
335
		$tax_query  = WC_Query::get_main_tax_query();
336
		$meta_query = WC_Query::get_main_meta_query();
337
338
		if ( 'or' === $query_type ) {
339
			foreach ( $tax_query as $key => $query ) {
340
				if ( $taxonomy === $query['taxonomy'] ) {
341
					unset( $tax_query[ $key ] );
342
				}
343
			}
344
		}
345
346
		$meta_query     = new WP_Meta_Query( $meta_query );
347
		$tax_query      = new WP_Tax_Query( $tax_query );
348
		$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
349
		$tax_query_sql  = $tax_query->get_sql( $wpdb->posts, 'ID' );
350
351
		$sql  = "
352
			SELECT COUNT( {$wpdb->posts}.ID ) as term_count, term_count_relationships.term_taxonomy_id as term_count_id FROM {$wpdb->posts}
353
			INNER JOIN {$wpdb->term_relationships} AS term_count_relationships ON ({$wpdb->posts}.ID = term_count_relationships.object_id)
354
			" . $tax_query_sql['join'] . $meta_query_sql['join'] . "
355
			WHERE {$wpdb->posts}.post_type = 'product' AND {$wpdb->posts}.post_status = 'publish'
356
			" . $tax_query_sql['where'] . $meta_query_sql['where'] . "
357
			AND term_count_relationships.term_taxonomy_id IN (" . implode( ',', array_map( 'absint', $term_ids ) ) . ")
358
			GROUP BY term_count_relationships.term_taxonomy_id;
359
		";
360
361
		$results = $wpdb->get_results( $sql );
362
363
		return wp_list_pluck( $results, 'term_count', 'term_count_id' );
364
	}
365
366
	/**
367
	 * Show list based layered nav.
368
	 * @param  array $terms
369
	 * @param  string $taxonomy
370
	 * @param  string $query_type
371
	 * @return bool Will nav display?
372
	 */
373
	protected function layered_nav_list( $terms, $taxonomy, $query_type ) {
374
		// List display
375
		echo '<ul>';
376
377
		$term_counts        = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, $query_type );
378
		$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
379
		$found              = false;
380
381
		foreach ( $terms as $term ) {
382
			$current_values    = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array();
383
			$option_is_set     = in_array( $term->slug, $current_values );
384
			$count             = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
385
386
			// skip the term for the current archive
387
			if ( $this->get_current_term_id() === $term->term_id ) {
388
				continue;
389
			}
390
391
			// Only show options with count > 0
392 View Code Duplication
			if ( 0 < $count ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
393
				$found = true;
394
			} elseif ( 'and' === $query_type && 0 === $count && ! $option_is_set ) {
395
				continue;
396
			}
397
398
			$filter_name    = 'filter_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) );
399
			$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( $_GET[ $filter_name ] ) ) : array();
400
			$current_filter = array_map( 'sanitize_title', $current_filter );
401
402
			if ( ! in_array( $term->slug, $current_filter ) ) {
403
				$current_filter[] = $term->slug;
404
			}
405
406
			$link = $this->get_page_base_url( $taxonomy );
407
408
			// Add current filters to URL.
409
			foreach ( $current_filter as $key => $value ) {
410
				// Exclude query arg for current term archive term
411
				if ( $value === $this->get_current_term_slug() ) {
412
					unset( $current_filter[ $key ] );
413
				}
414
415
				// Exclude self so filter can be unset on click.
416
				if ( $option_is_set && $value === $term->slug ) {
417
					unset( $current_filter[ $key ] );
418
				}
419
			}
420
421
			if ( ! empty( $current_filter ) ) {
422
				$link = add_query_arg( $filter_name, implode( ',', $current_filter ), $link );
423
424
				// Add Query type Arg to URL
425
				if ( $query_type === 'or' && ! ( 1 === sizeof( $current_filter ) && $option_is_set ) ) {
426
					$link = add_query_arg( 'query_type_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) ), 'or', $link );
427
				}
428
			}
429
430
			echo '<li class="wc-layered-nav-term ' . ( $option_is_set ? 'chosen' : '' ) . '">';
431
432
			echo ( $count > 0 || $option_is_set ) ? '<a href="' . esc_url( apply_filters( 'woocommerce_layered_nav_link', $link ) ) . '">' : '<span>';
433
434
			echo esc_html( $term->name );
435
436
			echo ( $count > 0 || $option_is_set ) ? '</a>' : '</span>';
437
438
			echo ' <span class="count">(' . absint( $count ) . ')</span></li>';
439
		}
440
441
		echo '</ul>';
442
443
		return $found;
444
	}
445
}
446