query_vars['adscaleOrders'] = sanitize_key(wp_unslash($_GET['adscaleOrders'])); } // make sure endpoint request if (!isset($wp->query_vars['adscaleOrders'])) { return; } try { ob_start(); // Error handler self::setErrorHandler(); Helper::nocache_headers(); // Check Request (Guards) self::checkRequest(); ob_end_clean(); // Handle Request self::handleRequest(); // Default error Helper::send_response_formatted_error(self::makeErrorBody()); } catch (PluginApiException $ex) { Helper::send_response_formatted_error( self::makeErrorBody($ex->getApiErrorCode(), $ex->getMessage(), $ex->getApiErrorDetails()) ); } catch (\Exception $ex) { Helper::send_response_formatted_error(self::makeErrorBody($ex->getCode(), $ex->getMessage())); } } public static function checkRequest() { parent::checkRequest(); self::initParams(); // last id from the previous page if (!self::checkParamAfter()) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'after' ) ); } // Number of items per page, min if (!self::checkParamLimitForMin()) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'limit' ) ); } // Number of items per page, max if (!self::checkParamLimitForMax()) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'limit' ) ); } // Start date if (self::checkIfRequestParamExists('start') && !self::checkParamStartFormatValid()) { Helper::send_response_formatted_error( self::makeErrorBody('F001','Wrong format', 'start, expected DDMMYYYY' ) ); } // End date if (self::checkIfRequestParamExists('end') && !self::checkParamEndFormatValid()) { Helper::send_response_formatted_error( self::makeErrorBody('F001','Wrong format', 'end, expected DDMMYYYY' ) ); } // Changed After date if (self::checkIfRequestParamExists('changed_after') && !self::checkParamChangedAfterFormatValid()) { Helper::send_response_formatted_error( self::makeErrorBody('F001','Wrong format', 'changed_after, expected DDMMYYYY' ) ); } } public static function initParams() { self::$requestParams['after'] = Helper::get_post_body_data_value('after', 'absint'); self::$requestParams['limit'] = Helper::get_post_body_data_value('limit', 'absint'); self::$requestParams['start'] = Helper::get_post_body_data_value('start'); self::$requestParams['end'] = Helper::get_post_body_data_value('end'); self::$requestParams['ids'] = Helper::get_post_body_data_value('ids'); self::$requestParams['changed_after'] = Helper::get_post_body_data_value('changed_after'); self::$requestParams['meta'] = Helper::get_post_body_data_value('meta', 'bool'); // Set default params if not exists if (null === self::$requestParams['after']) { self::$requestParams['after'] = 0; } if (null === self::$requestParams['limit']) { self::$requestParams['limit'] = self::$defaultLimit; } if (null === self::$requestParams['ids']) { self::$requestParams['ids'] = self::$defaultIds; } // Params Type Hinting self::$requestParams['after'] = (int)self::$requestParams['after']; self::$requestParams['limit'] = (int)self::$requestParams['limit']; self::$requestParams['ids'] = self::resolveIds(self::$requestParams['ids']); } /** * Last id from the previous page, check if is valid * * @return bool is valid */ public static function checkParamAfter() { return self::$requestParams['after'] >= 0; } /** * Check min Number of items per page * * @return bool is valid */ public static function checkParamLimitForMin() { return self::$requestParams['limit'] > 0; } /** * Check max Number of items per page * * @return bool is valid */ public static function checkParamLimitForMax() { return self::$requestParams['limit'] <= self::$maxLimit; } /** * Check date param 'start' * * @return bool */ public static function checkParamStartFormatValid() { return !empty(self::$requestParams['start']) && Helper::validate_date(self::$requestParams['start'], 'dmY'); } /** * Check date param 'end' * * @return bool is valid */ public static function checkParamEndFormatValid() { return !empty(self::$requestParams['end']) && Helper::validate_date(self::$requestParams['end'], 'dmY'); } /** * Check date param 'changed_after' * * @return bool is valid */ public static function checkParamChangedAfterFormatValid() { return !empty(self::$requestParams['changed_after']) && Helper::validate_date(self::$requestParams['changed_after'], 'dmY'); } /** * Resolve Ids * * @param mixed $ids * * @return array */ private static function resolveIds($ids) { if (is_float($ids)) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'ids' ) ); } if (is_numeric($ids)) { $ids = [(int)$ids]; } if (!is_array($ids)) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'ids' ) ); } $filteredIds = []; foreach ($ids as $id) { if (!is_numeric($id) || is_float($id)) { Helper::send_response_formatted_error( self::makeErrorBody('I001','Invalid argument value', 'ids' ) ); } $filteredIds[] = (int)$id; } return $filteredIds; } /** * Handle API request * * @throws PluginApiException */ public static function handleRequest() { $orders_data = array(); // Prefer WC order query so it works with both legacy CPT storage and HPOS. $use_hpos = false; if (class_exists(OrderUtil::class) && method_exists(OrderUtil::class, 'custom_orders_table_usage_is_enabled')) { $use_hpos = OrderUtil::custom_orders_table_usage_is_enabled(); } if ($use_hpos) { $orders = self::get_orders_via_wc_query(); } else { $orders = self::get_orders_via_wp_query(); } foreach ($orders as $order) { if (!$order instanceof \WC_Order) { continue; } $order_data = self::getOrderData($order); if ($order_data) { $orders_data[] = $order_data; } } Helper::send_response_formatted_success( array( 'orders' => $orders_data, ) ); } /** * Get orders using WP_Query (legacy CPT order storage). * * @return \WC_Order[] */ private static function get_orders_via_wp_query() { // Prepare query args. $query_args = array( 'post_type' => 'shop_order', 'post_status' => 'any', 'orderby' => 'ID', 'order' => 'ASC', 'posts_per_page' => isset(self::$requestParams['limit']) ? self::$requestParams['limit'] : -1, 'suppress_filters' => false, ); // Date filters. $query_args = self::addPostDateRestrictions($query_args); $query_args = self::addPostModifiedRestrictions($query_args); add_filter('posts_clauses', array(self::class, 'queryFilter'), 777, 2); $query = new \WP_Query($query_args); remove_filter('posts_clauses', array(self::class, 'queryFilter'), 777); $orders = array(); foreach ($query->posts as $post) { $order = wc_get_order($post->ID); if ($order) { $orders[] = $order; } } return $orders; } /** * Get orders using wc_get_orders (HPOS-compatible). * * @return \WC_Order[] */ private static function get_orders_via_wc_query() { $limit = isset(self::$requestParams['limit']) ? (int) self::$requestParams['limit'] : (int) self::$defaultLimit; $after = isset(self::$requestParams['after']) ? (int) self::$requestParams['after'] : 0; $args = array( 'limit' => $limit, 'orderby' => 'id', 'order' => 'ASC', 'return' => 'objects', 'type' => 'shop_order', 'status' => 'any', ); // Cursor pagination in HPOS: ID > after if ( $after > 0 ) { $args['field_query'] = array( array( 'field' => 'id', 'value' => $after, 'compare' => '>', ), ); } // date_created (your current string logic is OK) $start = !empty(self::$requestParams['start']) ? Helper::reformat_date(self::$requestParams['start'], 'dmY', 'Y-m-d') : ''; $end = !empty(self::$requestParams['end']) ? Helper::reformat_date(self::$requestParams['end'], 'dmY', 'Y-m-d') : ''; if ($start && $end) { $args['date_created'] = $start . '...' . $end; } elseif ($start) { $args['date_created'] = '>=' . $start; } elseif ($end) { $args['date_created'] = '<=' . $end; } $changed_after = !empty(self::$requestParams['changed_after']) ? Helper::reformat_date(self::$requestParams['changed_after'], 'dmY', 'Y-m-d') : ''; if ($changed_after) { $args['date_modified'] = '>=' . $changed_after; } // No overfetch, no PHP filtering, no array_slice needed $orders = wc_get_orders($args); return is_array($orders) ? $orders : array(); } /** * Add post date restrictions to the query args * * @param array $args * @return array */ public static function addPostDateRestrictions($args) { $start = self::$requestParams['start'] ? Helper::reformat_date(self::$requestParams['start'], 'dmY', 'Y-m-d') : ''; $end = self::$requestParams['end'] ? Helper::reformat_date(self::$requestParams['end'], 'dmY', 'Y-m-d') : ''; if ($start || $end) { // Initialize date_query and make the bounds inclusive. $args['date_query'] = array( 'inclusive' => true, ); if ($start) { // From start of day (site timezone) $args['date_query'][] = array( 'column' => 'post_date', 'after' => $start . ' 00:00:00', ); } if ($end) { // Until end of day (site timezone) $args['date_query'][] = array( 'column' => 'post_date', 'before' => $end . ' 23:59:59', ); } } return $args; } public static function addDateCreatedRestrictions($args) { $start = self::$requestParams['start'] ? Helper::reformat_date(self::$requestParams['start'], 'dmY', 'Y-m-d') : ''; $end = self::$requestParams['end'] ? Helper::reformat_date(self::$requestParams['end'], 'dmY', 'Y-m-d') : ''; if ($start && !$end) { $args['date_created'] = ">={$start}"; } if ($end && !$start) { $args['date_created'] = "<={$end}"; } if ($start && $end) { $args['date_created'] = "{$start}...{$end}"; } return $args; } public static function addDateModifiedRestrictions($args) { $changed_after = self::$requestParams['changed_after'] ? Helper::reformat_date(self::$requestParams['changed_after'], 'dmY', 'Y-m-d') : ''; if ($changed_after) { $args['date_modified'] = ">={$changed_after}"; } return $args; } /** * Add post modified date restrictions to the query args * * @param array $args * @return array */ public static function addPostModifiedRestrictions($args) { $changed_after = self::$requestParams['changed_after'] ? Helper::reformat_date(self::$requestParams['changed_after'], 'dmY', 'Y-m-d') : ''; if ($changed_after) { $args['date_query'][] = ['column' => 'post_modified', 'after' => $changed_after]; } return $args; } /** * Filters all query clauses at once. * * @param string[] $clauses Associative array of the clauses for the query. * @param \WP_Query $query The WP_Query instance (passed by reference). * * @return string[] $clauses Filtered Array of query SQL clauses. */ public static function queryFilter($clauses, $query) { global $wpdb; $clauses['orderby']= "{$wpdb->posts}.ID ASC"; if ( self::$requestParams['ids'] ) { $post__in = implode(',', array_map('absint', self::$requestParams['ids'])); $clauses['where'] .= " AND {$wpdb->posts}.ID IN ({$post__in})"; } if (self::$requestParams['after']) { $clauses['where'] .= ' AND ' . $wpdb->prepare("{$wpdb->posts}.ID > %d", (int)self::$requestParams['after']); } return $clauses; } /** * Get Order data * * @param \WC_Order $order * * @return array * @throws PluginApiException */ public static function getOrderData($order) { try { if (!$order instanceof \WC_Order) { return []; } $order_id = (int)$order->get_id(); // Custom adscale 'cart_id' (factually it is session cookie value) $cart_id = $order->get_meta('_adscale_cart_id', true); $cart_id = $cart_id ? $cart_id : null; // date $date = $order->get_date_created(); $date = $date instanceof \WC_DateTime ? $date->format('Y-m-d H:i:s') : null; // payment_date $payment_date = $order->get_date_paid(); $payment_date = $payment_date instanceof \WC_DateTime ? $payment_date->format('Y-m-d H:i:s') : null; // update_date $update_date = $order->get_date_modified(); $update_date = $update_date instanceof \WC_DateTime ? $update_date->format('Y-m-d H:i:s') : null; // customer info $customer_id = $order->get_customer_id(); // Opt-in Checkbox Setup $optin = 'none'; $optinValue = $order->get_meta( '_adscale__optin'); if ($optinValue) { $optin = 'yes'; } elseif ($optinValue === '0') { $optin = 'no'; } if ($customer_id) { $customer = new \WC_Customer($customer_id); $email = $customer->get_email(); // Get account email $phone = $customer->get_billing_phone(); $first_name = $customer->get_first_name(); $last_name = $customer->get_last_name(); } $email = !empty($email) ? $email : $order->get_billing_email(); $phone = !empty($phone) ? $phone : $order->get_billing_phone(); $first_name = !empty($first_name) ? $first_name : $order->get_billing_first_name(); $last_name = !empty($last_name) ? $last_name : $order->get_billing_last_name(); // country and state $shipping_country = $order->get_shipping_country(); $shipping_state = $order->get_shipping_state(); $shipping_state = Helper::get_full_state_name($shipping_country, $shipping_state); $billing_country = $order->get_billing_country(); $billing_state = $order->get_billing_state(); $billing_state = Helper::get_full_state_name($billing_country, $billing_state); // items $order_items_data = []; $order_items_objs = $order->get_items(); foreach ($order_items_objs as $order_item_obj) { if ($order_item_obj instanceof \WC_Order_Item_Product) { $order_items_data[] = self::getOrderItemData($order_item_obj); } } $order_meta_data = []; if (!empty(self::$requestParams['meta'])) { foreach ($order->get_meta_data() as $meta_item) { if (!$meta_item instanceof \WC_Meta_Data) { continue; } $meta_data = $meta_item->get_data(); if (empty($meta_data['key'])) { continue; } $order_meta_data[$meta_data['key']] = $meta_data['value']; } } $order_data = [ 'id' => $order_id, 'cart_id' => $cart_id, 'date' => $date, 'payment_date' => $payment_date, 'update_date' => $update_date, 'status' => $order->get_status(), 'payment_method' => $order->get_payment_method_title(), 'adscale_ad_link' => Helper::get_saved_adscale_ad_link($order_id), 'currency' => $order->get_currency(), 'total_price' => Helper::format_price_with_precision($order->get_total()), 'shipping_price' => Helper::format_price_with_precision(self::getResolvedTotalShipping($order)), 'discount_amount' => Helper::format_price_with_precision(self::getResolvedTotalDiscount($order)), 'ip' => $order->get_customer_ip_address(), 'email' => $email, 'phone' => $phone, 'first_name' => $first_name, 'last_name' => $last_name, 'optin_sms' => $optin, 'optin_email' => $optin, 'shipping_country' => $shipping_country, 'shipping_state' => $shipping_state, 'shipping_city' => $order->get_shipping_city(), 'shipping_address' => $order->get_shipping_address_1(), 'shipping_address2' => $order->get_shipping_address_2(), 'shipping_zip' => $order->get_shipping_postcode(), 'billing_country' => $billing_country, 'billing_state' => $billing_state, 'billing_city' => $order->get_billing_city(), 'billing_address' => $order->get_billing_address_1(), 'billing_address2' => $order->get_billing_address_2(), 'billing_zip' => $order->get_billing_postcode(), 'items' => $order_items_data, ]; if (!empty(self::$requestParams['meta'])) { $order_data['meta'] = $order_meta_data; } return $order_data; } catch (\Exception $e) { throw new PluginApiException('Unknown error', 'S000', $e->getMessage()); } } /** * Get resolved total shipping * * @param \WC_Order $order * * @return string */ public static function getResolvedTotalShipping($order) { // Show shipping including tax. $shipping_total = (float)$order->get_shipping_total(); $shipping_tax = (float)$order->get_shipping_tax(); $shipping = $shipping_total + $shipping_tax; return (string)$shipping; } /** * Get resolved total discount * * @param \WC_Order $order * * @return string */ public static function getResolvedTotalDiscount($order) { return (string)$order->get_total_discount(true); } /** * Get order item product data * * @param \WC_Order_Item_Product $order_item Order Item instance * * @return array * @throws PluginApiException */ public static function getOrderItemData($order_item) { try { $quantity = $order_item->get_quantity(); $total_price = (string)((float)$order_item->get_total() + (float)$order_item->get_total_tax()); // Discounted total with tax $single_item_price = $quantity > 0 ? $total_price / $quantity : null; $single_item_price_excl_tax = $quantity > 0 ? $order_item->get_total() / $quantity : null; $total_discount = $order_item->get_subtotal() - $order_item->get_total(); return [ 'product_id' => $order_item->get_product_id(), 'variant_id' => $order_item->get_variation_id(), 'quantity' => $order_item->get_quantity(), 'total_price' => Helper::format_price_with_precision($total_price), 'single_item_price' => Helper::format_price_with_precision($single_item_price), 'single_item_price_excl_tax' => Helper::format_price_with_precision($single_item_price_excl_tax), 'single_item_discount_amount' => '0', 'total_discount_amount' => Helper::format_price_with_precision($total_discount), ]; } catch (\Exception $e) { throw new PluginApiException('Unknown error', 'S000', $e->getMessage()); } } }