This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
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( '&', '%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
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. ![]() |
|||
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
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. ![]() |
|||
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 " 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
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. ![]() |
|||
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
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. ![]() |
|||
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
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. ![]() |
|||
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 |
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.