AI
1 year ago
RateLimits
4 weeks ago
Reports
4 weeks ago
Templates
3 years ago
views
4 weeks ago
AnalyticsImports.php
5 months ago
Coupons.php
4 years ago
CustomAttributeTraits.php
4 years ago
Customers.php
4 years ago
Data.php
4 years ago
DataCountries.php
4 years ago
DataDownloadIPs.php
4 years ago
Experiments.php
4 years ago
Features.php
4 years ago
Init.php
4 weeks ago
LaunchYourStore.php
1 year ago
Leaderboards.php
1 year ago
Marketing.php
2 months ago
MarketingCampaignTypes.php
3 years ago
MarketingCampaigns.php
2 years ago
MarketingChannels.php
3 years ago
MarketingOverview.php
2 months ago
MarketingRecommendations.php
2 years ago
MobileAppMagicLink.php
3 years ago
MobileAppQRLogin.php
4 weeks ago
NoteActions.php
4 weeks ago
Notes.php
4 weeks ago
Notice.php
1 year ago
OnboardingFreeExtensions.php
1 year ago
OnboardingPlugins.php
4 weeks ago
OnboardingProductTypes.php
4 years ago
OnboardingProducts.php
2 years ago
OnboardingProfile.php
1 year ago
OnboardingTasks.php
9 months ago
OnboardingThemes.php
9 months ago
Options.php
4 weeks ago
Orders.php
1 year ago
PaymentGatewaySuggestions.php
2 months ago
Plugins.php
2 months ago
ProductAttributeTerms.php
4 years ago
ProductAttributes.php
4 years ago
ProductCategories.php
4 years ago
ProductForm.php
3 years ago
ProductReviews.php
4 years ago
ProductVariations.php
1 year ago
Products.php
1 year ago
ProductsLowInStock.php
4 months ago
SettingOptions.php
3 years ago
ShippingPartnerSuggestions.php
1 month ago
Taxes.php
4 years ago
Themes.php
2 years ago
Orders.php
313 lines
| 1 | <?php |
| 2 | /** |
| 3 | * REST API Orders Controller |
| 4 | * |
| 5 | * Handles requests to /orders/* |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\WooCommerce\Admin\API; |
| 9 | |
| 10 | defined( 'ABSPATH' ) || exit; |
| 11 | |
| 12 | use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController; |
| 13 | use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; |
| 14 | use Automattic\WooCommerce\Utilities\OrderUtil; |
| 15 | |
| 16 | /** |
| 17 | * Orders controller. |
| 18 | * |
| 19 | * @internal |
| 20 | * @extends WC_REST_Orders_Controller |
| 21 | */ |
| 22 | class Orders extends \WC_REST_Orders_Controller { |
| 23 | /** |
| 24 | * Endpoint namespace. |
| 25 | * |
| 26 | * @var string |
| 27 | */ |
| 28 | protected $namespace = 'wc-analytics'; |
| 29 | |
| 30 | /** |
| 31 | * Get the query params for collections. |
| 32 | * |
| 33 | * @return array |
| 34 | */ |
| 35 | public function get_collection_params() { |
| 36 | $params = parent::get_collection_params(); |
| 37 | // This needs to remain a string to support extensions that filter Order Number. |
| 38 | $params['number'] = array( |
| 39 | 'description' => __( 'Limit result set to orders matching part of an order number.', 'woocommerce' ), |
| 40 | 'type' => 'string', |
| 41 | 'validate_callback' => 'rest_validate_request_arg', |
| 42 | ); |
| 43 | // Fix the default 'status' value until it can be patched in core. |
| 44 | $params['status']['default'] = array( 'any' ); |
| 45 | |
| 46 | // Analytics settings may affect the allowed status list. |
| 47 | $params['status']['items']['enum'] = ReportsController::get_order_statuses(); |
| 48 | |
| 49 | return $params; |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Prepare objects query. |
| 54 | * |
| 55 | * @param WP_REST_Request $request Full details about the request. |
| 56 | * @return array |
| 57 | */ |
| 58 | protected function prepare_objects_query( $request ) { |
| 59 | $args = parent::prepare_objects_query( $request ); |
| 60 | |
| 61 | if ( ! empty( $request['number'] ) ) { |
| 62 | $args = $this->search_partial_order_number( $request['number'], $args ); |
| 63 | } |
| 64 | |
| 65 | return $args; |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Helper method to allow searching by partial order number. |
| 70 | * |
| 71 | * @param int $number Partial order number match. |
| 72 | * @param array $args List of arguments for the request. |
| 73 | * |
| 74 | * @return array Modified args with partial order search included. |
| 75 | */ |
| 76 | private function search_partial_order_number( $number, $args ) { |
| 77 | global $wpdb; |
| 78 | |
| 79 | $partial_number = trim( $number ); |
| 80 | $limit = intval( $args['posts_per_page'] ); |
| 81 | if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { |
| 82 | $order_table_name = OrdersTableDataStore::get_orders_table_name(); |
| 83 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orders_table_name is hardcoded. |
| 84 | $order_ids = $wpdb->get_col( |
| 85 | $wpdb->prepare( |
| 86 | "SELECT id |
| 87 | FROM $order_table_name |
| 88 | WHERE type = 'shop_order' |
| 89 | AND id LIKE %s |
| 90 | LIMIT %d", |
| 91 | $wpdb->esc_like( absint( $partial_number ) ) . '%', |
| 92 | $limit |
| 93 | ) |
| 94 | ); |
| 95 | // phpcs:enable |
| 96 | } else { |
| 97 | $order_ids = $wpdb->get_col( |
| 98 | $wpdb->prepare( |
| 99 | "SELECT ID |
| 100 | FROM {$wpdb->prefix}posts |
| 101 | WHERE post_type = 'shop_order' |
| 102 | AND ID LIKE %s |
| 103 | LIMIT %d", |
| 104 | $wpdb->esc_like( absint( $partial_number ) ) . '%', |
| 105 | $limit |
| 106 | ) |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | // Force WP_Query return empty if don't found any order. |
| 111 | $order_ids = empty( $order_ids ) ? array( 0 ) : $order_ids; |
| 112 | $args['post__in'] = $order_ids; |
| 113 | |
| 114 | return $args; |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Get product IDs, names, and quantity from order ID. |
| 119 | * |
| 120 | * @param array $order_id ID of order. |
| 121 | * @return array |
| 122 | */ |
| 123 | protected function get_products_by_order_id( $order_id ) { |
| 124 | global $wpdb; |
| 125 | $order_items_table = $wpdb->prefix . 'woocommerce_order_items'; |
| 126 | $order_itemmeta_table = $wpdb->prefix . 'woocommerce_order_itemmeta'; |
| 127 | $products = $wpdb->get_results( |
| 128 | $wpdb->prepare( |
| 129 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 130 | "SELECT |
| 131 | order_id, |
| 132 | order_itemmeta.meta_value as product_id, |
| 133 | order_itemmeta_2.meta_value as product_quantity, |
| 134 | order_itemmeta_3.meta_value as variation_id, |
| 135 | {$wpdb->posts}.post_title as product_name |
| 136 | FROM {$order_items_table} order_items |
| 137 | LEFT JOIN {$order_itemmeta_table} order_itemmeta on order_items.order_item_id = order_itemmeta.order_item_id |
| 138 | LEFT JOIN {$order_itemmeta_table} order_itemmeta_2 on order_items.order_item_id = order_itemmeta_2.order_item_id |
| 139 | LEFT JOIN {$order_itemmeta_table} order_itemmeta_3 on order_items.order_item_id = order_itemmeta_3.order_item_id |
| 140 | LEFT JOIN {$wpdb->posts} on {$wpdb->posts}.ID = order_itemmeta.meta_value |
| 141 | WHERE |
| 142 | order_id = ( %d ) |
| 143 | AND order_itemmeta.meta_key = '_product_id' |
| 144 | AND order_itemmeta_2.meta_key = '_qty' |
| 145 | AND order_itemmeta_3.meta_key = '_variation_id' |
| 146 | GROUP BY product_id |
| 147 | ", // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 148 | $order_id |
| 149 | ), |
| 150 | ARRAY_A |
| 151 | ); |
| 152 | |
| 153 | return $products; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Get customer data from customer_id. |
| 158 | * |
| 159 | * @param array $customer_id ID of customer. |
| 160 | * @return array |
| 161 | */ |
| 162 | protected function get_customer_by_id( $customer_id ) { |
| 163 | global $wpdb; |
| 164 | |
| 165 | $customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup'; |
| 166 | |
| 167 | $customer = $wpdb->get_row( |
| 168 | $wpdb->prepare( |
| 169 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 170 | "SELECT * FROM {$customer_lookup_table} WHERE customer_id = ( %d )", |
| 171 | $customer_id |
| 172 | ), |
| 173 | ARRAY_A |
| 174 | ); |
| 175 | |
| 176 | return $customer; |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * Get formatted item data. |
| 181 | * |
| 182 | * @param WC_Data $object WC_Data instance. |
| 183 | * @return array |
| 184 | */ |
| 185 | protected function get_formatted_item_data( $object ) { |
| 186 | $extra_fields = array( 'customer', 'products' ); |
| 187 | $fields = false; |
| 188 | // Determine if the response fields were specified. |
| 189 | if ( ! empty( $this->request['_fields'] ) ) { |
| 190 | $fields = wp_parse_list( $this->request['_fields'] ); |
| 191 | |
| 192 | if ( 0 === count( $fields ) ) { |
| 193 | $fields = false; |
| 194 | } else { |
| 195 | $fields = array_map( 'trim', $fields ); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // Initially skip line items if we can. |
| 200 | $using_order_class_override = is_a( $object, '\Automattic\WooCommerce\Admin\Overrides\Order' ); |
| 201 | if ( $using_order_class_override ) { |
| 202 | $data = $object->get_data_without_line_items(); |
| 203 | } else { |
| 204 | $data = $object->get_data(); |
| 205 | } |
| 206 | |
| 207 | $extra_fields = false === $fields ? array() : array_intersect( $extra_fields, $fields ); |
| 208 | $format_decimal = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' ); |
| 209 | $format_date = array( 'date_created', 'date_modified', 'date_completed', 'date_paid' ); |
| 210 | $format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' ); |
| 211 | |
| 212 | // Add extra data as necessary. |
| 213 | $extra_data = array(); |
| 214 | foreach ( $extra_fields as $field ) { |
| 215 | switch ( $field ) { |
| 216 | case 'customer': |
| 217 | $extra_data['customer'] = $this->get_customer_by_id( $data['customer_id'] ); |
| 218 | break; |
| 219 | case 'products': |
| 220 | $extra_data['products'] = $this->get_products_by_order_id( $object->get_id() ); |
| 221 | break; |
| 222 | } |
| 223 | } |
| 224 | // Format decimal values. |
| 225 | foreach ( $format_decimal as $key ) { |
| 226 | $data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] ); |
| 227 | } |
| 228 | |
| 229 | // format total with order currency. |
| 230 | if ( $object instanceof \WC_Order ) { |
| 231 | $data['total_formatted'] = wp_strip_all_tags( html_entity_decode( $object->get_formatted_order_total() ), true ); |
| 232 | } |
| 233 | |
| 234 | // Format date values. |
| 235 | foreach ( $format_date as $key ) { |
| 236 | $datetime = $data[ $key ]; |
| 237 | $data[ $key ] = wc_rest_prepare_date_response( $datetime, false ); |
| 238 | $data[ $key . '_gmt' ] = wc_rest_prepare_date_response( $datetime ); |
| 239 | } |
| 240 | |
| 241 | // Format the order status. |
| 242 | $data['status'] = OrderUtil::remove_status_prefix( $data['status'] ); |
| 243 | |
| 244 | // Format requested line items. |
| 245 | $formatted_line_items = array(); |
| 246 | |
| 247 | foreach ( $format_line_items as $key ) { |
| 248 | if ( false === $fields || in_array( $key, $fields, true ) ) { |
| 249 | if ( $using_order_class_override ) { |
| 250 | $line_item_data = $object->get_line_item_data( $key ); |
| 251 | } else { |
| 252 | $line_item_data = $data[ $key ]; |
| 253 | } |
| 254 | $formatted_line_items[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $line_item_data ) ); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | // Refunds. |
| 259 | $data['refunds'] = array(); |
| 260 | foreach ( $object->get_refunds() as $refund ) { |
| 261 | $data['refunds'][] = array( |
| 262 | 'id' => $refund->get_id(), |
| 263 | 'reason' => $refund->get_reason() ? $refund->get_reason() : '', |
| 264 | 'total' => '-' . wc_format_decimal( $refund->get_amount(), $this->request['dp'] ), |
| 265 | ); |
| 266 | } |
| 267 | |
| 268 | return array_merge( |
| 269 | array( |
| 270 | 'id' => $object->get_id(), |
| 271 | 'parent_id' => $data['parent_id'], |
| 272 | 'number' => $data['number'], |
| 273 | 'order_key' => $data['order_key'], |
| 274 | 'created_via' => $data['created_via'], |
| 275 | 'version' => $data['version'], |
| 276 | 'status' => $data['status'], |
| 277 | 'currency' => $data['currency'], |
| 278 | 'date_created' => $data['date_created'], |
| 279 | 'date_created_gmt' => $data['date_created_gmt'], |
| 280 | 'date_modified' => $data['date_modified'], |
| 281 | 'date_modified_gmt' => $data['date_modified_gmt'], |
| 282 | 'discount_total' => $data['discount_total'], |
| 283 | 'discount_tax' => $data['discount_tax'], |
| 284 | 'shipping_total' => $data['shipping_total'], |
| 285 | 'shipping_tax' => $data['shipping_tax'], |
| 286 | 'cart_tax' => $data['cart_tax'], |
| 287 | 'total' => $data['total'], |
| 288 | 'total_formatted' => isset( $data['total_formatted'] ) ? $data['total_formatted'] : $data['total'], |
| 289 | 'total_tax' => $data['total_tax'], |
| 290 | 'prices_include_tax' => $data['prices_include_tax'], |
| 291 | 'customer_id' => $data['customer_id'], |
| 292 | 'customer_ip_address' => $data['customer_ip_address'], |
| 293 | 'customer_user_agent' => $data['customer_user_agent'], |
| 294 | 'customer_note' => $data['customer_note'], |
| 295 | 'billing' => $data['billing'], |
| 296 | 'shipping' => $data['shipping'], |
| 297 | 'payment_method' => $data['payment_method'], |
| 298 | 'payment_method_title' => $data['payment_method_title'], |
| 299 | 'transaction_id' => $data['transaction_id'], |
| 300 | 'date_paid' => $data['date_paid'], |
| 301 | 'date_paid_gmt' => $data['date_paid_gmt'], |
| 302 | 'date_completed' => $data['date_completed'], |
| 303 | 'date_completed_gmt' => $data['date_completed_gmt'], |
| 304 | 'cart_hash' => $data['cart_hash'], |
| 305 | 'meta_data' => $data['meta_data'], |
| 306 | 'refunds' => $data['refunds'], |
| 307 | ), |
| 308 | $formatted_line_items, |
| 309 | $extra_data |
| 310 | ); |
| 311 | } |
| 312 | } |
| 313 |