@@ -6,66 +6,66 @@ |
||
| 6 | 6 | */ |
| 7 | 7 | class Pagination { |
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * Add pagination headers to a response object. |
|
| 11 | - * |
|
| 12 | - * @param \WP_REST_Response $response Reference to the response object. |
|
| 13 | - * @param \WP_REST_Request $request The request object. |
|
| 14 | - * @param int $total_items Total items found. |
|
| 15 | - * @param int $total_pages Total pages found. |
|
| 16 | - * @return \WP_REST_Response |
|
| 17 | - */ |
|
| 18 | - public function add_headers( $response, $request, $total_items, $total_pages ) { |
|
| 19 | - $response->header( 'X-WP-Total', $total_items ); |
|
| 20 | - $response->header( 'X-WP-TotalPages', $total_pages ); |
|
| 9 | + /** |
|
| 10 | + * Add pagination headers to a response object. |
|
| 11 | + * |
|
| 12 | + * @param \WP_REST_Response $response Reference to the response object. |
|
| 13 | + * @param \WP_REST_Request $request The request object. |
|
| 14 | + * @param int $total_items Total items found. |
|
| 15 | + * @param int $total_pages Total pages found. |
|
| 16 | + * @return \WP_REST_Response |
|
| 17 | + */ |
|
| 18 | + public function add_headers( $response, $request, $total_items, $total_pages ) { |
|
| 19 | + $response->header( 'X-WP-Total', $total_items ); |
|
| 20 | + $response->header( 'X-WP-TotalPages', $total_pages ); |
|
| 21 | 21 | |
| 22 | - $current_page = $this->get_current_page( $request ); |
|
| 23 | - $link_base = $this->get_link_base( $request ); |
|
| 22 | + $current_page = $this->get_current_page( $request ); |
|
| 23 | + $link_base = $this->get_link_base( $request ); |
|
| 24 | 24 | |
| 25 | - if ( $current_page > 1 ) { |
|
| 26 | - $previous_page = $current_page - 1; |
|
| 27 | - if ( $previous_page > $total_pages ) { |
|
| 28 | - $previous_page = $total_pages; |
|
| 29 | - } |
|
| 30 | - $this->add_page_link( $response, 'prev', $previous_page, $link_base ); |
|
| 31 | - } |
|
| 25 | + if ( $current_page > 1 ) { |
|
| 26 | + $previous_page = $current_page - 1; |
|
| 27 | + if ( $previous_page > $total_pages ) { |
|
| 28 | + $previous_page = $total_pages; |
|
| 29 | + } |
|
| 30 | + $this->add_page_link( $response, 'prev', $previous_page, $link_base ); |
|
| 31 | + } |
|
| 32 | 32 | |
| 33 | - if ( $total_pages > $current_page ) { |
|
| 34 | - $this->add_page_link( $response, 'next', ( $current_page + 1 ), $link_base ); |
|
| 35 | - } |
|
| 33 | + if ( $total_pages > $current_page ) { |
|
| 34 | + $this->add_page_link( $response, 'next', ( $current_page + 1 ), $link_base ); |
|
| 35 | + } |
|
| 36 | 36 | |
| 37 | - return $response; |
|
| 38 | - } |
|
| 37 | + return $response; |
|
| 38 | + } |
|
| 39 | 39 | |
| 40 | - /** |
|
| 41 | - * Get current page. |
|
| 42 | - * |
|
| 43 | - * @param \WP_REST_Request $request The request object. |
|
| 44 | - * @return int Get the page from the request object. |
|
| 45 | - */ |
|
| 46 | - protected function get_current_page( $request ) { |
|
| 47 | - return (int) $request->get_param( 'page' ); |
|
| 48 | - } |
|
| 40 | + /** |
|
| 41 | + * Get current page. |
|
| 42 | + * |
|
| 43 | + * @param \WP_REST_Request $request The request object. |
|
| 44 | + * @return int Get the page from the request object. |
|
| 45 | + */ |
|
| 46 | + protected function get_current_page( $request ) { |
|
| 47 | + return (int) $request->get_param( 'page' ); |
|
| 48 | + } |
|
| 49 | 49 | |
| 50 | - /** |
|
| 51 | - * Get base for links from the request object. |
|
| 52 | - * |
|
| 53 | - * @param \WP_REST_Request $request The request object. |
|
| 54 | - * @return string |
|
| 55 | - */ |
|
| 56 | - protected function get_link_base( $request ) { |
|
| 57 | - return add_query_arg( $request->get_query_params(), rest_url( $request->get_route() ) ); |
|
| 58 | - } |
|
| 50 | + /** |
|
| 51 | + * Get base for links from the request object. |
|
| 52 | + * |
|
| 53 | + * @param \WP_REST_Request $request The request object. |
|
| 54 | + * @return string |
|
| 55 | + */ |
|
| 56 | + protected function get_link_base( $request ) { |
|
| 57 | + return add_query_arg( $request->get_query_params(), rest_url( $request->get_route() ) ); |
|
| 58 | + } |
|
| 59 | 59 | |
| 60 | - /** |
|
| 61 | - * Add a page link. |
|
| 62 | - * |
|
| 63 | - * @param \WP_REST_Response $response Reference to the response object. |
|
| 64 | - * @param string $name Page link name. e.g. prev. |
|
| 65 | - * @param int $page Page number. |
|
| 66 | - * @param string $link_base Base URL. |
|
| 67 | - */ |
|
| 68 | - protected function add_page_link( &$response, $name, $page, $link_base ) { |
|
| 69 | - $response->link_header( $name, add_query_arg( 'page', $page, $link_base ) ); |
|
| 70 | - } |
|
| 60 | + /** |
|
| 61 | + * Add a page link. |
|
| 62 | + * |
|
| 63 | + * @param \WP_REST_Response $response Reference to the response object. |
|
| 64 | + * @param string $name Page link name. e.g. prev. |
|
| 65 | + * @param int $page Page number. |
|
| 66 | + * @param string $link_base Base URL. |
|
| 67 | + */ |
|
| 68 | + protected function add_page_link( &$response, $name, $page, $link_base ) { |
|
| 69 | + $response->link_header( $name, add_query_arg( 'page', $page, $link_base ) ); |
|
| 70 | + } |
|
| 71 | 71 | } |
@@ -15,23 +15,23 @@ discard block |
||
| 15 | 15 | * @param int $total_pages Total pages found. |
| 16 | 16 | * @return \WP_REST_Response |
| 17 | 17 | */ |
| 18 | - public function add_headers( $response, $request, $total_items, $total_pages ) { |
|
| 19 | - $response->header( 'X-WP-Total', $total_items ); |
|
| 20 | - $response->header( 'X-WP-TotalPages', $total_pages ); |
|
| 18 | + public function add_headers($response, $request, $total_items, $total_pages) { |
|
| 19 | + $response->header('X-WP-Total', $total_items); |
|
| 20 | + $response->header('X-WP-TotalPages', $total_pages); |
|
| 21 | 21 | |
| 22 | - $current_page = $this->get_current_page( $request ); |
|
| 23 | - $link_base = $this->get_link_base( $request ); |
|
| 22 | + $current_page = $this->get_current_page($request); |
|
| 23 | + $link_base = $this->get_link_base($request); |
|
| 24 | 24 | |
| 25 | - if ( $current_page > 1 ) { |
|
| 25 | + if ($current_page > 1) { |
|
| 26 | 26 | $previous_page = $current_page - 1; |
| 27 | - if ( $previous_page > $total_pages ) { |
|
| 27 | + if ($previous_page > $total_pages) { |
|
| 28 | 28 | $previous_page = $total_pages; |
| 29 | 29 | } |
| 30 | - $this->add_page_link( $response, 'prev', $previous_page, $link_base ); |
|
| 30 | + $this->add_page_link($response, 'prev', $previous_page, $link_base); |
|
| 31 | 31 | } |
| 32 | 32 | |
| 33 | - if ( $total_pages > $current_page ) { |
|
| 34 | - $this->add_page_link( $response, 'next', ( $current_page + 1 ), $link_base ); |
|
| 33 | + if ($total_pages > $current_page) { |
|
| 34 | + $this->add_page_link($response, 'next', ($current_page + 1), $link_base); |
|
| 35 | 35 | } |
| 36 | 36 | |
| 37 | 37 | return $response; |
@@ -43,8 +43,8 @@ discard block |
||
| 43 | 43 | * @param \WP_REST_Request $request The request object. |
| 44 | 44 | * @return int Get the page from the request object. |
| 45 | 45 | */ |
| 46 | - protected function get_current_page( $request ) { |
|
| 47 | - return (int) $request->get_param( 'page' ); |
|
| 46 | + protected function get_current_page($request) { |
|
| 47 | + return (int) $request->get_param('page'); |
|
| 48 | 48 | } |
| 49 | 49 | |
| 50 | 50 | /** |
@@ -53,8 +53,8 @@ discard block |
||
| 53 | 53 | * @param \WP_REST_Request $request The request object. |
| 54 | 54 | * @return string |
| 55 | 55 | */ |
| 56 | - protected function get_link_base( $request ) { |
|
| 57 | - return add_query_arg( $request->get_query_params(), rest_url( $request->get_route() ) ); |
|
| 56 | + protected function get_link_base($request) { |
|
| 57 | + return add_query_arg($request->get_query_params(), rest_url($request->get_route())); |
|
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | /** |
@@ -65,7 +65,7 @@ discard block |
||
| 65 | 65 | * @param int $page Page number. |
| 66 | 66 | * @param string $link_base Base URL. |
| 67 | 67 | */ |
| 68 | - protected function add_page_link( &$response, $name, $page, $link_base ) { |
|
| 69 | - $response->link_header( $name, add_query_arg( 'page', $page, $link_base ) ); |
|
| 68 | + protected function add_page_link(&$response, $name, $page, $link_base) { |
|
| 69 | + $response->link_header($name, add_query_arg('page', $page, $link_base)); |
|
| 70 | 70 | } |
| 71 | 71 | } |
@@ -10,63 +10,63 @@ |
||
| 10 | 10 | */ |
| 11 | 11 | class NoticeHandler { |
| 12 | 12 | |
| 13 | - /** |
|
| 14 | - * Convert queued error notices into an exception. |
|
| 15 | - * |
|
| 16 | - * For example, Payment methods may add error notices during validate_fields call to prevent checkout. |
|
| 17 | - * Since we're not rendering notices at all, we need to convert them to exceptions. |
|
| 18 | - * |
|
| 19 | - * This method will find the first error message and thrown an exception instead. Discards notices once complete. |
|
| 20 | - * |
|
| 21 | - * @throws RouteException If an error notice is detected, Exception is thrown. |
|
| 22 | - * |
|
| 23 | - * @param string $error_code Error code for the thrown exceptions. |
|
| 24 | - */ |
|
| 25 | - public static function convert_notices_to_exceptions( $error_code = 'unknown_server_error' ) { |
|
| 26 | - if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 27 | - wc_clear_notices(); |
|
| 28 | - return; |
|
| 29 | - } |
|
| 13 | + /** |
|
| 14 | + * Convert queued error notices into an exception. |
|
| 15 | + * |
|
| 16 | + * For example, Payment methods may add error notices during validate_fields call to prevent checkout. |
|
| 17 | + * Since we're not rendering notices at all, we need to convert them to exceptions. |
|
| 18 | + * |
|
| 19 | + * This method will find the first error message and thrown an exception instead. Discards notices once complete. |
|
| 20 | + * |
|
| 21 | + * @throws RouteException If an error notice is detected, Exception is thrown. |
|
| 22 | + * |
|
| 23 | + * @param string $error_code Error code for the thrown exceptions. |
|
| 24 | + */ |
|
| 25 | + public static function convert_notices_to_exceptions( $error_code = 'unknown_server_error' ) { |
|
| 26 | + if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 27 | + wc_clear_notices(); |
|
| 28 | + return; |
|
| 29 | + } |
|
| 30 | 30 | |
| 31 | - $error_notices = wc_get_notices( 'error' ); |
|
| 31 | + $error_notices = wc_get_notices( 'error' ); |
|
| 32 | 32 | |
| 33 | - // Prevent notices from being output later on. |
|
| 34 | - wc_clear_notices(); |
|
| 33 | + // Prevent notices from being output later on. |
|
| 34 | + wc_clear_notices(); |
|
| 35 | 35 | |
| 36 | - foreach ( $error_notices as $error_notice ) { |
|
| 37 | - throw new RouteException( $error_code, wp_strip_all_tags( $error_notice['notice'] ), 400 ); |
|
| 38 | - } |
|
| 39 | - } |
|
| 36 | + foreach ( $error_notices as $error_notice ) { |
|
| 37 | + throw new RouteException( $error_code, wp_strip_all_tags( $error_notice['notice'] ), 400 ); |
|
| 38 | + } |
|
| 39 | + } |
|
| 40 | 40 | |
| 41 | - /** |
|
| 42 | - * Collects queued error notices into a \WP_Error. |
|
| 43 | - * |
|
| 44 | - * For example, cart validation processes may add error notices to prevent checkout. |
|
| 45 | - * Since we're not rendering notices at all, we need to catch them and group them in a single WP_Error instance. |
|
| 46 | - * |
|
| 47 | - * This method will discard notices once complete. |
|
| 48 | - * |
|
| 49 | - * @param string $error_code Error code for the thrown exceptions. |
|
| 50 | - * |
|
| 51 | - * @return \WP_Error The WP_Error object containing all error notices. |
|
| 52 | - */ |
|
| 53 | - public static function convert_notices_to_wp_errors( $error_code = 'unknown_server_error' ) { |
|
| 54 | - $errors = new WP_Error(); |
|
| 41 | + /** |
|
| 42 | + * Collects queued error notices into a \WP_Error. |
|
| 43 | + * |
|
| 44 | + * For example, cart validation processes may add error notices to prevent checkout. |
|
| 45 | + * Since we're not rendering notices at all, we need to catch them and group them in a single WP_Error instance. |
|
| 46 | + * |
|
| 47 | + * This method will discard notices once complete. |
|
| 48 | + * |
|
| 49 | + * @param string $error_code Error code for the thrown exceptions. |
|
| 50 | + * |
|
| 51 | + * @return \WP_Error The WP_Error object containing all error notices. |
|
| 52 | + */ |
|
| 53 | + public static function convert_notices_to_wp_errors( $error_code = 'unknown_server_error' ) { |
|
| 54 | + $errors = new WP_Error(); |
|
| 55 | 55 | |
| 56 | - if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 57 | - wc_clear_notices(); |
|
| 58 | - return $errors; |
|
| 59 | - } |
|
| 56 | + if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 57 | + wc_clear_notices(); |
|
| 58 | + return $errors; |
|
| 59 | + } |
|
| 60 | 60 | |
| 61 | - $error_notices = wc_get_notices( 'error' ); |
|
| 61 | + $error_notices = wc_get_notices( 'error' ); |
|
| 62 | 62 | |
| 63 | - // Prevent notices from being output later on. |
|
| 64 | - wc_clear_notices(); |
|
| 63 | + // Prevent notices from being output later on. |
|
| 64 | + wc_clear_notices(); |
|
| 65 | 65 | |
| 66 | - foreach ( $error_notices as $error_notice ) { |
|
| 67 | - $errors->add( $error_code, wp_strip_all_tags( $error_notice['notice'] ) ); |
|
| 68 | - } |
|
| 66 | + foreach ( $error_notices as $error_notice ) { |
|
| 67 | + $errors->add( $error_code, wp_strip_all_tags( $error_notice['notice'] ) ); |
|
| 68 | + } |
|
| 69 | 69 | |
| 70 | - return $errors; |
|
| 71 | - } |
|
| 70 | + return $errors; |
|
| 71 | + } |
|
| 72 | 72 | } |
@@ -22,19 +22,19 @@ discard block |
||
| 22 | 22 | * |
| 23 | 23 | * @param string $error_code Error code for the thrown exceptions. |
| 24 | 24 | */ |
| 25 | - public static function convert_notices_to_exceptions( $error_code = 'unknown_server_error' ) { |
|
| 26 | - if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 25 | + public static function convert_notices_to_exceptions($error_code = 'unknown_server_error') { |
|
| 26 | + if (0 === wc_notice_count('error')) { |
|
| 27 | 27 | wc_clear_notices(); |
| 28 | 28 | return; |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | - $error_notices = wc_get_notices( 'error' ); |
|
| 31 | + $error_notices = wc_get_notices('error'); |
|
| 32 | 32 | |
| 33 | 33 | // Prevent notices from being output later on. |
| 34 | 34 | wc_clear_notices(); |
| 35 | 35 | |
| 36 | - foreach ( $error_notices as $error_notice ) { |
|
| 37 | - throw new RouteException( $error_code, wp_strip_all_tags( $error_notice['notice'] ), 400 ); |
|
| 36 | + foreach ($error_notices as $error_notice) { |
|
| 37 | + throw new RouteException($error_code, wp_strip_all_tags($error_notice['notice']), 400); |
|
| 38 | 38 | } |
| 39 | 39 | } |
| 40 | 40 | |
@@ -50,21 +50,21 @@ discard block |
||
| 50 | 50 | * |
| 51 | 51 | * @return \WP_Error The WP_Error object containing all error notices. |
| 52 | 52 | */ |
| 53 | - public static function convert_notices_to_wp_errors( $error_code = 'unknown_server_error' ) { |
|
| 53 | + public static function convert_notices_to_wp_errors($error_code = 'unknown_server_error') { |
|
| 54 | 54 | $errors = new WP_Error(); |
| 55 | 55 | |
| 56 | - if ( 0 === wc_notice_count( 'error' ) ) { |
|
| 56 | + if (0 === wc_notice_count('error')) { |
|
| 57 | 57 | wc_clear_notices(); |
| 58 | 58 | return $errors; |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | - $error_notices = wc_get_notices( 'error' ); |
|
| 61 | + $error_notices = wc_get_notices('error'); |
|
| 62 | 62 | |
| 63 | 63 | // Prevent notices from being output later on. |
| 64 | 64 | wc_clear_notices(); |
| 65 | 65 | |
| 66 | - foreach ( $error_notices as $error_notice ) { |
|
| 67 | - $errors->add( $error_code, wp_strip_all_tags( $error_notice['notice'] ) ); |
|
| 66 | + foreach ($error_notices as $error_notice) { |
|
| 67 | + $errors->add($error_code, wp_strip_all_tags($error_notice['notice'])); |
|
| 68 | 68 | } |
| 69 | 69 | |
| 70 | 70 | return $errors; |
@@ -7,100 +7,100 @@ discard block |
||
| 7 | 7 | * Product Query filters class. |
| 8 | 8 | */ |
| 9 | 9 | class ProductQueryFilters { |
| 10 | - /** |
|
| 11 | - * Get filtered min price for current products. |
|
| 12 | - * |
|
| 13 | - * @param \WP_REST_Request $request The request object. |
|
| 14 | - * @return object |
|
| 15 | - */ |
|
| 16 | - public function get_filtered_price( $request ) { |
|
| 17 | - global $wpdb; |
|
| 18 | - |
|
| 19 | - // Regenerate the products query without min/max price request params. |
|
| 20 | - unset( $request['min_price'], $request['max_price'] ); |
|
| 21 | - |
|
| 22 | - // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 23 | - $product_query = new ProductQuery(); |
|
| 24 | - |
|
| 25 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 26 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 27 | - |
|
| 28 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 29 | - $query_args['no_found_rows'] = true; |
|
| 30 | - $query_args['posts_per_page'] = -1; |
|
| 31 | - $query = new \WP_Query(); |
|
| 32 | - $result = $query->query( $query_args ); |
|
| 33 | - $product_query_sql = $query->request; |
|
| 34 | - |
|
| 35 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 36 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 37 | - |
|
| 38 | - $price_filter_sql = " |
|
| 10 | + /** |
|
| 11 | + * Get filtered min price for current products. |
|
| 12 | + * |
|
| 13 | + * @param \WP_REST_Request $request The request object. |
|
| 14 | + * @return object |
|
| 15 | + */ |
|
| 16 | + public function get_filtered_price( $request ) { |
|
| 17 | + global $wpdb; |
|
| 18 | + |
|
| 19 | + // Regenerate the products query without min/max price request params. |
|
| 20 | + unset( $request['min_price'], $request['max_price'] ); |
|
| 21 | + |
|
| 22 | + // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 23 | + $product_query = new ProductQuery(); |
|
| 24 | + |
|
| 25 | + add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 26 | + add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 27 | + |
|
| 28 | + $query_args = $product_query->prepare_objects_query( $request ); |
|
| 29 | + $query_args['no_found_rows'] = true; |
|
| 30 | + $query_args['posts_per_page'] = -1; |
|
| 31 | + $query = new \WP_Query(); |
|
| 32 | + $result = $query->query( $query_args ); |
|
| 33 | + $product_query_sql = $query->request; |
|
| 34 | + |
|
| 35 | + remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 36 | + remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 37 | + |
|
| 38 | + $price_filter_sql = " |
|
| 39 | 39 | SELECT min( min_price ) as min_price, MAX( max_price ) as max_price |
| 40 | 40 | FROM {$wpdb->wc_product_meta_lookup} |
| 41 | 41 | WHERE product_id IN ( {$product_query_sql} ) |
| 42 | 42 | "; |
| 43 | 43 | |
| 44 | - return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore |
|
| 45 | - } |
|
| 46 | - |
|
| 47 | - /** |
|
| 48 | - * Get stock status counts for the current products. |
|
| 49 | - * |
|
| 50 | - * @param \WP_REST_Request $request The request object. |
|
| 51 | - * @return array status=>count pairs. |
|
| 52 | - */ |
|
| 53 | - public function get_stock_status_counts( $request ) { |
|
| 54 | - global $wpdb; |
|
| 55 | - $product_query = new ProductQuery(); |
|
| 56 | - $stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) ); |
|
| 57 | - $hide_outofstock_items = get_option( 'woocommerce_hide_out_of_stock_items' ); |
|
| 58 | - if ( 'yes' === $hide_outofstock_items ) { |
|
| 59 | - unset( $stock_status_options['outofstock'] ); |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 63 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 64 | - |
|
| 65 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 66 | - unset( $query_args['stock_status'] ); |
|
| 67 | - $query_args['no_found_rows'] = true; |
|
| 68 | - $query_args['posts_per_page'] = -1; |
|
| 69 | - $query = new \WP_Query(); |
|
| 70 | - $result = $query->query( $query_args ); |
|
| 71 | - $product_query_sql = $query->request; |
|
| 72 | - |
|
| 73 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 74 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 75 | - |
|
| 76 | - $stock_status_counts = array(); |
|
| 77 | - |
|
| 78 | - foreach ( $stock_status_options as $status ) { |
|
| 79 | - $stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ); |
|
| 80 | - |
|
| 81 | - $result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore |
|
| 82 | - $stock_status_counts[ $status ] = $result->status_count; |
|
| 83 | - } |
|
| 84 | - |
|
| 85 | - return $stock_status_counts; |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - /** |
|
| 89 | - * Generate calculate query by stock status. |
|
| 90 | - * |
|
| 91 | - * @param string $status status to calculate. |
|
| 92 | - * @param string $product_query_sql product query for current filter state. |
|
| 93 | - * @param array $stock_status_options available stock status options. |
|
| 94 | - * |
|
| 95 | - * @return false|string |
|
| 96 | - */ |
|
| 97 | - private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) { |
|
| 98 | - if ( ! in_array( $status, $stock_status_options, true ) ) { |
|
| 99 | - return false; |
|
| 100 | - } |
|
| 101 | - global $wpdb; |
|
| 102 | - $status = esc_sql( $status ); |
|
| 103 | - return " |
|
| 44 | + return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | + /** |
|
| 48 | + * Get stock status counts for the current products. |
|
| 49 | + * |
|
| 50 | + * @param \WP_REST_Request $request The request object. |
|
| 51 | + * @return array status=>count pairs. |
|
| 52 | + */ |
|
| 53 | + public function get_stock_status_counts( $request ) { |
|
| 54 | + global $wpdb; |
|
| 55 | + $product_query = new ProductQuery(); |
|
| 56 | + $stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) ); |
|
| 57 | + $hide_outofstock_items = get_option( 'woocommerce_hide_out_of_stock_items' ); |
|
| 58 | + if ( 'yes' === $hide_outofstock_items ) { |
|
| 59 | + unset( $stock_status_options['outofstock'] ); |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 63 | + add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 64 | + |
|
| 65 | + $query_args = $product_query->prepare_objects_query( $request ); |
|
| 66 | + unset( $query_args['stock_status'] ); |
|
| 67 | + $query_args['no_found_rows'] = true; |
|
| 68 | + $query_args['posts_per_page'] = -1; |
|
| 69 | + $query = new \WP_Query(); |
|
| 70 | + $result = $query->query( $query_args ); |
|
| 71 | + $product_query_sql = $query->request; |
|
| 72 | + |
|
| 73 | + remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 74 | + remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 75 | + |
|
| 76 | + $stock_status_counts = array(); |
|
| 77 | + |
|
| 78 | + foreach ( $stock_status_options as $status ) { |
|
| 79 | + $stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ); |
|
| 80 | + |
|
| 81 | + $result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore |
|
| 82 | + $stock_status_counts[ $status ] = $result->status_count; |
|
| 83 | + } |
|
| 84 | + |
|
| 85 | + return $stock_status_counts; |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + /** |
|
| 89 | + * Generate calculate query by stock status. |
|
| 90 | + * |
|
| 91 | + * @param string $status status to calculate. |
|
| 92 | + * @param string $product_query_sql product query for current filter state. |
|
| 93 | + * @param array $stock_status_options available stock status options. |
|
| 94 | + * |
|
| 95 | + * @return false|string |
|
| 96 | + */ |
|
| 97 | + private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) { |
|
| 98 | + if ( ! in_array( $status, $stock_status_options, true ) ) { |
|
| 99 | + return false; |
|
| 100 | + } |
|
| 101 | + global $wpdb; |
|
| 102 | + $status = esc_sql( $status ); |
|
| 103 | + return " |
|
| 104 | 104 | SELECT COUNT( DISTINCT posts.ID ) as status_count |
| 105 | 105 | FROM {$wpdb->posts} as posts |
| 106 | 106 | INNER JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id |
@@ -108,53 +108,53 @@ discard block |
||
| 108 | 108 | AND postmeta.meta_value = '{$status}' |
| 109 | 109 | WHERE posts.ID IN ( {$product_query_sql} ) |
| 110 | 110 | "; |
| 111 | - } |
|
| 112 | - |
|
| 113 | - /** |
|
| 114 | - * Get attribute counts for the current products. |
|
| 115 | - * |
|
| 116 | - * @param \WP_REST_Request $request The request object. |
|
| 117 | - * @param array $attributes Attributes to count, either names or ids. |
|
| 118 | - * @return array termId=>count pairs. |
|
| 119 | - */ |
|
| 120 | - public function get_attribute_counts( $request, $attributes = [] ) { |
|
| 121 | - global $wpdb; |
|
| 122 | - |
|
| 123 | - // Remove paging and sorting params from the request. |
|
| 124 | - $request->set_param( 'page', null ); |
|
| 125 | - $request->set_param( 'per_page', null ); |
|
| 126 | - $request->set_param( 'order', null ); |
|
| 127 | - $request->set_param( 'orderby', null ); |
|
| 128 | - |
|
| 129 | - // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 130 | - $product_query = new ProductQuery(); |
|
| 131 | - |
|
| 132 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 133 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 134 | - |
|
| 135 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 136 | - $query_args['no_found_rows'] = true; |
|
| 137 | - $query_args['posts_per_page'] = -1; |
|
| 138 | - $query = new \WP_Query(); |
|
| 139 | - $result = $query->query( $query_args ); |
|
| 140 | - $product_query_sql = $query->request; |
|
| 141 | - |
|
| 142 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 143 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 144 | - |
|
| 145 | - if ( count( $attributes ) === count( array_filter( $attributes, 'is_numeric' ) ) ) { |
|
| 146 | - $attributes = array_map( 'wc_attribute_taxonomy_name_by_id', wp_parse_id_list( $attributes ) ); |
|
| 147 | - } |
|
| 148 | - |
|
| 149 | - $attributes_to_count = array_map( |
|
| 150 | - function( $attribute ) { |
|
| 151 | - $attribute = wc_sanitize_taxonomy_name( $attribute ); |
|
| 152 | - return esc_sql( $attribute ); |
|
| 153 | - }, |
|
| 154 | - $attributes |
|
| 155 | - ); |
|
| 156 | - $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode( '","', $attributes_to_count ) . '")'; |
|
| 157 | - $attribute_count_sql = " |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + /** |
|
| 114 | + * Get attribute counts for the current products. |
|
| 115 | + * |
|
| 116 | + * @param \WP_REST_Request $request The request object. |
|
| 117 | + * @param array $attributes Attributes to count, either names or ids. |
|
| 118 | + * @return array termId=>count pairs. |
|
| 119 | + */ |
|
| 120 | + public function get_attribute_counts( $request, $attributes = [] ) { |
|
| 121 | + global $wpdb; |
|
| 122 | + |
|
| 123 | + // Remove paging and sorting params from the request. |
|
| 124 | + $request->set_param( 'page', null ); |
|
| 125 | + $request->set_param( 'per_page', null ); |
|
| 126 | + $request->set_param( 'order', null ); |
|
| 127 | + $request->set_param( 'orderby', null ); |
|
| 128 | + |
|
| 129 | + // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 130 | + $product_query = new ProductQuery(); |
|
| 131 | + |
|
| 132 | + add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 133 | + add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 134 | + |
|
| 135 | + $query_args = $product_query->prepare_objects_query( $request ); |
|
| 136 | + $query_args['no_found_rows'] = true; |
|
| 137 | + $query_args['posts_per_page'] = -1; |
|
| 138 | + $query = new \WP_Query(); |
|
| 139 | + $result = $query->query( $query_args ); |
|
| 140 | + $product_query_sql = $query->request; |
|
| 141 | + |
|
| 142 | + remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 143 | + remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 144 | + |
|
| 145 | + if ( count( $attributes ) === count( array_filter( $attributes, 'is_numeric' ) ) ) { |
|
| 146 | + $attributes = array_map( 'wc_attribute_taxonomy_name_by_id', wp_parse_id_list( $attributes ) ); |
|
| 147 | + } |
|
| 148 | + |
|
| 149 | + $attributes_to_count = array_map( |
|
| 150 | + function( $attribute ) { |
|
| 151 | + $attribute = wc_sanitize_taxonomy_name( $attribute ); |
|
| 152 | + return esc_sql( $attribute ); |
|
| 153 | + }, |
|
| 154 | + $attributes |
|
| 155 | + ); |
|
| 156 | + $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode( '","', $attributes_to_count ) . '")'; |
|
| 157 | + $attribute_count_sql = " |
|
| 158 | 158 | SELECT COUNT( DISTINCT posts.ID ) as term_count, terms.term_id as term_count_id |
| 159 | 159 | FROM {$wpdb->posts} AS posts |
| 160 | 160 | INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id |
@@ -165,40 +165,40 @@ discard block |
||
| 165 | 165 | GROUP BY terms.term_id |
| 166 | 166 | "; |
| 167 | 167 | |
| 168 | - $results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore |
|
| 168 | + $results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore |
|
| 169 | 169 | |
| 170 | - return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); |
|
| 171 | - } |
|
| 170 | + return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); |
|
| 171 | + } |
|
| 172 | 172 | |
| 173 | - /** |
|
| 174 | - * Get rating counts for the current products. |
|
| 175 | - * |
|
| 176 | - * @param \WP_REST_Request $request The request object. |
|
| 177 | - * @return array rating=>count pairs. |
|
| 178 | - */ |
|
| 179 | - public function get_rating_counts( $request ) { |
|
| 180 | - global $wpdb; |
|
| 173 | + /** |
|
| 174 | + * Get rating counts for the current products. |
|
| 175 | + * |
|
| 176 | + * @param \WP_REST_Request $request The request object. |
|
| 177 | + * @return array rating=>count pairs. |
|
| 178 | + */ |
|
| 179 | + public function get_rating_counts( $request ) { |
|
| 180 | + global $wpdb; |
|
| 181 | 181 | |
| 182 | - // Regenerate the products query without rating request params. |
|
| 183 | - unset( $request['rating'] ); |
|
| 182 | + // Regenerate the products query without rating request params. |
|
| 183 | + unset( $request['rating'] ); |
|
| 184 | 184 | |
| 185 | - // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 186 | - $product_query = new ProductQuery(); |
|
| 185 | + // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
|
| 186 | + $product_query = new ProductQuery(); |
|
| 187 | 187 | |
| 188 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 189 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 188 | + add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 189 | + add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 190 | 190 | |
| 191 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 192 | - $query_args['no_found_rows'] = true; |
|
| 193 | - $query_args['posts_per_page'] = -1; |
|
| 194 | - $query = new \WP_Query(); |
|
| 195 | - $result = $query->query( $query_args ); |
|
| 196 | - $product_query_sql = $query->request; |
|
| 191 | + $query_args = $product_query->prepare_objects_query( $request ); |
|
| 192 | + $query_args['no_found_rows'] = true; |
|
| 193 | + $query_args['posts_per_page'] = -1; |
|
| 194 | + $query = new \WP_Query(); |
|
| 195 | + $result = $query->query( $query_args ); |
|
| 196 | + $product_query_sql = $query->request; |
|
| 197 | 197 | |
| 198 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 199 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 198 | + remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 199 | + remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 200 | 200 | |
| 201 | - $rating_count_sql = " |
|
| 201 | + $rating_count_sql = " |
|
| 202 | 202 | SELECT COUNT( DISTINCT product_id ) as product_count, ROUND( average_rating, 0 ) as rounded_average_rating |
| 203 | 203 | FROM {$wpdb->wc_product_meta_lookup} |
| 204 | 204 | WHERE product_id IN ( {$product_query_sql} ) |
@@ -207,8 +207,8 @@ discard block |
||
| 207 | 207 | ORDER BY rounded_average_rating ASC |
| 208 | 208 | "; |
| 209 | 209 | |
| 210 | - $results = $wpdb->get_results( $rating_count_sql ); // phpcs:ignore |
|
| 210 | + $results = $wpdb->get_results( $rating_count_sql ); // phpcs:ignore |
|
| 211 | 211 | |
| 212 | - return array_map( 'absint', wp_list_pluck( $results, 'product_count', 'rounded_average_rating' ) ); |
|
| 213 | - } |
|
| 212 | + return array_map( 'absint', wp_list_pluck( $results, 'product_count', 'rounded_average_rating' ) ); |
|
| 213 | + } |
|
| 214 | 214 | } |
@@ -13,27 +13,27 @@ discard block |
||
| 13 | 13 | * @param \WP_REST_Request $request The request object. |
| 14 | 14 | * @return object |
| 15 | 15 | */ |
| 16 | - public function get_filtered_price( $request ) { |
|
| 16 | + public function get_filtered_price($request) { |
|
| 17 | 17 | global $wpdb; |
| 18 | 18 | |
| 19 | 19 | // Regenerate the products query without min/max price request params. |
| 20 | - unset( $request['min_price'], $request['max_price'] ); |
|
| 20 | + unset($request['min_price'], $request['max_price']); |
|
| 21 | 21 | |
| 22 | 22 | // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
| 23 | 23 | $product_query = new ProductQuery(); |
| 24 | 24 | |
| 25 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 26 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 25 | + add_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10, 2); |
|
| 26 | + add_filter('posts_pre_query', '__return_empty_array'); |
|
| 27 | 27 | |
| 28 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 28 | + $query_args = $product_query->prepare_objects_query($request); |
|
| 29 | 29 | $query_args['no_found_rows'] = true; |
| 30 | 30 | $query_args['posts_per_page'] = -1; |
| 31 | 31 | $query = new \WP_Query(); |
| 32 | - $result = $query->query( $query_args ); |
|
| 32 | + $result = $query->query($query_args); |
|
| 33 | 33 | $product_query_sql = $query->request; |
| 34 | 34 | |
| 35 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 36 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 35 | + remove_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10); |
|
| 36 | + remove_filter('posts_pre_query', '__return_empty_array'); |
|
| 37 | 37 | |
| 38 | 38 | $price_filter_sql = " |
| 39 | 39 | SELECT min( min_price ) as min_price, MAX( max_price ) as max_price |
@@ -41,7 +41,7 @@ discard block |
||
| 41 | 41 | WHERE product_id IN ( {$product_query_sql} ) |
| 42 | 42 | "; |
| 43 | 43 | |
| 44 | - return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore |
|
| 44 | + return $wpdb->get_row($price_filter_sql); // phpcs:ignore |
|
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | /** |
@@ -50,36 +50,36 @@ discard block |
||
| 50 | 50 | * @param \WP_REST_Request $request The request object. |
| 51 | 51 | * @return array status=>count pairs. |
| 52 | 52 | */ |
| 53 | - public function get_stock_status_counts( $request ) { |
|
| 53 | + public function get_stock_status_counts($request) { |
|
| 54 | 54 | global $wpdb; |
| 55 | 55 | $product_query = new ProductQuery(); |
| 56 | - $stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) ); |
|
| 57 | - $hide_outofstock_items = get_option( 'woocommerce_hide_out_of_stock_items' ); |
|
| 58 | - if ( 'yes' === $hide_outofstock_items ) { |
|
| 59 | - unset( $stock_status_options['outofstock'] ); |
|
| 56 | + $stock_status_options = array_map('esc_sql', array_keys(wc_get_product_stock_status_options())); |
|
| 57 | + $hide_outofstock_items = get_option('woocommerce_hide_out_of_stock_items'); |
|
| 58 | + if ('yes' === $hide_outofstock_items) { |
|
| 59 | + unset($stock_status_options['outofstock']); |
|
| 60 | 60 | } |
| 61 | 61 | |
| 62 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 63 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 62 | + add_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10, 2); |
|
| 63 | + add_filter('posts_pre_query', '__return_empty_array'); |
|
| 64 | 64 | |
| 65 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 66 | - unset( $query_args['stock_status'] ); |
|
| 65 | + $query_args = $product_query->prepare_objects_query($request); |
|
| 66 | + unset($query_args['stock_status']); |
|
| 67 | 67 | $query_args['no_found_rows'] = true; |
| 68 | 68 | $query_args['posts_per_page'] = -1; |
| 69 | 69 | $query = new \WP_Query(); |
| 70 | - $result = $query->query( $query_args ); |
|
| 70 | + $result = $query->query($query_args); |
|
| 71 | 71 | $product_query_sql = $query->request; |
| 72 | 72 | |
| 73 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 74 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 73 | + remove_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10); |
|
| 74 | + remove_filter('posts_pre_query', '__return_empty_array'); |
|
| 75 | 75 | |
| 76 | 76 | $stock_status_counts = array(); |
| 77 | 77 | |
| 78 | - foreach ( $stock_status_options as $status ) { |
|
| 79 | - $stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ); |
|
| 78 | + foreach ($stock_status_options as $status) { |
|
| 79 | + $stock_status_count_sql = $this->generate_stock_status_count_query($status, $product_query_sql, $stock_status_options); |
|
| 80 | 80 | |
| 81 | - $result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore |
|
| 82 | - $stock_status_counts[ $status ] = $result->status_count; |
|
| 81 | + $result = $wpdb->get_row($stock_status_count_sql); // phpcs:ignore |
|
| 82 | + $stock_status_counts[$status] = $result->status_count; |
|
| 83 | 83 | } |
| 84 | 84 | |
| 85 | 85 | return $stock_status_counts; |
@@ -94,12 +94,12 @@ discard block |
||
| 94 | 94 | * |
| 95 | 95 | * @return false|string |
| 96 | 96 | */ |
| 97 | - private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) { |
|
| 98 | - if ( ! in_array( $status, $stock_status_options, true ) ) { |
|
| 97 | + private function generate_stock_status_count_query($status, $product_query_sql, $stock_status_options) { |
|
| 98 | + if (!in_array($status, $stock_status_options, true)) { |
|
| 99 | 99 | return false; |
| 100 | 100 | } |
| 101 | 101 | global $wpdb; |
| 102 | - $status = esc_sql( $status ); |
|
| 102 | + $status = esc_sql($status); |
|
| 103 | 103 | return " |
| 104 | 104 | SELECT COUNT( DISTINCT posts.ID ) as status_count |
| 105 | 105 | FROM {$wpdb->posts} as posts |
@@ -117,43 +117,43 @@ discard block |
||
| 117 | 117 | * @param array $attributes Attributes to count, either names or ids. |
| 118 | 118 | * @return array termId=>count pairs. |
| 119 | 119 | */ |
| 120 | - public function get_attribute_counts( $request, $attributes = [] ) { |
|
| 120 | + public function get_attribute_counts($request, $attributes = []) { |
|
| 121 | 121 | global $wpdb; |
| 122 | 122 | |
| 123 | 123 | // Remove paging and sorting params from the request. |
| 124 | - $request->set_param( 'page', null ); |
|
| 125 | - $request->set_param( 'per_page', null ); |
|
| 126 | - $request->set_param( 'order', null ); |
|
| 127 | - $request->set_param( 'orderby', null ); |
|
| 124 | + $request->set_param('page', null); |
|
| 125 | + $request->set_param('per_page', null); |
|
| 126 | + $request->set_param('order', null); |
|
| 127 | + $request->set_param('orderby', null); |
|
| 128 | 128 | |
| 129 | 129 | // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
| 130 | 130 | $product_query = new ProductQuery(); |
| 131 | 131 | |
| 132 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 133 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 132 | + add_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10, 2); |
|
| 133 | + add_filter('posts_pre_query', '__return_empty_array'); |
|
| 134 | 134 | |
| 135 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 135 | + $query_args = $product_query->prepare_objects_query($request); |
|
| 136 | 136 | $query_args['no_found_rows'] = true; |
| 137 | 137 | $query_args['posts_per_page'] = -1; |
| 138 | 138 | $query = new \WP_Query(); |
| 139 | - $result = $query->query( $query_args ); |
|
| 139 | + $result = $query->query($query_args); |
|
| 140 | 140 | $product_query_sql = $query->request; |
| 141 | 141 | |
| 142 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 143 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 142 | + remove_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10); |
|
| 143 | + remove_filter('posts_pre_query', '__return_empty_array'); |
|
| 144 | 144 | |
| 145 | - if ( count( $attributes ) === count( array_filter( $attributes, 'is_numeric' ) ) ) { |
|
| 146 | - $attributes = array_map( 'wc_attribute_taxonomy_name_by_id', wp_parse_id_list( $attributes ) ); |
|
| 145 | + if (count($attributes) === count(array_filter($attributes, 'is_numeric'))) { |
|
| 146 | + $attributes = array_map('wc_attribute_taxonomy_name_by_id', wp_parse_id_list($attributes)); |
|
| 147 | 147 | } |
| 148 | 148 | |
| 149 | - $attributes_to_count = array_map( |
|
| 150 | - function( $attribute ) { |
|
| 151 | - $attribute = wc_sanitize_taxonomy_name( $attribute ); |
|
| 152 | - return esc_sql( $attribute ); |
|
| 149 | + $attributes_to_count = array_map( |
|
| 150 | + function($attribute) { |
|
| 151 | + $attribute = wc_sanitize_taxonomy_name($attribute); |
|
| 152 | + return esc_sql($attribute); |
|
| 153 | 153 | }, |
| 154 | 154 | $attributes |
| 155 | 155 | ); |
| 156 | - $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode( '","', $attributes_to_count ) . '")'; |
|
| 156 | + $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode('","', $attributes_to_count) . '")'; |
|
| 157 | 157 | $attribute_count_sql = " |
| 158 | 158 | SELECT COUNT( DISTINCT posts.ID ) as term_count, terms.term_id as term_count_id |
| 159 | 159 | FROM {$wpdb->posts} AS posts |
@@ -165,9 +165,9 @@ discard block |
||
| 165 | 165 | GROUP BY terms.term_id |
| 166 | 166 | "; |
| 167 | 167 | |
| 168 | - $results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore |
|
| 168 | + $results = $wpdb->get_results($attribute_count_sql); // phpcs:ignore |
|
| 169 | 169 | |
| 170 | - return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); |
|
| 170 | + return array_map('absint', wp_list_pluck($results, 'term_count', 'term_count_id')); |
|
| 171 | 171 | } |
| 172 | 172 | |
| 173 | 173 | /** |
@@ -176,27 +176,27 @@ discard block |
||
| 176 | 176 | * @param \WP_REST_Request $request The request object. |
| 177 | 177 | * @return array rating=>count pairs. |
| 178 | 178 | */ |
| 179 | - public function get_rating_counts( $request ) { |
|
| 179 | + public function get_rating_counts($request) { |
|
| 180 | 180 | global $wpdb; |
| 181 | 181 | |
| 182 | 182 | // Regenerate the products query without rating request params. |
| 183 | - unset( $request['rating'] ); |
|
| 183 | + unset($request['rating']); |
|
| 184 | 184 | |
| 185 | 185 | // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. |
| 186 | 186 | $product_query = new ProductQuery(); |
| 187 | 187 | |
| 188 | - add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); |
|
| 189 | - add_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 188 | + add_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10, 2); |
|
| 189 | + add_filter('posts_pre_query', '__return_empty_array'); |
|
| 190 | 190 | |
| 191 | - $query_args = $product_query->prepare_objects_query( $request ); |
|
| 191 | + $query_args = $product_query->prepare_objects_query($request); |
|
| 192 | 192 | $query_args['no_found_rows'] = true; |
| 193 | 193 | $query_args['posts_per_page'] = -1; |
| 194 | 194 | $query = new \WP_Query(); |
| 195 | - $result = $query->query( $query_args ); |
|
| 195 | + $result = $query->query($query_args); |
|
| 196 | 196 | $product_query_sql = $query->request; |
| 197 | 197 | |
| 198 | - remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); |
|
| 199 | - remove_filter( 'posts_pre_query', '__return_empty_array' ); |
|
| 198 | + remove_filter('posts_clauses', array($product_query, 'add_query_clauses'), 10); |
|
| 199 | + remove_filter('posts_pre_query', '__return_empty_array'); |
|
| 200 | 200 | |
| 201 | 201 | $rating_count_sql = " |
| 202 | 202 | SELECT COUNT( DISTINCT product_id ) as product_count, ROUND( average_rating, 0 ) as rounded_average_rating |
@@ -207,8 +207,8 @@ discard block |
||
| 207 | 207 | ORDER BY rounded_average_rating ASC |
| 208 | 208 | "; |
| 209 | 209 | |
| 210 | - $results = $wpdb->get_results( $rating_count_sql ); // phpcs:ignore |
|
| 210 | + $results = $wpdb->get_results($rating_count_sql); // phpcs:ignore |
|
| 211 | 211 | |
| 212 | - return array_map( 'absint', wp_list_pluck( $results, 'product_count', 'rounded_average_rating' ) ); |
|
| 212 | + return array_map('absint', wp_list_pluck($results, 'product_count', 'rounded_average_rating')); |
|
| 213 | 213 | } |
| 214 | 214 | } |
@@ -9,490 +9,490 @@ |
||
| 9 | 9 | * Helper class to handle product queries for the API. |
| 10 | 10 | */ |
| 11 | 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 | - 'fields' => 'ids', |
|
| 31 | - 'ignore_sticky_posts' => true, |
|
| 32 | - 'post_status' => 'publish', |
|
| 33 | - 'date_query' => [], |
|
| 34 | - 'post_type' => 'product', |
|
| 35 | - ]; |
|
| 36 | - |
|
| 37 | - // If searching for a specific SKU, allow any post type. |
|
| 38 | - if ( ! empty( $request['sku'] ) ) { |
|
| 39 | - $args['post_type'] = [ 'product', 'product_variation' ]; |
|
| 40 | - } |
|
| 41 | - |
|
| 42 | - // Taxonomy query to filter products by type, category, tag, shipping class, and attribute. |
|
| 43 | - $tax_query = []; |
|
| 44 | - |
|
| 45 | - // Filter product type by slug. |
|
| 46 | - if ( ! empty( $request['type'] ) ) { |
|
| 47 | - if ( 'variation' === $request['type'] ) { |
|
| 48 | - $args['post_type'] = 'product_variation'; |
|
| 49 | - } else { |
|
| 50 | - $args['post_type'] = 'product'; |
|
| 51 | - $tax_query[] = [ |
|
| 52 | - 'taxonomy' => 'product_type', |
|
| 53 | - 'field' => 'slug', |
|
| 54 | - 'terms' => $request['type'], |
|
| 55 | - ]; |
|
| 56 | - } |
|
| 57 | - } |
|
| 58 | - |
|
| 59 | - if ( 'date' === $args['orderby'] ) { |
|
| 60 | - $args['orderby'] = 'date ID'; |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - // Set before into date query. Date query must be specified as an array of an array. |
|
| 64 | - if ( isset( $request['before'] ) ) { |
|
| 65 | - $args['date_query'][0]['before'] = $request['before']; |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - // Set after into date query. Date query must be specified as an array of an array. |
|
| 69 | - if ( isset( $request['after'] ) ) { |
|
| 70 | - $args['date_query'][0]['after'] = $request['after']; |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - // Set date query column. Defaults to post_date. |
|
| 74 | - if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) { |
|
| 75 | - $args['date_query'][0]['column'] = 'post_' . $request['date_column']; |
|
| 76 | - } |
|
| 77 | - |
|
| 78 | - // Set custom args to handle later during clauses. |
|
| 79 | - $custom_keys = [ |
|
| 80 | - 'sku', |
|
| 81 | - 'min_price', |
|
| 82 | - 'max_price', |
|
| 83 | - 'stock_status', |
|
| 84 | - ]; |
|
| 85 | - |
|
| 86 | - foreach ( $custom_keys as $key ) { |
|
| 87 | - if ( ! empty( $request[ $key ] ) ) { |
|
| 88 | - $args[ $key ] = $request[ $key ]; |
|
| 89 | - } |
|
| 90 | - } |
|
| 91 | - |
|
| 92 | - $operator_mapping = [ |
|
| 93 | - 'in' => 'IN', |
|
| 94 | - 'not_in' => 'NOT IN', |
|
| 95 | - 'and' => 'AND', |
|
| 96 | - ]; |
|
| 97 | - |
|
| 98 | - // Gets all registered product taxonomies and prefixes them with `tax_`. |
|
| 99 | - // This is neeeded to avoid situations where a users registers a new product taxonomy with the same name as default field. |
|
| 100 | - // eg an `sku` taxonomy will be mapped to `tax_sku`. |
|
| 101 | - $all_product_taxonomies = array_map( |
|
| 102 | - function ( $value ) { |
|
| 103 | - return '_unstable_tax_' . $value; |
|
| 104 | - }, |
|
| 105 | - get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ) |
|
| 106 | - ); |
|
| 107 | - |
|
| 108 | - // Map between taxonomy name and arg key. |
|
| 109 | - $default_taxonomies = [ |
|
| 110 | - 'product_cat' => 'category', |
|
| 111 | - 'product_tag' => 'tag', |
|
| 112 | - ]; |
|
| 113 | - |
|
| 114 | - $taxonomies = array_merge( $all_product_taxonomies, $default_taxonomies ); |
|
| 115 | - |
|
| 116 | - // Set tax_query for each passed arg. |
|
| 117 | - foreach ( $taxonomies as $taxonomy => $key ) { |
|
| 118 | - if ( ! empty( $request[ $key ] ) ) { |
|
| 119 | - $operator = $request->get_param( $key . '_operator' ) && isset( $operator_mapping[ $request->get_param( $key . '_operator' ) ] ) ? $operator_mapping[ $request->get_param( $key . '_operator' ) ] : 'IN'; |
|
| 120 | - $tax_query[] = [ |
|
| 121 | - 'taxonomy' => $taxonomy, |
|
| 122 | - 'field' => 'term_id', |
|
| 123 | - 'terms' => $request[ $key ], |
|
| 124 | - 'operator' => $operator, |
|
| 125 | - ]; |
|
| 126 | - } |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - // Filter by attributes. |
|
| 130 | - if ( ! empty( $request['attributes'] ) ) { |
|
| 131 | - $att_queries = []; |
|
| 132 | - |
|
| 133 | - foreach ( $request['attributes'] as $attribute ) { |
|
| 134 | - if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { |
|
| 135 | - continue; |
|
| 136 | - } |
|
| 137 | - if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { |
|
| 138 | - $operator = isset( $attribute['operator'], $operator_mapping[ $attribute['operator'] ] ) ? $operator_mapping[ $attribute['operator'] ] : 'IN'; |
|
| 139 | - $att_queries[] = [ |
|
| 140 | - 'taxonomy' => $attribute['attribute'], |
|
| 141 | - 'field' => ! empty( $attribute['term_id'] ) ? 'term_id' : 'slug', |
|
| 142 | - 'terms' => ! empty( $attribute['term_id'] ) ? $attribute['term_id'] : $attribute['slug'], |
|
| 143 | - 'operator' => $operator, |
|
| 144 | - ]; |
|
| 145 | - } |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - if ( 1 < count( $att_queries ) ) { |
|
| 149 | - // Add relation arg when using multiple attributes. |
|
| 150 | - $relation = $request->get_param( 'attribute_relation' ) && isset( $operator_mapping[ $request->get_param( 'attribute_relation' ) ] ) ? $operator_mapping[ $request->get_param( 'attribute_relation' ) ] : 'IN'; |
|
| 151 | - $tax_query[] = [ |
|
| 152 | - 'relation' => $relation, |
|
| 153 | - $att_queries, |
|
| 154 | - ]; |
|
| 155 | - } else { |
|
| 156 | - $tax_query = array_merge( $tax_query, $att_queries ); |
|
| 157 | - } |
|
| 158 | - } |
|
| 159 | - |
|
| 160 | - // Build tax_query if taxonomies are set. |
|
| 161 | - if ( ! empty( $tax_query ) ) { |
|
| 162 | - if ( ! empty( $args['tax_query'] ) ) { |
|
| 163 | - $args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // phpcs:ignore |
|
| 164 | - } else { |
|
| 165 | - $args['tax_query'] = $tax_query; // phpcs:ignore |
|
| 166 | - } |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - // Filter featured. |
|
| 170 | - if ( is_bool( $request['featured'] ) ) { |
|
| 171 | - $args['tax_query'][] = [ |
|
| 172 | - 'taxonomy' => 'product_visibility', |
|
| 173 | - 'field' => 'name', |
|
| 174 | - 'terms' => 'featured', |
|
| 175 | - 'operator' => true === $request['featured'] ? 'IN' : 'NOT IN', |
|
| 176 | - ]; |
|
| 177 | - } |
|
| 178 | - |
|
| 179 | - // Filter by on sale products. |
|
| 180 | - if ( is_bool( $request['on_sale'] ) ) { |
|
| 181 | - $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; |
|
| 182 | - $on_sale_ids = wc_get_product_ids_on_sale(); |
|
| 183 | - |
|
| 184 | - // Use 0 when there's no on sale products to avoid return all products. |
|
| 185 | - $on_sale_ids = empty( $on_sale_ids ) ? [ 0 ] : $on_sale_ids; |
|
| 186 | - |
|
| 187 | - $args[ $on_sale_key ] += $on_sale_ids; |
|
| 188 | - } |
|
| 189 | - |
|
| 190 | - $catalog_visibility = $request->get_param( 'catalog_visibility' ); |
|
| 191 | - $rating = $request->get_param( 'rating' ); |
|
| 192 | - $visibility_options = wc_get_product_visibility_options(); |
|
| 193 | - |
|
| 194 | - if ( in_array( $catalog_visibility, array_keys( $visibility_options ), true ) ) { |
|
| 195 | - $exclude_from_catalog = 'search' === $catalog_visibility ? '' : 'exclude-from-catalog'; |
|
| 196 | - $exclude_from_search = 'catalog' === $catalog_visibility ? '' : 'exclude-from-search'; |
|
| 197 | - |
|
| 198 | - $args['tax_query'][] = [ |
|
| 199 | - 'taxonomy' => 'product_visibility', |
|
| 200 | - 'field' => 'name', |
|
| 201 | - 'terms' => [ $exclude_from_catalog, $exclude_from_search ], |
|
| 202 | - 'operator' => 'hidden' === $catalog_visibility ? 'AND' : 'NOT IN', |
|
| 203 | - 'rating_filter' => true, |
|
| 204 | - ]; |
|
| 205 | - } |
|
| 206 | - |
|
| 207 | - if ( $rating ) { |
|
| 208 | - $rating_terms = []; |
|
| 209 | - foreach ( $rating as $value ) { |
|
| 210 | - $rating_terms[] = 'rated-' . $value; |
|
| 211 | - } |
|
| 212 | - $args['tax_query'][] = [ |
|
| 213 | - 'taxonomy' => 'product_visibility', |
|
| 214 | - 'field' => 'name', |
|
| 215 | - 'terms' => $rating_terms, |
|
| 216 | - ]; |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - $orderby = $request->get_param( 'orderby' ); |
|
| 220 | - $order = $request->get_param( 'order' ); |
|
| 221 | - |
|
| 222 | - $ordering_args = wc()->query->get_catalog_ordering_args( $orderby, $order ); |
|
| 223 | - $args['orderby'] = $ordering_args['orderby']; |
|
| 224 | - $args['order'] = $ordering_args['order']; |
|
| 225 | - |
|
| 226 | - if ( 'include' === $orderby ) { |
|
| 227 | - $args['orderby'] = 'post__in'; |
|
| 228 | - } elseif ( 'id' === $orderby ) { |
|
| 229 | - $args['orderby'] = 'ID'; // ID must be capitalized. |
|
| 230 | - } elseif ( 'slug' === $orderby ) { |
|
| 231 | - $args['orderby'] = 'name'; |
|
| 232 | - } |
|
| 233 | - |
|
| 234 | - if ( $ordering_args['meta_key'] ) { |
|
| 235 | - $args['meta_key'] = $ordering_args['meta_key']; // phpcs:ignore |
|
| 236 | - } |
|
| 237 | - |
|
| 238 | - return $args; |
|
| 239 | - } |
|
| 240 | - |
|
| 241 | - /** |
|
| 242 | - * Get results of query. |
|
| 243 | - * |
|
| 244 | - * @param \WP_REST_Request $request Request data. |
|
| 245 | - * @return array |
|
| 246 | - */ |
|
| 247 | - public function get_results( $request ) { |
|
| 248 | - $query_args = $this->prepare_objects_query( $request ); |
|
| 249 | - |
|
| 250 | - add_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10, 2 ); |
|
| 251 | - |
|
| 252 | - $query = new \WP_Query(); |
|
| 253 | - $results = $query->query( $query_args ); |
|
| 254 | - $total_posts = $query->found_posts; |
|
| 255 | - |
|
| 256 | - // Out-of-bounds, run the query again without LIMIT for total count. |
|
| 257 | - if ( $total_posts < 1 && $query_args['paged'] > 1 ) { |
|
| 258 | - unset( $query_args['paged'] ); |
|
| 259 | - $count_query = new \WP_Query(); |
|
| 260 | - $count_query->query( $query_args ); |
|
| 261 | - $total_posts = $count_query->found_posts; |
|
| 262 | - } |
|
| 263 | - |
|
| 264 | - remove_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10 ); |
|
| 265 | - |
|
| 266 | - return [ |
|
| 267 | - 'results' => $results, |
|
| 268 | - 'total' => (int) $total_posts, |
|
| 269 | - 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ) : 1, |
|
| 270 | - ]; |
|
| 271 | - } |
|
| 272 | - |
|
| 273 | - /** |
|
| 274 | - * Get objects. |
|
| 275 | - * |
|
| 276 | - * @param \WP_REST_Request $request Request data. |
|
| 277 | - * @return array |
|
| 278 | - */ |
|
| 279 | - public function get_objects( $request ) { |
|
| 280 | - $results = $this->get_results( $request ); |
|
| 281 | - |
|
| 282 | - return [ |
|
| 283 | - 'objects' => array_map( 'wc_get_product', $results['results'] ), |
|
| 284 | - 'total' => $results['total'], |
|
| 285 | - 'pages' => $results['pages'], |
|
| 286 | - ]; |
|
| 287 | - } |
|
| 288 | - |
|
| 289 | - /** |
|
| 290 | - * Get last modified date for all products. |
|
| 291 | - * |
|
| 292 | - * @return int timestamp. |
|
| 293 | - */ |
|
| 294 | - public function get_last_modified() { |
|
| 295 | - global $wpdb; |
|
| 296 | - |
|
| 297 | - return strtotime( $wpdb->get_var( "SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );" ) ); |
|
| 298 | - } |
|
| 299 | - |
|
| 300 | - /** |
|
| 301 | - * Add in conditional search filters for products. |
|
| 302 | - * |
|
| 303 | - * @param array $args Query args. |
|
| 304 | - * @param \WC_Query $wp_query WC_Query object. |
|
| 305 | - * @return array |
|
| 306 | - */ |
|
| 307 | - public function add_query_clauses( $args, $wp_query ) { |
|
| 308 | - global $wpdb; |
|
| 309 | - |
|
| 310 | - if ( $wp_query->get( 'search' ) ) { |
|
| 311 | - $search = '%' . $wpdb->esc_like( $wp_query->get( 'search' ) ) . '%'; |
|
| 312 | - $search_query = wc_product_sku_enabled() |
|
| 313 | - ? $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search ) |
|
| 314 | - : $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", $search ); |
|
| 315 | - $args['where'] .= $search_query; |
|
| 316 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - if ( $wp_query->get( 'sku' ) ) { |
|
| 320 | - $skus = explode( ',', $wp_query->get( 'sku' ) ); |
|
| 321 | - // Include the current string as a SKU too. |
|
| 322 | - if ( 1 < count( $skus ) ) { |
|
| 323 | - $skus[] = $wp_query->get( 'sku' ); |
|
| 324 | - } |
|
| 325 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 326 | - $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode( '","', array_map( 'esc_sql', $skus ) ) . '")'; |
|
| 327 | - } |
|
| 328 | - |
|
| 329 | - if ( $wp_query->get( 'stock_status' ) ) { |
|
| 330 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 331 | - $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; |
|
| 332 | - } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { |
|
| 333 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 334 | - $args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")'; |
|
| 335 | - } |
|
| 336 | - |
|
| 337 | - if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { |
|
| 338 | - $args = $this->add_price_filter_clauses( $args, $wp_query ); |
|
| 339 | - } |
|
| 340 | - |
|
| 341 | - return $args; |
|
| 342 | - } |
|
| 343 | - |
|
| 344 | - /** |
|
| 345 | - * Add in conditional price filters. |
|
| 346 | - * |
|
| 347 | - * @param array $args Query args. |
|
| 348 | - * @param \WC_Query $wp_query WC_Query object. |
|
| 349 | - * @return array |
|
| 350 | - */ |
|
| 351 | - protected function add_price_filter_clauses( $args, $wp_query ) { |
|
| 352 | - global $wpdb; |
|
| 353 | - |
|
| 354 | - $adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes(); |
|
| 355 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 356 | - |
|
| 357 | - if ( $wp_query->get( 'min_price' ) ) { |
|
| 358 | - $min_price_filter = $this->prepare_price_filter( $wp_query->get( 'min_price' ) ); |
|
| 359 | - |
|
| 360 | - if ( $adjust_for_taxes ) { |
|
| 361 | - $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'min_price', '>=' ); |
|
| 362 | - } else { |
|
| 363 | - $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter ); |
|
| 364 | - } |
|
| 365 | - } |
|
| 366 | - |
|
| 367 | - if ( $wp_query->get( 'max_price' ) ) { |
|
| 368 | - $max_price_filter = $this->prepare_price_filter( $wp_query->get( 'max_price' ) ); |
|
| 369 | - |
|
| 370 | - if ( $adjust_for_taxes ) { |
|
| 371 | - $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'max_price', '<=' ); |
|
| 372 | - } else { |
|
| 373 | - $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter ); |
|
| 374 | - } |
|
| 375 | - } |
|
| 376 | - |
|
| 377 | - return $args; |
|
| 378 | - } |
|
| 379 | - |
|
| 380 | - /** |
|
| 381 | - * Get query for price filters when dealing with displayed taxes. |
|
| 382 | - * |
|
| 383 | - * @param float $price_filter Price filter to apply. |
|
| 384 | - * @param string $column Price being filtered (min or max). |
|
| 385 | - * @param string $operator Comparison operator for column. |
|
| 386 | - * @return string Constructed query. |
|
| 387 | - */ |
|
| 388 | - protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) { |
|
| 389 | - global $wpdb; |
|
| 390 | - |
|
| 391 | - // Select only used tax classes to avoid unwanted calculations. |
|
| 392 | - $product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ); |
|
| 393 | - |
|
| 394 | - if ( empty( $product_tax_classes ) ) { |
|
| 395 | - return ''; |
|
| 396 | - } |
|
| 397 | - |
|
| 398 | - $or_queries = []; |
|
| 399 | - |
|
| 400 | - // We need to adjust the filter for each possible tax class and combine the queries into one. |
|
| 401 | - foreach ( $product_tax_classes as $tax_class ) { |
|
| 402 | - $adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class ); |
|
| 403 | - $or_queries[] = $wpdb->prepare( |
|
| 404 | - '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )', |
|
| 405 | - $tax_class, |
|
| 406 | - $adjusted_price_filter |
|
| 407 | - ); |
|
| 408 | - } |
|
| 409 | - |
|
| 410 | - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
|
| 411 | - return $wpdb->prepare( |
|
| 412 | - ' AND ( |
|
| 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 | + 'fields' => 'ids', |
|
| 31 | + 'ignore_sticky_posts' => true, |
|
| 32 | + 'post_status' => 'publish', |
|
| 33 | + 'date_query' => [], |
|
| 34 | + 'post_type' => 'product', |
|
| 35 | + ]; |
|
| 36 | + |
|
| 37 | + // If searching for a specific SKU, allow any post type. |
|
| 38 | + if ( ! empty( $request['sku'] ) ) { |
|
| 39 | + $args['post_type'] = [ 'product', 'product_variation' ]; |
|
| 40 | + } |
|
| 41 | + |
|
| 42 | + // Taxonomy query to filter products by type, category, tag, shipping class, and attribute. |
|
| 43 | + $tax_query = []; |
|
| 44 | + |
|
| 45 | + // Filter product type by slug. |
|
| 46 | + if ( ! empty( $request['type'] ) ) { |
|
| 47 | + if ( 'variation' === $request['type'] ) { |
|
| 48 | + $args['post_type'] = 'product_variation'; |
|
| 49 | + } else { |
|
| 50 | + $args['post_type'] = 'product'; |
|
| 51 | + $tax_query[] = [ |
|
| 52 | + 'taxonomy' => 'product_type', |
|
| 53 | + 'field' => 'slug', |
|
| 54 | + 'terms' => $request['type'], |
|
| 55 | + ]; |
|
| 56 | + } |
|
| 57 | + } |
|
| 58 | + |
|
| 59 | + if ( 'date' === $args['orderby'] ) { |
|
| 60 | + $args['orderby'] = 'date ID'; |
|
| 61 | + } |
|
| 62 | + |
|
| 63 | + // Set before into date query. Date query must be specified as an array of an array. |
|
| 64 | + if ( isset( $request['before'] ) ) { |
|
| 65 | + $args['date_query'][0]['before'] = $request['before']; |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + // Set after into date query. Date query must be specified as an array of an array. |
|
| 69 | + if ( isset( $request['after'] ) ) { |
|
| 70 | + $args['date_query'][0]['after'] = $request['after']; |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + // Set date query column. Defaults to post_date. |
|
| 74 | + if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) { |
|
| 75 | + $args['date_query'][0]['column'] = 'post_' . $request['date_column']; |
|
| 76 | + } |
|
| 77 | + |
|
| 78 | + // Set custom args to handle later during clauses. |
|
| 79 | + $custom_keys = [ |
|
| 80 | + 'sku', |
|
| 81 | + 'min_price', |
|
| 82 | + 'max_price', |
|
| 83 | + 'stock_status', |
|
| 84 | + ]; |
|
| 85 | + |
|
| 86 | + foreach ( $custom_keys as $key ) { |
|
| 87 | + if ( ! empty( $request[ $key ] ) ) { |
|
| 88 | + $args[ $key ] = $request[ $key ]; |
|
| 89 | + } |
|
| 90 | + } |
|
| 91 | + |
|
| 92 | + $operator_mapping = [ |
|
| 93 | + 'in' => 'IN', |
|
| 94 | + 'not_in' => 'NOT IN', |
|
| 95 | + 'and' => 'AND', |
|
| 96 | + ]; |
|
| 97 | + |
|
| 98 | + // Gets all registered product taxonomies and prefixes them with `tax_`. |
|
| 99 | + // This is neeeded to avoid situations where a users registers a new product taxonomy with the same name as default field. |
|
| 100 | + // eg an `sku` taxonomy will be mapped to `tax_sku`. |
|
| 101 | + $all_product_taxonomies = array_map( |
|
| 102 | + function ( $value ) { |
|
| 103 | + return '_unstable_tax_' . $value; |
|
| 104 | + }, |
|
| 105 | + get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ) |
|
| 106 | + ); |
|
| 107 | + |
|
| 108 | + // Map between taxonomy name and arg key. |
|
| 109 | + $default_taxonomies = [ |
|
| 110 | + 'product_cat' => 'category', |
|
| 111 | + 'product_tag' => 'tag', |
|
| 112 | + ]; |
|
| 113 | + |
|
| 114 | + $taxonomies = array_merge( $all_product_taxonomies, $default_taxonomies ); |
|
| 115 | + |
|
| 116 | + // Set tax_query for each passed arg. |
|
| 117 | + foreach ( $taxonomies as $taxonomy => $key ) { |
|
| 118 | + if ( ! empty( $request[ $key ] ) ) { |
|
| 119 | + $operator = $request->get_param( $key . '_operator' ) && isset( $operator_mapping[ $request->get_param( $key . '_operator' ) ] ) ? $operator_mapping[ $request->get_param( $key . '_operator' ) ] : 'IN'; |
|
| 120 | + $tax_query[] = [ |
|
| 121 | + 'taxonomy' => $taxonomy, |
|
| 122 | + 'field' => 'term_id', |
|
| 123 | + 'terms' => $request[ $key ], |
|
| 124 | + 'operator' => $operator, |
|
| 125 | + ]; |
|
| 126 | + } |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + // Filter by attributes. |
|
| 130 | + if ( ! empty( $request['attributes'] ) ) { |
|
| 131 | + $att_queries = []; |
|
| 132 | + |
|
| 133 | + foreach ( $request['attributes'] as $attribute ) { |
|
| 134 | + if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { |
|
| 135 | + continue; |
|
| 136 | + } |
|
| 137 | + if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { |
|
| 138 | + $operator = isset( $attribute['operator'], $operator_mapping[ $attribute['operator'] ] ) ? $operator_mapping[ $attribute['operator'] ] : 'IN'; |
|
| 139 | + $att_queries[] = [ |
|
| 140 | + 'taxonomy' => $attribute['attribute'], |
|
| 141 | + 'field' => ! empty( $attribute['term_id'] ) ? 'term_id' : 'slug', |
|
| 142 | + 'terms' => ! empty( $attribute['term_id'] ) ? $attribute['term_id'] : $attribute['slug'], |
|
| 143 | + 'operator' => $operator, |
|
| 144 | + ]; |
|
| 145 | + } |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + if ( 1 < count( $att_queries ) ) { |
|
| 149 | + // Add relation arg when using multiple attributes. |
|
| 150 | + $relation = $request->get_param( 'attribute_relation' ) && isset( $operator_mapping[ $request->get_param( 'attribute_relation' ) ] ) ? $operator_mapping[ $request->get_param( 'attribute_relation' ) ] : 'IN'; |
|
| 151 | + $tax_query[] = [ |
|
| 152 | + 'relation' => $relation, |
|
| 153 | + $att_queries, |
|
| 154 | + ]; |
|
| 155 | + } else { |
|
| 156 | + $tax_query = array_merge( $tax_query, $att_queries ); |
|
| 157 | + } |
|
| 158 | + } |
|
| 159 | + |
|
| 160 | + // Build tax_query if taxonomies are set. |
|
| 161 | + if ( ! empty( $tax_query ) ) { |
|
| 162 | + if ( ! empty( $args['tax_query'] ) ) { |
|
| 163 | + $args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // phpcs:ignore |
|
| 164 | + } else { |
|
| 165 | + $args['tax_query'] = $tax_query; // phpcs:ignore |
|
| 166 | + } |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + // Filter featured. |
|
| 170 | + if ( is_bool( $request['featured'] ) ) { |
|
| 171 | + $args['tax_query'][] = [ |
|
| 172 | + 'taxonomy' => 'product_visibility', |
|
| 173 | + 'field' => 'name', |
|
| 174 | + 'terms' => 'featured', |
|
| 175 | + 'operator' => true === $request['featured'] ? 'IN' : 'NOT IN', |
|
| 176 | + ]; |
|
| 177 | + } |
|
| 178 | + |
|
| 179 | + // Filter by on sale products. |
|
| 180 | + if ( is_bool( $request['on_sale'] ) ) { |
|
| 181 | + $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; |
|
| 182 | + $on_sale_ids = wc_get_product_ids_on_sale(); |
|
| 183 | + |
|
| 184 | + // Use 0 when there's no on sale products to avoid return all products. |
|
| 185 | + $on_sale_ids = empty( $on_sale_ids ) ? [ 0 ] : $on_sale_ids; |
|
| 186 | + |
|
| 187 | + $args[ $on_sale_key ] += $on_sale_ids; |
|
| 188 | + } |
|
| 189 | + |
|
| 190 | + $catalog_visibility = $request->get_param( 'catalog_visibility' ); |
|
| 191 | + $rating = $request->get_param( 'rating' ); |
|
| 192 | + $visibility_options = wc_get_product_visibility_options(); |
|
| 193 | + |
|
| 194 | + if ( in_array( $catalog_visibility, array_keys( $visibility_options ), true ) ) { |
|
| 195 | + $exclude_from_catalog = 'search' === $catalog_visibility ? '' : 'exclude-from-catalog'; |
|
| 196 | + $exclude_from_search = 'catalog' === $catalog_visibility ? '' : 'exclude-from-search'; |
|
| 197 | + |
|
| 198 | + $args['tax_query'][] = [ |
|
| 199 | + 'taxonomy' => 'product_visibility', |
|
| 200 | + 'field' => 'name', |
|
| 201 | + 'terms' => [ $exclude_from_catalog, $exclude_from_search ], |
|
| 202 | + 'operator' => 'hidden' === $catalog_visibility ? 'AND' : 'NOT IN', |
|
| 203 | + 'rating_filter' => true, |
|
| 204 | + ]; |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + if ( $rating ) { |
|
| 208 | + $rating_terms = []; |
|
| 209 | + foreach ( $rating as $value ) { |
|
| 210 | + $rating_terms[] = 'rated-' . $value; |
|
| 211 | + } |
|
| 212 | + $args['tax_query'][] = [ |
|
| 213 | + 'taxonomy' => 'product_visibility', |
|
| 214 | + 'field' => 'name', |
|
| 215 | + 'terms' => $rating_terms, |
|
| 216 | + ]; |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + $orderby = $request->get_param( 'orderby' ); |
|
| 220 | + $order = $request->get_param( 'order' ); |
|
| 221 | + |
|
| 222 | + $ordering_args = wc()->query->get_catalog_ordering_args( $orderby, $order ); |
|
| 223 | + $args['orderby'] = $ordering_args['orderby']; |
|
| 224 | + $args['order'] = $ordering_args['order']; |
|
| 225 | + |
|
| 226 | + if ( 'include' === $orderby ) { |
|
| 227 | + $args['orderby'] = 'post__in'; |
|
| 228 | + } elseif ( 'id' === $orderby ) { |
|
| 229 | + $args['orderby'] = 'ID'; // ID must be capitalized. |
|
| 230 | + } elseif ( 'slug' === $orderby ) { |
|
| 231 | + $args['orderby'] = 'name'; |
|
| 232 | + } |
|
| 233 | + |
|
| 234 | + if ( $ordering_args['meta_key'] ) { |
|
| 235 | + $args['meta_key'] = $ordering_args['meta_key']; // phpcs:ignore |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + return $args; |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + /** |
|
| 242 | + * Get results of query. |
|
| 243 | + * |
|
| 244 | + * @param \WP_REST_Request $request Request data. |
|
| 245 | + * @return array |
|
| 246 | + */ |
|
| 247 | + public function get_results( $request ) { |
|
| 248 | + $query_args = $this->prepare_objects_query( $request ); |
|
| 249 | + |
|
| 250 | + add_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10, 2 ); |
|
| 251 | + |
|
| 252 | + $query = new \WP_Query(); |
|
| 253 | + $results = $query->query( $query_args ); |
|
| 254 | + $total_posts = $query->found_posts; |
|
| 255 | + |
|
| 256 | + // Out-of-bounds, run the query again without LIMIT for total count. |
|
| 257 | + if ( $total_posts < 1 && $query_args['paged'] > 1 ) { |
|
| 258 | + unset( $query_args['paged'] ); |
|
| 259 | + $count_query = new \WP_Query(); |
|
| 260 | + $count_query->query( $query_args ); |
|
| 261 | + $total_posts = $count_query->found_posts; |
|
| 262 | + } |
|
| 263 | + |
|
| 264 | + remove_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10 ); |
|
| 265 | + |
|
| 266 | + return [ |
|
| 267 | + 'results' => $results, |
|
| 268 | + 'total' => (int) $total_posts, |
|
| 269 | + 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ) : 1, |
|
| 270 | + ]; |
|
| 271 | + } |
|
| 272 | + |
|
| 273 | + /** |
|
| 274 | + * Get objects. |
|
| 275 | + * |
|
| 276 | + * @param \WP_REST_Request $request Request data. |
|
| 277 | + * @return array |
|
| 278 | + */ |
|
| 279 | + public function get_objects( $request ) { |
|
| 280 | + $results = $this->get_results( $request ); |
|
| 281 | + |
|
| 282 | + return [ |
|
| 283 | + 'objects' => array_map( 'wc_get_product', $results['results'] ), |
|
| 284 | + 'total' => $results['total'], |
|
| 285 | + 'pages' => $results['pages'], |
|
| 286 | + ]; |
|
| 287 | + } |
|
| 288 | + |
|
| 289 | + /** |
|
| 290 | + * Get last modified date for all products. |
|
| 291 | + * |
|
| 292 | + * @return int timestamp. |
|
| 293 | + */ |
|
| 294 | + public function get_last_modified() { |
|
| 295 | + global $wpdb; |
|
| 296 | + |
|
| 297 | + return strtotime( $wpdb->get_var( "SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );" ) ); |
|
| 298 | + } |
|
| 299 | + |
|
| 300 | + /** |
|
| 301 | + * Add in conditional search filters for products. |
|
| 302 | + * |
|
| 303 | + * @param array $args Query args. |
|
| 304 | + * @param \WC_Query $wp_query WC_Query object. |
|
| 305 | + * @return array |
|
| 306 | + */ |
|
| 307 | + public function add_query_clauses( $args, $wp_query ) { |
|
| 308 | + global $wpdb; |
|
| 309 | + |
|
| 310 | + if ( $wp_query->get( 'search' ) ) { |
|
| 311 | + $search = '%' . $wpdb->esc_like( $wp_query->get( 'search' ) ) . '%'; |
|
| 312 | + $search_query = wc_product_sku_enabled() |
|
| 313 | + ? $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search ) |
|
| 314 | + : $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", $search ); |
|
| 315 | + $args['where'] .= $search_query; |
|
| 316 | + $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + if ( $wp_query->get( 'sku' ) ) { |
|
| 320 | + $skus = explode( ',', $wp_query->get( 'sku' ) ); |
|
| 321 | + // Include the current string as a SKU too. |
|
| 322 | + if ( 1 < count( $skus ) ) { |
|
| 323 | + $skus[] = $wp_query->get( 'sku' ); |
|
| 324 | + } |
|
| 325 | + $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 326 | + $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode( '","', array_map( 'esc_sql', $skus ) ) . '")'; |
|
| 327 | + } |
|
| 328 | + |
|
| 329 | + if ( $wp_query->get( 'stock_status' ) ) { |
|
| 330 | + $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 331 | + $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; |
|
| 332 | + } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { |
|
| 333 | + $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 334 | + $args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")'; |
|
| 335 | + } |
|
| 336 | + |
|
| 337 | + if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { |
|
| 338 | + $args = $this->add_price_filter_clauses( $args, $wp_query ); |
|
| 339 | + } |
|
| 340 | + |
|
| 341 | + return $args; |
|
| 342 | + } |
|
| 343 | + |
|
| 344 | + /** |
|
| 345 | + * Add in conditional price filters. |
|
| 346 | + * |
|
| 347 | + * @param array $args Query args. |
|
| 348 | + * @param \WC_Query $wp_query WC_Query object. |
|
| 349 | + * @return array |
|
| 350 | + */ |
|
| 351 | + protected function add_price_filter_clauses( $args, $wp_query ) { |
|
| 352 | + global $wpdb; |
|
| 353 | + |
|
| 354 | + $adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes(); |
|
| 355 | + $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 356 | + |
|
| 357 | + if ( $wp_query->get( 'min_price' ) ) { |
|
| 358 | + $min_price_filter = $this->prepare_price_filter( $wp_query->get( 'min_price' ) ); |
|
| 359 | + |
|
| 360 | + if ( $adjust_for_taxes ) { |
|
| 361 | + $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'min_price', '>=' ); |
|
| 362 | + } else { |
|
| 363 | + $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter ); |
|
| 364 | + } |
|
| 365 | + } |
|
| 366 | + |
|
| 367 | + if ( $wp_query->get( 'max_price' ) ) { |
|
| 368 | + $max_price_filter = $this->prepare_price_filter( $wp_query->get( 'max_price' ) ); |
|
| 369 | + |
|
| 370 | + if ( $adjust_for_taxes ) { |
|
| 371 | + $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'max_price', '<=' ); |
|
| 372 | + } else { |
|
| 373 | + $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter ); |
|
| 374 | + } |
|
| 375 | + } |
|
| 376 | + |
|
| 377 | + return $args; |
|
| 378 | + } |
|
| 379 | + |
|
| 380 | + /** |
|
| 381 | + * Get query for price filters when dealing with displayed taxes. |
|
| 382 | + * |
|
| 383 | + * @param float $price_filter Price filter to apply. |
|
| 384 | + * @param string $column Price being filtered (min or max). |
|
| 385 | + * @param string $operator Comparison operator for column. |
|
| 386 | + * @return string Constructed query. |
|
| 387 | + */ |
|
| 388 | + protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) { |
|
| 389 | + global $wpdb; |
|
| 390 | + |
|
| 391 | + // Select only used tax classes to avoid unwanted calculations. |
|
| 392 | + $product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ); |
|
| 393 | + |
|
| 394 | + if ( empty( $product_tax_classes ) ) { |
|
| 395 | + return ''; |
|
| 396 | + } |
|
| 397 | + |
|
| 398 | + $or_queries = []; |
|
| 399 | + |
|
| 400 | + // We need to adjust the filter for each possible tax class and combine the queries into one. |
|
| 401 | + foreach ( $product_tax_classes as $tax_class ) { |
|
| 402 | + $adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class ); |
|
| 403 | + $or_queries[] = $wpdb->prepare( |
|
| 404 | + '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )', |
|
| 405 | + $tax_class, |
|
| 406 | + $adjusted_price_filter |
|
| 407 | + ); |
|
| 408 | + } |
|
| 409 | + |
|
| 410 | + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
|
| 411 | + return $wpdb->prepare( |
|
| 412 | + ' AND ( |
|
| 413 | 413 | wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ') |
| 414 | 414 | OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f ) |
| 415 | 415 | ) ', |
| 416 | - $price_filter |
|
| 417 | - ); |
|
| 418 | - // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
|
| 419 | - } |
|
| 420 | - |
|
| 421 | - /** |
|
| 422 | - * If price filters need adjustment to work with displayed taxes, this returns true. |
|
| 423 | - * |
|
| 424 | - * This logic is used when prices are stored in the database differently to how they are being displayed, with regards |
|
| 425 | - * to taxes. |
|
| 426 | - * |
|
| 427 | - * @return boolean |
|
| 428 | - */ |
|
| 429 | - protected function adjust_price_filters_for_displayed_taxes() { |
|
| 430 | - $display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 431 | - $database = wc_prices_include_tax() ? 'incl' : 'excl'; |
|
| 432 | - |
|
| 433 | - return $display !== $database; |
|
| 434 | - } |
|
| 435 | - |
|
| 436 | - /** |
|
| 437 | - * Converts price filter from subunits to decimal. |
|
| 438 | - * |
|
| 439 | - * @param string|int $price_filter Raw price filter in subunit format. |
|
| 440 | - * @return float Price filter in decimal format. |
|
| 441 | - */ |
|
| 442 | - protected function prepare_price_filter( $price_filter ) { |
|
| 443 | - return floatval( $price_filter / ( 10 ** wc_get_price_decimals() ) ); |
|
| 444 | - } |
|
| 445 | - |
|
| 446 | - /** |
|
| 447 | - * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes. |
|
| 448 | - * |
|
| 449 | - * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core. |
|
| 450 | - * |
|
| 451 | - * @param float $price_filter Price filter amount as entered. |
|
| 452 | - * @param string $tax_class Tax class for adjustment. |
|
| 453 | - * @return float |
|
| 454 | - */ |
|
| 455 | - protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) { |
|
| 456 | - $tax_display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 457 | - $tax_rates = WC_Tax::get_rates( $tax_class ); |
|
| 458 | - $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); |
|
| 459 | - |
|
| 460 | - // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. |
|
| 461 | - if ( 'incl' === $tax_display ) { |
|
| 462 | - /** |
|
| 463 | - * Filters if taxes should be removed from locations outside the store base location. |
|
| 464 | - * |
|
| 465 | - * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing |
|
| 466 | - * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10 |
|
| 467 | - * regardless of location and taxes. |
|
| 468 | - * |
|
| 469 | - * @internal Matches filter name in WooCommerce core. |
|
| 470 | - * |
|
| 471 | - * @param boolean $adjust_non_base_location_prices True by default. |
|
| 472 | - * @return boolean |
|
| 473 | - */ |
|
| 474 | - $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 ); |
|
| 475 | - return $price_filter - array_sum( $taxes ); |
|
| 476 | - } |
|
| 477 | - |
|
| 478 | - // If prices are shown excl. tax, add taxes to match the prices stored in the DB. |
|
| 479 | - $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); |
|
| 480 | - |
|
| 481 | - return $price_filter + array_sum( $taxes ); |
|
| 482 | - } |
|
| 483 | - |
|
| 484 | - /** |
|
| 485 | - * Join wc_product_meta_lookup to posts if not already joined. |
|
| 486 | - * |
|
| 487 | - * @param string $sql SQL join. |
|
| 488 | - * @return string |
|
| 489 | - */ |
|
| 490 | - protected function append_product_sorting_table_join( $sql ) { |
|
| 491 | - global $wpdb; |
|
| 492 | - |
|
| 493 | - if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { |
|
| 494 | - $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; |
|
| 495 | - } |
|
| 496 | - return $sql; |
|
| 497 | - } |
|
| 416 | + $price_filter |
|
| 417 | + ); |
|
| 418 | + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
|
| 419 | + } |
|
| 420 | + |
|
| 421 | + /** |
|
| 422 | + * If price filters need adjustment to work with displayed taxes, this returns true. |
|
| 423 | + * |
|
| 424 | + * This logic is used when prices are stored in the database differently to how they are being displayed, with regards |
|
| 425 | + * to taxes. |
|
| 426 | + * |
|
| 427 | + * @return boolean |
|
| 428 | + */ |
|
| 429 | + protected function adjust_price_filters_for_displayed_taxes() { |
|
| 430 | + $display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 431 | + $database = wc_prices_include_tax() ? 'incl' : 'excl'; |
|
| 432 | + |
|
| 433 | + return $display !== $database; |
|
| 434 | + } |
|
| 435 | + |
|
| 436 | + /** |
|
| 437 | + * Converts price filter from subunits to decimal. |
|
| 438 | + * |
|
| 439 | + * @param string|int $price_filter Raw price filter in subunit format. |
|
| 440 | + * @return float Price filter in decimal format. |
|
| 441 | + */ |
|
| 442 | + protected function prepare_price_filter( $price_filter ) { |
|
| 443 | + return floatval( $price_filter / ( 10 ** wc_get_price_decimals() ) ); |
|
| 444 | + } |
|
| 445 | + |
|
| 446 | + /** |
|
| 447 | + * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes. |
|
| 448 | + * |
|
| 449 | + * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core. |
|
| 450 | + * |
|
| 451 | + * @param float $price_filter Price filter amount as entered. |
|
| 452 | + * @param string $tax_class Tax class for adjustment. |
|
| 453 | + * @return float |
|
| 454 | + */ |
|
| 455 | + protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) { |
|
| 456 | + $tax_display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 457 | + $tax_rates = WC_Tax::get_rates( $tax_class ); |
|
| 458 | + $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); |
|
| 459 | + |
|
| 460 | + // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. |
|
| 461 | + if ( 'incl' === $tax_display ) { |
|
| 462 | + /** |
|
| 463 | + * Filters if taxes should be removed from locations outside the store base location. |
|
| 464 | + * |
|
| 465 | + * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing |
|
| 466 | + * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10 |
|
| 467 | + * regardless of location and taxes. |
|
| 468 | + * |
|
| 469 | + * @internal Matches filter name in WooCommerce core. |
|
| 470 | + * |
|
| 471 | + * @param boolean $adjust_non_base_location_prices True by default. |
|
| 472 | + * @return boolean |
|
| 473 | + */ |
|
| 474 | + $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 ); |
|
| 475 | + return $price_filter - array_sum( $taxes ); |
|
| 476 | + } |
|
| 477 | + |
|
| 478 | + // If prices are shown excl. tax, add taxes to match the prices stored in the DB. |
|
| 479 | + $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); |
|
| 480 | + |
|
| 481 | + return $price_filter + array_sum( $taxes ); |
|
| 482 | + } |
|
| 483 | + |
|
| 484 | + /** |
|
| 485 | + * Join wc_product_meta_lookup to posts if not already joined. |
|
| 486 | + * |
|
| 487 | + * @param string $sql SQL join. |
|
| 488 | + * @return string |
|
| 489 | + */ |
|
| 490 | + protected function append_product_sorting_table_join( $sql ) { |
|
| 491 | + global $wpdb; |
|
| 492 | + |
|
| 493 | + if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { |
|
| 494 | + $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; |
|
| 495 | + } |
|
| 496 | + return $sql; |
|
| 497 | + } |
|
| 498 | 498 | } |
@@ -15,7 +15,7 @@ discard block |
||
| 15 | 15 | * @param \WP_REST_Request $request Request data. |
| 16 | 16 | * @return array |
| 17 | 17 | */ |
| 18 | - public function prepare_objects_query( $request ) { |
|
| 18 | + public function prepare_objects_query($request) { |
|
| 19 | 19 | $args = [ |
| 20 | 20 | 'offset' => $request['offset'], |
| 21 | 21 | 'order' => $request['order'], |
@@ -35,16 +35,16 @@ discard block |
||
| 35 | 35 | ]; |
| 36 | 36 | |
| 37 | 37 | // If searching for a specific SKU, allow any post type. |
| 38 | - if ( ! empty( $request['sku'] ) ) { |
|
| 39 | - $args['post_type'] = [ 'product', 'product_variation' ]; |
|
| 38 | + if (!empty($request['sku'])) { |
|
| 39 | + $args['post_type'] = ['product', 'product_variation']; |
|
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | // Taxonomy query to filter products by type, category, tag, shipping class, and attribute. |
| 43 | 43 | $tax_query = []; |
| 44 | 44 | |
| 45 | 45 | // Filter product type by slug. |
| 46 | - if ( ! empty( $request['type'] ) ) { |
|
| 47 | - if ( 'variation' === $request['type'] ) { |
|
| 46 | + if (!empty($request['type'])) { |
|
| 47 | + if ('variation' === $request['type']) { |
|
| 48 | 48 | $args['post_type'] = 'product_variation'; |
| 49 | 49 | } else { |
| 50 | 50 | $args['post_type'] = 'product'; |
@@ -56,22 +56,22 @@ discard block |
||
| 56 | 56 | } |
| 57 | 57 | } |
| 58 | 58 | |
| 59 | - if ( 'date' === $args['orderby'] ) { |
|
| 59 | + if ('date' === $args['orderby']) { |
|
| 60 | 60 | $args['orderby'] = 'date ID'; |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | // Set before into date query. Date query must be specified as an array of an array. |
| 64 | - if ( isset( $request['before'] ) ) { |
|
| 64 | + if (isset($request['before'])) { |
|
| 65 | 65 | $args['date_query'][0]['before'] = $request['before']; |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | // Set after into date query. Date query must be specified as an array of an array. |
| 69 | - if ( isset( $request['after'] ) ) { |
|
| 69 | + if (isset($request['after'])) { |
|
| 70 | 70 | $args['date_query'][0]['after'] = $request['after']; |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | // Set date query column. Defaults to post_date. |
| 74 | - if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) { |
|
| 74 | + if (isset($request['date_column']) && !empty($args['date_query'][0])) { |
|
| 75 | 75 | $args['date_query'][0]['column'] = 'post_' . $request['date_column']; |
| 76 | 76 | } |
| 77 | 77 | |
@@ -83,9 +83,9 @@ discard block |
||
| 83 | 83 | 'stock_status', |
| 84 | 84 | ]; |
| 85 | 85 | |
| 86 | - foreach ( $custom_keys as $key ) { |
|
| 87 | - if ( ! empty( $request[ $key ] ) ) { |
|
| 88 | - $args[ $key ] = $request[ $key ]; |
|
| 86 | + foreach ($custom_keys as $key) { |
|
| 87 | + if (!empty($request[$key])) { |
|
| 88 | + $args[$key] = $request[$key]; |
|
| 89 | 89 | } |
| 90 | 90 | } |
| 91 | 91 | |
@@ -99,10 +99,10 @@ discard block |
||
| 99 | 99 | // This is neeeded to avoid situations where a users registers a new product taxonomy with the same name as default field. |
| 100 | 100 | // eg an `sku` taxonomy will be mapped to `tax_sku`. |
| 101 | 101 | $all_product_taxonomies = array_map( |
| 102 | - function ( $value ) { |
|
| 102 | + function($value) { |
|
| 103 | 103 | return '_unstable_tax_' . $value; |
| 104 | 104 | }, |
| 105 | - get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ) |
|
| 105 | + get_taxonomies(array('object_type' => array('product')), 'names') |
|
| 106 | 106 | ); |
| 107 | 107 | |
| 108 | 108 | // Map between taxonomy name and arg key. |
@@ -111,63 +111,63 @@ discard block |
||
| 111 | 111 | 'product_tag' => 'tag', |
| 112 | 112 | ]; |
| 113 | 113 | |
| 114 | - $taxonomies = array_merge( $all_product_taxonomies, $default_taxonomies ); |
|
| 114 | + $taxonomies = array_merge($all_product_taxonomies, $default_taxonomies); |
|
| 115 | 115 | |
| 116 | 116 | // Set tax_query for each passed arg. |
| 117 | - foreach ( $taxonomies as $taxonomy => $key ) { |
|
| 118 | - if ( ! empty( $request[ $key ] ) ) { |
|
| 119 | - $operator = $request->get_param( $key . '_operator' ) && isset( $operator_mapping[ $request->get_param( $key . '_operator' ) ] ) ? $operator_mapping[ $request->get_param( $key . '_operator' ) ] : 'IN'; |
|
| 117 | + foreach ($taxonomies as $taxonomy => $key) { |
|
| 118 | + if (!empty($request[$key])) { |
|
| 119 | + $operator = $request->get_param($key . '_operator') && isset($operator_mapping[$request->get_param($key . '_operator')]) ? $operator_mapping[$request->get_param($key . '_operator')] : 'IN'; |
|
| 120 | 120 | $tax_query[] = [ |
| 121 | 121 | 'taxonomy' => $taxonomy, |
| 122 | 122 | 'field' => 'term_id', |
| 123 | - 'terms' => $request[ $key ], |
|
| 123 | + 'terms' => $request[$key], |
|
| 124 | 124 | 'operator' => $operator, |
| 125 | 125 | ]; |
| 126 | 126 | } |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | 129 | // Filter by attributes. |
| 130 | - if ( ! empty( $request['attributes'] ) ) { |
|
| 130 | + if (!empty($request['attributes'])) { |
|
| 131 | 131 | $att_queries = []; |
| 132 | 132 | |
| 133 | - foreach ( $request['attributes'] as $attribute ) { |
|
| 134 | - if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { |
|
| 133 | + foreach ($request['attributes'] as $attribute) { |
|
| 134 | + if (empty($attribute['term_id']) && empty($attribute['slug'])) { |
|
| 135 | 135 | continue; |
| 136 | 136 | } |
| 137 | - if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { |
|
| 138 | - $operator = isset( $attribute['operator'], $operator_mapping[ $attribute['operator'] ] ) ? $operator_mapping[ $attribute['operator'] ] : 'IN'; |
|
| 137 | + if (in_array($attribute['attribute'], wc_get_attribute_taxonomy_names(), true)) { |
|
| 138 | + $operator = isset($attribute['operator'], $operator_mapping[$attribute['operator']]) ? $operator_mapping[$attribute['operator']] : 'IN'; |
|
| 139 | 139 | $att_queries[] = [ |
| 140 | 140 | 'taxonomy' => $attribute['attribute'], |
| 141 | - 'field' => ! empty( $attribute['term_id'] ) ? 'term_id' : 'slug', |
|
| 142 | - 'terms' => ! empty( $attribute['term_id'] ) ? $attribute['term_id'] : $attribute['slug'], |
|
| 141 | + 'field' => !empty($attribute['term_id']) ? 'term_id' : 'slug', |
|
| 142 | + 'terms' => !empty($attribute['term_id']) ? $attribute['term_id'] : $attribute['slug'], |
|
| 143 | 143 | 'operator' => $operator, |
| 144 | 144 | ]; |
| 145 | 145 | } |
| 146 | 146 | } |
| 147 | 147 | |
| 148 | - if ( 1 < count( $att_queries ) ) { |
|
| 148 | + if (1 < count($att_queries)) { |
|
| 149 | 149 | // Add relation arg when using multiple attributes. |
| 150 | - $relation = $request->get_param( 'attribute_relation' ) && isset( $operator_mapping[ $request->get_param( 'attribute_relation' ) ] ) ? $operator_mapping[ $request->get_param( 'attribute_relation' ) ] : 'IN'; |
|
| 150 | + $relation = $request->get_param('attribute_relation') && isset($operator_mapping[$request->get_param('attribute_relation')]) ? $operator_mapping[$request->get_param('attribute_relation')] : 'IN'; |
|
| 151 | 151 | $tax_query[] = [ |
| 152 | 152 | 'relation' => $relation, |
| 153 | 153 | $att_queries, |
| 154 | 154 | ]; |
| 155 | 155 | } else { |
| 156 | - $tax_query = array_merge( $tax_query, $att_queries ); |
|
| 156 | + $tax_query = array_merge($tax_query, $att_queries); |
|
| 157 | 157 | } |
| 158 | 158 | } |
| 159 | 159 | |
| 160 | 160 | // Build tax_query if taxonomies are set. |
| 161 | - if ( ! empty( $tax_query ) ) { |
|
| 162 | - if ( ! empty( $args['tax_query'] ) ) { |
|
| 163 | - $args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // phpcs:ignore |
|
| 161 | + if (!empty($tax_query)) { |
|
| 162 | + if (!empty($args['tax_query'])) { |
|
| 163 | + $args['tax_query'] = array_merge($tax_query, $args['tax_query']); // phpcs:ignore |
|
| 164 | 164 | } else { |
| 165 | 165 | $args['tax_query'] = $tax_query; // phpcs:ignore |
| 166 | 166 | } |
| 167 | 167 | } |
| 168 | 168 | |
| 169 | 169 | // Filter featured. |
| 170 | - if ( is_bool( $request['featured'] ) ) { |
|
| 170 | + if (is_bool($request['featured'])) { |
|
| 171 | 171 | $args['tax_query'][] = [ |
| 172 | 172 | 'taxonomy' => 'product_visibility', |
| 173 | 173 | 'field' => 'name', |
@@ -177,36 +177,36 @@ discard block |
||
| 177 | 177 | } |
| 178 | 178 | |
| 179 | 179 | // Filter by on sale products. |
| 180 | - if ( is_bool( $request['on_sale'] ) ) { |
|
| 180 | + if (is_bool($request['on_sale'])) { |
|
| 181 | 181 | $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; |
| 182 | 182 | $on_sale_ids = wc_get_product_ids_on_sale(); |
| 183 | 183 | |
| 184 | 184 | // Use 0 when there's no on sale products to avoid return all products. |
| 185 | - $on_sale_ids = empty( $on_sale_ids ) ? [ 0 ] : $on_sale_ids; |
|
| 185 | + $on_sale_ids = empty($on_sale_ids) ? [0] : $on_sale_ids; |
|
| 186 | 186 | |
| 187 | - $args[ $on_sale_key ] += $on_sale_ids; |
|
| 187 | + $args[$on_sale_key] += $on_sale_ids; |
|
| 188 | 188 | } |
| 189 | 189 | |
| 190 | - $catalog_visibility = $request->get_param( 'catalog_visibility' ); |
|
| 191 | - $rating = $request->get_param( 'rating' ); |
|
| 190 | + $catalog_visibility = $request->get_param('catalog_visibility'); |
|
| 191 | + $rating = $request->get_param('rating'); |
|
| 192 | 192 | $visibility_options = wc_get_product_visibility_options(); |
| 193 | 193 | |
| 194 | - if ( in_array( $catalog_visibility, array_keys( $visibility_options ), true ) ) { |
|
| 194 | + if (in_array($catalog_visibility, array_keys($visibility_options), true)) { |
|
| 195 | 195 | $exclude_from_catalog = 'search' === $catalog_visibility ? '' : 'exclude-from-catalog'; |
| 196 | 196 | $exclude_from_search = 'catalog' === $catalog_visibility ? '' : 'exclude-from-search'; |
| 197 | 197 | |
| 198 | 198 | $args['tax_query'][] = [ |
| 199 | 199 | 'taxonomy' => 'product_visibility', |
| 200 | 200 | 'field' => 'name', |
| 201 | - 'terms' => [ $exclude_from_catalog, $exclude_from_search ], |
|
| 201 | + 'terms' => [$exclude_from_catalog, $exclude_from_search], |
|
| 202 | 202 | 'operator' => 'hidden' === $catalog_visibility ? 'AND' : 'NOT IN', |
| 203 | 203 | 'rating_filter' => true, |
| 204 | 204 | ]; |
| 205 | 205 | } |
| 206 | 206 | |
| 207 | - if ( $rating ) { |
|
| 207 | + if ($rating) { |
|
| 208 | 208 | $rating_terms = []; |
| 209 | - foreach ( $rating as $value ) { |
|
| 209 | + foreach ($rating as $value) { |
|
| 210 | 210 | $rating_terms[] = 'rated-' . $value; |
| 211 | 211 | } |
| 212 | 212 | $args['tax_query'][] = [ |
@@ -216,22 +216,22 @@ discard block |
||
| 216 | 216 | ]; |
| 217 | 217 | } |
| 218 | 218 | |
| 219 | - $orderby = $request->get_param( 'orderby' ); |
|
| 220 | - $order = $request->get_param( 'order' ); |
|
| 219 | + $orderby = $request->get_param('orderby'); |
|
| 220 | + $order = $request->get_param('order'); |
|
| 221 | 221 | |
| 222 | - $ordering_args = wc()->query->get_catalog_ordering_args( $orderby, $order ); |
|
| 222 | + $ordering_args = wc()->query->get_catalog_ordering_args($orderby, $order); |
|
| 223 | 223 | $args['orderby'] = $ordering_args['orderby']; |
| 224 | 224 | $args['order'] = $ordering_args['order']; |
| 225 | 225 | |
| 226 | - if ( 'include' === $orderby ) { |
|
| 226 | + if ('include' === $orderby) { |
|
| 227 | 227 | $args['orderby'] = 'post__in'; |
| 228 | - } elseif ( 'id' === $orderby ) { |
|
| 228 | + } elseif ('id' === $orderby) { |
|
| 229 | 229 | $args['orderby'] = 'ID'; // ID must be capitalized. |
| 230 | - } elseif ( 'slug' === $orderby ) { |
|
| 230 | + } elseif ('slug' === $orderby) { |
|
| 231 | 231 | $args['orderby'] = 'name'; |
| 232 | 232 | } |
| 233 | 233 | |
| 234 | - if ( $ordering_args['meta_key'] ) { |
|
| 234 | + if ($ordering_args['meta_key']) { |
|
| 235 | 235 | $args['meta_key'] = $ordering_args['meta_key']; // phpcs:ignore |
| 236 | 236 | } |
| 237 | 237 | |
@@ -244,29 +244,29 @@ discard block |
||
| 244 | 244 | * @param \WP_REST_Request $request Request data. |
| 245 | 245 | * @return array |
| 246 | 246 | */ |
| 247 | - public function get_results( $request ) { |
|
| 248 | - $query_args = $this->prepare_objects_query( $request ); |
|
| 247 | + public function get_results($request) { |
|
| 248 | + $query_args = $this->prepare_objects_query($request); |
|
| 249 | 249 | |
| 250 | - add_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10, 2 ); |
|
| 250 | + add_filter('posts_clauses', [$this, 'add_query_clauses'], 10, 2); |
|
| 251 | 251 | |
| 252 | 252 | $query = new \WP_Query(); |
| 253 | - $results = $query->query( $query_args ); |
|
| 253 | + $results = $query->query($query_args); |
|
| 254 | 254 | $total_posts = $query->found_posts; |
| 255 | 255 | |
| 256 | 256 | // Out-of-bounds, run the query again without LIMIT for total count. |
| 257 | - if ( $total_posts < 1 && $query_args['paged'] > 1 ) { |
|
| 258 | - unset( $query_args['paged'] ); |
|
| 257 | + if ($total_posts < 1 && $query_args['paged'] > 1) { |
|
| 258 | + unset($query_args['paged']); |
|
| 259 | 259 | $count_query = new \WP_Query(); |
| 260 | - $count_query->query( $query_args ); |
|
| 260 | + $count_query->query($query_args); |
|
| 261 | 261 | $total_posts = $count_query->found_posts; |
| 262 | 262 | } |
| 263 | 263 | |
| 264 | - remove_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10 ); |
|
| 264 | + remove_filter('posts_clauses', [$this, 'add_query_clauses'], 10); |
|
| 265 | 265 | |
| 266 | 266 | return [ |
| 267 | 267 | 'results' => $results, |
| 268 | 268 | 'total' => (int) $total_posts, |
| 269 | - 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ) : 1, |
|
| 269 | + 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil($total_posts / (int) $query->query_vars['posts_per_page']) : 1, |
|
| 270 | 270 | ]; |
| 271 | 271 | } |
| 272 | 272 | |
@@ -276,11 +276,11 @@ discard block |
||
| 276 | 276 | * @param \WP_REST_Request $request Request data. |
| 277 | 277 | * @return array |
| 278 | 278 | */ |
| 279 | - public function get_objects( $request ) { |
|
| 280 | - $results = $this->get_results( $request ); |
|
| 279 | + public function get_objects($request) { |
|
| 280 | + $results = $this->get_results($request); |
|
| 281 | 281 | |
| 282 | 282 | return [ |
| 283 | - 'objects' => array_map( 'wc_get_product', $results['results'] ), |
|
| 283 | + 'objects' => array_map('wc_get_product', $results['results']), |
|
| 284 | 284 | 'total' => $results['total'], |
| 285 | 285 | 'pages' => $results['pages'], |
| 286 | 286 | ]; |
@@ -294,7 +294,7 @@ discard block |
||
| 294 | 294 | public function get_last_modified() { |
| 295 | 295 | global $wpdb; |
| 296 | 296 | |
| 297 | - return strtotime( $wpdb->get_var( "SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );" ) ); |
|
| 297 | + return strtotime($wpdb->get_var("SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );")); |
|
| 298 | 298 | } |
| 299 | 299 | |
| 300 | 300 | /** |
@@ -304,38 +304,38 @@ discard block |
||
| 304 | 304 | * @param \WC_Query $wp_query WC_Query object. |
| 305 | 305 | * @return array |
| 306 | 306 | */ |
| 307 | - public function add_query_clauses( $args, $wp_query ) { |
|
| 307 | + public function add_query_clauses($args, $wp_query) { |
|
| 308 | 308 | global $wpdb; |
| 309 | 309 | |
| 310 | - if ( $wp_query->get( 'search' ) ) { |
|
| 311 | - $search = '%' . $wpdb->esc_like( $wp_query->get( 'search' ) ) . '%'; |
|
| 310 | + if ($wp_query->get('search')) { |
|
| 311 | + $search = '%' . $wpdb->esc_like($wp_query->get('search')) . '%'; |
|
| 312 | 312 | $search_query = wc_product_sku_enabled() |
| 313 | - ? $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search ) |
|
| 314 | - : $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", $search ); |
|
| 313 | + ? $wpdb->prepare(" AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search) |
|
| 314 | + : $wpdb->prepare(" AND $wpdb->posts.post_title LIKE %s ", $search); |
|
| 315 | 315 | $args['where'] .= $search_query; |
| 316 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 316 | + $args['join'] = $this->append_product_sorting_table_join($args['join']); |
|
| 317 | 317 | } |
| 318 | 318 | |
| 319 | - if ( $wp_query->get( 'sku' ) ) { |
|
| 320 | - $skus = explode( ',', $wp_query->get( 'sku' ) ); |
|
| 319 | + if ($wp_query->get('sku')) { |
|
| 320 | + $skus = explode(',', $wp_query->get('sku')); |
|
| 321 | 321 | // Include the current string as a SKU too. |
| 322 | - if ( 1 < count( $skus ) ) { |
|
| 323 | - $skus[] = $wp_query->get( 'sku' ); |
|
| 322 | + if (1 < count($skus)) { |
|
| 323 | + $skus[] = $wp_query->get('sku'); |
|
| 324 | 324 | } |
| 325 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 326 | - $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode( '","', array_map( 'esc_sql', $skus ) ) . '")'; |
|
| 325 | + $args['join'] = $this->append_product_sorting_table_join($args['join']); |
|
| 326 | + $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode('","', array_map('esc_sql', $skus)) . '")'; |
|
| 327 | 327 | } |
| 328 | 328 | |
| 329 | - if ( $wp_query->get( 'stock_status' ) ) { |
|
| 330 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 331 | - $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; |
|
| 332 | - } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { |
|
| 333 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 329 | + if ($wp_query->get('stock_status')) { |
|
| 330 | + $args['join'] = $this->append_product_sorting_table_join($args['join']); |
|
| 331 | + $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode('","', array_map('esc_sql', $wp_query->get('stock_status'))) . '")'; |
|
| 332 | + } elseif ('yes' === get_option('woocommerce_hide_out_of_stock_items')) { |
|
| 333 | + $args['join'] = $this->append_product_sorting_table_join($args['join']); |
|
| 334 | 334 | $args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")'; |
| 335 | 335 | } |
| 336 | 336 | |
| 337 | - if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { |
|
| 338 | - $args = $this->add_price_filter_clauses( $args, $wp_query ); |
|
| 337 | + if ($wp_query->get('min_price') || $wp_query->get('max_price')) { |
|
| 338 | + $args = $this->add_price_filter_clauses($args, $wp_query); |
|
| 339 | 339 | } |
| 340 | 340 | |
| 341 | 341 | return $args; |
@@ -348,29 +348,29 @@ discard block |
||
| 348 | 348 | * @param \WC_Query $wp_query WC_Query object. |
| 349 | 349 | * @return array |
| 350 | 350 | */ |
| 351 | - protected function add_price_filter_clauses( $args, $wp_query ) { |
|
| 351 | + protected function add_price_filter_clauses($args, $wp_query) { |
|
| 352 | 352 | global $wpdb; |
| 353 | 353 | |
| 354 | 354 | $adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes(); |
| 355 | - $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); |
|
| 355 | + $args['join'] = $this->append_product_sorting_table_join($args['join']); |
|
| 356 | 356 | |
| 357 | - if ( $wp_query->get( 'min_price' ) ) { |
|
| 358 | - $min_price_filter = $this->prepare_price_filter( $wp_query->get( 'min_price' ) ); |
|
| 357 | + if ($wp_query->get('min_price')) { |
|
| 358 | + $min_price_filter = $this->prepare_price_filter($wp_query->get('min_price')); |
|
| 359 | 359 | |
| 360 | - if ( $adjust_for_taxes ) { |
|
| 361 | - $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'min_price', '>=' ); |
|
| 360 | + if ($adjust_for_taxes) { |
|
| 361 | + $args['where'] .= $this->get_price_filter_query_for_displayed_taxes($min_price_filter, 'min_price', '>='); |
|
| 362 | 362 | } else { |
| 363 | - $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter ); |
|
| 363 | + $args['where'] .= $wpdb->prepare(' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter); |
|
| 364 | 364 | } |
| 365 | 365 | } |
| 366 | 366 | |
| 367 | - if ( $wp_query->get( 'max_price' ) ) { |
|
| 368 | - $max_price_filter = $this->prepare_price_filter( $wp_query->get( 'max_price' ) ); |
|
| 367 | + if ($wp_query->get('max_price')) { |
|
| 368 | + $max_price_filter = $this->prepare_price_filter($wp_query->get('max_price')); |
|
| 369 | 369 | |
| 370 | - if ( $adjust_for_taxes ) { |
|
| 371 | - $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'max_price', '<=' ); |
|
| 370 | + if ($adjust_for_taxes) { |
|
| 371 | + $args['where'] .= $this->get_price_filter_query_for_displayed_taxes($max_price_filter, 'max_price', '<='); |
|
| 372 | 372 | } else { |
| 373 | - $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter ); |
|
| 373 | + $args['where'] .= $wpdb->prepare(' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter); |
|
| 374 | 374 | } |
| 375 | 375 | } |
| 376 | 376 | |
@@ -385,23 +385,23 @@ discard block |
||
| 385 | 385 | * @param string $operator Comparison operator for column. |
| 386 | 386 | * @return string Constructed query. |
| 387 | 387 | */ |
| 388 | - protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) { |
|
| 388 | + protected function get_price_filter_query_for_displayed_taxes($price_filter, $column = 'min_price', $operator = '>=') { |
|
| 389 | 389 | global $wpdb; |
| 390 | 390 | |
| 391 | 391 | // Select only used tax classes to avoid unwanted calculations. |
| 392 | - $product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ); |
|
| 392 | + $product_tax_classes = $wpdb->get_col("SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};"); |
|
| 393 | 393 | |
| 394 | - if ( empty( $product_tax_classes ) ) { |
|
| 394 | + if (empty($product_tax_classes)) { |
|
| 395 | 395 | return ''; |
| 396 | 396 | } |
| 397 | 397 | |
| 398 | 398 | $or_queries = []; |
| 399 | 399 | |
| 400 | 400 | // We need to adjust the filter for each possible tax class and combine the queries into one. |
| 401 | - foreach ( $product_tax_classes as $tax_class ) { |
|
| 402 | - $adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class ); |
|
| 401 | + foreach ($product_tax_classes as $tax_class) { |
|
| 402 | + $adjusted_price_filter = $this->adjust_price_filter_for_tax_class($price_filter, $tax_class); |
|
| 403 | 403 | $or_queries[] = $wpdb->prepare( |
| 404 | - '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )', |
|
| 404 | + '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql($column) . '` ' . esc_sql($operator) . ' %f )', |
|
| 405 | 405 | $tax_class, |
| 406 | 406 | $adjusted_price_filter |
| 407 | 407 | ); |
@@ -410,8 +410,8 @@ discard block |
||
| 410 | 410 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared |
| 411 | 411 | return $wpdb->prepare( |
| 412 | 412 | ' AND ( |
| 413 | - wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ') |
|
| 414 | - OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f ) |
|
| 413 | + wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode(' OR ', $or_queries) . ') |
|
| 414 | + OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql($column) . '` ' . esc_sql($operator) . ' %f ) |
|
| 415 | 415 | ) ', |
| 416 | 416 | $price_filter |
| 417 | 417 | ); |
@@ -427,7 +427,7 @@ discard block |
||
| 427 | 427 | * @return boolean |
| 428 | 428 | */ |
| 429 | 429 | protected function adjust_price_filters_for_displayed_taxes() { |
| 430 | - $display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 430 | + $display = get_option('woocommerce_tax_display_shop'); |
|
| 431 | 431 | $database = wc_prices_include_tax() ? 'incl' : 'excl'; |
| 432 | 432 | |
| 433 | 433 | return $display !== $database; |
@@ -439,8 +439,8 @@ discard block |
||
| 439 | 439 | * @param string|int $price_filter Raw price filter in subunit format. |
| 440 | 440 | * @return float Price filter in decimal format. |
| 441 | 441 | */ |
| 442 | - protected function prepare_price_filter( $price_filter ) { |
|
| 443 | - return floatval( $price_filter / ( 10 ** wc_get_price_decimals() ) ); |
|
| 442 | + protected function prepare_price_filter($price_filter) { |
|
| 443 | + return floatval($price_filter / (10 ** wc_get_price_decimals())); |
|
| 444 | 444 | } |
| 445 | 445 | |
| 446 | 446 | /** |
@@ -452,13 +452,13 @@ discard block |
||
| 452 | 452 | * @param string $tax_class Tax class for adjustment. |
| 453 | 453 | * @return float |
| 454 | 454 | */ |
| 455 | - protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) { |
|
| 456 | - $tax_display = get_option( 'woocommerce_tax_display_shop' ); |
|
| 457 | - $tax_rates = WC_Tax::get_rates( $tax_class ); |
|
| 458 | - $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); |
|
| 455 | + protected function adjust_price_filter_for_tax_class($price_filter, $tax_class) { |
|
| 456 | + $tax_display = get_option('woocommerce_tax_display_shop'); |
|
| 457 | + $tax_rates = WC_Tax::get_rates($tax_class); |
|
| 458 | + $base_tax_rates = WC_Tax::get_base_tax_rates($tax_class); |
|
| 459 | 459 | |
| 460 | 460 | // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. |
| 461 | - if ( 'incl' === $tax_display ) { |
|
| 461 | + if ('incl' === $tax_display) { |
|
| 462 | 462 | /** |
| 463 | 463 | * Filters if taxes should be removed from locations outside the store base location. |
| 464 | 464 | * |
@@ -471,14 +471,14 @@ discard block |
||
| 471 | 471 | * @param boolean $adjust_non_base_location_prices True by default. |
| 472 | 472 | * @return boolean |
| 473 | 473 | */ |
| 474 | - $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 ); |
|
| 475 | - return $price_filter - array_sum( $taxes ); |
|
| 474 | + $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); |
|
| 475 | + return $price_filter - array_sum($taxes); |
|
| 476 | 476 | } |
| 477 | 477 | |
| 478 | 478 | // If prices are shown excl. tax, add taxes to match the prices stored in the DB. |
| 479 | - $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); |
|
| 479 | + $taxes = WC_Tax::calc_tax($price_filter, $tax_rates, false); |
|
| 480 | 480 | |
| 481 | - return $price_filter + array_sum( $taxes ); |
|
| 481 | + return $price_filter + array_sum($taxes); |
|
| 482 | 482 | } |
| 483 | 483 | |
| 484 | 484 | /** |
@@ -487,10 +487,10 @@ discard block |
||
| 487 | 487 | * @param string $sql SQL join. |
| 488 | 488 | * @return string |
| 489 | 489 | */ |
| 490 | - protected function append_product_sorting_table_join( $sql ) { |
|
| 490 | + protected function append_product_sorting_table_join($sql) { |
|
| 491 | 491 | global $wpdb; |
| 492 | 492 | |
| 493 | - if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { |
|
| 493 | + if (!strstr($sql, 'wc_product_meta_lookup')) { |
|
| 494 | 494 | $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; |
| 495 | 495 | } |
| 496 | 496 | return $sql; |
@@ -10,199 +10,199 @@ |
||
| 10 | 10 | * Returns limits for products and cart items when using the StoreAPI and supporting classes. |
| 11 | 11 | */ |
| 12 | 12 | final class QuantityLimits { |
| 13 | - use DraftOrderTrait; |
|
| 14 | - |
|
| 15 | - /** |
|
| 16 | - * Get quantity limits (min, max, step/multiple) for a product or cart item. |
|
| 17 | - * |
|
| 18 | - * @param array $cart_item A cart item array. |
|
| 19 | - * @return array |
|
| 20 | - */ |
|
| 21 | - public function get_cart_item_quantity_limits( $cart_item ) { |
|
| 22 | - $product = $cart_item['data'] ?? false; |
|
| 23 | - |
|
| 24 | - if ( ! $product instanceof \WC_Product ) { |
|
| 25 | - return [ |
|
| 26 | - 'minimum' => 1, |
|
| 27 | - 'maximum' => null, |
|
| 28 | - 'multiple_of' => 1, |
|
| 29 | - 'editable' => true, |
|
| 30 | - ]; |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - $multiple_of = (int) $this->filter_value( 1, 'multiple_of', $cart_item ); |
|
| 34 | - $minimum = (int) $this->filter_value( 1, 'minimum', $cart_item ); |
|
| 35 | - $maximum = (int) $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $cart_item ); |
|
| 36 | - $editable = (bool) $this->filter_value( ! $product->is_sold_individually(), 'editable', $cart_item ); |
|
| 37 | - |
|
| 38 | - return [ |
|
| 39 | - 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 40 | - 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 41 | - 'multiple_of' => $multiple_of, |
|
| 42 | - 'editable' => $editable, |
|
| 43 | - ]; |
|
| 44 | - } |
|
| 45 | - |
|
| 46 | - /** |
|
| 47 | - * Get limits for product add to cart forms. |
|
| 48 | - * |
|
| 49 | - * @param \WC_Product $product Product instance. |
|
| 50 | - * @return array |
|
| 51 | - */ |
|
| 52 | - public function get_add_to_cart_limits( \WC_Product $product ) { |
|
| 53 | - $multiple_of = $this->filter_value( 1, 'multiple_of', $product ); |
|
| 54 | - $minimum = $this->filter_value( 1, 'minimum', $product ); |
|
| 55 | - $maximum = $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $product ); |
|
| 56 | - |
|
| 57 | - return [ |
|
| 58 | - 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 59 | - 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 60 | - 'multiple_of' => $multiple_of, |
|
| 61 | - ]; |
|
| 62 | - } |
|
| 63 | - |
|
| 64 | - /** |
|
| 65 | - * Return a number using the closest multiple of another number. Used to enforce step/multiple values. |
|
| 66 | - * |
|
| 67 | - * @param int $number Number to round. |
|
| 68 | - * @param int $multiple_of The multiple. |
|
| 69 | - * @param string $rounding_function ceil, floor, or round. |
|
| 70 | - * @return int |
|
| 71 | - */ |
|
| 72 | - public function limit_to_multiple( int $number, int $multiple_of, string $rounding_function = 'round' ) { |
|
| 73 | - if ( $multiple_of <= 1 ) { |
|
| 74 | - return $number; |
|
| 75 | - } |
|
| 76 | - $rounding_function = in_array( $rounding_function, [ 'ceil', 'floor', 'round' ], true ) ? $rounding_function : 'round'; |
|
| 77 | - return $rounding_function( $number / $multiple_of ) * $multiple_of; |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - /** |
|
| 81 | - * Check that a given quantity is valid according to any limits in place. |
|
| 82 | - * |
|
| 83 | - * @param integer $quantity Quantity to validate. |
|
| 84 | - * @param \WC_Product|array $cart_item Cart item. |
|
| 85 | - * @return \WP_Error|true |
|
| 86 | - */ |
|
| 87 | - public function validate_cart_item_quantity( $quantity, $cart_item ) { |
|
| 88 | - $limits = $this->get_cart_item_quantity_limits( $cart_item ); |
|
| 89 | - |
|
| 90 | - if ( ! $limits['editable'] ) { |
|
| 91 | - return new \WP_Error( |
|
| 92 | - 'readonly_quantity', |
|
| 93 | - __( 'This item is already in the cart and its quantity cannot be edited', 'woocommerce' ) |
|
| 94 | - ); |
|
| 95 | - } |
|
| 96 | - |
|
| 97 | - if ( $quantity < $limits['minimum'] ) { |
|
| 98 | - return new \WP_Error( |
|
| 99 | - 'invalid_quantity', |
|
| 100 | - sprintf( |
|
| 101 | - // Translators: %s amount. |
|
| 102 | - __( 'The minimum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 103 | - $limits['minimum'] |
|
| 104 | - ) |
|
| 105 | - ); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - if ( $quantity > $limits['maximum'] ) { |
|
| 109 | - return new \WP_Error( |
|
| 110 | - 'invalid_quantity', |
|
| 111 | - sprintf( |
|
| 112 | - // Translators: %s amount. |
|
| 113 | - __( 'The maximum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 114 | - $limits['maximum'] |
|
| 115 | - ) |
|
| 116 | - ); |
|
| 117 | - } |
|
| 118 | - |
|
| 119 | - if ( $quantity % $limits['multiple_of'] ) { |
|
| 120 | - return new \WP_Error( |
|
| 121 | - 'invalid_quantity', |
|
| 122 | - sprintf( |
|
| 123 | - // Translators: %s amount. |
|
| 124 | - __( 'The quantity added to the cart must be a multiple of %s', 'woocommerce' ), |
|
| 125 | - $limits['multiple_of'] |
|
| 126 | - ) |
|
| 127 | - ); |
|
| 128 | - } |
|
| 129 | - |
|
| 130 | - return true; |
|
| 131 | - } |
|
| 132 | - |
|
| 133 | - /** |
|
| 134 | - * Get the limit for the total number of a product allowed in the cart. |
|
| 135 | - * |
|
| 136 | - * This is based on product properties, including remaining stock, and defaults to a maximum of 9999 of any product |
|
| 137 | - * in the cart at once. |
|
| 138 | - * |
|
| 139 | - * @param \WC_Product $product Product instance. |
|
| 140 | - * @return int |
|
| 141 | - */ |
|
| 142 | - protected function get_product_quantity_limit( \WC_Product $product ) { |
|
| 143 | - $limits = [ 9999 ]; |
|
| 144 | - |
|
| 145 | - if ( $product->is_sold_individually() ) { |
|
| 146 | - $limits[] = 1; |
|
| 147 | - } elseif ( ! $product->backorders_allowed() ) { |
|
| 148 | - $limits[] = $this->get_remaining_stock( $product ); |
|
| 149 | - } |
|
| 150 | - |
|
| 151 | - /** |
|
| 152 | - * Filters the quantity limit for a product being added to the cart via the Store API. |
|
| 153 | - * |
|
| 154 | - * Filters the variation option name for custom option slugs. |
|
| 155 | - * |
|
| 156 | - * @param integer $quantity_limit Quantity limit which defaults to 9999 unless sold individually. |
|
| 157 | - * @param \WC_Product $product Product instance. |
|
| 158 | - * @return integer |
|
| 159 | - */ |
|
| 160 | - return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product ); |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - /** |
|
| 164 | - * Returns the remaining stock for a product if it has stock. |
|
| 165 | - * |
|
| 166 | - * This also factors in draft orders. |
|
| 167 | - * |
|
| 168 | - * @param \WC_Product $product Product instance. |
|
| 169 | - * @return integer|null |
|
| 170 | - */ |
|
| 171 | - protected function get_remaining_stock( \WC_Product $product ) { |
|
| 172 | - if ( is_null( $product->get_stock_quantity() ) ) { |
|
| 173 | - return null; |
|
| 174 | - } |
|
| 175 | - |
|
| 176 | - $reserve_stock = new ReserveStock(); |
|
| 177 | - $reserved_stock = $reserve_stock->get_reserved_stock( $product, $this->get_draft_order_id() ); |
|
| 178 | - |
|
| 179 | - return $product->get_stock_quantity() - $reserved_stock; |
|
| 180 | - } |
|
| 181 | - |
|
| 182 | - /** |
|
| 183 | - * Get a quantity for a product or cart item by running it through a filter hook. |
|
| 184 | - * |
|
| 185 | - * @param int|null $value Value to filter. |
|
| 186 | - * @param string $value_type Type of value. Used for filter suffix. |
|
| 187 | - * @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance. |
|
| 188 | - * @return mixed |
|
| 189 | - */ |
|
| 190 | - protected function filter_value( $value, string $value_type, $cart_item_or_product ) { |
|
| 191 | - $is_product = $cart_item_or_product instanceof \WC_Product; |
|
| 192 | - $product = $is_product ? $cart_item_or_product : $cart_item_or_product['data']; |
|
| 193 | - $cart_item = $is_product ? null : $cart_item_or_product; |
|
| 194 | - /** |
|
| 195 | - * Filters the quantity minimum for a cart item in Store API. This allows extensions to control the minimum qty |
|
| 196 | - * of items already within the cart. |
|
| 197 | - * |
|
| 198 | - * The suffix of the hook will vary depending on the value being filtered. |
|
| 199 | - * For example, minimum, maximum, multiple_of, editable. |
|
| 200 | - * |
|
| 201 | - * @param mixed $value The value being filtered. |
|
| 202 | - * @param \WC_Product $product The product object. |
|
| 203 | - * @param array|null $cart_item The cart item if the product exists in the cart, or null. |
|
| 204 | - * @return mixed |
|
| 205 | - */ |
|
| 206 | - return apply_filters( "woocommerce_store_api_product_quantity_{$value_type}", $value, $product, $cart_item ); |
|
| 207 | - } |
|
| 13 | + use DraftOrderTrait; |
|
| 14 | + |
|
| 15 | + /** |
|
| 16 | + * Get quantity limits (min, max, step/multiple) for a product or cart item. |
|
| 17 | + * |
|
| 18 | + * @param array $cart_item A cart item array. |
|
| 19 | + * @return array |
|
| 20 | + */ |
|
| 21 | + public function get_cart_item_quantity_limits( $cart_item ) { |
|
| 22 | + $product = $cart_item['data'] ?? false; |
|
| 23 | + |
|
| 24 | + if ( ! $product instanceof \WC_Product ) { |
|
| 25 | + return [ |
|
| 26 | + 'minimum' => 1, |
|
| 27 | + 'maximum' => null, |
|
| 28 | + 'multiple_of' => 1, |
|
| 29 | + 'editable' => true, |
|
| 30 | + ]; |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + $multiple_of = (int) $this->filter_value( 1, 'multiple_of', $cart_item ); |
|
| 34 | + $minimum = (int) $this->filter_value( 1, 'minimum', $cart_item ); |
|
| 35 | + $maximum = (int) $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $cart_item ); |
|
| 36 | + $editable = (bool) $this->filter_value( ! $product->is_sold_individually(), 'editable', $cart_item ); |
|
| 37 | + |
|
| 38 | + return [ |
|
| 39 | + 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 40 | + 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 41 | + 'multiple_of' => $multiple_of, |
|
| 42 | + 'editable' => $editable, |
|
| 43 | + ]; |
|
| 44 | + } |
|
| 45 | + |
|
| 46 | + /** |
|
| 47 | + * Get limits for product add to cart forms. |
|
| 48 | + * |
|
| 49 | + * @param \WC_Product $product Product instance. |
|
| 50 | + * @return array |
|
| 51 | + */ |
|
| 52 | + public function get_add_to_cart_limits( \WC_Product $product ) { |
|
| 53 | + $multiple_of = $this->filter_value( 1, 'multiple_of', $product ); |
|
| 54 | + $minimum = $this->filter_value( 1, 'minimum', $product ); |
|
| 55 | + $maximum = $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $product ); |
|
| 56 | + |
|
| 57 | + return [ |
|
| 58 | + 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 59 | + 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 60 | + 'multiple_of' => $multiple_of, |
|
| 61 | + ]; |
|
| 62 | + } |
|
| 63 | + |
|
| 64 | + /** |
|
| 65 | + * Return a number using the closest multiple of another number. Used to enforce step/multiple values. |
|
| 66 | + * |
|
| 67 | + * @param int $number Number to round. |
|
| 68 | + * @param int $multiple_of The multiple. |
|
| 69 | + * @param string $rounding_function ceil, floor, or round. |
|
| 70 | + * @return int |
|
| 71 | + */ |
|
| 72 | + public function limit_to_multiple( int $number, int $multiple_of, string $rounding_function = 'round' ) { |
|
| 73 | + if ( $multiple_of <= 1 ) { |
|
| 74 | + return $number; |
|
| 75 | + } |
|
| 76 | + $rounding_function = in_array( $rounding_function, [ 'ceil', 'floor', 'round' ], true ) ? $rounding_function : 'round'; |
|
| 77 | + return $rounding_function( $number / $multiple_of ) * $multiple_of; |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + /** |
|
| 81 | + * Check that a given quantity is valid according to any limits in place. |
|
| 82 | + * |
|
| 83 | + * @param integer $quantity Quantity to validate. |
|
| 84 | + * @param \WC_Product|array $cart_item Cart item. |
|
| 85 | + * @return \WP_Error|true |
|
| 86 | + */ |
|
| 87 | + public function validate_cart_item_quantity( $quantity, $cart_item ) { |
|
| 88 | + $limits = $this->get_cart_item_quantity_limits( $cart_item ); |
|
| 89 | + |
|
| 90 | + if ( ! $limits['editable'] ) { |
|
| 91 | + return new \WP_Error( |
|
| 92 | + 'readonly_quantity', |
|
| 93 | + __( 'This item is already in the cart and its quantity cannot be edited', 'woocommerce' ) |
|
| 94 | + ); |
|
| 95 | + } |
|
| 96 | + |
|
| 97 | + if ( $quantity < $limits['minimum'] ) { |
|
| 98 | + return new \WP_Error( |
|
| 99 | + 'invalid_quantity', |
|
| 100 | + sprintf( |
|
| 101 | + // Translators: %s amount. |
|
| 102 | + __( 'The minimum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 103 | + $limits['minimum'] |
|
| 104 | + ) |
|
| 105 | + ); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + if ( $quantity > $limits['maximum'] ) { |
|
| 109 | + return new \WP_Error( |
|
| 110 | + 'invalid_quantity', |
|
| 111 | + sprintf( |
|
| 112 | + // Translators: %s amount. |
|
| 113 | + __( 'The maximum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 114 | + $limits['maximum'] |
|
| 115 | + ) |
|
| 116 | + ); |
|
| 117 | + } |
|
| 118 | + |
|
| 119 | + if ( $quantity % $limits['multiple_of'] ) { |
|
| 120 | + return new \WP_Error( |
|
| 121 | + 'invalid_quantity', |
|
| 122 | + sprintf( |
|
| 123 | + // Translators: %s amount. |
|
| 124 | + __( 'The quantity added to the cart must be a multiple of %s', 'woocommerce' ), |
|
| 125 | + $limits['multiple_of'] |
|
| 126 | + ) |
|
| 127 | + ); |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + return true; |
|
| 131 | + } |
|
| 132 | + |
|
| 133 | + /** |
|
| 134 | + * Get the limit for the total number of a product allowed in the cart. |
|
| 135 | + * |
|
| 136 | + * This is based on product properties, including remaining stock, and defaults to a maximum of 9999 of any product |
|
| 137 | + * in the cart at once. |
|
| 138 | + * |
|
| 139 | + * @param \WC_Product $product Product instance. |
|
| 140 | + * @return int |
|
| 141 | + */ |
|
| 142 | + protected function get_product_quantity_limit( \WC_Product $product ) { |
|
| 143 | + $limits = [ 9999 ]; |
|
| 144 | + |
|
| 145 | + if ( $product->is_sold_individually() ) { |
|
| 146 | + $limits[] = 1; |
|
| 147 | + } elseif ( ! $product->backorders_allowed() ) { |
|
| 148 | + $limits[] = $this->get_remaining_stock( $product ); |
|
| 149 | + } |
|
| 150 | + |
|
| 151 | + /** |
|
| 152 | + * Filters the quantity limit for a product being added to the cart via the Store API. |
|
| 153 | + * |
|
| 154 | + * Filters the variation option name for custom option slugs. |
|
| 155 | + * |
|
| 156 | + * @param integer $quantity_limit Quantity limit which defaults to 9999 unless sold individually. |
|
| 157 | + * @param \WC_Product $product Product instance. |
|
| 158 | + * @return integer |
|
| 159 | + */ |
|
| 160 | + return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product ); |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + /** |
|
| 164 | + * Returns the remaining stock for a product if it has stock. |
|
| 165 | + * |
|
| 166 | + * This also factors in draft orders. |
|
| 167 | + * |
|
| 168 | + * @param \WC_Product $product Product instance. |
|
| 169 | + * @return integer|null |
|
| 170 | + */ |
|
| 171 | + protected function get_remaining_stock( \WC_Product $product ) { |
|
| 172 | + if ( is_null( $product->get_stock_quantity() ) ) { |
|
| 173 | + return null; |
|
| 174 | + } |
|
| 175 | + |
|
| 176 | + $reserve_stock = new ReserveStock(); |
|
| 177 | + $reserved_stock = $reserve_stock->get_reserved_stock( $product, $this->get_draft_order_id() ); |
|
| 178 | + |
|
| 179 | + return $product->get_stock_quantity() - $reserved_stock; |
|
| 180 | + } |
|
| 181 | + |
|
| 182 | + /** |
|
| 183 | + * Get a quantity for a product or cart item by running it through a filter hook. |
|
| 184 | + * |
|
| 185 | + * @param int|null $value Value to filter. |
|
| 186 | + * @param string $value_type Type of value. Used for filter suffix. |
|
| 187 | + * @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance. |
|
| 188 | + * @return mixed |
|
| 189 | + */ |
|
| 190 | + protected function filter_value( $value, string $value_type, $cart_item_or_product ) { |
|
| 191 | + $is_product = $cart_item_or_product instanceof \WC_Product; |
|
| 192 | + $product = $is_product ? $cart_item_or_product : $cart_item_or_product['data']; |
|
| 193 | + $cart_item = $is_product ? null : $cart_item_or_product; |
|
| 194 | + /** |
|
| 195 | + * Filters the quantity minimum for a cart item in Store API. This allows extensions to control the minimum qty |
|
| 196 | + * of items already within the cart. |
|
| 197 | + * |
|
| 198 | + * The suffix of the hook will vary depending on the value being filtered. |
|
| 199 | + * For example, minimum, maximum, multiple_of, editable. |
|
| 200 | + * |
|
| 201 | + * @param mixed $value The value being filtered. |
|
| 202 | + * @param \WC_Product $product The product object. |
|
| 203 | + * @param array|null $cart_item The cart item if the product exists in the cart, or null. |
|
| 204 | + * @return mixed |
|
| 205 | + */ |
|
| 206 | + return apply_filters( "woocommerce_store_api_product_quantity_{$value_type}", $value, $product, $cart_item ); |
|
| 207 | + } |
|
| 208 | 208 | } |
@@ -18,10 +18,10 @@ discard block |
||
| 18 | 18 | * @param array $cart_item A cart item array. |
| 19 | 19 | * @return array |
| 20 | 20 | */ |
| 21 | - public function get_cart_item_quantity_limits( $cart_item ) { |
|
| 21 | + public function get_cart_item_quantity_limits($cart_item) { |
|
| 22 | 22 | $product = $cart_item['data'] ?? false; |
| 23 | 23 | |
| 24 | - if ( ! $product instanceof \WC_Product ) { |
|
| 24 | + if (!$product instanceof \WC_Product) { |
|
| 25 | 25 | return [ |
| 26 | 26 | 'minimum' => 1, |
| 27 | 27 | 'maximum' => null, |
@@ -30,14 +30,14 @@ discard block |
||
| 30 | 30 | ]; |
| 31 | 31 | } |
| 32 | 32 | |
| 33 | - $multiple_of = (int) $this->filter_value( 1, 'multiple_of', $cart_item ); |
|
| 34 | - $minimum = (int) $this->filter_value( 1, 'minimum', $cart_item ); |
|
| 35 | - $maximum = (int) $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $cart_item ); |
|
| 36 | - $editable = (bool) $this->filter_value( ! $product->is_sold_individually(), 'editable', $cart_item ); |
|
| 33 | + $multiple_of = (int) $this->filter_value(1, 'multiple_of', $cart_item); |
|
| 34 | + $minimum = (int) $this->filter_value(1, 'minimum', $cart_item); |
|
| 35 | + $maximum = (int) $this->filter_value($this->get_product_quantity_limit($product), 'maximum', $cart_item); |
|
| 36 | + $editable = (bool) $this->filter_value(!$product->is_sold_individually(), 'editable', $cart_item); |
|
| 37 | 37 | |
| 38 | 38 | return [ |
| 39 | - 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 40 | - 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 39 | + 'minimum' => $this->limit_to_multiple($minimum, $multiple_of, 'ceil'), |
|
| 40 | + 'maximum' => $this->limit_to_multiple($maximum, $multiple_of, 'floor'), |
|
| 41 | 41 | 'multiple_of' => $multiple_of, |
| 42 | 42 | 'editable' => $editable, |
| 43 | 43 | ]; |
@@ -49,14 +49,14 @@ discard block |
||
| 49 | 49 | * @param \WC_Product $product Product instance. |
| 50 | 50 | * @return array |
| 51 | 51 | */ |
| 52 | - public function get_add_to_cart_limits( \WC_Product $product ) { |
|
| 53 | - $multiple_of = $this->filter_value( 1, 'multiple_of', $product ); |
|
| 54 | - $minimum = $this->filter_value( 1, 'minimum', $product ); |
|
| 55 | - $maximum = $this->filter_value( $this->get_product_quantity_limit( $product ), 'maximum', $product ); |
|
| 52 | + public function get_add_to_cart_limits(\WC_Product $product) { |
|
| 53 | + $multiple_of = $this->filter_value(1, 'multiple_of', $product); |
|
| 54 | + $minimum = $this->filter_value(1, 'minimum', $product); |
|
| 55 | + $maximum = $this->filter_value($this->get_product_quantity_limit($product), 'maximum', $product); |
|
| 56 | 56 | |
| 57 | 57 | return [ |
| 58 | - 'minimum' => $this->limit_to_multiple( $minimum, $multiple_of, 'ceil' ), |
|
| 59 | - 'maximum' => $this->limit_to_multiple( $maximum, $multiple_of, 'floor' ), |
|
| 58 | + 'minimum' => $this->limit_to_multiple($minimum, $multiple_of, 'ceil'), |
|
| 59 | + 'maximum' => $this->limit_to_multiple($maximum, $multiple_of, 'floor'), |
|
| 60 | 60 | 'multiple_of' => $multiple_of, |
| 61 | 61 | ]; |
| 62 | 62 | } |
@@ -69,12 +69,12 @@ discard block |
||
| 69 | 69 | * @param string $rounding_function ceil, floor, or round. |
| 70 | 70 | * @return int |
| 71 | 71 | */ |
| 72 | - public function limit_to_multiple( int $number, int $multiple_of, string $rounding_function = 'round' ) { |
|
| 73 | - if ( $multiple_of <= 1 ) { |
|
| 72 | + public function limit_to_multiple(int $number, int $multiple_of, string $rounding_function = 'round') { |
|
| 73 | + if ($multiple_of <= 1) { |
|
| 74 | 74 | return $number; |
| 75 | 75 | } |
| 76 | - $rounding_function = in_array( $rounding_function, [ 'ceil', 'floor', 'round' ], true ) ? $rounding_function : 'round'; |
|
| 77 | - return $rounding_function( $number / $multiple_of ) * $multiple_of; |
|
| 76 | + $rounding_function = in_array($rounding_function, ['ceil', 'floor', 'round'], true) ? $rounding_function : 'round'; |
|
| 77 | + return $rounding_function($number / $multiple_of) * $multiple_of; |
|
| 78 | 78 | } |
| 79 | 79 | |
| 80 | 80 | /** |
@@ -84,44 +84,44 @@ discard block |
||
| 84 | 84 | * @param \WC_Product|array $cart_item Cart item. |
| 85 | 85 | * @return \WP_Error|true |
| 86 | 86 | */ |
| 87 | - public function validate_cart_item_quantity( $quantity, $cart_item ) { |
|
| 88 | - $limits = $this->get_cart_item_quantity_limits( $cart_item ); |
|
| 87 | + public function validate_cart_item_quantity($quantity, $cart_item) { |
|
| 88 | + $limits = $this->get_cart_item_quantity_limits($cart_item); |
|
| 89 | 89 | |
| 90 | - if ( ! $limits['editable'] ) { |
|
| 90 | + if (!$limits['editable']) { |
|
| 91 | 91 | return new \WP_Error( |
| 92 | 92 | 'readonly_quantity', |
| 93 | - __( 'This item is already in the cart and its quantity cannot be edited', 'woocommerce' ) |
|
| 93 | + __('This item is already in the cart and its quantity cannot be edited', 'woocommerce') |
|
| 94 | 94 | ); |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | - if ( $quantity < $limits['minimum'] ) { |
|
| 97 | + if ($quantity < $limits['minimum']) { |
|
| 98 | 98 | return new \WP_Error( |
| 99 | 99 | 'invalid_quantity', |
| 100 | 100 | sprintf( |
| 101 | 101 | // Translators: %s amount. |
| 102 | - __( 'The minimum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 102 | + __('The minimum quantity that can be added to the cart is %s', 'woocommerce'), |
|
| 103 | 103 | $limits['minimum'] |
| 104 | 104 | ) |
| 105 | 105 | ); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | - if ( $quantity > $limits['maximum'] ) { |
|
| 108 | + if ($quantity > $limits['maximum']) { |
|
| 109 | 109 | return new \WP_Error( |
| 110 | 110 | 'invalid_quantity', |
| 111 | 111 | sprintf( |
| 112 | 112 | // Translators: %s amount. |
| 113 | - __( 'The maximum quantity that can be added to the cart is %s', 'woocommerce' ), |
|
| 113 | + __('The maximum quantity that can be added to the cart is %s', 'woocommerce'), |
|
| 114 | 114 | $limits['maximum'] |
| 115 | 115 | ) |
| 116 | 116 | ); |
| 117 | 117 | } |
| 118 | 118 | |
| 119 | - if ( $quantity % $limits['multiple_of'] ) { |
|
| 119 | + if ($quantity % $limits['multiple_of']) { |
|
| 120 | 120 | return new \WP_Error( |
| 121 | 121 | 'invalid_quantity', |
| 122 | 122 | sprintf( |
| 123 | 123 | // Translators: %s amount. |
| 124 | - __( 'The quantity added to the cart must be a multiple of %s', 'woocommerce' ), |
|
| 124 | + __('The quantity added to the cart must be a multiple of %s', 'woocommerce'), |
|
| 125 | 125 | $limits['multiple_of'] |
| 126 | 126 | ) |
| 127 | 127 | ); |
@@ -139,13 +139,13 @@ discard block |
||
| 139 | 139 | * @param \WC_Product $product Product instance. |
| 140 | 140 | * @return int |
| 141 | 141 | */ |
| 142 | - protected function get_product_quantity_limit( \WC_Product $product ) { |
|
| 143 | - $limits = [ 9999 ]; |
|
| 142 | + protected function get_product_quantity_limit(\WC_Product $product) { |
|
| 143 | + $limits = [9999]; |
|
| 144 | 144 | |
| 145 | - if ( $product->is_sold_individually() ) { |
|
| 145 | + if ($product->is_sold_individually()) { |
|
| 146 | 146 | $limits[] = 1; |
| 147 | - } elseif ( ! $product->backorders_allowed() ) { |
|
| 148 | - $limits[] = $this->get_remaining_stock( $product ); |
|
| 147 | + } elseif (!$product->backorders_allowed()) { |
|
| 148 | + $limits[] = $this->get_remaining_stock($product); |
|
| 149 | 149 | } |
| 150 | 150 | |
| 151 | 151 | /** |
@@ -157,7 +157,7 @@ discard block |
||
| 157 | 157 | * @param \WC_Product $product Product instance. |
| 158 | 158 | * @return integer |
| 159 | 159 | */ |
| 160 | - return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product ); |
|
| 160 | + return apply_filters('woocommerce_store_api_product_quantity_limit', max(min(array_filter($limits)), 1), $product); |
|
| 161 | 161 | } |
| 162 | 162 | |
| 163 | 163 | /** |
@@ -168,13 +168,13 @@ discard block |
||
| 168 | 168 | * @param \WC_Product $product Product instance. |
| 169 | 169 | * @return integer|null |
| 170 | 170 | */ |
| 171 | - protected function get_remaining_stock( \WC_Product $product ) { |
|
| 172 | - if ( is_null( $product->get_stock_quantity() ) ) { |
|
| 171 | + protected function get_remaining_stock(\WC_Product $product) { |
|
| 172 | + if (is_null($product->get_stock_quantity())) { |
|
| 173 | 173 | return null; |
| 174 | 174 | } |
| 175 | 175 | |
| 176 | 176 | $reserve_stock = new ReserveStock(); |
| 177 | - $reserved_stock = $reserve_stock->get_reserved_stock( $product, $this->get_draft_order_id() ); |
|
| 177 | + $reserved_stock = $reserve_stock->get_reserved_stock($product, $this->get_draft_order_id()); |
|
| 178 | 178 | |
| 179 | 179 | return $product->get_stock_quantity() - $reserved_stock; |
| 180 | 180 | } |
@@ -187,7 +187,7 @@ discard block |
||
| 187 | 187 | * @param \WC_Product|array $cart_item_or_product Either a cart item or a product instance. |
| 188 | 188 | * @return mixed |
| 189 | 189 | */ |
| 190 | - protected function filter_value( $value, string $value_type, $cart_item_or_product ) { |
|
| 190 | + protected function filter_value($value, string $value_type, $cart_item_or_product) { |
|
| 191 | 191 | $is_product = $cart_item_or_product instanceof \WC_Product; |
| 192 | 192 | $product = $is_product ? $cart_item_or_product : $cart_item_or_product['data']; |
| 193 | 193 | $cart_item = $is_product ? null : $cart_item_or_product; |
@@ -203,6 +203,6 @@ discard block |
||
| 203 | 203 | * @param array|null $cart_item The cart item if the product exists in the cart, or null. |
| 204 | 204 | * @return mixed |
| 205 | 205 | */ |
| 206 | - return apply_filters( "woocommerce_store_api_product_quantity_{$value_type}", $value, $product, $cart_item ); |
|
| 206 | + return apply_filters("woocommerce_store_api_product_quantity_{$value_type}", $value, $product, $cart_item); |
|
| 207 | 207 | } |
| 208 | 208 | } |
@@ -5,32 +5,32 @@ |
||
| 5 | 5 | * ArrayUtils class used for custom functions to operate on arrays |
| 6 | 6 | */ |
| 7 | 7 | class ArrayUtils { |
| 8 | - /** |
|
| 9 | - * Join a string with a natural language conjunction at the end. |
|
| 10 | - * |
|
| 11 | - * @param array $array The array to join together with the natural language conjunction. |
|
| 12 | - * @param bool $enclose_items_with_quotes Whether each item in the array should be enclosed within quotation marks. |
|
| 13 | - * |
|
| 14 | - * @return string a string containing a list of items and a natural language conjuction. |
|
| 15 | - */ |
|
| 16 | - public static function natural_language_join( $array, $enclose_items_with_quotes = false ) { |
|
| 17 | - if ( true === $enclose_items_with_quotes ) { |
|
| 18 | - $array = array_map( |
|
| 19 | - function( $item ) { |
|
| 20 | - return '"' . $item . '"'; |
|
| 21 | - }, |
|
| 22 | - $array |
|
| 23 | - ); |
|
| 24 | - } |
|
| 25 | - $last = array_pop( $array ); |
|
| 26 | - if ( $array ) { |
|
| 27 | - return sprintf( |
|
| 28 | - /* translators: 1: The first n-1 items of a list 2: the last item in the list. */ |
|
| 29 | - __( '%1$s and %2$s', 'woocommerce' ), |
|
| 30 | - implode( ', ', $array ), |
|
| 31 | - $last |
|
| 32 | - ); |
|
| 33 | - } |
|
| 34 | - return $last; |
|
| 35 | - } |
|
| 8 | + /** |
|
| 9 | + * Join a string with a natural language conjunction at the end. |
|
| 10 | + * |
|
| 11 | + * @param array $array The array to join together with the natural language conjunction. |
|
| 12 | + * @param bool $enclose_items_with_quotes Whether each item in the array should be enclosed within quotation marks. |
|
| 13 | + * |
|
| 14 | + * @return string a string containing a list of items and a natural language conjuction. |
|
| 15 | + */ |
|
| 16 | + public static function natural_language_join( $array, $enclose_items_with_quotes = false ) { |
|
| 17 | + if ( true === $enclose_items_with_quotes ) { |
|
| 18 | + $array = array_map( |
|
| 19 | + function( $item ) { |
|
| 20 | + return '"' . $item . '"'; |
|
| 21 | + }, |
|
| 22 | + $array |
|
| 23 | + ); |
|
| 24 | + } |
|
| 25 | + $last = array_pop( $array ); |
|
| 26 | + if ( $array ) { |
|
| 27 | + return sprintf( |
|
| 28 | + /* translators: 1: The first n-1 items of a list 2: the last item in the list. */ |
|
| 29 | + __( '%1$s and %2$s', 'woocommerce' ), |
|
| 30 | + implode( ', ', $array ), |
|
| 31 | + $last |
|
| 32 | + ); |
|
| 33 | + } |
|
| 34 | + return $last; |
|
| 35 | + } |
|
| 36 | 36 | } |
@@ -13,21 +13,21 @@ |
||
| 13 | 13 | * |
| 14 | 14 | * @return string a string containing a list of items and a natural language conjuction. |
| 15 | 15 | */ |
| 16 | - public static function natural_language_join( $array, $enclose_items_with_quotes = false ) { |
|
| 17 | - if ( true === $enclose_items_with_quotes ) { |
|
| 16 | + public static function natural_language_join($array, $enclose_items_with_quotes = false) { |
|
| 17 | + if (true === $enclose_items_with_quotes) { |
|
| 18 | 18 | $array = array_map( |
| 19 | - function( $item ) { |
|
| 19 | + function($item) { |
|
| 20 | 20 | return '"' . $item . '"'; |
| 21 | 21 | }, |
| 22 | 22 | $array |
| 23 | 23 | ); |
| 24 | 24 | } |
| 25 | - $last = array_pop( $array ); |
|
| 26 | - if ( $array ) { |
|
| 25 | + $last = array_pop($array); |
|
| 26 | + if ($array) { |
|
| 27 | 27 | return sprintf( |
| 28 | 28 | /* translators: 1: The first n-1 items of a list 2: the last item in the list. */ |
| 29 | - __( '%1$s and %2$s', 'woocommerce' ), |
|
| 30 | - implode( ', ', $array ), |
|
| 29 | + __('%1$s and %2$s', 'woocommerce'), |
|
| 30 | + implode(', ', $array), |
|
| 31 | 31 | $last |
| 32 | 32 | ); |
| 33 | 33 | } |
@@ -7,64 +7,64 @@ |
||
| 7 | 7 | * Shared functionality for getting and setting draft order IDs from session. |
| 8 | 8 | */ |
| 9 | 9 | trait DraftOrderTrait { |
| 10 | - /** |
|
| 11 | - * Gets draft order data from the customer session. |
|
| 12 | - * |
|
| 13 | - * @return integer |
|
| 14 | - */ |
|
| 15 | - protected function get_draft_order_id() { |
|
| 16 | - if ( ! wc()->session ) { |
|
| 17 | - wc()->initialize_session(); |
|
| 18 | - } |
|
| 19 | - return wc()->session->get( 'store_api_draft_order', 0 ); |
|
| 20 | - } |
|
| 10 | + /** |
|
| 11 | + * Gets draft order data from the customer session. |
|
| 12 | + * |
|
| 13 | + * @return integer |
|
| 14 | + */ |
|
| 15 | + protected function get_draft_order_id() { |
|
| 16 | + if ( ! wc()->session ) { |
|
| 17 | + wc()->initialize_session(); |
|
| 18 | + } |
|
| 19 | + return wc()->session->get( 'store_api_draft_order', 0 ); |
|
| 20 | + } |
|
| 21 | 21 | |
| 22 | - /** |
|
| 23 | - * Updates draft order data in the customer session. |
|
| 24 | - * |
|
| 25 | - * @param integer $order_id Draft order ID. |
|
| 26 | - */ |
|
| 27 | - protected function set_draft_order_id( $order_id ) { |
|
| 28 | - if ( ! wc()->session ) { |
|
| 29 | - wc()->initialize_session(); |
|
| 30 | - } |
|
| 31 | - wc()->session->set( 'store_api_draft_order', $order_id ); |
|
| 32 | - } |
|
| 22 | + /** |
|
| 23 | + * Updates draft order data in the customer session. |
|
| 24 | + * |
|
| 25 | + * @param integer $order_id Draft order ID. |
|
| 26 | + */ |
|
| 27 | + protected function set_draft_order_id( $order_id ) { |
|
| 28 | + if ( ! wc()->session ) { |
|
| 29 | + wc()->initialize_session(); |
|
| 30 | + } |
|
| 31 | + wc()->session->set( 'store_api_draft_order', $order_id ); |
|
| 32 | + } |
|
| 33 | 33 | |
| 34 | - /** |
|
| 35 | - * Uses the draft order ID to return an order object, if valid. |
|
| 36 | - * |
|
| 37 | - * @return \WC_Order|null; |
|
| 38 | - */ |
|
| 39 | - protected function get_draft_order() { |
|
| 40 | - $draft_order_id = $this->get_draft_order_id(); |
|
| 41 | - $draft_order = $draft_order_id ? wc_get_order( $draft_order_id ) : false; |
|
| 34 | + /** |
|
| 35 | + * Uses the draft order ID to return an order object, if valid. |
|
| 36 | + * |
|
| 37 | + * @return \WC_Order|null; |
|
| 38 | + */ |
|
| 39 | + protected function get_draft_order() { |
|
| 40 | + $draft_order_id = $this->get_draft_order_id(); |
|
| 41 | + $draft_order = $draft_order_id ? wc_get_order( $draft_order_id ) : false; |
|
| 42 | 42 | |
| 43 | - return $this->is_valid_draft_order( $draft_order ) ? $draft_order : null; |
|
| 44 | - } |
|
| 43 | + return $this->is_valid_draft_order( $draft_order ) ? $draft_order : null; |
|
| 44 | + } |
|
| 45 | 45 | |
| 46 | - /** |
|
| 47 | - * Whether the passed argument is a draft order or an order that is |
|
| 48 | - * pending/failed and the cart hasn't changed. |
|
| 49 | - * |
|
| 50 | - * @param \WC_Order $order_object Order object to check. |
|
| 51 | - * @return boolean Whether the order is valid as a draft order. |
|
| 52 | - */ |
|
| 53 | - protected function is_valid_draft_order( $order_object ) { |
|
| 54 | - if ( ! $order_object instanceof \WC_Order ) { |
|
| 55 | - return false; |
|
| 56 | - } |
|
| 46 | + /** |
|
| 47 | + * Whether the passed argument is a draft order or an order that is |
|
| 48 | + * pending/failed and the cart hasn't changed. |
|
| 49 | + * |
|
| 50 | + * @param \WC_Order $order_object Order object to check. |
|
| 51 | + * @return boolean Whether the order is valid as a draft order. |
|
| 52 | + */ |
|
| 53 | + protected function is_valid_draft_order( $order_object ) { |
|
| 54 | + if ( ! $order_object instanceof \WC_Order ) { |
|
| 55 | + return false; |
|
| 56 | + } |
|
| 57 | 57 | |
| 58 | - // Draft orders are okay. |
|
| 59 | - if ( $order_object->has_status( 'checkout-draft' ) ) { |
|
| 60 | - return true; |
|
| 61 | - } |
|
| 58 | + // Draft orders are okay. |
|
| 59 | + if ( $order_object->has_status( 'checkout-draft' ) ) { |
|
| 60 | + return true; |
|
| 61 | + } |
|
| 62 | 62 | |
| 63 | - // Pending and failed orders can be retried if the cart hasn't changed. |
|
| 64 | - if ( $order_object->needs_payment() && $order_object->has_cart_hash( wc()->cart->get_cart_hash() ) ) { |
|
| 65 | - return true; |
|
| 66 | - } |
|
| 63 | + // Pending and failed orders can be retried if the cart hasn't changed. |
|
| 64 | + if ( $order_object->needs_payment() && $order_object->has_cart_hash( wc()->cart->get_cart_hash() ) ) { |
|
| 65 | + return true; |
|
| 66 | + } |
|
| 67 | 67 | |
| 68 | - return false; |
|
| 69 | - } |
|
| 68 | + return false; |
|
| 69 | + } |
|
| 70 | 70 | } |
@@ -13,10 +13,10 @@ discard block |
||
| 13 | 13 | * @return integer |
| 14 | 14 | */ |
| 15 | 15 | protected function get_draft_order_id() { |
| 16 | - if ( ! wc()->session ) { |
|
| 16 | + if (!wc()->session) { |
|
| 17 | 17 | wc()->initialize_session(); |
| 18 | 18 | } |
| 19 | - return wc()->session->get( 'store_api_draft_order', 0 ); |
|
| 19 | + return wc()->session->get('store_api_draft_order', 0); |
|
| 20 | 20 | } |
| 21 | 21 | |
| 22 | 22 | /** |
@@ -24,11 +24,11 @@ discard block |
||
| 24 | 24 | * |
| 25 | 25 | * @param integer $order_id Draft order ID. |
| 26 | 26 | */ |
| 27 | - protected function set_draft_order_id( $order_id ) { |
|
| 28 | - if ( ! wc()->session ) { |
|
| 27 | + protected function set_draft_order_id($order_id) { |
|
| 28 | + if (!wc()->session) { |
|
| 29 | 29 | wc()->initialize_session(); |
| 30 | 30 | } |
| 31 | - wc()->session->set( 'store_api_draft_order', $order_id ); |
|
| 31 | + wc()->session->set('store_api_draft_order', $order_id); |
|
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | /** |
@@ -38,9 +38,9 @@ discard block |
||
| 38 | 38 | */ |
| 39 | 39 | protected function get_draft_order() { |
| 40 | 40 | $draft_order_id = $this->get_draft_order_id(); |
| 41 | - $draft_order = $draft_order_id ? wc_get_order( $draft_order_id ) : false; |
|
| 41 | + $draft_order = $draft_order_id ? wc_get_order($draft_order_id) : false; |
|
| 42 | 42 | |
| 43 | - return $this->is_valid_draft_order( $draft_order ) ? $draft_order : null; |
|
| 43 | + return $this->is_valid_draft_order($draft_order) ? $draft_order : null; |
|
| 44 | 44 | } |
| 45 | 45 | |
| 46 | 46 | /** |
@@ -50,18 +50,18 @@ discard block |
||
| 50 | 50 | * @param \WC_Order $order_object Order object to check. |
| 51 | 51 | * @return boolean Whether the order is valid as a draft order. |
| 52 | 52 | */ |
| 53 | - protected function is_valid_draft_order( $order_object ) { |
|
| 54 | - if ( ! $order_object instanceof \WC_Order ) { |
|
| 53 | + protected function is_valid_draft_order($order_object) { |
|
| 54 | + if (!$order_object instanceof \WC_Order) { |
|
| 55 | 55 | return false; |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | // Draft orders are okay. |
| 59 | - if ( $order_object->has_status( 'checkout-draft' ) ) { |
|
| 59 | + if ($order_object->has_status('checkout-draft')) { |
|
| 60 | 60 | return true; |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | // Pending and failed orders can be retried if the cart hasn't changed. |
| 64 | - if ( $order_object->needs_payment() && $order_object->has_cart_hash( wc()->cart->get_cart_hash() ) ) { |
|
| 64 | + if ($order_object->needs_payment() && $order_object->has_cart_hash(wc()->cart->get_cart_hash())) { |
|
| 65 | 65 | return true; |
| 66 | 66 | } |
| 67 | 67 | |
@@ -10,521 +10,521 @@ |
||
| 10 | 10 | */ |
| 11 | 11 | class OrderController { |
| 12 | 12 | |
| 13 | - /** |
|
| 14 | - * Create order and set props based on global settings. |
|
| 15 | - * |
|
| 16 | - * @throws RouteException Exception if invalid data is detected. |
|
| 17 | - * |
|
| 18 | - * @return \WC_Order A new order object. |
|
| 19 | - */ |
|
| 20 | - public function create_order_from_cart() { |
|
| 21 | - if ( wc()->cart->is_empty() ) { |
|
| 22 | - throw new RouteException( |
|
| 23 | - 'woocommerce_rest_cart_empty', |
|
| 24 | - __( 'Cannot create order from empty cart.', 'woocommerce' ), |
|
| 25 | - 400 |
|
| 26 | - ); |
|
| 27 | - } |
|
| 28 | - |
|
| 29 | - add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 30 | - |
|
| 31 | - $order = new \WC_Order(); |
|
| 32 | - $order->set_status( 'checkout-draft' ); |
|
| 33 | - $order->set_created_via( 'store-api' ); |
|
| 34 | - $this->update_order_from_cart( $order ); |
|
| 35 | - |
|
| 36 | - remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 37 | - |
|
| 38 | - return $order; |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - /** |
|
| 42 | - * Update an order using data from the current cart. |
|
| 43 | - * |
|
| 44 | - * @param \WC_Order $order The order object to update. |
|
| 45 | - */ |
|
| 46 | - public function update_order_from_cart( \WC_Order $order ) { |
|
| 47 | - // Ensures Local pickups are accounted for. |
|
| 48 | - add_filter( 'woocommerce_order_get_tax_location', array( $this, 'handle_local_pickup_taxes' ) ); |
|
| 49 | - |
|
| 50 | - // Ensure cart is current. |
|
| 51 | - wc()->cart->calculate_shipping(); |
|
| 52 | - wc()->cart->calculate_totals(); |
|
| 53 | - |
|
| 54 | - // Update the current order to match the current cart. |
|
| 55 | - $this->update_line_items_from_cart( $order ); |
|
| 56 | - $this->update_addresses_from_cart( $order ); |
|
| 57 | - $order->set_currency( get_woocommerce_currency() ); |
|
| 58 | - $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); |
|
| 59 | - $order->set_customer_id( get_current_user_id() ); |
|
| 60 | - $order->set_customer_ip_address( \WC_Geolocation::get_ip_address() ); |
|
| 61 | - $order->set_customer_user_agent( wc_get_user_agent() ); |
|
| 62 | - $order->update_meta_data( 'is_vat_exempt', wc()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' ); |
|
| 63 | - $order->calculate_totals(); |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - /** |
|
| 67 | - * Copies order data to customer object (not the session), so values persist for future checkouts. |
|
| 68 | - * |
|
| 69 | - * @param \WC_Order $order Order object. |
|
| 70 | - */ |
|
| 71 | - public function sync_customer_data_with_order( \WC_Order $order ) { |
|
| 72 | - if ( $order->get_customer_id() ) { |
|
| 73 | - $customer = new \WC_Customer( $order->get_customer_id() ); |
|
| 74 | - $customer->set_props( |
|
| 75 | - [ |
|
| 76 | - 'billing_first_name' => $order->get_billing_first_name(), |
|
| 77 | - 'billing_last_name' => $order->get_billing_last_name(), |
|
| 78 | - 'billing_company' => $order->get_billing_company(), |
|
| 79 | - 'billing_address_1' => $order->get_billing_address_1(), |
|
| 80 | - 'billing_address_2' => $order->get_billing_address_2(), |
|
| 81 | - 'billing_city' => $order->get_billing_city(), |
|
| 82 | - 'billing_state' => $order->get_billing_state(), |
|
| 83 | - 'billing_postcode' => $order->get_billing_postcode(), |
|
| 84 | - 'billing_country' => $order->get_billing_country(), |
|
| 85 | - 'billing_email' => $order->get_billing_email(), |
|
| 86 | - 'billing_phone' => $order->get_billing_phone(), |
|
| 87 | - 'shipping_first_name' => $order->get_shipping_first_name(), |
|
| 88 | - 'shipping_last_name' => $order->get_shipping_last_name(), |
|
| 89 | - 'shipping_company' => $order->get_shipping_company(), |
|
| 90 | - 'shipping_address_1' => $order->get_shipping_address_1(), |
|
| 91 | - 'shipping_address_2' => $order->get_shipping_address_2(), |
|
| 92 | - 'shipping_city' => $order->get_shipping_city(), |
|
| 93 | - 'shipping_state' => $order->get_shipping_state(), |
|
| 94 | - 'shipping_postcode' => $order->get_shipping_postcode(), |
|
| 95 | - 'shipping_country' => $order->get_shipping_country(), |
|
| 96 | - 'shipping_phone' => $order->get_shipping_phone(), |
|
| 97 | - ] |
|
| 98 | - ); |
|
| 99 | - |
|
| 100 | - $customer->save(); |
|
| 101 | - }; |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - /** |
|
| 105 | - * Final validation ran before payment is taken. |
|
| 106 | - * |
|
| 107 | - * By this point we have an order populated with customer data and items. |
|
| 108 | - * |
|
| 109 | - * @throws RouteException Exception if invalid data is detected. |
|
| 110 | - * @param \WC_Order $order Order object. |
|
| 111 | - */ |
|
| 112 | - public function validate_order_before_payment( \WC_Order $order ) { |
|
| 113 | - $needs_shipping = wc()->cart->needs_shipping(); |
|
| 114 | - $chosen_shipping_methods = wc()->session->get( 'chosen_shipping_methods' ); |
|
| 115 | - |
|
| 116 | - $this->validate_coupons( $order ); |
|
| 117 | - $this->validate_email( $order ); |
|
| 118 | - $this->validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods ); |
|
| 119 | - $this->validate_addresses( $order ); |
|
| 120 | - } |
|
| 121 | - |
|
| 122 | - /** |
|
| 123 | - * Convert a coupon code to a coupon object. |
|
| 124 | - * |
|
| 125 | - * @param string $coupon_code Coupon code. |
|
| 126 | - * @return \WC_Coupon Coupon object. |
|
| 127 | - */ |
|
| 128 | - protected function get_coupon( $coupon_code ) { |
|
| 129 | - return new \WC_Coupon( $coupon_code ); |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - /** |
|
| 133 | - * Validate coupons applied to the order and remove those that are not valid. |
|
| 134 | - * |
|
| 135 | - * @throws RouteException Exception if invalid data is detected. |
|
| 136 | - * @param \WC_Order $order Order object. |
|
| 137 | - */ |
|
| 138 | - protected function validate_coupons( \WC_Order $order ) { |
|
| 139 | - $coupon_codes = $order->get_coupon_codes(); |
|
| 140 | - $coupons = array_filter( array_map( [ $this, 'get_coupon' ], $coupon_codes ) ); |
|
| 141 | - $validators = [ 'validate_coupon_email_restriction', 'validate_coupon_usage_limit' ]; |
|
| 142 | - $coupon_errors = []; |
|
| 143 | - |
|
| 144 | - foreach ( $coupons as $coupon ) { |
|
| 145 | - try { |
|
| 146 | - array_walk( |
|
| 147 | - $validators, |
|
| 148 | - function( $validator, $index, $params ) { |
|
| 149 | - call_user_func_array( [ $this, $validator ], $params ); |
|
| 150 | - }, |
|
| 151 | - [ $coupon, $order ] |
|
| 152 | - ); |
|
| 153 | - } catch ( Exception $error ) { |
|
| 154 | - $coupon_errors[ $coupon->get_code() ] = $error->getMessage(); |
|
| 155 | - } |
|
| 156 | - } |
|
| 157 | - |
|
| 158 | - if ( $coupon_errors ) { |
|
| 159 | - // Remove all coupons that were not valid. |
|
| 160 | - foreach ( $coupon_errors as $coupon_code => $message ) { |
|
| 161 | - wc()->cart->remove_coupon( $coupon_code ); |
|
| 162 | - } |
|
| 163 | - |
|
| 164 | - // Recalculate totals. |
|
| 165 | - wc()->cart->calculate_totals(); |
|
| 166 | - |
|
| 167 | - // Re-sync order with cart. |
|
| 168 | - $this->update_order_from_cart( $order ); |
|
| 169 | - |
|
| 170 | - // Return exception so customer can review before payment. |
|
| 171 | - throw new RouteException( |
|
| 172 | - 'woocommerce_rest_cart_coupon_errors', |
|
| 173 | - sprintf( |
|
| 174 | - /* translators: %s Coupon codes. */ |
|
| 175 | - __( 'Invalid coupons were removed from the cart: "%s"', 'woocommerce' ), |
|
| 176 | - implode( '", "', array_keys( $coupon_errors ) ) |
|
| 177 | - ), |
|
| 178 | - 409, |
|
| 179 | - [ |
|
| 180 | - 'removed_coupons' => $coupon_errors, |
|
| 181 | - ] |
|
| 182 | - ); |
|
| 183 | - } |
|
| 184 | - } |
|
| 185 | - |
|
| 186 | - /** |
|
| 187 | - * Validates the customer email. This is a required field. |
|
| 188 | - * |
|
| 189 | - * @throws RouteException Exception if invalid data is detected. |
|
| 190 | - * @param \WC_Order $order Order object. |
|
| 191 | - */ |
|
| 192 | - protected function validate_email( \WC_Order $order ) { |
|
| 193 | - $email = $order->get_billing_email(); |
|
| 194 | - |
|
| 195 | - if ( empty( $email ) ) { |
|
| 196 | - throw new RouteException( |
|
| 197 | - 'woocommerce_rest_missing_email_address', |
|
| 198 | - __( 'A valid email address is required', 'woocommerce' ), |
|
| 199 | - 400 |
|
| 200 | - ); |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - if ( ! is_email( $email ) ) { |
|
| 204 | - throw new RouteException( |
|
| 205 | - 'woocommerce_rest_invalid_email_address', |
|
| 206 | - sprintf( |
|
| 207 | - /* translators: %s provided email. */ |
|
| 208 | - __( 'The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce' ), |
|
| 209 | - esc_html( $email ) |
|
| 210 | - ), |
|
| 211 | - 400 |
|
| 212 | - ); |
|
| 213 | - } |
|
| 214 | - } |
|
| 215 | - |
|
| 216 | - /** |
|
| 217 | - * Validates customer address data based on the locale to ensure required fields are set. |
|
| 218 | - * |
|
| 219 | - * @throws RouteException Exception if invalid data is detected. |
|
| 220 | - * @param \WC_Order $order Order object. |
|
| 221 | - */ |
|
| 222 | - protected function validate_addresses( \WC_Order $order ) { |
|
| 223 | - $errors = new \WP_Error(); |
|
| 224 | - $needs_shipping = wc()->cart->needs_shipping(); |
|
| 225 | - $billing_address = $order->get_address( 'billing' ); |
|
| 226 | - $shipping_address = $order->get_address( 'shipping' ); |
|
| 227 | - |
|
| 228 | - if ( $needs_shipping && ! $this->validate_allowed_country( $shipping_address['country'], (array) wc()->countries->get_shipping_countries() ) ) { |
|
| 229 | - throw new RouteException( |
|
| 230 | - 'woocommerce_rest_invalid_address_country', |
|
| 231 | - sprintf( |
|
| 232 | - /* translators: %s country code. */ |
|
| 233 | - __( 'Sorry, we do not ship orders to the provided country (%s)', 'woocommerce' ), |
|
| 234 | - $shipping_address['country'] |
|
| 235 | - ), |
|
| 236 | - 400, |
|
| 237 | - [ |
|
| 238 | - 'allowed_countries' => array_keys( wc()->countries->get_shipping_countries() ), |
|
| 239 | - ] |
|
| 240 | - ); |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - if ( ! $this->validate_allowed_country( $billing_address['country'], (array) wc()->countries->get_allowed_countries() ) ) { |
|
| 244 | - throw new RouteException( |
|
| 245 | - 'woocommerce_rest_invalid_address_country', |
|
| 246 | - sprintf( |
|
| 247 | - /* translators: %s country code. */ |
|
| 248 | - __( 'Sorry, we do not allow orders from the provided country (%s)', 'woocommerce' ), |
|
| 249 | - $billing_address['country'] |
|
| 250 | - ), |
|
| 251 | - 400, |
|
| 252 | - [ |
|
| 253 | - 'allowed_countries' => array_keys( wc()->countries->get_allowed_countries() ), |
|
| 254 | - ] |
|
| 255 | - ); |
|
| 256 | - } |
|
| 257 | - |
|
| 258 | - if ( $needs_shipping ) { |
|
| 259 | - $this->validate_address_fields( $shipping_address, 'shipping', $errors ); |
|
| 260 | - } |
|
| 261 | - $this->validate_address_fields( $billing_address, 'billing', $errors ); |
|
| 262 | - |
|
| 263 | - if ( ! $errors->has_errors() ) { |
|
| 264 | - return; |
|
| 265 | - } |
|
| 266 | - |
|
| 267 | - $errors_by_code = []; |
|
| 268 | - $error_codes = $errors->get_error_codes(); |
|
| 269 | - foreach ( $error_codes as $code ) { |
|
| 270 | - $errors_by_code[ $code ] = $errors->get_error_messages( $code ); |
|
| 271 | - } |
|
| 272 | - |
|
| 273 | - // Surface errors from first code. |
|
| 274 | - foreach ( $errors_by_code as $code => $error_messages ) { |
|
| 275 | - throw new RouteException( |
|
| 276 | - 'woocommerce_rest_invalid_address', |
|
| 277 | - sprintf( |
|
| 278 | - /* translators: %s Address type. */ |
|
| 279 | - __( 'There was a problem with the provided %s:', 'woocommerce' ) . ' ' . implode( ', ', $error_messages ), |
|
| 280 | - 'shipping' === $code ? __( 'shipping address', 'woocommerce' ) : __( 'billing address', 'woocommerce' ) |
|
| 281 | - ), |
|
| 282 | - 400, |
|
| 283 | - [ |
|
| 284 | - 'errors' => $errors_by_code, |
|
| 285 | - ] |
|
| 286 | - ); |
|
| 287 | - } |
|
| 288 | - } |
|
| 289 | - |
|
| 290 | - /** |
|
| 291 | - * Check all required address fields are set and return errors if not. |
|
| 292 | - * |
|
| 293 | - * @param string $country Country code. |
|
| 294 | - * @param array $allowed_countries List of valid country codes. |
|
| 295 | - * @return boolean True if valid. |
|
| 296 | - */ |
|
| 297 | - protected function validate_allowed_country( $country, array $allowed_countries ) { |
|
| 298 | - return array_key_exists( $country, $allowed_countries ); |
|
| 299 | - } |
|
| 300 | - |
|
| 301 | - /** |
|
| 302 | - * Check all required address fields are set and return errors if not. |
|
| 303 | - * |
|
| 304 | - * @param array $address Address array. |
|
| 305 | - * @param string $address_type billing or shipping address, used in error messages. |
|
| 306 | - * @param \WP_Error $errors Error object. |
|
| 307 | - */ |
|
| 308 | - protected function validate_address_fields( $address, $address_type, \WP_Error $errors ) { |
|
| 309 | - $all_locales = wc()->countries->get_country_locale(); |
|
| 310 | - $current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : []; |
|
| 311 | - |
|
| 312 | - /** |
|
| 313 | - * We are not using wc()->counties->get_default_address_fields() here because that is filtered. Instead, this array |
|
| 314 | - * is based on assets/js/base/components/cart-checkout/address-form/default-address-fields.js |
|
| 315 | - */ |
|
| 316 | - $address_fields = [ |
|
| 317 | - 'first_name' => [ |
|
| 318 | - 'label' => __( 'First name', 'woocommerce' ), |
|
| 319 | - 'required' => true, |
|
| 320 | - ], |
|
| 321 | - 'last_name' => [ |
|
| 322 | - 'label' => __( 'Last name', 'woocommerce' ), |
|
| 323 | - 'required' => true, |
|
| 324 | - ], |
|
| 325 | - 'company' => [ |
|
| 326 | - 'label' => __( 'Company', 'woocommerce' ), |
|
| 327 | - 'required' => false, |
|
| 328 | - ], |
|
| 329 | - 'address_1' => [ |
|
| 330 | - 'label' => __( 'Address', 'woocommerce' ), |
|
| 331 | - 'required' => true, |
|
| 332 | - ], |
|
| 333 | - 'address_2' => [ |
|
| 334 | - 'label' => __( 'Apartment, suite, etc.', 'woocommerce' ), |
|
| 335 | - 'required' => false, |
|
| 336 | - ], |
|
| 337 | - 'country' => [ |
|
| 338 | - 'label' => __( 'Country/Region', 'woocommerce' ), |
|
| 339 | - 'required' => true, |
|
| 340 | - ], |
|
| 341 | - 'city' => [ |
|
| 342 | - 'label' => __( 'City', 'woocommerce' ), |
|
| 343 | - 'required' => true, |
|
| 344 | - ], |
|
| 345 | - 'state' => [ |
|
| 346 | - 'label' => __( 'State/County', 'woocommerce' ), |
|
| 347 | - 'required' => true, |
|
| 348 | - ], |
|
| 349 | - 'postcode' => [ |
|
| 350 | - 'label' => __( 'Postal code', 'woocommerce' ), |
|
| 351 | - 'required' => true, |
|
| 352 | - ], |
|
| 353 | - ]; |
|
| 354 | - |
|
| 355 | - if ( $current_locale ) { |
|
| 356 | - foreach ( $current_locale as $key => $field ) { |
|
| 357 | - if ( isset( $address_fields[ $key ] ) ) { |
|
| 358 | - $address_fields[ $key ]['label'] = isset( $field['label'] ) ? $field['label'] : $address_fields[ $key ]['label']; |
|
| 359 | - $address_fields[ $key ]['required'] = isset( $field['required'] ) ? $field['required'] : $address_fields[ $key ]['required']; |
|
| 360 | - } |
|
| 361 | - } |
|
| 362 | - } |
|
| 363 | - |
|
| 364 | - foreach ( $address_fields as $address_field_key => $address_field ) { |
|
| 365 | - if ( empty( $address[ $address_field_key ] ) && $address_field['required'] ) { |
|
| 366 | - /* translators: %s Field label. */ |
|
| 367 | - $errors->add( $address_type, sprintf( __( '%s is required', 'woocommerce' ), $address_field['label'] ), $address_field_key ); |
|
| 368 | - } |
|
| 369 | - } |
|
| 370 | - } |
|
| 371 | - |
|
| 372 | - /** |
|
| 373 | - * Check email restrictions of a coupon against the order. |
|
| 374 | - * |
|
| 375 | - * @throws Exception Exception if invalid data is detected. |
|
| 376 | - * @param \WC_Coupon $coupon Coupon object applied to the cart. |
|
| 377 | - * @param \WC_Order $order Order object. |
|
| 378 | - */ |
|
| 379 | - protected function validate_coupon_email_restriction( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 380 | - $restrictions = $coupon->get_email_restrictions(); |
|
| 381 | - |
|
| 382 | - if ( ! empty( $restrictions ) && $order->get_billing_email() && ! wc()->cart->is_coupon_emails_allowed( [ $order->get_billing_email() ], $restrictions ) ) { |
|
| 383 | - throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ) ); |
|
| 384 | - } |
|
| 385 | - } |
|
| 386 | - |
|
| 387 | - /** |
|
| 388 | - * Check usage restrictions of a coupon against the order. |
|
| 389 | - * |
|
| 390 | - * @throws Exception Exception if invalid data is detected. |
|
| 391 | - * @param \WC_Coupon $coupon Coupon object applied to the cart. |
|
| 392 | - * @param \WC_Order $order Order object. |
|
| 393 | - */ |
|
| 394 | - protected function validate_coupon_usage_limit( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 395 | - $coupon_usage_limit = $coupon->get_usage_limit_per_user(); |
|
| 396 | - |
|
| 397 | - if ( $coupon_usage_limit > 0 ) { |
|
| 398 | - $data_store = $coupon->get_data_store(); |
|
| 399 | - $usage_count = $order->get_customer_id() ? $data_store->get_usage_by_user_id( $coupon, $order->get_customer_id() ) : $data_store->get_usage_by_email( $coupon, $order->get_billing_email() ); |
|
| 400 | - |
|
| 401 | - if ( $usage_count >= $coupon_usage_limit ) { |
|
| 402 | - throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ) ); |
|
| 403 | - } |
|
| 404 | - } |
|
| 405 | - } |
|
| 406 | - |
|
| 407 | - /** |
|
| 408 | - * Check there is a shipping method if it requires shipping. |
|
| 409 | - * |
|
| 410 | - * @throws RouteException Exception if invalid data is detected. |
|
| 411 | - * @param boolean $needs_shipping Current order needs shipping. |
|
| 412 | - * @param array $chosen_shipping_methods Array of shipping methods. |
|
| 413 | - */ |
|
| 414 | - public function validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods = array() ) { |
|
| 415 | - if ( ! $needs_shipping || ! is_array( $chosen_shipping_methods ) ) { |
|
| 416 | - return; |
|
| 417 | - } |
|
| 418 | - |
|
| 419 | - foreach ( $chosen_shipping_methods as $chosen_shipping_method ) { |
|
| 420 | - if ( false === $chosen_shipping_method ) { |
|
| 421 | - throw new RouteException( |
|
| 422 | - 'woocommerce_rest_invalid_shipping_option', |
|
| 423 | - __( 'Sorry, this order requires a shipping option.', 'woocommerce' ), |
|
| 424 | - 400, |
|
| 425 | - [] |
|
| 426 | - ); |
|
| 427 | - } |
|
| 428 | - } |
|
| 429 | - } |
|
| 430 | - |
|
| 431 | - /** |
|
| 432 | - * Changes default order status to draft for orders created via this API. |
|
| 433 | - * |
|
| 434 | - * @return string |
|
| 435 | - */ |
|
| 436 | - public function default_order_status() { |
|
| 437 | - return 'checkout-draft'; |
|
| 438 | - } |
|
| 439 | - |
|
| 440 | - /** |
|
| 441 | - * Passes the correct base for local pick orders |
|
| 442 | - * |
|
| 443 | - * @todo: Remove custom local pickup handling once WooCommerce 6.8.0 is the minimum version. |
|
| 444 | - * |
|
| 445 | - * @param array $location Taxes location. |
|
| 446 | - * @return array updated location that accounts for local pickup. |
|
| 447 | - */ |
|
| 448 | - public function handle_local_pickup_taxes( $location ) { |
|
| 449 | - $customer = wc()->customer; |
|
| 450 | - |
|
| 451 | - if ( ! empty( $customer ) ) { |
|
| 452 | - return $customer->get_taxable_address(); |
|
| 453 | - } |
|
| 454 | - |
|
| 455 | - return $location; |
|
| 456 | - } |
|
| 457 | - /** |
|
| 458 | - * Create order line items. |
|
| 459 | - * |
|
| 460 | - * @param \WC_Order $order The order object to update. |
|
| 461 | - */ |
|
| 462 | - protected function update_line_items_from_cart( \WC_Order $order ) { |
|
| 463 | - $cart_controller = new CartController(); |
|
| 464 | - $cart = $cart_controller->get_cart_instance(); |
|
| 465 | - $cart_hashes = $cart_controller->get_cart_hashes(); |
|
| 466 | - |
|
| 467 | - if ( $order->get_cart_hash() !== $cart_hashes['line_items'] ) { |
|
| 468 | - $order->set_cart_hash( $cart_hashes['line_items'] ); |
|
| 469 | - $order->remove_order_items( 'line_item' ); |
|
| 470 | - wc()->checkout->create_order_line_items( $order, $cart ); |
|
| 471 | - } |
|
| 472 | - |
|
| 473 | - if ( $order->get_meta_data( '_shipping_hash' ) !== $cart_hashes['shipping'] ) { |
|
| 474 | - $order->update_meta_data( '_shipping_hash', $cart_hashes['shipping'] ); |
|
| 475 | - $order->remove_order_items( 'shipping' ); |
|
| 476 | - wc()->checkout->create_order_shipping_lines( $order, wc()->session->get( 'chosen_shipping_methods' ), wc()->shipping()->get_packages() ); |
|
| 477 | - } |
|
| 478 | - |
|
| 479 | - if ( $order->get_meta_data( '_coupons_hash' ) !== $cart_hashes['coupons'] ) { |
|
| 480 | - $order->remove_order_items( 'coupon' ); |
|
| 481 | - $order->update_meta_data( '_coupons_hash', $cart_hashes['coupons'] ); |
|
| 482 | - wc()->checkout->create_order_coupon_lines( $order, $cart ); |
|
| 483 | - } |
|
| 484 | - |
|
| 485 | - if ( $order->get_meta_data( '_fees_hash' ) !== $cart_hashes['fees'] ) { |
|
| 486 | - $order->update_meta_data( '_fees_hash', $cart_hashes['fees'] ); |
|
| 487 | - $order->remove_order_items( 'fee' ); |
|
| 488 | - wc()->checkout->create_order_fee_lines( $order, $cart ); |
|
| 489 | - } |
|
| 490 | - |
|
| 491 | - if ( $order->get_meta_data( '_taxes_hash' ) !== $cart_hashes['taxes'] ) { |
|
| 492 | - $order->update_meta_data( '_taxes_hash', $cart_hashes['taxes'] ); |
|
| 493 | - $order->remove_order_items( 'tax' ); |
|
| 494 | - wc()->checkout->create_order_tax_lines( $order, $cart ); |
|
| 495 | - } |
|
| 496 | - } |
|
| 497 | - |
|
| 498 | - /** |
|
| 499 | - * Update address data from cart and/or customer session data. |
|
| 500 | - * |
|
| 501 | - * @param \WC_Order $order The order object to update. |
|
| 502 | - */ |
|
| 503 | - protected function update_addresses_from_cart( \WC_Order $order ) { |
|
| 504 | - $order->set_props( |
|
| 505 | - [ |
|
| 506 | - 'billing_first_name' => wc()->customer->get_billing_first_name(), |
|
| 507 | - 'billing_last_name' => wc()->customer->get_billing_last_name(), |
|
| 508 | - 'billing_company' => wc()->customer->get_billing_company(), |
|
| 509 | - 'billing_address_1' => wc()->customer->get_billing_address_1(), |
|
| 510 | - 'billing_address_2' => wc()->customer->get_billing_address_2(), |
|
| 511 | - 'billing_city' => wc()->customer->get_billing_city(), |
|
| 512 | - 'billing_state' => wc()->customer->get_billing_state(), |
|
| 513 | - 'billing_postcode' => wc()->customer->get_billing_postcode(), |
|
| 514 | - 'billing_country' => wc()->customer->get_billing_country(), |
|
| 515 | - 'billing_email' => wc()->customer->get_billing_email(), |
|
| 516 | - 'billing_phone' => wc()->customer->get_billing_phone(), |
|
| 517 | - 'shipping_first_name' => wc()->customer->get_shipping_first_name(), |
|
| 518 | - 'shipping_last_name' => wc()->customer->get_shipping_last_name(), |
|
| 519 | - 'shipping_company' => wc()->customer->get_shipping_company(), |
|
| 520 | - 'shipping_address_1' => wc()->customer->get_shipping_address_1(), |
|
| 521 | - 'shipping_address_2' => wc()->customer->get_shipping_address_2(), |
|
| 522 | - 'shipping_city' => wc()->customer->get_shipping_city(), |
|
| 523 | - 'shipping_state' => wc()->customer->get_shipping_state(), |
|
| 524 | - 'shipping_postcode' => wc()->customer->get_shipping_postcode(), |
|
| 525 | - 'shipping_country' => wc()->customer->get_shipping_country(), |
|
| 526 | - 'shipping_phone' => wc()->customer->get_shipping_phone(), |
|
| 527 | - ] |
|
| 528 | - ); |
|
| 529 | - } |
|
| 13 | + /** |
|
| 14 | + * Create order and set props based on global settings. |
|
| 15 | + * |
|
| 16 | + * @throws RouteException Exception if invalid data is detected. |
|
| 17 | + * |
|
| 18 | + * @return \WC_Order A new order object. |
|
| 19 | + */ |
|
| 20 | + public function create_order_from_cart() { |
|
| 21 | + if ( wc()->cart->is_empty() ) { |
|
| 22 | + throw new RouteException( |
|
| 23 | + 'woocommerce_rest_cart_empty', |
|
| 24 | + __( 'Cannot create order from empty cart.', 'woocommerce' ), |
|
| 25 | + 400 |
|
| 26 | + ); |
|
| 27 | + } |
|
| 28 | + |
|
| 29 | + add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 30 | + |
|
| 31 | + $order = new \WC_Order(); |
|
| 32 | + $order->set_status( 'checkout-draft' ); |
|
| 33 | + $order->set_created_via( 'store-api' ); |
|
| 34 | + $this->update_order_from_cart( $order ); |
|
| 35 | + |
|
| 36 | + remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 37 | + |
|
| 38 | + return $order; |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + /** |
|
| 42 | + * Update an order using data from the current cart. |
|
| 43 | + * |
|
| 44 | + * @param \WC_Order $order The order object to update. |
|
| 45 | + */ |
|
| 46 | + public function update_order_from_cart( \WC_Order $order ) { |
|
| 47 | + // Ensures Local pickups are accounted for. |
|
| 48 | + add_filter( 'woocommerce_order_get_tax_location', array( $this, 'handle_local_pickup_taxes' ) ); |
|
| 49 | + |
|
| 50 | + // Ensure cart is current. |
|
| 51 | + wc()->cart->calculate_shipping(); |
|
| 52 | + wc()->cart->calculate_totals(); |
|
| 53 | + |
|
| 54 | + // Update the current order to match the current cart. |
|
| 55 | + $this->update_line_items_from_cart( $order ); |
|
| 56 | + $this->update_addresses_from_cart( $order ); |
|
| 57 | + $order->set_currency( get_woocommerce_currency() ); |
|
| 58 | + $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); |
|
| 59 | + $order->set_customer_id( get_current_user_id() ); |
|
| 60 | + $order->set_customer_ip_address( \WC_Geolocation::get_ip_address() ); |
|
| 61 | + $order->set_customer_user_agent( wc_get_user_agent() ); |
|
| 62 | + $order->update_meta_data( 'is_vat_exempt', wc()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' ); |
|
| 63 | + $order->calculate_totals(); |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + /** |
|
| 67 | + * Copies order data to customer object (not the session), so values persist for future checkouts. |
|
| 68 | + * |
|
| 69 | + * @param \WC_Order $order Order object. |
|
| 70 | + */ |
|
| 71 | + public function sync_customer_data_with_order( \WC_Order $order ) { |
|
| 72 | + if ( $order->get_customer_id() ) { |
|
| 73 | + $customer = new \WC_Customer( $order->get_customer_id() ); |
|
| 74 | + $customer->set_props( |
|
| 75 | + [ |
|
| 76 | + 'billing_first_name' => $order->get_billing_first_name(), |
|
| 77 | + 'billing_last_name' => $order->get_billing_last_name(), |
|
| 78 | + 'billing_company' => $order->get_billing_company(), |
|
| 79 | + 'billing_address_1' => $order->get_billing_address_1(), |
|
| 80 | + 'billing_address_2' => $order->get_billing_address_2(), |
|
| 81 | + 'billing_city' => $order->get_billing_city(), |
|
| 82 | + 'billing_state' => $order->get_billing_state(), |
|
| 83 | + 'billing_postcode' => $order->get_billing_postcode(), |
|
| 84 | + 'billing_country' => $order->get_billing_country(), |
|
| 85 | + 'billing_email' => $order->get_billing_email(), |
|
| 86 | + 'billing_phone' => $order->get_billing_phone(), |
|
| 87 | + 'shipping_first_name' => $order->get_shipping_first_name(), |
|
| 88 | + 'shipping_last_name' => $order->get_shipping_last_name(), |
|
| 89 | + 'shipping_company' => $order->get_shipping_company(), |
|
| 90 | + 'shipping_address_1' => $order->get_shipping_address_1(), |
|
| 91 | + 'shipping_address_2' => $order->get_shipping_address_2(), |
|
| 92 | + 'shipping_city' => $order->get_shipping_city(), |
|
| 93 | + 'shipping_state' => $order->get_shipping_state(), |
|
| 94 | + 'shipping_postcode' => $order->get_shipping_postcode(), |
|
| 95 | + 'shipping_country' => $order->get_shipping_country(), |
|
| 96 | + 'shipping_phone' => $order->get_shipping_phone(), |
|
| 97 | + ] |
|
| 98 | + ); |
|
| 99 | + |
|
| 100 | + $customer->save(); |
|
| 101 | + }; |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + /** |
|
| 105 | + * Final validation ran before payment is taken. |
|
| 106 | + * |
|
| 107 | + * By this point we have an order populated with customer data and items. |
|
| 108 | + * |
|
| 109 | + * @throws RouteException Exception if invalid data is detected. |
|
| 110 | + * @param \WC_Order $order Order object. |
|
| 111 | + */ |
|
| 112 | + public function validate_order_before_payment( \WC_Order $order ) { |
|
| 113 | + $needs_shipping = wc()->cart->needs_shipping(); |
|
| 114 | + $chosen_shipping_methods = wc()->session->get( 'chosen_shipping_methods' ); |
|
| 115 | + |
|
| 116 | + $this->validate_coupons( $order ); |
|
| 117 | + $this->validate_email( $order ); |
|
| 118 | + $this->validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods ); |
|
| 119 | + $this->validate_addresses( $order ); |
|
| 120 | + } |
|
| 121 | + |
|
| 122 | + /** |
|
| 123 | + * Convert a coupon code to a coupon object. |
|
| 124 | + * |
|
| 125 | + * @param string $coupon_code Coupon code. |
|
| 126 | + * @return \WC_Coupon Coupon object. |
|
| 127 | + */ |
|
| 128 | + protected function get_coupon( $coupon_code ) { |
|
| 129 | + return new \WC_Coupon( $coupon_code ); |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + /** |
|
| 133 | + * Validate coupons applied to the order and remove those that are not valid. |
|
| 134 | + * |
|
| 135 | + * @throws RouteException Exception if invalid data is detected. |
|
| 136 | + * @param \WC_Order $order Order object. |
|
| 137 | + */ |
|
| 138 | + protected function validate_coupons( \WC_Order $order ) { |
|
| 139 | + $coupon_codes = $order->get_coupon_codes(); |
|
| 140 | + $coupons = array_filter( array_map( [ $this, 'get_coupon' ], $coupon_codes ) ); |
|
| 141 | + $validators = [ 'validate_coupon_email_restriction', 'validate_coupon_usage_limit' ]; |
|
| 142 | + $coupon_errors = []; |
|
| 143 | + |
|
| 144 | + foreach ( $coupons as $coupon ) { |
|
| 145 | + try { |
|
| 146 | + array_walk( |
|
| 147 | + $validators, |
|
| 148 | + function( $validator, $index, $params ) { |
|
| 149 | + call_user_func_array( [ $this, $validator ], $params ); |
|
| 150 | + }, |
|
| 151 | + [ $coupon, $order ] |
|
| 152 | + ); |
|
| 153 | + } catch ( Exception $error ) { |
|
| 154 | + $coupon_errors[ $coupon->get_code() ] = $error->getMessage(); |
|
| 155 | + } |
|
| 156 | + } |
|
| 157 | + |
|
| 158 | + if ( $coupon_errors ) { |
|
| 159 | + // Remove all coupons that were not valid. |
|
| 160 | + foreach ( $coupon_errors as $coupon_code => $message ) { |
|
| 161 | + wc()->cart->remove_coupon( $coupon_code ); |
|
| 162 | + } |
|
| 163 | + |
|
| 164 | + // Recalculate totals. |
|
| 165 | + wc()->cart->calculate_totals(); |
|
| 166 | + |
|
| 167 | + // Re-sync order with cart. |
|
| 168 | + $this->update_order_from_cart( $order ); |
|
| 169 | + |
|
| 170 | + // Return exception so customer can review before payment. |
|
| 171 | + throw new RouteException( |
|
| 172 | + 'woocommerce_rest_cart_coupon_errors', |
|
| 173 | + sprintf( |
|
| 174 | + /* translators: %s Coupon codes. */ |
|
| 175 | + __( 'Invalid coupons were removed from the cart: "%s"', 'woocommerce' ), |
|
| 176 | + implode( '", "', array_keys( $coupon_errors ) ) |
|
| 177 | + ), |
|
| 178 | + 409, |
|
| 179 | + [ |
|
| 180 | + 'removed_coupons' => $coupon_errors, |
|
| 181 | + ] |
|
| 182 | + ); |
|
| 183 | + } |
|
| 184 | + } |
|
| 185 | + |
|
| 186 | + /** |
|
| 187 | + * Validates the customer email. This is a required field. |
|
| 188 | + * |
|
| 189 | + * @throws RouteException Exception if invalid data is detected. |
|
| 190 | + * @param \WC_Order $order Order object. |
|
| 191 | + */ |
|
| 192 | + protected function validate_email( \WC_Order $order ) { |
|
| 193 | + $email = $order->get_billing_email(); |
|
| 194 | + |
|
| 195 | + if ( empty( $email ) ) { |
|
| 196 | + throw new RouteException( |
|
| 197 | + 'woocommerce_rest_missing_email_address', |
|
| 198 | + __( 'A valid email address is required', 'woocommerce' ), |
|
| 199 | + 400 |
|
| 200 | + ); |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + if ( ! is_email( $email ) ) { |
|
| 204 | + throw new RouteException( |
|
| 205 | + 'woocommerce_rest_invalid_email_address', |
|
| 206 | + sprintf( |
|
| 207 | + /* translators: %s provided email. */ |
|
| 208 | + __( 'The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce' ), |
|
| 209 | + esc_html( $email ) |
|
| 210 | + ), |
|
| 211 | + 400 |
|
| 212 | + ); |
|
| 213 | + } |
|
| 214 | + } |
|
| 215 | + |
|
| 216 | + /** |
|
| 217 | + * Validates customer address data based on the locale to ensure required fields are set. |
|
| 218 | + * |
|
| 219 | + * @throws RouteException Exception if invalid data is detected. |
|
| 220 | + * @param \WC_Order $order Order object. |
|
| 221 | + */ |
|
| 222 | + protected function validate_addresses( \WC_Order $order ) { |
|
| 223 | + $errors = new \WP_Error(); |
|
| 224 | + $needs_shipping = wc()->cart->needs_shipping(); |
|
| 225 | + $billing_address = $order->get_address( 'billing' ); |
|
| 226 | + $shipping_address = $order->get_address( 'shipping' ); |
|
| 227 | + |
|
| 228 | + if ( $needs_shipping && ! $this->validate_allowed_country( $shipping_address['country'], (array) wc()->countries->get_shipping_countries() ) ) { |
|
| 229 | + throw new RouteException( |
|
| 230 | + 'woocommerce_rest_invalid_address_country', |
|
| 231 | + sprintf( |
|
| 232 | + /* translators: %s country code. */ |
|
| 233 | + __( 'Sorry, we do not ship orders to the provided country (%s)', 'woocommerce' ), |
|
| 234 | + $shipping_address['country'] |
|
| 235 | + ), |
|
| 236 | + 400, |
|
| 237 | + [ |
|
| 238 | + 'allowed_countries' => array_keys( wc()->countries->get_shipping_countries() ), |
|
| 239 | + ] |
|
| 240 | + ); |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + if ( ! $this->validate_allowed_country( $billing_address['country'], (array) wc()->countries->get_allowed_countries() ) ) { |
|
| 244 | + throw new RouteException( |
|
| 245 | + 'woocommerce_rest_invalid_address_country', |
|
| 246 | + sprintf( |
|
| 247 | + /* translators: %s country code. */ |
|
| 248 | + __( 'Sorry, we do not allow orders from the provided country (%s)', 'woocommerce' ), |
|
| 249 | + $billing_address['country'] |
|
| 250 | + ), |
|
| 251 | + 400, |
|
| 252 | + [ |
|
| 253 | + 'allowed_countries' => array_keys( wc()->countries->get_allowed_countries() ), |
|
| 254 | + ] |
|
| 255 | + ); |
|
| 256 | + } |
|
| 257 | + |
|
| 258 | + if ( $needs_shipping ) { |
|
| 259 | + $this->validate_address_fields( $shipping_address, 'shipping', $errors ); |
|
| 260 | + } |
|
| 261 | + $this->validate_address_fields( $billing_address, 'billing', $errors ); |
|
| 262 | + |
|
| 263 | + if ( ! $errors->has_errors() ) { |
|
| 264 | + return; |
|
| 265 | + } |
|
| 266 | + |
|
| 267 | + $errors_by_code = []; |
|
| 268 | + $error_codes = $errors->get_error_codes(); |
|
| 269 | + foreach ( $error_codes as $code ) { |
|
| 270 | + $errors_by_code[ $code ] = $errors->get_error_messages( $code ); |
|
| 271 | + } |
|
| 272 | + |
|
| 273 | + // Surface errors from first code. |
|
| 274 | + foreach ( $errors_by_code as $code => $error_messages ) { |
|
| 275 | + throw new RouteException( |
|
| 276 | + 'woocommerce_rest_invalid_address', |
|
| 277 | + sprintf( |
|
| 278 | + /* translators: %s Address type. */ |
|
| 279 | + __( 'There was a problem with the provided %s:', 'woocommerce' ) . ' ' . implode( ', ', $error_messages ), |
|
| 280 | + 'shipping' === $code ? __( 'shipping address', 'woocommerce' ) : __( 'billing address', 'woocommerce' ) |
|
| 281 | + ), |
|
| 282 | + 400, |
|
| 283 | + [ |
|
| 284 | + 'errors' => $errors_by_code, |
|
| 285 | + ] |
|
| 286 | + ); |
|
| 287 | + } |
|
| 288 | + } |
|
| 289 | + |
|
| 290 | + /** |
|
| 291 | + * Check all required address fields are set and return errors if not. |
|
| 292 | + * |
|
| 293 | + * @param string $country Country code. |
|
| 294 | + * @param array $allowed_countries List of valid country codes. |
|
| 295 | + * @return boolean True if valid. |
|
| 296 | + */ |
|
| 297 | + protected function validate_allowed_country( $country, array $allowed_countries ) { |
|
| 298 | + return array_key_exists( $country, $allowed_countries ); |
|
| 299 | + } |
|
| 300 | + |
|
| 301 | + /** |
|
| 302 | + * Check all required address fields are set and return errors if not. |
|
| 303 | + * |
|
| 304 | + * @param array $address Address array. |
|
| 305 | + * @param string $address_type billing or shipping address, used in error messages. |
|
| 306 | + * @param \WP_Error $errors Error object. |
|
| 307 | + */ |
|
| 308 | + protected function validate_address_fields( $address, $address_type, \WP_Error $errors ) { |
|
| 309 | + $all_locales = wc()->countries->get_country_locale(); |
|
| 310 | + $current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : []; |
|
| 311 | + |
|
| 312 | + /** |
|
| 313 | + * We are not using wc()->counties->get_default_address_fields() here because that is filtered. Instead, this array |
|
| 314 | + * is based on assets/js/base/components/cart-checkout/address-form/default-address-fields.js |
|
| 315 | + */ |
|
| 316 | + $address_fields = [ |
|
| 317 | + 'first_name' => [ |
|
| 318 | + 'label' => __( 'First name', 'woocommerce' ), |
|
| 319 | + 'required' => true, |
|
| 320 | + ], |
|
| 321 | + 'last_name' => [ |
|
| 322 | + 'label' => __( 'Last name', 'woocommerce' ), |
|
| 323 | + 'required' => true, |
|
| 324 | + ], |
|
| 325 | + 'company' => [ |
|
| 326 | + 'label' => __( 'Company', 'woocommerce' ), |
|
| 327 | + 'required' => false, |
|
| 328 | + ], |
|
| 329 | + 'address_1' => [ |
|
| 330 | + 'label' => __( 'Address', 'woocommerce' ), |
|
| 331 | + 'required' => true, |
|
| 332 | + ], |
|
| 333 | + 'address_2' => [ |
|
| 334 | + 'label' => __( 'Apartment, suite, etc.', 'woocommerce' ), |
|
| 335 | + 'required' => false, |
|
| 336 | + ], |
|
| 337 | + 'country' => [ |
|
| 338 | + 'label' => __( 'Country/Region', 'woocommerce' ), |
|
| 339 | + 'required' => true, |
|
| 340 | + ], |
|
| 341 | + 'city' => [ |
|
| 342 | + 'label' => __( 'City', 'woocommerce' ), |
|
| 343 | + 'required' => true, |
|
| 344 | + ], |
|
| 345 | + 'state' => [ |
|
| 346 | + 'label' => __( 'State/County', 'woocommerce' ), |
|
| 347 | + 'required' => true, |
|
| 348 | + ], |
|
| 349 | + 'postcode' => [ |
|
| 350 | + 'label' => __( 'Postal code', 'woocommerce' ), |
|
| 351 | + 'required' => true, |
|
| 352 | + ], |
|
| 353 | + ]; |
|
| 354 | + |
|
| 355 | + if ( $current_locale ) { |
|
| 356 | + foreach ( $current_locale as $key => $field ) { |
|
| 357 | + if ( isset( $address_fields[ $key ] ) ) { |
|
| 358 | + $address_fields[ $key ]['label'] = isset( $field['label'] ) ? $field['label'] : $address_fields[ $key ]['label']; |
|
| 359 | + $address_fields[ $key ]['required'] = isset( $field['required'] ) ? $field['required'] : $address_fields[ $key ]['required']; |
|
| 360 | + } |
|
| 361 | + } |
|
| 362 | + } |
|
| 363 | + |
|
| 364 | + foreach ( $address_fields as $address_field_key => $address_field ) { |
|
| 365 | + if ( empty( $address[ $address_field_key ] ) && $address_field['required'] ) { |
|
| 366 | + /* translators: %s Field label. */ |
|
| 367 | + $errors->add( $address_type, sprintf( __( '%s is required', 'woocommerce' ), $address_field['label'] ), $address_field_key ); |
|
| 368 | + } |
|
| 369 | + } |
|
| 370 | + } |
|
| 371 | + |
|
| 372 | + /** |
|
| 373 | + * Check email restrictions of a coupon against the order. |
|
| 374 | + * |
|
| 375 | + * @throws Exception Exception if invalid data is detected. |
|
| 376 | + * @param \WC_Coupon $coupon Coupon object applied to the cart. |
|
| 377 | + * @param \WC_Order $order Order object. |
|
| 378 | + */ |
|
| 379 | + protected function validate_coupon_email_restriction( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 380 | + $restrictions = $coupon->get_email_restrictions(); |
|
| 381 | + |
|
| 382 | + if ( ! empty( $restrictions ) && $order->get_billing_email() && ! wc()->cart->is_coupon_emails_allowed( [ $order->get_billing_email() ], $restrictions ) ) { |
|
| 383 | + throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ) ); |
|
| 384 | + } |
|
| 385 | + } |
|
| 386 | + |
|
| 387 | + /** |
|
| 388 | + * Check usage restrictions of a coupon against the order. |
|
| 389 | + * |
|
| 390 | + * @throws Exception Exception if invalid data is detected. |
|
| 391 | + * @param \WC_Coupon $coupon Coupon object applied to the cart. |
|
| 392 | + * @param \WC_Order $order Order object. |
|
| 393 | + */ |
|
| 394 | + protected function validate_coupon_usage_limit( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 395 | + $coupon_usage_limit = $coupon->get_usage_limit_per_user(); |
|
| 396 | + |
|
| 397 | + if ( $coupon_usage_limit > 0 ) { |
|
| 398 | + $data_store = $coupon->get_data_store(); |
|
| 399 | + $usage_count = $order->get_customer_id() ? $data_store->get_usage_by_user_id( $coupon, $order->get_customer_id() ) : $data_store->get_usage_by_email( $coupon, $order->get_billing_email() ); |
|
| 400 | + |
|
| 401 | + if ( $usage_count >= $coupon_usage_limit ) { |
|
| 402 | + throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ) ); |
|
| 403 | + } |
|
| 404 | + } |
|
| 405 | + } |
|
| 406 | + |
|
| 407 | + /** |
|
| 408 | + * Check there is a shipping method if it requires shipping. |
|
| 409 | + * |
|
| 410 | + * @throws RouteException Exception if invalid data is detected. |
|
| 411 | + * @param boolean $needs_shipping Current order needs shipping. |
|
| 412 | + * @param array $chosen_shipping_methods Array of shipping methods. |
|
| 413 | + */ |
|
| 414 | + public function validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods = array() ) { |
|
| 415 | + if ( ! $needs_shipping || ! is_array( $chosen_shipping_methods ) ) { |
|
| 416 | + return; |
|
| 417 | + } |
|
| 418 | + |
|
| 419 | + foreach ( $chosen_shipping_methods as $chosen_shipping_method ) { |
|
| 420 | + if ( false === $chosen_shipping_method ) { |
|
| 421 | + throw new RouteException( |
|
| 422 | + 'woocommerce_rest_invalid_shipping_option', |
|
| 423 | + __( 'Sorry, this order requires a shipping option.', 'woocommerce' ), |
|
| 424 | + 400, |
|
| 425 | + [] |
|
| 426 | + ); |
|
| 427 | + } |
|
| 428 | + } |
|
| 429 | + } |
|
| 430 | + |
|
| 431 | + /** |
|
| 432 | + * Changes default order status to draft for orders created via this API. |
|
| 433 | + * |
|
| 434 | + * @return string |
|
| 435 | + */ |
|
| 436 | + public function default_order_status() { |
|
| 437 | + return 'checkout-draft'; |
|
| 438 | + } |
|
| 439 | + |
|
| 440 | + /** |
|
| 441 | + * Passes the correct base for local pick orders |
|
| 442 | + * |
|
| 443 | + * @todo: Remove custom local pickup handling once WooCommerce 6.8.0 is the minimum version. |
|
| 444 | + * |
|
| 445 | + * @param array $location Taxes location. |
|
| 446 | + * @return array updated location that accounts for local pickup. |
|
| 447 | + */ |
|
| 448 | + public function handle_local_pickup_taxes( $location ) { |
|
| 449 | + $customer = wc()->customer; |
|
| 450 | + |
|
| 451 | + if ( ! empty( $customer ) ) { |
|
| 452 | + return $customer->get_taxable_address(); |
|
| 453 | + } |
|
| 454 | + |
|
| 455 | + return $location; |
|
| 456 | + } |
|
| 457 | + /** |
|
| 458 | + * Create order line items. |
|
| 459 | + * |
|
| 460 | + * @param \WC_Order $order The order object to update. |
|
| 461 | + */ |
|
| 462 | + protected function update_line_items_from_cart( \WC_Order $order ) { |
|
| 463 | + $cart_controller = new CartController(); |
|
| 464 | + $cart = $cart_controller->get_cart_instance(); |
|
| 465 | + $cart_hashes = $cart_controller->get_cart_hashes(); |
|
| 466 | + |
|
| 467 | + if ( $order->get_cart_hash() !== $cart_hashes['line_items'] ) { |
|
| 468 | + $order->set_cart_hash( $cart_hashes['line_items'] ); |
|
| 469 | + $order->remove_order_items( 'line_item' ); |
|
| 470 | + wc()->checkout->create_order_line_items( $order, $cart ); |
|
| 471 | + } |
|
| 472 | + |
|
| 473 | + if ( $order->get_meta_data( '_shipping_hash' ) !== $cart_hashes['shipping'] ) { |
|
| 474 | + $order->update_meta_data( '_shipping_hash', $cart_hashes['shipping'] ); |
|
| 475 | + $order->remove_order_items( 'shipping' ); |
|
| 476 | + wc()->checkout->create_order_shipping_lines( $order, wc()->session->get( 'chosen_shipping_methods' ), wc()->shipping()->get_packages() ); |
|
| 477 | + } |
|
| 478 | + |
|
| 479 | + if ( $order->get_meta_data( '_coupons_hash' ) !== $cart_hashes['coupons'] ) { |
|
| 480 | + $order->remove_order_items( 'coupon' ); |
|
| 481 | + $order->update_meta_data( '_coupons_hash', $cart_hashes['coupons'] ); |
|
| 482 | + wc()->checkout->create_order_coupon_lines( $order, $cart ); |
|
| 483 | + } |
|
| 484 | + |
|
| 485 | + if ( $order->get_meta_data( '_fees_hash' ) !== $cart_hashes['fees'] ) { |
|
| 486 | + $order->update_meta_data( '_fees_hash', $cart_hashes['fees'] ); |
|
| 487 | + $order->remove_order_items( 'fee' ); |
|
| 488 | + wc()->checkout->create_order_fee_lines( $order, $cart ); |
|
| 489 | + } |
|
| 490 | + |
|
| 491 | + if ( $order->get_meta_data( '_taxes_hash' ) !== $cart_hashes['taxes'] ) { |
|
| 492 | + $order->update_meta_data( '_taxes_hash', $cart_hashes['taxes'] ); |
|
| 493 | + $order->remove_order_items( 'tax' ); |
|
| 494 | + wc()->checkout->create_order_tax_lines( $order, $cart ); |
|
| 495 | + } |
|
| 496 | + } |
|
| 497 | + |
|
| 498 | + /** |
|
| 499 | + * Update address data from cart and/or customer session data. |
|
| 500 | + * |
|
| 501 | + * @param \WC_Order $order The order object to update. |
|
| 502 | + */ |
|
| 503 | + protected function update_addresses_from_cart( \WC_Order $order ) { |
|
| 504 | + $order->set_props( |
|
| 505 | + [ |
|
| 506 | + 'billing_first_name' => wc()->customer->get_billing_first_name(), |
|
| 507 | + 'billing_last_name' => wc()->customer->get_billing_last_name(), |
|
| 508 | + 'billing_company' => wc()->customer->get_billing_company(), |
|
| 509 | + 'billing_address_1' => wc()->customer->get_billing_address_1(), |
|
| 510 | + 'billing_address_2' => wc()->customer->get_billing_address_2(), |
|
| 511 | + 'billing_city' => wc()->customer->get_billing_city(), |
|
| 512 | + 'billing_state' => wc()->customer->get_billing_state(), |
|
| 513 | + 'billing_postcode' => wc()->customer->get_billing_postcode(), |
|
| 514 | + 'billing_country' => wc()->customer->get_billing_country(), |
|
| 515 | + 'billing_email' => wc()->customer->get_billing_email(), |
|
| 516 | + 'billing_phone' => wc()->customer->get_billing_phone(), |
|
| 517 | + 'shipping_first_name' => wc()->customer->get_shipping_first_name(), |
|
| 518 | + 'shipping_last_name' => wc()->customer->get_shipping_last_name(), |
|
| 519 | + 'shipping_company' => wc()->customer->get_shipping_company(), |
|
| 520 | + 'shipping_address_1' => wc()->customer->get_shipping_address_1(), |
|
| 521 | + 'shipping_address_2' => wc()->customer->get_shipping_address_2(), |
|
| 522 | + 'shipping_city' => wc()->customer->get_shipping_city(), |
|
| 523 | + 'shipping_state' => wc()->customer->get_shipping_state(), |
|
| 524 | + 'shipping_postcode' => wc()->customer->get_shipping_postcode(), |
|
| 525 | + 'shipping_country' => wc()->customer->get_shipping_country(), |
|
| 526 | + 'shipping_phone' => wc()->customer->get_shipping_phone(), |
|
| 527 | + ] |
|
| 528 | + ); |
|
| 529 | + } |
|
| 530 | 530 | } |
@@ -18,22 +18,22 @@ discard block |
||
| 18 | 18 | * @return \WC_Order A new order object. |
| 19 | 19 | */ |
| 20 | 20 | public function create_order_from_cart() { |
| 21 | - if ( wc()->cart->is_empty() ) { |
|
| 21 | + if (wc()->cart->is_empty()) { |
|
| 22 | 22 | throw new RouteException( |
| 23 | 23 | 'woocommerce_rest_cart_empty', |
| 24 | - __( 'Cannot create order from empty cart.', 'woocommerce' ), |
|
| 24 | + __('Cannot create order from empty cart.', 'woocommerce'), |
|
| 25 | 25 | 400 |
| 26 | 26 | ); |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | - add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 29 | + add_filter('woocommerce_default_order_status', array($this, 'default_order_status')); |
|
| 30 | 30 | |
| 31 | 31 | $order = new \WC_Order(); |
| 32 | - $order->set_status( 'checkout-draft' ); |
|
| 33 | - $order->set_created_via( 'store-api' ); |
|
| 34 | - $this->update_order_from_cart( $order ); |
|
| 32 | + $order->set_status('checkout-draft'); |
|
| 33 | + $order->set_created_via('store-api'); |
|
| 34 | + $this->update_order_from_cart($order); |
|
| 35 | 35 | |
| 36 | - remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); |
|
| 36 | + remove_filter('woocommerce_default_order_status', array($this, 'default_order_status')); |
|
| 37 | 37 | |
| 38 | 38 | return $order; |
| 39 | 39 | } |
@@ -43,23 +43,23 @@ discard block |
||
| 43 | 43 | * |
| 44 | 44 | * @param \WC_Order $order The order object to update. |
| 45 | 45 | */ |
| 46 | - public function update_order_from_cart( \WC_Order $order ) { |
|
| 46 | + public function update_order_from_cart(\WC_Order $order) { |
|
| 47 | 47 | // Ensures Local pickups are accounted for. |
| 48 | - add_filter( 'woocommerce_order_get_tax_location', array( $this, 'handle_local_pickup_taxes' ) ); |
|
| 48 | + add_filter('woocommerce_order_get_tax_location', array($this, 'handle_local_pickup_taxes')); |
|
| 49 | 49 | |
| 50 | 50 | // Ensure cart is current. |
| 51 | 51 | wc()->cart->calculate_shipping(); |
| 52 | 52 | wc()->cart->calculate_totals(); |
| 53 | 53 | |
| 54 | 54 | // Update the current order to match the current cart. |
| 55 | - $this->update_line_items_from_cart( $order ); |
|
| 56 | - $this->update_addresses_from_cart( $order ); |
|
| 57 | - $order->set_currency( get_woocommerce_currency() ); |
|
| 58 | - $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); |
|
| 59 | - $order->set_customer_id( get_current_user_id() ); |
|
| 60 | - $order->set_customer_ip_address( \WC_Geolocation::get_ip_address() ); |
|
| 61 | - $order->set_customer_user_agent( wc_get_user_agent() ); |
|
| 62 | - $order->update_meta_data( 'is_vat_exempt', wc()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' ); |
|
| 55 | + $this->update_line_items_from_cart($order); |
|
| 56 | + $this->update_addresses_from_cart($order); |
|
| 57 | + $order->set_currency(get_woocommerce_currency()); |
|
| 58 | + $order->set_prices_include_tax('yes' === get_option('woocommerce_prices_include_tax')); |
|
| 59 | + $order->set_customer_id(get_current_user_id()); |
|
| 60 | + $order->set_customer_ip_address(\WC_Geolocation::get_ip_address()); |
|
| 61 | + $order->set_customer_user_agent(wc_get_user_agent()); |
|
| 62 | + $order->update_meta_data('is_vat_exempt', wc()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no'); |
|
| 63 | 63 | $order->calculate_totals(); |
| 64 | 64 | } |
| 65 | 65 | |
@@ -68,9 +68,9 @@ discard block |
||
| 68 | 68 | * |
| 69 | 69 | * @param \WC_Order $order Order object. |
| 70 | 70 | */ |
| 71 | - public function sync_customer_data_with_order( \WC_Order $order ) { |
|
| 72 | - if ( $order->get_customer_id() ) { |
|
| 73 | - $customer = new \WC_Customer( $order->get_customer_id() ); |
|
| 71 | + public function sync_customer_data_with_order(\WC_Order $order) { |
|
| 72 | + if ($order->get_customer_id()) { |
|
| 73 | + $customer = new \WC_Customer($order->get_customer_id()); |
|
| 74 | 74 | $customer->set_props( |
| 75 | 75 | [ |
| 76 | 76 | 'billing_first_name' => $order->get_billing_first_name(), |
@@ -109,14 +109,14 @@ discard block |
||
| 109 | 109 | * @throws RouteException Exception if invalid data is detected. |
| 110 | 110 | * @param \WC_Order $order Order object. |
| 111 | 111 | */ |
| 112 | - public function validate_order_before_payment( \WC_Order $order ) { |
|
| 112 | + public function validate_order_before_payment(\WC_Order $order) { |
|
| 113 | 113 | $needs_shipping = wc()->cart->needs_shipping(); |
| 114 | - $chosen_shipping_methods = wc()->session->get( 'chosen_shipping_methods' ); |
|
| 114 | + $chosen_shipping_methods = wc()->session->get('chosen_shipping_methods'); |
|
| 115 | 115 | |
| 116 | - $this->validate_coupons( $order ); |
|
| 117 | - $this->validate_email( $order ); |
|
| 118 | - $this->validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods ); |
|
| 119 | - $this->validate_addresses( $order ); |
|
| 116 | + $this->validate_coupons($order); |
|
| 117 | + $this->validate_email($order); |
|
| 118 | + $this->validate_selected_shipping_methods($needs_shipping, $chosen_shipping_methods); |
|
| 119 | + $this->validate_addresses($order); |
|
| 120 | 120 | } |
| 121 | 121 | |
| 122 | 122 | /** |
@@ -125,8 +125,8 @@ discard block |
||
| 125 | 125 | * @param string $coupon_code Coupon code. |
| 126 | 126 | * @return \WC_Coupon Coupon object. |
| 127 | 127 | */ |
| 128 | - protected function get_coupon( $coupon_code ) { |
|
| 129 | - return new \WC_Coupon( $coupon_code ); |
|
| 128 | + protected function get_coupon($coupon_code) { |
|
| 129 | + return new \WC_Coupon($coupon_code); |
|
| 130 | 130 | } |
| 131 | 131 | |
| 132 | 132 | /** |
@@ -135,45 +135,45 @@ discard block |
||
| 135 | 135 | * @throws RouteException Exception if invalid data is detected. |
| 136 | 136 | * @param \WC_Order $order Order object. |
| 137 | 137 | */ |
| 138 | - protected function validate_coupons( \WC_Order $order ) { |
|
| 138 | + protected function validate_coupons(\WC_Order $order) { |
|
| 139 | 139 | $coupon_codes = $order->get_coupon_codes(); |
| 140 | - $coupons = array_filter( array_map( [ $this, 'get_coupon' ], $coupon_codes ) ); |
|
| 141 | - $validators = [ 'validate_coupon_email_restriction', 'validate_coupon_usage_limit' ]; |
|
| 140 | + $coupons = array_filter(array_map([$this, 'get_coupon'], $coupon_codes)); |
|
| 141 | + $validators = ['validate_coupon_email_restriction', 'validate_coupon_usage_limit']; |
|
| 142 | 142 | $coupon_errors = []; |
| 143 | 143 | |
| 144 | - foreach ( $coupons as $coupon ) { |
|
| 144 | + foreach ($coupons as $coupon) { |
|
| 145 | 145 | try { |
| 146 | 146 | array_walk( |
| 147 | 147 | $validators, |
| 148 | - function( $validator, $index, $params ) { |
|
| 149 | - call_user_func_array( [ $this, $validator ], $params ); |
|
| 148 | + function($validator, $index, $params) { |
|
| 149 | + call_user_func_array([$this, $validator], $params); |
|
| 150 | 150 | }, |
| 151 | - [ $coupon, $order ] |
|
| 151 | + [$coupon, $order] |
|
| 152 | 152 | ); |
| 153 | - } catch ( Exception $error ) { |
|
| 154 | - $coupon_errors[ $coupon->get_code() ] = $error->getMessage(); |
|
| 153 | + } catch (Exception $error) { |
|
| 154 | + $coupon_errors[$coupon->get_code()] = $error->getMessage(); |
|
| 155 | 155 | } |
| 156 | 156 | } |
| 157 | 157 | |
| 158 | - if ( $coupon_errors ) { |
|
| 158 | + if ($coupon_errors) { |
|
| 159 | 159 | // Remove all coupons that were not valid. |
| 160 | - foreach ( $coupon_errors as $coupon_code => $message ) { |
|
| 161 | - wc()->cart->remove_coupon( $coupon_code ); |
|
| 160 | + foreach ($coupon_errors as $coupon_code => $message) { |
|
| 161 | + wc()->cart->remove_coupon($coupon_code); |
|
| 162 | 162 | } |
| 163 | 163 | |
| 164 | 164 | // Recalculate totals. |
| 165 | 165 | wc()->cart->calculate_totals(); |
| 166 | 166 | |
| 167 | 167 | // Re-sync order with cart. |
| 168 | - $this->update_order_from_cart( $order ); |
|
| 168 | + $this->update_order_from_cart($order); |
|
| 169 | 169 | |
| 170 | 170 | // Return exception so customer can review before payment. |
| 171 | 171 | throw new RouteException( |
| 172 | 172 | 'woocommerce_rest_cart_coupon_errors', |
| 173 | 173 | sprintf( |
| 174 | 174 | /* translators: %s Coupon codes. */ |
| 175 | - __( 'Invalid coupons were removed from the cart: "%s"', 'woocommerce' ), |
|
| 176 | - implode( '", "', array_keys( $coupon_errors ) ) |
|
| 175 | + __('Invalid coupons were removed from the cart: "%s"', 'woocommerce'), |
|
| 176 | + implode('", "', array_keys($coupon_errors)) |
|
| 177 | 177 | ), |
| 178 | 178 | 409, |
| 179 | 179 | [ |
@@ -189,24 +189,24 @@ discard block |
||
| 189 | 189 | * @throws RouteException Exception if invalid data is detected. |
| 190 | 190 | * @param \WC_Order $order Order object. |
| 191 | 191 | */ |
| 192 | - protected function validate_email( \WC_Order $order ) { |
|
| 192 | + protected function validate_email(\WC_Order $order) { |
|
| 193 | 193 | $email = $order->get_billing_email(); |
| 194 | 194 | |
| 195 | - if ( empty( $email ) ) { |
|
| 195 | + if (empty($email)) { |
|
| 196 | 196 | throw new RouteException( |
| 197 | 197 | 'woocommerce_rest_missing_email_address', |
| 198 | - __( 'A valid email address is required', 'woocommerce' ), |
|
| 198 | + __('A valid email address is required', 'woocommerce'), |
|
| 199 | 199 | 400 |
| 200 | 200 | ); |
| 201 | 201 | } |
| 202 | 202 | |
| 203 | - if ( ! is_email( $email ) ) { |
|
| 203 | + if (!is_email($email)) { |
|
| 204 | 204 | throw new RouteException( |
| 205 | 205 | 'woocommerce_rest_invalid_email_address', |
| 206 | 206 | sprintf( |
| 207 | 207 | /* translators: %s provided email. */ |
| 208 | - __( 'The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce' ), |
|
| 209 | - esc_html( $email ) |
|
| 208 | + __('The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce'), |
|
| 209 | + esc_html($email) |
|
| 210 | 210 | ), |
| 211 | 211 | 400 |
| 212 | 212 | ); |
@@ -219,65 +219,65 @@ discard block |
||
| 219 | 219 | * @throws RouteException Exception if invalid data is detected. |
| 220 | 220 | * @param \WC_Order $order Order object. |
| 221 | 221 | */ |
| 222 | - protected function validate_addresses( \WC_Order $order ) { |
|
| 222 | + protected function validate_addresses(\WC_Order $order) { |
|
| 223 | 223 | $errors = new \WP_Error(); |
| 224 | 224 | $needs_shipping = wc()->cart->needs_shipping(); |
| 225 | - $billing_address = $order->get_address( 'billing' ); |
|
| 226 | - $shipping_address = $order->get_address( 'shipping' ); |
|
| 225 | + $billing_address = $order->get_address('billing'); |
|
| 226 | + $shipping_address = $order->get_address('shipping'); |
|
| 227 | 227 | |
| 228 | - if ( $needs_shipping && ! $this->validate_allowed_country( $shipping_address['country'], (array) wc()->countries->get_shipping_countries() ) ) { |
|
| 228 | + if ($needs_shipping && !$this->validate_allowed_country($shipping_address['country'], (array) wc()->countries->get_shipping_countries())) { |
|
| 229 | 229 | throw new RouteException( |
| 230 | 230 | 'woocommerce_rest_invalid_address_country', |
| 231 | 231 | sprintf( |
| 232 | 232 | /* translators: %s country code. */ |
| 233 | - __( 'Sorry, we do not ship orders to the provided country (%s)', 'woocommerce' ), |
|
| 233 | + __('Sorry, we do not ship orders to the provided country (%s)', 'woocommerce'), |
|
| 234 | 234 | $shipping_address['country'] |
| 235 | 235 | ), |
| 236 | 236 | 400, |
| 237 | 237 | [ |
| 238 | - 'allowed_countries' => array_keys( wc()->countries->get_shipping_countries() ), |
|
| 238 | + 'allowed_countries' => array_keys(wc()->countries->get_shipping_countries()), |
|
| 239 | 239 | ] |
| 240 | 240 | ); |
| 241 | 241 | } |
| 242 | 242 | |
| 243 | - if ( ! $this->validate_allowed_country( $billing_address['country'], (array) wc()->countries->get_allowed_countries() ) ) { |
|
| 243 | + if (!$this->validate_allowed_country($billing_address['country'], (array) wc()->countries->get_allowed_countries())) { |
|
| 244 | 244 | throw new RouteException( |
| 245 | 245 | 'woocommerce_rest_invalid_address_country', |
| 246 | 246 | sprintf( |
| 247 | 247 | /* translators: %s country code. */ |
| 248 | - __( 'Sorry, we do not allow orders from the provided country (%s)', 'woocommerce' ), |
|
| 248 | + __('Sorry, we do not allow orders from the provided country (%s)', 'woocommerce'), |
|
| 249 | 249 | $billing_address['country'] |
| 250 | 250 | ), |
| 251 | 251 | 400, |
| 252 | 252 | [ |
| 253 | - 'allowed_countries' => array_keys( wc()->countries->get_allowed_countries() ), |
|
| 253 | + 'allowed_countries' => array_keys(wc()->countries->get_allowed_countries()), |
|
| 254 | 254 | ] |
| 255 | 255 | ); |
| 256 | 256 | } |
| 257 | 257 | |
| 258 | - if ( $needs_shipping ) { |
|
| 259 | - $this->validate_address_fields( $shipping_address, 'shipping', $errors ); |
|
| 258 | + if ($needs_shipping) { |
|
| 259 | + $this->validate_address_fields($shipping_address, 'shipping', $errors); |
|
| 260 | 260 | } |
| 261 | - $this->validate_address_fields( $billing_address, 'billing', $errors ); |
|
| 261 | + $this->validate_address_fields($billing_address, 'billing', $errors); |
|
| 262 | 262 | |
| 263 | - if ( ! $errors->has_errors() ) { |
|
| 263 | + if (!$errors->has_errors()) { |
|
| 264 | 264 | return; |
| 265 | 265 | } |
| 266 | 266 | |
| 267 | 267 | $errors_by_code = []; |
| 268 | 268 | $error_codes = $errors->get_error_codes(); |
| 269 | - foreach ( $error_codes as $code ) { |
|
| 270 | - $errors_by_code[ $code ] = $errors->get_error_messages( $code ); |
|
| 269 | + foreach ($error_codes as $code) { |
|
| 270 | + $errors_by_code[$code] = $errors->get_error_messages($code); |
|
| 271 | 271 | } |
| 272 | 272 | |
| 273 | 273 | // Surface errors from first code. |
| 274 | - foreach ( $errors_by_code as $code => $error_messages ) { |
|
| 274 | + foreach ($errors_by_code as $code => $error_messages) { |
|
| 275 | 275 | throw new RouteException( |
| 276 | 276 | 'woocommerce_rest_invalid_address', |
| 277 | 277 | sprintf( |
| 278 | 278 | /* translators: %s Address type. */ |
| 279 | - __( 'There was a problem with the provided %s:', 'woocommerce' ) . ' ' . implode( ', ', $error_messages ), |
|
| 280 | - 'shipping' === $code ? __( 'shipping address', 'woocommerce' ) : __( 'billing address', 'woocommerce' ) |
|
| 279 | + __('There was a problem with the provided %s:', 'woocommerce') . ' ' . implode(', ', $error_messages), |
|
| 280 | + 'shipping' === $code ? __('shipping address', 'woocommerce') : __('billing address', 'woocommerce') |
|
| 281 | 281 | ), |
| 282 | 282 | 400, |
| 283 | 283 | [ |
@@ -294,8 +294,8 @@ discard block |
||
| 294 | 294 | * @param array $allowed_countries List of valid country codes. |
| 295 | 295 | * @return boolean True if valid. |
| 296 | 296 | */ |
| 297 | - protected function validate_allowed_country( $country, array $allowed_countries ) { |
|
| 298 | - return array_key_exists( $country, $allowed_countries ); |
|
| 297 | + protected function validate_allowed_country($country, array $allowed_countries) { |
|
| 298 | + return array_key_exists($country, $allowed_countries); |
|
| 299 | 299 | } |
| 300 | 300 | |
| 301 | 301 | /** |
@@ -305,9 +305,9 @@ discard block |
||
| 305 | 305 | * @param string $address_type billing or shipping address, used in error messages. |
| 306 | 306 | * @param \WP_Error $errors Error object. |
| 307 | 307 | */ |
| 308 | - protected function validate_address_fields( $address, $address_type, \WP_Error $errors ) { |
|
| 308 | + protected function validate_address_fields($address, $address_type, \WP_Error $errors) { |
|
| 309 | 309 | $all_locales = wc()->countries->get_country_locale(); |
| 310 | - $current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : []; |
|
| 310 | + $current_locale = isset($all_locales[$address['country']]) ? $all_locales[$address['country']] : []; |
|
| 311 | 311 | |
| 312 | 312 | /** |
| 313 | 313 | * We are not using wc()->counties->get_default_address_fields() here because that is filtered. Instead, this array |
@@ -315,56 +315,56 @@ discard block |
||
| 315 | 315 | */ |
| 316 | 316 | $address_fields = [ |
| 317 | 317 | 'first_name' => [ |
| 318 | - 'label' => __( 'First name', 'woocommerce' ), |
|
| 318 | + 'label' => __('First name', 'woocommerce'), |
|
| 319 | 319 | 'required' => true, |
| 320 | 320 | ], |
| 321 | 321 | 'last_name' => [ |
| 322 | - 'label' => __( 'Last name', 'woocommerce' ), |
|
| 322 | + 'label' => __('Last name', 'woocommerce'), |
|
| 323 | 323 | 'required' => true, |
| 324 | 324 | ], |
| 325 | 325 | 'company' => [ |
| 326 | - 'label' => __( 'Company', 'woocommerce' ), |
|
| 326 | + 'label' => __('Company', 'woocommerce'), |
|
| 327 | 327 | 'required' => false, |
| 328 | 328 | ], |
| 329 | 329 | 'address_1' => [ |
| 330 | - 'label' => __( 'Address', 'woocommerce' ), |
|
| 330 | + 'label' => __('Address', 'woocommerce'), |
|
| 331 | 331 | 'required' => true, |
| 332 | 332 | ], |
| 333 | 333 | 'address_2' => [ |
| 334 | - 'label' => __( 'Apartment, suite, etc.', 'woocommerce' ), |
|
| 334 | + 'label' => __('Apartment, suite, etc.', 'woocommerce'), |
|
| 335 | 335 | 'required' => false, |
| 336 | 336 | ], |
| 337 | 337 | 'country' => [ |
| 338 | - 'label' => __( 'Country/Region', 'woocommerce' ), |
|
| 338 | + 'label' => __('Country/Region', 'woocommerce'), |
|
| 339 | 339 | 'required' => true, |
| 340 | 340 | ], |
| 341 | 341 | 'city' => [ |
| 342 | - 'label' => __( 'City', 'woocommerce' ), |
|
| 342 | + 'label' => __('City', 'woocommerce'), |
|
| 343 | 343 | 'required' => true, |
| 344 | 344 | ], |
| 345 | 345 | 'state' => [ |
| 346 | - 'label' => __( 'State/County', 'woocommerce' ), |
|
| 346 | + 'label' => __('State/County', 'woocommerce'), |
|
| 347 | 347 | 'required' => true, |
| 348 | 348 | ], |
| 349 | 349 | 'postcode' => [ |
| 350 | - 'label' => __( 'Postal code', 'woocommerce' ), |
|
| 350 | + 'label' => __('Postal code', 'woocommerce'), |
|
| 351 | 351 | 'required' => true, |
| 352 | 352 | ], |
| 353 | 353 | ]; |
| 354 | 354 | |
| 355 | - if ( $current_locale ) { |
|
| 356 | - foreach ( $current_locale as $key => $field ) { |
|
| 357 | - if ( isset( $address_fields[ $key ] ) ) { |
|
| 358 | - $address_fields[ $key ]['label'] = isset( $field['label'] ) ? $field['label'] : $address_fields[ $key ]['label']; |
|
| 359 | - $address_fields[ $key ]['required'] = isset( $field['required'] ) ? $field['required'] : $address_fields[ $key ]['required']; |
|
| 355 | + if ($current_locale) { |
|
| 356 | + foreach ($current_locale as $key => $field) { |
|
| 357 | + if (isset($address_fields[$key])) { |
|
| 358 | + $address_fields[$key]['label'] = isset($field['label']) ? $field['label'] : $address_fields[$key]['label']; |
|
| 359 | + $address_fields[$key]['required'] = isset($field['required']) ? $field['required'] : $address_fields[$key]['required']; |
|
| 360 | 360 | } |
| 361 | 361 | } |
| 362 | 362 | } |
| 363 | 363 | |
| 364 | - foreach ( $address_fields as $address_field_key => $address_field ) { |
|
| 365 | - if ( empty( $address[ $address_field_key ] ) && $address_field['required'] ) { |
|
| 364 | + foreach ($address_fields as $address_field_key => $address_field) { |
|
| 365 | + if (empty($address[$address_field_key]) && $address_field['required']) { |
|
| 366 | 366 | /* translators: %s Field label. */ |
| 367 | - $errors->add( $address_type, sprintf( __( '%s is required', 'woocommerce' ), $address_field['label'] ), $address_field_key ); |
|
| 367 | + $errors->add($address_type, sprintf(__('%s is required', 'woocommerce'), $address_field['label']), $address_field_key); |
|
| 368 | 368 | } |
| 369 | 369 | } |
| 370 | 370 | } |
@@ -376,11 +376,11 @@ discard block |
||
| 376 | 376 | * @param \WC_Coupon $coupon Coupon object applied to the cart. |
| 377 | 377 | * @param \WC_Order $order Order object. |
| 378 | 378 | */ |
| 379 | - protected function validate_coupon_email_restriction( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 379 | + protected function validate_coupon_email_restriction(\WC_Coupon $coupon, \WC_Order $order) { |
|
| 380 | 380 | $restrictions = $coupon->get_email_restrictions(); |
| 381 | 381 | |
| 382 | - if ( ! empty( $restrictions ) && $order->get_billing_email() && ! wc()->cart->is_coupon_emails_allowed( [ $order->get_billing_email() ], $restrictions ) ) { |
|
| 383 | - throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ) ); |
|
| 382 | + if (!empty($restrictions) && $order->get_billing_email() && !wc()->cart->is_coupon_emails_allowed([$order->get_billing_email()], $restrictions)) { |
|
| 383 | + throw new Exception($coupon->get_coupon_error(\WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED)); |
|
| 384 | 384 | } |
| 385 | 385 | } |
| 386 | 386 | |
@@ -391,15 +391,15 @@ discard block |
||
| 391 | 391 | * @param \WC_Coupon $coupon Coupon object applied to the cart. |
| 392 | 392 | * @param \WC_Order $order Order object. |
| 393 | 393 | */ |
| 394 | - protected function validate_coupon_usage_limit( \WC_Coupon $coupon, \WC_Order $order ) { |
|
| 394 | + protected function validate_coupon_usage_limit(\WC_Coupon $coupon, \WC_Order $order) { |
|
| 395 | 395 | $coupon_usage_limit = $coupon->get_usage_limit_per_user(); |
| 396 | 396 | |
| 397 | - if ( $coupon_usage_limit > 0 ) { |
|
| 397 | + if ($coupon_usage_limit > 0) { |
|
| 398 | 398 | $data_store = $coupon->get_data_store(); |
| 399 | - $usage_count = $order->get_customer_id() ? $data_store->get_usage_by_user_id( $coupon, $order->get_customer_id() ) : $data_store->get_usage_by_email( $coupon, $order->get_billing_email() ); |
|
| 399 | + $usage_count = $order->get_customer_id() ? $data_store->get_usage_by_user_id($coupon, $order->get_customer_id()) : $data_store->get_usage_by_email($coupon, $order->get_billing_email()); |
|
| 400 | 400 | |
| 401 | - if ( $usage_count >= $coupon_usage_limit ) { |
|
| 402 | - throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ) ); |
|
| 401 | + if ($usage_count >= $coupon_usage_limit) { |
|
| 402 | + throw new Exception($coupon->get_coupon_error(\WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED)); |
|
| 403 | 403 | } |
| 404 | 404 | } |
| 405 | 405 | } |
@@ -411,16 +411,16 @@ discard block |
||
| 411 | 411 | * @param boolean $needs_shipping Current order needs shipping. |
| 412 | 412 | * @param array $chosen_shipping_methods Array of shipping methods. |
| 413 | 413 | */ |
| 414 | - public function validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods = array() ) { |
|
| 415 | - if ( ! $needs_shipping || ! is_array( $chosen_shipping_methods ) ) { |
|
| 414 | + public function validate_selected_shipping_methods($needs_shipping, $chosen_shipping_methods = array()) { |
|
| 415 | + if (!$needs_shipping || !is_array($chosen_shipping_methods)) { |
|
| 416 | 416 | return; |
| 417 | 417 | } |
| 418 | 418 | |
| 419 | - foreach ( $chosen_shipping_methods as $chosen_shipping_method ) { |
|
| 420 | - if ( false === $chosen_shipping_method ) { |
|
| 419 | + foreach ($chosen_shipping_methods as $chosen_shipping_method) { |
|
| 420 | + if (false === $chosen_shipping_method) { |
|
| 421 | 421 | throw new RouteException( |
| 422 | 422 | 'woocommerce_rest_invalid_shipping_option', |
| 423 | - __( 'Sorry, this order requires a shipping option.', 'woocommerce' ), |
|
| 423 | + __('Sorry, this order requires a shipping option.', 'woocommerce'), |
|
| 424 | 424 | 400, |
| 425 | 425 | [] |
| 426 | 426 | ); |
@@ -445,10 +445,10 @@ discard block |
||
| 445 | 445 | * @param array $location Taxes location. |
| 446 | 446 | * @return array updated location that accounts for local pickup. |
| 447 | 447 | */ |
| 448 | - public function handle_local_pickup_taxes( $location ) { |
|
| 448 | + public function handle_local_pickup_taxes($location) { |
|
| 449 | 449 | $customer = wc()->customer; |
| 450 | 450 | |
| 451 | - if ( ! empty( $customer ) ) { |
|
| 451 | + if (!empty($customer)) { |
|
| 452 | 452 | return $customer->get_taxable_address(); |
| 453 | 453 | } |
| 454 | 454 | |
@@ -459,39 +459,39 @@ discard block |
||
| 459 | 459 | * |
| 460 | 460 | * @param \WC_Order $order The order object to update. |
| 461 | 461 | */ |
| 462 | - protected function update_line_items_from_cart( \WC_Order $order ) { |
|
| 462 | + protected function update_line_items_from_cart(\WC_Order $order) { |
|
| 463 | 463 | $cart_controller = new CartController(); |
| 464 | 464 | $cart = $cart_controller->get_cart_instance(); |
| 465 | 465 | $cart_hashes = $cart_controller->get_cart_hashes(); |
| 466 | 466 | |
| 467 | - if ( $order->get_cart_hash() !== $cart_hashes['line_items'] ) { |
|
| 468 | - $order->set_cart_hash( $cart_hashes['line_items'] ); |
|
| 469 | - $order->remove_order_items( 'line_item' ); |
|
| 470 | - wc()->checkout->create_order_line_items( $order, $cart ); |
|
| 467 | + if ($order->get_cart_hash() !== $cart_hashes['line_items']) { |
|
| 468 | + $order->set_cart_hash($cart_hashes['line_items']); |
|
| 469 | + $order->remove_order_items('line_item'); |
|
| 470 | + wc()->checkout->create_order_line_items($order, $cart); |
|
| 471 | 471 | } |
| 472 | 472 | |
| 473 | - if ( $order->get_meta_data( '_shipping_hash' ) !== $cart_hashes['shipping'] ) { |
|
| 474 | - $order->update_meta_data( '_shipping_hash', $cart_hashes['shipping'] ); |
|
| 475 | - $order->remove_order_items( 'shipping' ); |
|
| 476 | - wc()->checkout->create_order_shipping_lines( $order, wc()->session->get( 'chosen_shipping_methods' ), wc()->shipping()->get_packages() ); |
|
| 473 | + if ($order->get_meta_data('_shipping_hash') !== $cart_hashes['shipping']) { |
|
| 474 | + $order->update_meta_data('_shipping_hash', $cart_hashes['shipping']); |
|
| 475 | + $order->remove_order_items('shipping'); |
|
| 476 | + wc()->checkout->create_order_shipping_lines($order, wc()->session->get('chosen_shipping_methods'), wc()->shipping()->get_packages()); |
|
| 477 | 477 | } |
| 478 | 478 | |
| 479 | - if ( $order->get_meta_data( '_coupons_hash' ) !== $cart_hashes['coupons'] ) { |
|
| 480 | - $order->remove_order_items( 'coupon' ); |
|
| 481 | - $order->update_meta_data( '_coupons_hash', $cart_hashes['coupons'] ); |
|
| 482 | - wc()->checkout->create_order_coupon_lines( $order, $cart ); |
|
| 479 | + if ($order->get_meta_data('_coupons_hash') !== $cart_hashes['coupons']) { |
|
| 480 | + $order->remove_order_items('coupon'); |
|
| 481 | + $order->update_meta_data('_coupons_hash', $cart_hashes['coupons']); |
|
| 482 | + wc()->checkout->create_order_coupon_lines($order, $cart); |
|
| 483 | 483 | } |
| 484 | 484 | |
| 485 | - if ( $order->get_meta_data( '_fees_hash' ) !== $cart_hashes['fees'] ) { |
|
| 486 | - $order->update_meta_data( '_fees_hash', $cart_hashes['fees'] ); |
|
| 487 | - $order->remove_order_items( 'fee' ); |
|
| 488 | - wc()->checkout->create_order_fee_lines( $order, $cart ); |
|
| 485 | + if ($order->get_meta_data('_fees_hash') !== $cart_hashes['fees']) { |
|
| 486 | + $order->update_meta_data('_fees_hash', $cart_hashes['fees']); |
|
| 487 | + $order->remove_order_items('fee'); |
|
| 488 | + wc()->checkout->create_order_fee_lines($order, $cart); |
|
| 489 | 489 | } |
| 490 | 490 | |
| 491 | - if ( $order->get_meta_data( '_taxes_hash' ) !== $cart_hashes['taxes'] ) { |
|
| 492 | - $order->update_meta_data( '_taxes_hash', $cart_hashes['taxes'] ); |
|
| 493 | - $order->remove_order_items( 'tax' ); |
|
| 494 | - wc()->checkout->create_order_tax_lines( $order, $cart ); |
|
| 491 | + if ($order->get_meta_data('_taxes_hash') !== $cart_hashes['taxes']) { |
|
| 492 | + $order->update_meta_data('_taxes_hash', $cart_hashes['taxes']); |
|
| 493 | + $order->remove_order_items('tax'); |
|
| 494 | + wc()->checkout->create_order_tax_lines($order, $cart); |
|
| 495 | 495 | } |
| 496 | 496 | } |
| 497 | 497 | |
@@ -500,7 +500,7 @@ discard block |
||
| 500 | 500 | * |
| 501 | 501 | * @param \WC_Order $order The order object to update. |
| 502 | 502 | */ |
| 503 | - protected function update_addresses_from_cart( \WC_Order $order ) { |
|
| 503 | + protected function update_addresses_from_cart(\WC_Order $order) { |
|
| 504 | 504 | $order->set_props( |
| 505 | 505 | [ |
| 506 | 506 | 'billing_first_name' => wc()->customer->get_billing_first_name(), |
@@ -10,38 +10,38 @@ |
||
| 10 | 10 | * Allows formatter classes to be registered. Formatters are exposed to extensions via the ExtendSchema class. |
| 11 | 11 | */ |
| 12 | 12 | class Formatters { |
| 13 | - /** |
|
| 14 | - * Holds an array of formatter class instances. |
|
| 15 | - * |
|
| 16 | - * @var array |
|
| 17 | - */ |
|
| 18 | - private $formatters = []; |
|
| 13 | + /** |
|
| 14 | + * Holds an array of formatter class instances. |
|
| 15 | + * |
|
| 16 | + * @var array |
|
| 17 | + */ |
|
| 18 | + private $formatters = []; |
|
| 19 | 19 | |
| 20 | - /** |
|
| 21 | - * Get a new instance of a formatter class. |
|
| 22 | - * |
|
| 23 | - * @throws Exception An Exception is thrown if a non-existing formatter is used and the user is admin. |
|
| 24 | - * |
|
| 25 | - * @param string $name Name of the formatter. |
|
| 26 | - * @return FormatterInterface Formatter class instance. |
|
| 27 | - */ |
|
| 28 | - public function __get( $name ) { |
|
| 29 | - if ( ! isset( $this->formatters[ $name ] ) ) { |
|
| 30 | - if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { |
|
| 31 | - throw new Exception( $name . ' formatter does not exist' ); |
|
| 32 | - } |
|
| 33 | - return new DefaultFormatter(); |
|
| 34 | - } |
|
| 35 | - return $this->formatters[ $name ]; |
|
| 36 | - } |
|
| 20 | + /** |
|
| 21 | + * Get a new instance of a formatter class. |
|
| 22 | + * |
|
| 23 | + * @throws Exception An Exception is thrown if a non-existing formatter is used and the user is admin. |
|
| 24 | + * |
|
| 25 | + * @param string $name Name of the formatter. |
|
| 26 | + * @return FormatterInterface Formatter class instance. |
|
| 27 | + */ |
|
| 28 | + public function __get( $name ) { |
|
| 29 | + if ( ! isset( $this->formatters[ $name ] ) ) { |
|
| 30 | + if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { |
|
| 31 | + throw new Exception( $name . ' formatter does not exist' ); |
|
| 32 | + } |
|
| 33 | + return new DefaultFormatter(); |
|
| 34 | + } |
|
| 35 | + return $this->formatters[ $name ]; |
|
| 36 | + } |
|
| 37 | 37 | |
| 38 | - /** |
|
| 39 | - * Register a formatter class for usage. |
|
| 40 | - * |
|
| 41 | - * @param string $name Name of the formatter. |
|
| 42 | - * @param string $class A formatter class name. |
|
| 43 | - */ |
|
| 44 | - public function register( $name, $class ) { |
|
| 45 | - $this->formatters[ $name ] = new $class(); |
|
| 46 | - } |
|
| 38 | + /** |
|
| 39 | + * Register a formatter class for usage. |
|
| 40 | + * |
|
| 41 | + * @param string $name Name of the formatter. |
|
| 42 | + * @param string $class A formatter class name. |
|
| 43 | + */ |
|
| 44 | + public function register( $name, $class ) { |
|
| 45 | + $this->formatters[ $name ] = new $class(); |
|
| 46 | + } |
|
| 47 | 47 | } |
@@ -25,14 +25,14 @@ discard block |
||
| 25 | 25 | * @param string $name Name of the formatter. |
| 26 | 26 | * @return FormatterInterface Formatter class instance. |
| 27 | 27 | */ |
| 28 | - public function __get( $name ) { |
|
| 29 | - if ( ! isset( $this->formatters[ $name ] ) ) { |
|
| 30 | - if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { |
|
| 31 | - throw new Exception( $name . ' formatter does not exist' ); |
|
| 28 | + public function __get($name) { |
|
| 29 | + if (!isset($this->formatters[$name])) { |
|
| 30 | + if (defined('WP_DEBUG') && WP_DEBUG && current_user_can('manage_woocommerce')) { |
|
| 31 | + throw new Exception($name . ' formatter does not exist'); |
|
| 32 | 32 | } |
| 33 | 33 | return new DefaultFormatter(); |
| 34 | 34 | } |
| 35 | - return $this->formatters[ $name ]; |
|
| 35 | + return $this->formatters[$name]; |
|
| 36 | 36 | } |
| 37 | 37 | |
| 38 | 38 | /** |
@@ -41,7 +41,7 @@ discard block |
||
| 41 | 41 | * @param string $name Name of the formatter. |
| 42 | 42 | * @param string $class A formatter class name. |
| 43 | 43 | */ |
| 44 | - public function register( $name, $class ) { |
|
| 45 | - $this->formatters[ $name ] = new $class(); |
|
| 44 | + public function register($name, $class) { |
|
| 45 | + $this->formatters[$name] = new $class(); |
|
| 46 | 46 | } |
| 47 | 47 | } |