WC_Widget_Layered_Nav::layered_nav_dropdown()   D
last analyzed

Complexity

Conditions 10
Paths 2

Size

Total Lines 45
Code Lines 24

Duplication

Lines 5
Ratio 11.11 %

Importance

Changes 0
Metric Value
cc 10
dl 5
loc 45
rs 4.8196
c 0
b 0
f 0
eloc 24
nc 2
nop 3

How to fix   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
		ob_start();
152
153
		$this->widget_start( $args, $instance );
154
155
		if ( 'dropdown' === $display_type ) {
156
			$found = $this->layered_nav_dropdown( $terms, $taxonomy, $query_type );
157
		} else {
158
			$found = $this->layered_nav_list( $terms, $taxonomy, $query_type );
159
		}
160
161
		$this->widget_end( $args );
162
163
		// Force found when option is selected - do not force found on taxonomy attributes
164
		if ( ! is_tax() && is_array( $_chosen_attributes ) && array_key_exists( $taxonomy, $_chosen_attributes ) ) {
165
			$found = true;
166
		}
167
168
		if ( ! $found ) {
169
			ob_end_clean();
170
		} else {
171
			echo ob_get_clean();
172
		}
173
	}
174
175
	/**
176
	 * Return the currently viewed taxonomy name.
177
	 * @return string
178
	 */
179
	protected function get_current_taxonomy() {
180
		return is_tax() ? get_queried_object()->taxonomy : '';
181
	}
182
183
	/**
184
	 * Return the currently viewed term ID.
185
	 * @return int
186
	 */
187
	protected function get_current_term_id() {
188
		return absint( is_tax() ? get_queried_object()->term_id : 0 );
189
	}
190
191
	/**
192
	 * Return the currently viewed term slug.
193
	 * @return int
194
	 */
195
	protected function get_current_term_slug() {
196
		return absint( is_tax() ? get_queried_object()->slug : 0 );
197
	}
198
199
	/**
200
	 * Show dropdown layered nav.
201
	 * @param  array $terms
202
	 * @param  string $taxonomy
203
	 * @param  string $query_type
204
	 * @return bool Will nav display?
205
	 */
206
	protected function layered_nav_dropdown( $terms, $taxonomy, $query_type ) {
207
		$found = false;
208
209
		if ( $taxonomy !== $this->get_current_taxonomy() ) {
210
			$term_counts          = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, $query_type );
211
			$_chosen_attributes   = WC_Query::get_layered_nav_chosen_attributes();
212
			$taxonomy_filter_name = str_replace( 'pa_', '', $taxonomy );
213
214
			echo '<select class="dropdown_layered_nav_' . esc_attr( $taxonomy_filter_name ) . '">';
215
			echo '<option value="">' . sprintf( __( 'Any %s', 'woocommerce' ), wc_attribute_label( $taxonomy ) ) . '</option>';
216
217
			foreach ( $terms as $term ) {
218
219
				// If on a term page, skip that term in widget list
220
				if ( $term->term_id === $this->get_current_term_id() ) {
221
					continue;
222
				}
223
224
				// Get count based on current view
225
				$current_values    = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array();
226
				$option_is_set     = in_array( $term->slug, $current_values );
227
				$count             = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
228
229
				// Only show options with count > 0
230 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...
231
					$found = true;
232
				} elseif ( 'and' === $query_type && 0 === $count && ! $option_is_set ) {
233
					continue;
234
				}
235
236
				echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( $option_is_set, true, false ) . '>' . esc_html( $term->name ) . '</option>';
237
			}
238
239
			echo '</select>';
240
241
			wc_enqueue_js( "
242
				jQuery( '.dropdown_layered_nav_". esc_js( $taxonomy_filter_name ) . "' ).change( function() {
243
					var slug = jQuery( this ).val();
244
					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;
245
				});
246
			" );
247
		}
248
249
		return $found;
250
	}
251
252
	/**
253
	 * Get current page URL for layered nav items.
254
	 * @return string
255
	 */
256
	protected function get_page_base_url( $taxonomy ) {
257 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...
258
			$link = home_url();
259
		} elseif ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id('shop') ) ) {
260
			$link = get_post_type_archive_link( 'product' );
261
		} else {
262
			$link = get_term_link( get_query_var('term'), get_query_var('taxonomy') );
263
		}
264
265
		// Min/Max
266
		if ( isset( $_GET['min_price'] ) ) {
267
			$link = add_query_arg( 'min_price', wc_clean( $_GET['min_price'] ), $link );
268
		}
269
270
		if ( isset( $_GET['max_price'] ) ) {
271
			$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link );
272
		}
273
274
		// Orderby
275 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...
276
			$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link );
277
		}
278
279
		/**
280
		 * Search Arg.
281
		 * To support quote characters, first they are decoded from &quot; entities, then URL encoded.
282
		 */
283
		if ( get_search_query() ) {
284
			$link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
285
		}
286
287
		// Post Type Arg
288
		if ( isset( $_GET['post_type'] ) ) {
289
			$link = add_query_arg( 'post_type', wc_clean( $_GET['post_type'] ), $link );
290
		}
291
292
		// Min Rating Arg
293 View Code Duplication
		if ( isset( $_GET['min_rating'] ) ) {
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...
294
			$link = add_query_arg( 'min_rating', wc_clean( $_GET['min_rating'] ), $link );
295
		}
296
297
		// All current filters
298 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...
299
			foreach ( $_chosen_attributes as $name => $data ) {
300
				if ( $name === $taxonomy ) {
301
					continue;
302
				}
303
				$filter_name = sanitize_title( str_replace( 'pa_', '', $name ) );
304
				if ( ! empty( $data['terms'] ) ) {
305
					$link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
306
				}
307
				if ( 'or' == $data['query_type'] ) {
308
					$link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
309
				}
310
			}
311
		}
312
313
		return $link;
314
	}
315
316
	/**
317
	 * Count products within certain terms, taking the main WP query into consideration.
318
	 * @param  array $term_ids
319
	 * @param  string $taxonomy
320
	 * @param  string $query_type
321
	 * @return array
322
	 */
323
	protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
324
		global $wpdb;
325
326
		$tax_query  = WC_Query::get_main_tax_query();
327
		$meta_query = WC_Query::get_main_meta_query();
328
329
		if ( 'or' === $query_type ) {
330
			foreach ( $tax_query as $key => $query ) {
331
				if ( $taxonomy === $query['taxonomy'] ) {
332
					unset( $tax_query[ $key ] );
333
				}
334
			}
335
		}
336
337
		$meta_query     = new WP_Meta_Query( $meta_query );
338
		$tax_query      = new WP_Tax_Query( $tax_query );
339
		$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
340
		$tax_query_sql  = $tax_query->get_sql( $wpdb->posts, 'ID' );
341
342
		$sql  = "
343
			SELECT COUNT( {$wpdb->posts}.ID ) as term_count, term_count_relationships.term_taxonomy_id as term_count_id FROM {$wpdb->posts}
344
			INNER JOIN {$wpdb->term_relationships} AS term_count_relationships ON ({$wpdb->posts}.ID = term_count_relationships.object_id)
345
			" . $tax_query_sql['join'] . $meta_query_sql['join'] . "
346
			WHERE {$wpdb->posts}.post_type = 'product' AND {$wpdb->posts}.post_status = 'publish'
347
			" . $tax_query_sql['where'] . $meta_query_sql['where'] . "
348
			AND term_count_relationships.term_taxonomy_id IN (" . implode( ',', array_map( 'absint', $term_ids ) ) . ")
349
			GROUP BY term_count_relationships.term_taxonomy_id;
350
		";
351
352
		$results = $wpdb->get_results( $sql );
353
354
		return wp_list_pluck( $results, 'term_count', 'term_count_id' );
355
	}
356
357
	/**
358
	 * Show list based layered nav.
359
	 * @param  array $terms
360
	 * @param  string $taxonomy
361
	 * @param  string $query_type
362
	 * @return bool Will nav display?
363
	 */
364
	protected function layered_nav_list( $terms, $taxonomy, $query_type ) {
365
		// List display
366
		echo '<ul>';
367
368
		$term_counts        = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, $query_type );
369
		$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
370
		$found              = false;
371
372
		foreach ( $terms as $term ) {
373
			$current_values    = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array();
374
			$option_is_set     = in_array( $term->slug, $current_values );
375
			$count             = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0;
376
377
			// skip the term for the current archive
378
			if ( $this->get_current_term_id() === $term->term_id ) {
379
				continue;
380
			}
381
382
			// Only show options with count > 0
383 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...
384
				$found = true;
385
			} elseif ( 'and' === $query_type && 0 === $count && ! $option_is_set ) {
386
				continue;
387
			}
388
389
			$filter_name    = 'filter_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) );
390
			$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( $_GET[ $filter_name ] ) ) : array();
391
			$current_filter = array_map( 'sanitize_title', $current_filter );
392
393
			if ( ! in_array( $term->slug, $current_filter ) ) {
394
				$current_filter[] = $term->slug;
395
			}
396
397
			$link = $this->get_page_base_url( $taxonomy );
398
399
			// Add current filters to URL.
400
			foreach ( $current_filter as $key => $value ) {
401
				// Exclude query arg for current term archive term
402
				if ( $value === $this->get_current_term_slug() ) {
403
					unset( $current_filter[ $key ] );
404
				}
405
406
				// Exclude self so filter can be unset on click.
407
				if ( $option_is_set && $value === $term->slug ) {
408
					unset( $current_filter[ $key ] );
409
				}
410
			}
411
412
			if ( ! empty( $current_filter ) ) {
413
				$link = add_query_arg( $filter_name, implode( ',', $current_filter ), $link );
414
415
				// Add Query type Arg to URL
416
				if ( $query_type === 'or' && ! ( 1 === sizeof( $current_filter ) && $option_is_set ) ) {
417
					$link = add_query_arg( 'query_type_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) ), 'or', $link );
418
				}
419
			}
420
421
			echo '<li class="wc-layered-nav-term ' . ( $option_is_set ? 'chosen' : '' ) . '">';
422
423
			echo ( $count > 0 || $option_is_set ) ? '<a href="' . esc_url( apply_filters( 'woocommerce_layered_nav_link', $link ) ) . '">' : '<span>';
424
425
			echo esc_html( $term->name );
426
427
			echo ( $count > 0 || $option_is_set ) ? '</a>' : '</span>';
428
429
			echo ' <span class="count">(' . absint( $count ) . ')</span></li>';
430
		}
431
432
		echo '</ul>';
433
434
		return $found;
435
	}
436
}
437