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 ( $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
|
|
|
global $_chosen_attributes; |
114
|
|
|
|
115
|
|
|
if ( ! is_post_type_archive( 'product' ) && ! is_tax( get_object_taxonomies( 'product' ) ) ) { |
116
|
|
|
return; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
$taxonomy = isset( $instance['attribute'] ) ? wc_attribute_taxonomy_name( $instance['attribute'] ) : $this->settings['attribute']['std']; |
120
|
|
|
$query_type = isset( $instance['query_type'] ) ? $instance['query_type'] : $this->settings['query_type']['std']; |
121
|
|
|
$display_type = isset( $instance['display_type'] ) ? $instance['display_type'] : $this->settings['display_type']['std']; |
122
|
|
|
|
123
|
|
|
if ( ! taxonomy_exists( $taxonomy ) ) { |
124
|
|
|
return; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$get_terms_args = array( 'hide_empty' => '1' ); |
128
|
|
|
|
129
|
|
|
$orderby = wc_attribute_orderby( $taxonomy ); |
130
|
|
|
|
131
|
|
View Code Duplication |
switch ( $orderby ) { |
|
|
|
|
132
|
|
|
case 'name' : |
133
|
|
|
$get_terms_args['orderby'] = 'name'; |
134
|
|
|
$get_terms_args['menu_order'] = false; |
135
|
|
|
break; |
136
|
|
|
case 'id' : |
137
|
|
|
$get_terms_args['orderby'] = 'id'; |
138
|
|
|
$get_terms_args['order'] = 'ASC'; |
139
|
|
|
$get_terms_args['menu_order'] = false; |
140
|
|
|
break; |
141
|
|
|
case 'menu_order' : |
142
|
|
|
$get_terms_args['menu_order'] = 'ASC'; |
143
|
|
|
break; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
$terms = get_terms( $taxonomy, $get_terms_args ); |
147
|
|
|
|
148
|
|
|
if ( 0 === sizeof( $terms ) ) { |
149
|
|
|
return; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
ob_start(); |
153
|
|
|
|
154
|
|
|
$this->widget_start( $args, $instance ); |
155
|
|
|
|
156
|
|
|
if ( 'dropdown' === $display_type ) { |
157
|
|
|
$found = $this->layered_nav_dropdown( $terms, $taxonomy, $query_type ); |
158
|
|
|
} else { |
159
|
|
|
$found = $this->layered_nav_list( $terms, $taxonomy, $query_type ); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$this->widget_end( $args ); |
163
|
|
|
|
164
|
|
|
// Force found when option is selected - do not force found on taxonomy attributes |
165
|
|
|
if ( ! is_tax() && is_array( $_chosen_attributes ) && array_key_exists( $taxonomy, $_chosen_attributes ) ) { |
166
|
|
|
$found = true; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if ( ! $found ) { |
170
|
|
|
ob_end_clean(); |
171
|
|
|
} else { |
172
|
|
|
echo ob_get_clean(); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Return the currently viewed taxonomy name. |
178
|
|
|
* @return string |
179
|
|
|
*/ |
180
|
|
|
protected function get_current_taxonomy() { |
181
|
|
|
return is_tax() ? get_queried_object()->taxonomy : ''; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Return the currently viewed term ID. |
186
|
|
|
* @return int |
187
|
|
|
*/ |
188
|
|
|
protected function get_current_term_id() { |
189
|
|
|
return absint( is_tax() ? get_queried_object()->term_id : 0 ); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Return the currently viewed term slug. |
194
|
|
|
* @return int |
195
|
|
|
*/ |
196
|
|
|
protected function get_current_term_slug() { |
197
|
|
|
return absint( is_tax() ? get_queried_object()->slug : 0 ); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Show dropdown layered nav. |
202
|
|
|
* @param array $terms |
203
|
|
|
* @param string $taxonomy |
204
|
|
|
* @param string $query_type |
205
|
|
|
* @return bool Will nav display? |
206
|
|
|
*/ |
207
|
|
|
protected function layered_nav_dropdown( $terms, $taxonomy, $query_type ) { |
208
|
|
|
global $_chosen_attributes; |
209
|
|
|
|
210
|
|
|
$found = false; |
211
|
|
|
|
212
|
|
|
if ( $taxonomy !== $this->get_current_taxonomy() ) { |
213
|
|
|
$taxonomy_filter_name = str_replace( 'pa_', '', $taxonomy ); |
214
|
|
|
|
215
|
|
|
echo '<select class="dropdown_layered_nav_' . esc_attr( $taxonomy_filter_name ) . '">'; |
216
|
|
|
echo '<option value="">' . sprintf( __( 'Any %s', 'woocommerce' ), wc_attribute_label( $taxonomy ) ) . '</option>'; |
217
|
|
|
|
218
|
|
|
foreach ( $terms as $term ) { |
219
|
|
|
|
220
|
|
|
// If on a term page, skip that term in widget list |
221
|
|
|
if ( $term->term_id === $this->get_current_term_id() ) { |
222
|
|
|
continue; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
// Get count based on current view |
226
|
|
|
$_products_in_term = wc_get_term_product_ids( $term->term_id, $taxonomy ); |
227
|
|
|
$current_values = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array(); |
228
|
|
|
$option_is_set = in_array( $term->slug, $current_values ); |
229
|
|
|
|
230
|
|
|
// If this is an AND query, only show options with count > 0 |
231
|
|
|
if ( 'and' === $query_type ) { |
232
|
|
|
$count = sizeof( array_intersect( $_products_in_term, WC()->query->filtered_product_ids ) ); |
233
|
|
|
|
234
|
|
|
if ( 0 < $count ) { |
235
|
|
|
$found = true; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
if ( 0 === $count && ! $option_is_set ) { |
239
|
|
|
continue; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// If this is an OR query, show all options so search can be expanded |
243
|
|
|
} else { |
244
|
|
|
$count = sizeof( array_intersect( $_products_in_term, WC()->query->unfiltered_product_ids ) ); |
245
|
|
|
|
246
|
|
|
if ( 0 < $count ) { |
247
|
|
|
$found = true; |
248
|
|
|
} |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( $option_is_set, true, false ) . '>' . esc_html( $term->name ) . '</option>'; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
echo '</select>'; |
255
|
|
|
|
256
|
|
|
wc_enqueue_js( " |
257
|
|
|
jQuery( '.dropdown_layered_nav_". esc_js( $taxonomy_filter_name ) . "' ).change( function() { |
258
|
|
|
var slug = jQuery( this ).val(); |
259
|
|
|
location.href = '" . preg_replace( '%\/page\/[0-9]+%', '', str_replace( array( '&', '%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; |
260
|
|
|
}); |
261
|
|
|
" ); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
return $found; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Get current page URL for layered nav items. |
269
|
|
|
* @return string |
270
|
|
|
*/ |
271
|
|
|
protected function get_page_base_url() { |
272
|
|
|
if ( defined( 'SHOP_IS_ON_FRONT' ) ) { |
273
|
|
|
$link = home_url(); |
274
|
|
|
} elseif ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id('shop') ) ) { |
275
|
|
|
$link = get_post_type_archive_link( 'product' ); |
276
|
|
|
} else { |
277
|
|
|
$link = get_term_link( get_query_var('term'), get_query_var('taxonomy') ); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Min/Max |
281
|
|
|
if ( isset( $_GET['min_price'] ) ) { |
282
|
|
|
$link = add_query_arg( 'min_price', wc_clean( $_GET['min_price'] ), $link ); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
if ( isset( $_GET['max_price'] ) ) { |
286
|
|
|
$link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link ); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
// Orderby |
290
|
|
View Code Duplication |
if ( isset( $_GET['orderby'] ) ) { |
|
|
|
|
291
|
|
|
$link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link ); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// Search Arg |
295
|
|
|
if ( get_search_query() ) { |
296
|
|
|
$link = add_query_arg( 's', get_search_query(), $link ); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
// Post Type Arg |
300
|
|
View Code Duplication |
if ( isset( $_GET['post_type'] ) ) { |
|
|
|
|
301
|
|
|
$link = add_query_arg( 'post_type', wc_clean( $_GET['post_type'] ), $link ); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
return $link; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* Show list based layered nav. |
309
|
|
|
* @param array $terms |
310
|
|
|
* @param string $taxonomy |
311
|
|
|
* @param string $query_type |
312
|
|
|
* @return bool Will nav display? |
313
|
|
|
*/ |
314
|
|
|
protected function layered_nav_list( $terms, $taxonomy, $query_type ) { |
315
|
|
|
global $_chosen_attributes; |
316
|
|
|
|
317
|
|
|
// List display |
318
|
|
|
echo '<ul>'; |
319
|
|
|
|
320
|
|
|
// flip the filtered_products_ids array so that we can use the more efficient array_intersect_key |
321
|
|
|
$filtered_product_ids = array_flip( WC()->query->filtered_product_ids ); |
322
|
|
|
$unfiltered_product_ids = array_flip( WC()->query->unfiltered_product_ids ); |
323
|
|
|
$found = false; |
324
|
|
|
|
325
|
|
|
foreach ( $terms as $term ) { |
326
|
|
|
// Get count based on current view - uses transients |
327
|
|
|
// flip the product_in_term array so that we can use array_intersect_key |
328
|
|
|
$_products_in_term = array_flip( wc_get_term_product_ids( $term->term_id, $taxonomy ) ); |
329
|
|
|
$current_values = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array(); |
330
|
|
|
$option_is_set = in_array( $term->slug, $current_values ); |
331
|
|
|
|
332
|
|
|
// skip the term for the current archive |
333
|
|
|
if ( $this->get_current_term_id() === $term->term_id ) { |
334
|
|
|
continue; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
// If this is an AND query, only show options with count > 0 |
338
|
|
|
if ( 'and' === $query_type ) { |
339
|
|
|
// Intersect both arrays now they have been flipped so that we can use their keys |
340
|
|
|
$count = sizeof( array_intersect_key( $_products_in_term, $filtered_product_ids ) ); |
341
|
|
|
|
342
|
|
|
if ( 0 < $count ) { |
343
|
|
|
$found = true; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
if ( 0 === $count && ! $option_is_set ) { |
347
|
|
|
continue; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
// If this is an OR query, show all options so search can be expanded |
351
|
|
|
} else { |
352
|
|
|
// Intersect both arrays now they have been flipped so that we can use their keys |
353
|
|
|
$count = sizeof( array_intersect_key( $_products_in_term, $unfiltered_product_ids ) ); |
354
|
|
|
|
355
|
|
|
if ( 0 < $count ) { |
356
|
|
|
$found = true; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
$filter_name = 'filter_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) ); |
361
|
|
|
$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( $_GET[ $filter_name ] ) ) : array(); |
362
|
|
|
$current_filter = array_map( 'sanitize_title', $current_filter ); |
363
|
|
|
|
364
|
|
|
if ( ! in_array( $term->slug, $current_filter ) ) { |
365
|
|
|
$current_filter[] = $term->slug; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$link = $this->get_page_base_url(); |
369
|
|
|
|
370
|
|
|
// Add current filters to URL. |
371
|
|
|
foreach ( $current_filter as $key => $value ) { |
372
|
|
|
// Exclude query arg for current term archive term |
373
|
|
|
if ( $value === $this->get_current_term_slug() ) { |
374
|
|
|
unset( $current_filter[ $key ] ); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
// Exclude self so filter can be unset on click. |
378
|
|
|
if ( $option_is_set && $value === $term->slug ) { |
379
|
|
|
unset( $current_filter[ $key ] ); |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
if ( ! empty( $current_filter ) ) { |
384
|
|
|
$link = add_query_arg( $filter_name, implode( ',', $current_filter ), $link ); |
385
|
|
|
|
386
|
|
|
// Add Query type Arg to URL |
387
|
|
|
if ( $query_type === 'or' && ! ( 1 === sizeof( $current_filter ) && $option_is_set ) ) { |
388
|
|
|
$link = add_query_arg( 'query_type_' . sanitize_title( str_replace( 'pa_', '', $taxonomy ) ), 'or', $link ); |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
echo '<li class="wc-layered-nav-term ' . ( $option_is_set ? 'chosen' : '' ) . '">'; |
393
|
|
|
|
394
|
|
|
echo ( $count > 0 || $option_is_set ) ? '<a href="' . esc_url( apply_filters( 'woocommerce_layered_nav_link', $link ) ) . '">' : '<span>'; |
395
|
|
|
|
396
|
|
|
echo esc_html( $term->name ); |
397
|
|
|
|
398
|
|
|
echo ( $count > 0 || $option_is_set ) ? '</a>' : '</span>'; |
399
|
|
|
|
400
|
|
|
echo ' <span class="count">(' . absint( $count ) . ')</span></li>'; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
echo '</ul>'; |
404
|
|
|
|
405
|
|
|
return $found; |
406
|
|
|
} |
407
|
|
|
} |
408
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.