ArrayUtils.php
2 years ago
CartController.php
2 years ago
CheckoutTrait.php
2 years ago
DraftOrderTrait.php
2 years ago
JsonWebToken.php
2 years ago
LocalPickupUtils.php
2 years ago
NoticeHandler.php
2 years ago
OrderAuthorizationTrait.php
2 years ago
OrderController.php
2 years ago
Pagination.php
2 years ago
ProductItemTrait.php
2 years ago
ProductQuery.php
2 years ago
ProductQueryFilters.php
2 years ago
QuantityLimits.php
2 years ago
RateLimits.php
2 years ago
ValidationUtils.php
2 years ago
ProductQuery.php
548 lines
| 1 | <?php |
| 2 | namespace Automattic\WooCommerce\StoreApi\Utilities; |
| 3 | |
| 4 | use WC_Tax; |
| 5 | |
| 6 | /** |
| 7 | * Product Query class. |
| 8 | * |
| 9 | * Helper class to handle product queries for the API. |
| 10 | */ |
| 11 | class ProductQuery { |
| 12 | /** |
| 13 | * Prepare query args to pass to WP_Query for a REST API request. |
| 14 | * |
| 15 | * @param \WP_REST_Request $request Request data. |
| 16 | * @return array |
| 17 | */ |
| 18 | public function prepare_objects_query( $request ) { |
| 19 | $args = [ |
| 20 | 'offset' => $request['offset'], |
| 21 | 'order' => $request['order'], |
| 22 | 'orderby' => $request['orderby'], |
| 23 | 'paged' => $request['page'], |
| 24 | 'post__in' => $request['include'], |
| 25 | 'post__not_in' => $request['exclude'], |
| 26 | 'posts_per_page' => $request['per_page'] ? $request['per_page'] : -1, |
| 27 | 'post_parent__in' => $request['parent'], |
| 28 | 'post_parent__not_in' => $request['parent_exclude'], |
| 29 | 'search' => $request['search'], // This uses search rather than s intentionally to handle searches internally. |
| 30 | 'slug' => $request['slug'], |
| 31 | 'fields' => 'ids', |
| 32 | 'ignore_sticky_posts' => true, |
| 33 | 'post_status' => 'publish', |
| 34 | 'date_query' => [], |
| 35 | 'post_type' => 'product', |
| 36 | ]; |
| 37 | |
| 38 | // If searching for a specific SKU or slug, allow any post type. |
| 39 | if ( ! empty( $request['sku'] ) || ! empty( $request['slug'] ) ) { |
| 40 | $args['post_type'] = [ 'product', 'product_variation' ]; |
| 41 | } |
| 42 | |
| 43 | // Taxonomy query to filter products by type, category, tag, shipping class, and attribute. |
| 44 | $tax_query = []; |
| 45 | |
| 46 | // Filter product type by slug. |
| 47 | if ( ! empty( $request['type'] ) ) { |
| 48 | if ( 'variation' === $request['type'] ) { |
| 49 | $args['post_type'] = 'product_variation'; |
| 50 | } else { |
| 51 | $args['post_type'] = 'product'; |
| 52 | $tax_query[] = [ |
| 53 | 'taxonomy' => 'product_type', |
| 54 | 'field' => 'slug', |
| 55 | 'terms' => $request['type'], |
| 56 | ]; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | if ( 'date' === $args['orderby'] ) { |
| 61 | $args['orderby'] = 'date ID'; |
| 62 | } |
| 63 | |
| 64 | // Set before into date query. Date query must be specified as an array of an array. |
| 65 | if ( isset( $request['before'] ) ) { |
| 66 | $args['date_query'][0]['before'] = $request['before']; |
| 67 | } |
| 68 | |
| 69 | // Set after into date query. Date query must be specified as an array of an array. |
| 70 | if ( isset( $request['after'] ) ) { |
| 71 | $args['date_query'][0]['after'] = $request['after']; |
| 72 | } |
| 73 | |
| 74 | // Set date query column. Defaults to post_date. |
| 75 | if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) { |
| 76 | $args['date_query'][0]['column'] = 'post_' . $request['date_column']; |
| 77 | } |
| 78 | |
| 79 | // Set custom args to handle later during clauses. |
| 80 | $custom_keys = [ |
| 81 | 'sku', |
| 82 | 'min_price', |
| 83 | 'max_price', |
| 84 | 'stock_status', |
| 85 | ]; |
| 86 | |
| 87 | foreach ( $custom_keys as $key ) { |
| 88 | if ( ! empty( $request[ $key ] ) ) { |
| 89 | $args[ $key ] = $request[ $key ]; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | $operator_mapping = [ |
| 94 | 'in' => 'IN', |
| 95 | 'not_in' => 'NOT IN', |
| 96 | 'and' => 'AND', |
| 97 | ]; |
| 98 | |
| 99 | // Gets all registered product taxonomies and prefixes them with `tax_`. |
| 100 | // This is needed to avoid situations where a user registers a new product taxonomy with the same name as default field. |
| 101 | // eg an `sku` taxonomy will be mapped to `tax_sku`. |
| 102 | $all_product_taxonomies = array_map( |
| 103 | function ( $value ) { |
| 104 | return '_unstable_tax_' . $value; |
| 105 | }, |
| 106 | get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ) |
| 107 | ); |
| 108 | |
| 109 | // Map between taxonomy name and arg key. |
| 110 | $default_taxonomies = [ |
| 111 | 'product_cat' => 'category', |
| 112 | 'product_tag' => 'tag', |
| 113 | ]; |
| 114 | |
| 115 | $taxonomies = array_merge( $all_product_taxonomies, $default_taxonomies ); |
| 116 | |
| 117 | // Set tax_query for each passed arg. |
| 118 | foreach ( $taxonomies as $taxonomy => $key ) { |
| 119 | if ( ! empty( $request[ $key ] ) ) { |
| 120 | $operator = $request->get_param( $key . '_operator' ) && isset( $operator_mapping[ $request->get_param( $key . '_operator' ) ] ) ? $operator_mapping[ $request->get_param( $key . '_operator' ) ] : 'IN'; |
| 121 | $tax_query[] = [ |
| 122 | 'taxonomy' => $taxonomy, |
| 123 | 'field' => 'term_id', |
| 124 | 'terms' => $request[ $key ], |
| 125 | 'operator' => $operator, |
| 126 | ]; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // Filter by attributes. |
| 131 | if ( ! empty( $request['attributes'] ) ) { |
| 132 | $att_queries = []; |
| 133 | |
| 134 | foreach ( $request['attributes'] as $attribute ) { |
| 135 | if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { |
| 136 | continue; |
| 137 | } |
| 138 | if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { |
| 139 | $operator = isset( $attribute['operator'], $operator_mapping[ $attribute['operator'] ] ) ? $operator_mapping[ $attribute['operator'] ] : 'IN'; |
| 140 | $att_queries[] = [ |
| 141 | 'taxonomy' => $attribute['attribute'], |
| 142 | 'field' => ! empty( $attribute['term_id'] ) ? 'term_id' : 'slug', |
| 143 | 'terms' => ! empty( $attribute['term_id'] ) ? $attribute['term_id'] : $attribute['slug'], |
| 144 | 'operator' => $operator, |
| 145 | ]; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | if ( 1 < count( $att_queries ) ) { |
| 150 | // Add relation arg when using multiple attributes. |
| 151 | $relation = $request->get_param( 'attribute_relation' ) && isset( $operator_mapping[ $request->get_param( 'attribute_relation' ) ] ) ? $operator_mapping[ $request->get_param( 'attribute_relation' ) ] : 'IN'; |
| 152 | $tax_query[] = [ |
| 153 | 'relation' => $relation, |
| 154 | $att_queries, |
| 155 | ]; |
| 156 | } else { |
| 157 | $tax_query = array_merge( $tax_query, $att_queries ); |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | // Build tax_query if taxonomies are set. |
| 162 | if ( ! empty( $tax_query ) && 'product_variation' !== $args['post_type'] ) { |
| 163 | if ( ! empty( $args['tax_query'] ) ) { |
| 164 | $args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // phpcs:ignore |
| 165 | } else { |
| 166 | $args['tax_query'] = $tax_query; // phpcs:ignore |
| 167 | } |
| 168 | } else { |
| 169 | // For product_variantions we need to convert the tax_query to a meta_query. |
| 170 | if ( ! empty( $args['tax_query'] ) ) { |
| 171 | $args['meta_query'] = $this->convert_tax_query_to_meta_query( array_merge( $tax_query, $args['tax_query'] ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query |
| 172 | } else { |
| 173 | $args['meta_query'] = $this->convert_tax_query_to_meta_query( $tax_query ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // Filter featured. |
| 178 | if ( is_bool( $request['featured'] ) ) { |
| 179 | $args['tax_query'][] = [ |
| 180 | 'taxonomy' => 'product_visibility', |
| 181 | 'field' => 'name', |
| 182 | 'terms' => 'featured', |
| 183 | 'operator' => true === $request['featured'] ? 'IN' : 'NOT IN', |
| 184 | ]; |
| 185 | } |
| 186 | |
| 187 | // Filter by on sale products. |
| 188 | if ( is_bool( $request['on_sale'] ) ) { |
| 189 | $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; |
| 190 | $on_sale_ids = wc_get_product_ids_on_sale(); |
| 191 | |
| 192 | // Use 0 when there's no on sale products to avoid return all products. |
| 193 | $on_sale_ids = empty( $on_sale_ids ) ? [ 0 ] : $on_sale_ids; |
| 194 | |
| 195 | $args[ $on_sale_key ] += $on_sale_ids; |
| 196 | } |
| 197 | |
| 198 | $catalog_visibility = $request->get_param( 'catalog_visibility' ); |
| 199 | $rating = $request->get_param( 'rating' ); |
| 200 | $visibility_options = wc_get_product_visibility_options(); |
| 201 | |
| 202 | if ( in_array( $catalog_visibility, array_keys( $visibility_options ), true ) ) { |
| 203 | $exclude_from_catalog = 'search' === $catalog_visibility ? '' : 'exclude-from-catalog'; |
| 204 | $exclude_from_search = 'catalog' === $catalog_visibility ? '' : 'exclude-from-search'; |
| 205 | |
| 206 | $args['tax_query'][] = [ |
| 207 | 'taxonomy' => 'product_visibility', |
| 208 | 'field' => 'name', |
| 209 | 'terms' => [ $exclude_from_catalog, $exclude_from_search ], |
| 210 | 'operator' => 'hidden' === $catalog_visibility ? 'AND' : 'NOT IN', |
| 211 | 'rating_filter' => true, |
| 212 | ]; |
| 213 | } |
| 214 | |
| 215 | if ( $rating ) { |
| 216 | $rating_terms = []; |
| 217 | foreach ( $rating as $value ) { |
| 218 | $rating_terms[] = 'rated-' . $value; |
| 219 | } |
| 220 | $args['tax_query'][] = [ |
| 221 | 'taxonomy' => 'product_visibility', |
| 222 | 'field' => 'name', |
| 223 | 'terms' => $rating_terms, |
| 224 | ]; |
| 225 | } |
| 226 | |
| 227 | $orderby = $request->get_param( 'orderby' ); |
| 228 | $order = $request->get_param( 'order' ); |
| 229 | |
| 230 | $ordering_args = wc()->query->get_catalog_ordering_args( $orderby, $order ); |
| 231 | $args['orderby'] = $ordering_args['orderby']; |
| 232 | $args['order'] = $ordering_args['order']; |
| 233 | |
| 234 | if ( 'include' === $orderby ) { |
| 235 | $args['orderby'] = 'post__in'; |
| 236 | } elseif ( 'id' === $orderby ) { |
| 237 | $args['orderby'] = 'ID'; // ID must be capitalized. |
| 238 | } elseif ( 'slug' === $orderby ) { |
| 239 | $args['orderby'] = 'name'; |
| 240 | } |
| 241 | |
| 242 | if ( $ordering_args['meta_key'] ) { |
| 243 | $args['meta_key'] = $ordering_args['meta_key']; // phpcs:ignore |
| 244 | } |
| 245 | |
| 246 | return $args; |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Convert the tax_query to a meta_query which is needed to support filtering by attributes for variations. |
| 251 | * |
| 252 | * @param array $tax_query The tax_query to convert. |
| 253 | * @return array |
| 254 | */ |
| 255 | public function convert_tax_query_to_meta_query( $tax_query ) { |
| 256 | $meta_query = array(); |
| 257 | |
| 258 | foreach ( $tax_query as $tax_query_item ) { |
| 259 | $taxonomy = $tax_query_item['taxonomy']; |
| 260 | $terms = $tax_query_item['terms']; |
| 261 | |
| 262 | $meta_key = 'attribute_' . $taxonomy; |
| 263 | |
| 264 | $meta_query[] = array( |
| 265 | 'key' => $meta_key, |
| 266 | 'value' => $terms, |
| 267 | ); |
| 268 | |
| 269 | if ( isset( $tax_query_item['operator'] ) ) { |
| 270 | $meta_query[0]['compare'] = $tax_query_item['operator']; |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | return $meta_query; |
| 275 | } |
| 276 | |
| 277 | /** |
| 278 | * Get results of query. |
| 279 | * |
| 280 | * @param \WP_REST_Request $request Request data. |
| 281 | * @return array |
| 282 | */ |
| 283 | public function get_results( $request ) { |
| 284 | $query_args = $this->prepare_objects_query( $request ); |
| 285 | |
| 286 | add_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10, 2 ); |
| 287 | |
| 288 | $query = new \WP_Query(); |
| 289 | $results = $query->query( $query_args ); |
| 290 | $total_posts = $query->found_posts; |
| 291 | |
| 292 | // Out-of-bounds, run the query again without LIMIT for total count. |
| 293 | if ( $total_posts < 1 && $query_args['paged'] > 1 ) { |
| 294 | unset( $query_args['paged'] ); |
| 295 | $count_query = new \WP_Query(); |
| 296 | $count_query->query( $query_args ); |
| 297 | $total_posts = $count_query->found_posts; |
| 298 | } |
| 299 | |
| 300 | remove_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10 ); |
| 301 | |
| 302 | return [ |
| 303 | 'results' => $results, |
| 304 | 'total' => (int) $total_posts, |
| 305 | 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ) : 1, |
| 306 | ]; |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Get objects. |
| 311 | * |
| 312 | * @param \WP_REST_Request $request Request data. |
| 313 | * @return array |
| 314 | */ |
| 315 | public function get_objects( $request ) { |
| 316 | $results = $this->get_results( $request ); |
| 317 | |
| 318 | return [ |
| 319 | 'objects' => array_map( 'wc_get_product', $results['results'] ), |
| 320 | 'total' => $results['total'], |
| 321 | 'pages' => $results['pages'], |
| 322 | ]; |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * Get last modified date for all products. |
| 327 | * |
| 328 | * @return int timestamp. |
| 329 | */ |
| 330 | public function get_last_modified() { |
| 331 | global $wpdb; |
| 332 | |
| 333 | return strtotime( $wpdb->get_var( "SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );" ) ); |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Add in conditional search filters for products. |
| 338 | * |
| 339 | * @param array $args Query args. |
| 340 | * @param \WC_Query $wp_query WC_Query object. |
| 341 | * @return array |
| 342 | */ |
| 343 | public function add_query_clauses( $args, $wp_query ) { |
| 344 | global $wpdb; |
| 345 | |
| 346 | if ( $wp_query->get( 'search' ) ) { |
| 347 | $search = '%' . $wpdb->esc_like( $wp_query->get( 'search' ) ) . '%'; |
| 348 | $search_query = wc_product_sku_enabled() |
| 349 | ? $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search ) |
| 350 | : $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", $search ); |
| 351 | $args['where'] .= $search_query; |
| 352 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 353 | } |
| 354 | |
| 355 | if ( $wp_query->get( 'sku' ) ) { |
| 356 | $skus = explode( ',', $wp_query->get( 'sku' ) ); |
| 357 | // Include the current string as a SKU too. |
| 358 | if ( 1 < count( $skus ) ) { |
| 359 | $skus[] = $wp_query->get( 'sku' ); |
| 360 | } |
| 361 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 362 | $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode( '","', array_map( 'esc_sql', $skus ) ) . '")'; |
| 363 | } |
| 364 | |
| 365 | if ( $wp_query->get( 'slug' ) ) { |
| 366 | $slugs = explode( ',', $wp_query->get( 'slug' ) ); |
| 367 | // Include the current string as a slug too. |
| 368 | if ( 1 < count( $slugs ) ) { |
| 369 | $slugs[] = $wp_query->get( 'slug' ); |
| 370 | } |
| 371 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 372 | $post_name__in = implode( '","', array_map( 'esc_sql', $slugs ) ); |
| 373 | $args['where'] .= " AND $wpdb->posts.post_name IN (\"$post_name__in\")"; |
| 374 | } |
| 375 | |
| 376 | if ( $wp_query->get( 'stock_status' ) ) { |
| 377 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 378 | $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; |
| 379 | } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { |
| 380 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 381 | $args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")'; |
| 382 | } |
| 383 | |
| 384 | if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { |
| 385 | $args = $this->add_price_filter_clauses( $args, $wp_query ); |
| 386 | } |
| 387 | |
| 388 | return $args; |
| 389 | } |
| 390 | |
| 391 | /** |
| 392 | * Add in conditional price filters. |
| 393 | * |
| 394 | * @param array $args Query args. |
| 395 | * @param \WC_Query $wp_query WC_Query object. |
| 396 | * @return array |
| 397 | */ |
| 398 | protected function add_price_filter_clauses( $args, $wp_query ) { |
| 399 | global $wpdb; |
| 400 | |
| 401 | $adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes(); |
| 402 | $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
| 403 | |
| 404 | if ( $wp_query->get( 'min_price' ) ) { |
| 405 | $min_price_filter = $this->prepare_price_filter( $wp_query->get( 'min_price' ) ); |
| 406 | |
| 407 | if ( $adjust_for_taxes ) { |
| 408 | $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'min_price', '>=' ); |
| 409 | } else { |
| 410 | $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter ); |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | if ( $wp_query->get( 'max_price' ) ) { |
| 415 | $max_price_filter = $this->prepare_price_filter( $wp_query->get( 'max_price' ) ); |
| 416 | |
| 417 | if ( $adjust_for_taxes ) { |
| 418 | $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'max_price', '<=' ); |
| 419 | } else { |
| 420 | $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter ); |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | return $args; |
| 425 | } |
| 426 | |
| 427 | /** |
| 428 | * Get query for price filters when dealing with displayed taxes. |
| 429 | * |
| 430 | * @param float $price_filter Price filter to apply. |
| 431 | * @param string $column Price being filtered (min or max). |
| 432 | * @param string $operator Comparison operator for column. |
| 433 | * @return string Constructed query. |
| 434 | */ |
| 435 | protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) { |
| 436 | global $wpdb; |
| 437 | |
| 438 | // Select only used tax classes to avoid unwanted calculations. |
| 439 | $product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ); |
| 440 | |
| 441 | if ( empty( $product_tax_classes ) ) { |
| 442 | return ''; |
| 443 | } |
| 444 | |
| 445 | $or_queries = []; |
| 446 | |
| 447 | // We need to adjust the filter for each possible tax class and combine the queries into one. |
| 448 | foreach ( $product_tax_classes as $tax_class ) { |
| 449 | $adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class ); |
| 450 | $or_queries[] = $wpdb->prepare( |
| 451 | '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )', |
| 452 | $tax_class, |
| 453 | $adjusted_price_filter |
| 454 | ); |
| 455 | } |
| 456 | |
| 457 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
| 458 | return $wpdb->prepare( |
| 459 | ' AND ( |
| 460 | wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ') |
| 461 | OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f ) |
| 462 | ) ', |
| 463 | $price_filter |
| 464 | ); |
| 465 | // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
| 466 | } |
| 467 | |
| 468 | /** |
| 469 | * If price filters need adjustment to work with displayed taxes, this returns true. |
| 470 | * |
| 471 | * This logic is used when prices are stored in the database differently to how they are being displayed, with regards |
| 472 | * to taxes. |
| 473 | * |
| 474 | * @return boolean |
| 475 | */ |
| 476 | protected function adjust_price_filters_for_displayed_taxes() { |
| 477 | $display = get_option( 'woocommerce_tax_display_shop' ); |
| 478 | $database = wc_prices_include_tax() ? 'incl' : 'excl'; |
| 479 | |
| 480 | return $display !== $database; |
| 481 | } |
| 482 | |
| 483 | /** |
| 484 | * Converts price filter from subunits to decimal. |
| 485 | * |
| 486 | * @param string|int $price_filter Raw price filter in subunit format. |
| 487 | * @return float Price filter in decimal format. |
| 488 | */ |
| 489 | protected function prepare_price_filter( $price_filter ) { |
| 490 | return floatval( $price_filter / ( 10 ** wc_get_price_decimals() ) ); |
| 491 | } |
| 492 | |
| 493 | /** |
| 494 | * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes. |
| 495 | * |
| 496 | * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core. |
| 497 | * |
| 498 | * @param float $price_filter Price filter amount as entered. |
| 499 | * @param string $tax_class Tax class for adjustment. |
| 500 | * @return float |
| 501 | */ |
| 502 | protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) { |
| 503 | $tax_display = get_option( 'woocommerce_tax_display_shop' ); |
| 504 | $tax_rates = WC_Tax::get_rates( $tax_class ); |
| 505 | $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); |
| 506 | |
| 507 | // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. |
| 508 | if ( 'incl' === $tax_display ) { |
| 509 | /** |
| 510 | * Filters if taxes should be removed from locations outside the store base location. |
| 511 | * |
| 512 | * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing |
| 513 | * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10 |
| 514 | * regardless of location and taxes. |
| 515 | * |
| 516 | * @since 2.6.0 |
| 517 | * |
| 518 | * @internal Matches filter name in WooCommerce core. |
| 519 | * |
| 520 | * @param boolean $adjust_non_base_location_prices True by default. |
| 521 | * @return boolean |
| 522 | */ |
| 523 | $taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $price_filter, $base_tax_rates, true ) : WC_Tax::calc_tax( $price_filter, $tax_rates, true ); |
| 524 | return $price_filter - array_sum( $taxes ); |
| 525 | } |
| 526 | |
| 527 | // If prices are shown excl. tax, add taxes to match the prices stored in the DB. |
| 528 | $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); |
| 529 | |
| 530 | return $price_filter + array_sum( $taxes ); |
| 531 | } |
| 532 | |
| 533 | /** |
| 534 | * Join wc_product_meta_lookup to posts if not already joined. |
| 535 | * |
| 536 | * @param string $sql SQL join. |
| 537 | * @return string |
| 538 | */ |
| 539 | protected function append_product_sorting_table_join( $sql ) { |
| 540 | global $wpdb; |
| 541 | |
| 542 | if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { |
| 543 | $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; |
| 544 | } |
| 545 | return $sql; |
| 546 | } |
| 547 | } |
| 548 |