/** * Get order line items (products) in a neatly-formatted array of objects * with properties: * * + id - item ID * + name - item name, usually product title, processed through htmlentities() * + description - formatted item meta (e.g. Size: Medium, Color: blue), processed through htmlentities() * + quantity - item quantity * + item_total - item total (line total divided by quantity, excluding tax & rounded) * + line_total - line item total (excluding tax & rounded) * + meta - formatted item meta array * + product - item product or null if getting product from item failed * + item - raw item array * * @since 3.0.0 * @param \WC_Order $order * @return array */ public static function get_order_line_items($order) { $line_items = array(); foreach ($order->get_items() as $id => $item) { $line_item = new stdClass(); $product = $order->get_product_from_item($item); $meta = SV_WC_Plugin_Compatibility::is_wc_version_gte_2_4() ? $item : $item['item_meta']; // get meta + format it $item_meta = new WC_Order_Item_Meta($meta); $item_meta = $item_meta->get_formatted(); if (!empty($item_meta)) { $item_desc = array(); foreach ($item_meta as $meta) { $item_desc[] = sprintf('%s: %s', $meta['label'], $meta['value']); } $item_desc = implode(', ', $item_desc); } else { // default description to SKU $item_desc = is_callable(array($product, 'get_sku')) && $product->get_sku() ? sprintf('SKU: %s', $product->get_sku()) : null; } $line_item->id = $id; $line_item->name = htmlentities($item['name'], ENT_QUOTES, 'UTF-8', false); $line_item->description = htmlentities($item_desc, ENT_QUOTES, 'UTF-8', false); $line_item->quantity = $item['qty']; $line_item->item_total = isset($item['recurring_line_total']) ? $item['recurring_line_total'] : $order->get_item_total($item); $line_item->line_total = $order->get_line_total($item); $line_item->meta = $item_meta; $line_item->product = is_object($product) ? $product : null; $line_item->item = $item; $line_items[] = $line_item; } return $line_items; }
/** * Get the invoice line items for a given order. This includes: * * + Products, mapped as FreshBooks items when the admin has linked them * + Fees, mapped as a `Fee` item * + Shipping, mapped as a `Shipping` item * + Taxes, mapped using the tax code as the item * * Note that taxes cannot be added per-product as WooCommerce doesn't provide * any way to get individual tax information per product and FreshBooks requires * a tax name/percentage to be set on a per-product basis. Further, FreshBooks * only allows 2 taxes per product where realistically most stores will have * more * * @param \WC_FreshBooks_Order $order order instance * @return array line items * @since 3.0 */ private function get_invoice_lines(WC_FreshBooks_Order $order) { $line_items = array(); // add products foreach ($order->get_items() as $item_key => $item) { $product = $order->get_product_from_item($item); // must be a valid product if (is_object($product)) { $product_id = $product->is_type('variation') ? $product->variation_id : $product->id; $item_name = metadata_exists('post', $product_id, '_wc_freshbooks_item_name') ? get_post_meta($product_id, '_wc_freshbooks_item_name', true) : $product->get_sku(); $item_meta = new WC_Order_Item_Meta(SV_WC_Plugin_Compatibility::is_wc_version_gte_2_4() ? $item : $item['item_meta']); // variation data, item meta, etc $meta = $item_meta->display(true, true); // grouped products include a &arr; in the name which must be converted back to an arrow $item_description = html_entity_decode($product->get_title(), ENT_QUOTES, 'UTF-8') . ($meta ? sprintf(' (%s)', $meta) : ''); } else { $item_name = __('Product', WC_FreshBooks::TEXT_DOMAIN); $item_description = $item['name']; } $line_items[] = array('name' => $item_name, 'description' => apply_filters('wc_freshbooks_line_item_description', $item_description, $item, $order, $item_key), 'unit_cost' => $order->get_item_subtotal($item), 'quantity' => $item['qty']); } $coupons = $order->get_items('coupon'); // add coupons foreach ($coupons as $coupon_item) { $coupon = new WC_Coupon($coupon_item['name']); $coupon_post = get_post($coupon->id); $coupon_type = false !== strpos($coupon->type, '_cart') ? __('Cart Discount', WC_FreshBooks::TEXT_DOMAIN) : __('Product Discount', WC_FreshBooks::TEXT_DOMAIN); $line_items[] = array('name' => $coupon_item['name'], 'description' => is_object($coupon_post) && $coupon_post->post_excerpt ? sprintf(__('%s - %s', WC_FreshBooks::TEXT_DOMAIN), $coupon_type, $coupon_post->post_excerpt) : $coupon_type, 'unit_cost' => wc_format_decimal($coupon_item['discount_amount'] * -1, 2), 'quantity' => 1); } // manually created orders can't have coupon items, but may have an order discount set // which must be added as a line item if (0 == count($coupons) && $order->get_total_discount() > 0) { $line_items[] = array('name' => __('Order Discount', WC_FreshBooks::TEXT_DOMAIN), 'description' => __('Order Discount', WC_FreshBooks::TEXT_DOMAIN), 'unit_cost' => wc_format_decimal($order->get_total_discount() * -1, 2), 'quantity' => 1); } // add fees foreach ($order->get_fees() as $fee_key => $fee) { $line_items[] = array('name' => __('Fee', WC_FreshBooks::TEXT_DOMAIN), 'description' => $fee['name'], 'unit_cost' => $order->get_line_total($fee), 'quantity' => 1); } // add shipping foreach ($order->get_shipping_methods() as $shipping_method_key => $shipping_method) { $line_items[] = array('name' => __('Shipping', WC_FreshBooks::TEXT_DOMAIN), 'description' => ucwords(str_replace('_', ' ', $shipping_method['method_id'])), 'unit_cost' => $shipping_method['cost'], 'quantity' => 1); } // add taxes foreach ($order->get_tax_totals() as $tax_code => $tax) { $line_items[] = array('name' => $tax_code, 'description' => $tax->label, 'unit_cost' => number_Format($tax->amount, 2, '.', ''), 'quantity' => 1); } return $line_items; }
/** * Get the order data for a single CSV row * * Note items are keyed according to the column header keys above so these can be modified using * the provider filter without needing to worry about the array order * * @since 3.0 * @param int $order_id the WC_Order ID * @return array order data in the format key => content */ private function get_orders_csv_row($order_id) { $order = wc_get_order($order_id); $line_items = $shipping_items = $fee_items = $tax_items = $coupon_items = array(); // get line items foreach ($order->get_items() as $item_id => $item) { $product = $order->get_product_from_item($item); if (!is_object($product)) { $product = new WC_Product(0); } $item_meta = new WC_Order_Item_Meta(SV_WC_Plugin_Compatibility::is_wc_version_gte_2_4() ? $item : $item['item_meta']); $meta = $item_meta->display(true, true); if ($meta) { // remove newlines $meta = str_replace(array("\r", "\r\n", "\n"), '', $meta); // switch reserved chars (:;|) to = $meta = str_replace(array(': ', ':', ';', '|'), '=', $meta); } $line_item = array('name' => html_entity_decode($product->get_title() ? $product->get_title() : $item['name'], ENT_NOQUOTES, 'UTF-8'), 'sku' => $product->get_sku(), 'quantity' => $item['qty'], 'total' => wc_format_decimal($order->get_line_total($item), 2), 'refunded' => wc_format_decimal($order->get_total_refunded_for_item($item_id), 2), 'meta' => html_entity_decode($meta, ENT_NOQUOTES, 'UTF-8')); // add line item tax $line_tax_data = isset($item['line_tax_data']) ? $item['line_tax_data'] : array(); $tax_data = maybe_unserialize($line_tax_data); $line_item['tax'] = isset($tax_data['total']) ? wc_format_decimal(wc_round_tax_total(array_sum((array) $tax_data['total'])), 2) : ''; /** * CSV Order Export Line Item. * * Filter the individual line item entry for the default export * * @since 3.0.6 * @param array $line_item { * line item data in key => value format * the keys are for convenience and not used for exporting. Make * sure to prefix the values with the desired line item entry name * } * * @param array $item WC order item data * @param WC_Product $product the product * @param WC_Order $order the order * @param \WC_Customer_Order_CSV_Export_Generator $this, generator instance */ $line_item = apply_filters('wc_customer_order_csv_export_order_line_item', $line_item, $item, $product, $order, $this); if ('default_one_row_per_item' !== $this->order_format && is_array($line_item)) { foreach ($line_item as $name => $value) { $line_item[$name] = $name . ':' . $value; } $line_item = implode('|', $line_item); } if ($line_item) { $line_items[] = $line_item; } } foreach ($order->get_shipping_methods() as $_ => $shipping_item) { $shipping_items[] = implode('|', array('method:' . $shipping_item['name'], 'total:' . wc_format_decimal($shipping_item['cost'], 2))); } // get fee items & total $fee_total = 0; $fee_tax_total = 0; foreach ($order->get_fees() as $fee_id => $fee) { $fee_items[] = implode('|', array('name:' . $fee['name'], 'total:' . wc_format_decimal($fee['line_total'], 2), 'tax:' . wc_format_decimal($fee['line_tax'], 2))); $fee_total += $fee['line_total']; $fee_tax_total += $fee['line_tax']; } // get tax items foreach ($order->get_tax_totals() as $tax_code => $tax) { $tax_items[] = implode('|', array('code:' . $tax_code, 'total:' . wc_format_decimal($tax->amount, 2))); } // add coupons foreach ($order->get_items('coupon') as $_ => $coupon_item) { $coupon = new WC_Coupon($coupon_item['name']); $coupon_post = get_post($coupon->id); $coupon_items[] = implode('|', array('code:' . $coupon_item['name'], 'description:' . (is_object($coupon_post) ? $coupon_post->post_excerpt : ''), 'amount:' . wc_format_decimal($coupon_item['discount_amount'], 2))); } $order_data = array('order_id' => $order->id, 'order_number' => $order->get_order_number(), 'order_date' => $order->order_date, 'status' => $order->get_status(), 'shipping_total' => $order->get_total_shipping(), 'shipping_tax_total' => wc_format_decimal($order->get_shipping_tax(), 2), 'fee_total' => wc_format_decimal($fee_total, 2), 'fee_tax_total' => wc_format_decimal($fee_tax_total, 2), 'tax_total' => wc_format_decimal($order->get_total_tax(), 2), 'cart_discount' => SV_WC_Plugin_Compatibility::is_wc_version_gte_2_3() ? wc_format_decimal($order->get_total_discount(), 2) : wc_format_decimal($order->get_cart_discount(), 2), 'order_discount' => SV_WC_Plugin_Compatibility::is_wc_version_gte_2_3() ? wc_format_decimal($order->get_total_discount(), 2) : wc_format_decimal($order->get_order_discount(), 2), 'discount_total' => wc_format_decimal($order->get_total_discount(), 2), 'order_total' => wc_format_decimal($order->get_total(), 2), 'refunded_total' => wc_format_decimal($order->get_total_refunded(), 2), 'order_currency' => $order->get_order_currency(), 'payment_method' => $order->payment_method, 'shipping_method' => $order->get_shipping_method(), 'customer_id' => $order->get_user_id(), 'billing_first_name' => $order->billing_first_name, 'billing_last_name' => $order->billing_last_name, 'billing_company' => $order->billing_company, 'billing_email' => $order->billing_email, 'billing_phone' => $order->billing_phone, 'billing_address_1' => $order->billing_address_1, 'billing_address_2' => $order->billing_address_2, 'billing_postcode' => $order->billing_postcode, 'billing_city' => $order->billing_city, 'billing_state' => $order->billing_state, 'billing_country' => $order->billing_country, 'shipping_first_name' => $order->shipping_first_name, 'shipping_last_name' => $order->shipping_last_name, 'shipping_company' => $order->shipping_company, 'shipping_address_1' => $order->shipping_address_1, 'shipping_address_2' => $order->shipping_address_2, 'shipping_postcode' => $order->shipping_postcode, 'shipping_city' => $order->shipping_city, 'shipping_state' => $order->shipping_state, 'shipping_country' => $order->shipping_country, 'customer_note' => $order->customer_note, 'shipping_items' => implode(';', $shipping_items), 'fee_items' => implode(';', $fee_items), 'tax_items' => implode(';', $tax_items), 'coupon_items' => implode(';', $coupon_items), 'order_notes' => implode('|', $this->get_order_notes($order)), 'download_permissions' => $order->download_permissions_granted ? $order->download_permissions_granted : 0); if ('default_one_row_per_item' === $this->order_format) { $new_order_data = array(); foreach ($line_items as $item) { $order_data['item_name'] = $item['name']; $order_data['item_sku'] = $item['sku']; $order_data['item_quantity'] = $item['quantity']; $order_data['item_tax'] = $item['tax']; $order_data['item_total'] = $item['total']; $order_data['item_refunded'] = $item['refunded']; $order_data['item_meta'] = $item['meta']; /** * CSV Order Export Row for One Row per Item. * * Filter the individual row data for the order export * * @since 3.3.0 * @param array $order_data { * order data in key => value format * to modify the row data, ensure the key matches any of the header keys and set your own value * } * @param array $item * @param \WC_Order $order WC Order object * @param \WC_Customer_Order_CSV_Export_Generator $this, generator instance */ $new_order_data[] = apply_filters('wc_customer_order_csv_export_order_row_one_row_per_item', $order_data, $item, $order, $this); } $order_data = $new_order_data; } else { $order_data['line_items'] = implode(';', $line_items); } /** * CSV Order Export Row. * * Filter the individual row data for the order export * * @since 3.0 * @param array $order_data { * order data in key => value format * to modify the row data, ensure the key matches any of the header keys and set your own value * } * @param \WC_Order $order WC Order object * @param \WC_Customer_Order_CSV_Export_Generator $this, generator instance */ return apply_filters('wc_customer_order_csv_export_order_row', $order_data, $order, $this); }
/** * Get the order data format for a single column for all line items, compatible with the legacy (pre 3.0) CSV Export format * * Note this code was adapted from the old code to maintain compatibility as close as possible, so it should * not be modified unless absolutely necessary * * @since 3.0 * @param array $order_data an array of order data for the given order * @param WC_Order $order the WC_Order object * @return array modified order data */ private function get_legacy_single_column_line_item($order_data, WC_Order $order) { $line_items = array(); foreach ($order->get_items() as $_ => $item) { $product = $order->get_product_from_item($item); if (!is_object($product)) { $product = new WC_Product(0); } $line_item = $item['name']; if ($product->get_sku()) { $line_item .= ' (' . $product->get_sku() . ')'; } $line_item .= ' x' . $item['qty']; $item_meta = new WC_Order_Item_Meta(SV_WC_Plugin_Compatibility::is_wc_version_gte_2_4() ? $item : $item['item_meta']); $variation = $item_meta->display(true, true); if ($variation) { $line_item .= ' - ' . str_replace(array("\r", "\r\n", "\n"), '', $variation); } $line_items[] = str_replace(array('“', '”'), '', $line_item); } $order_data['order_items'] = implode('; ', $line_items); // convert country codes to full name if (isset(WC()->countries->countries[$order->billing_country])) { $order_data['billing_country'] = WC()->countries->countries[$order->billing_country]; } if (isset(WC()->countries->countries[$order->shipping_country])) { $order_data['shipping_country'] = WC()->countries->countries[$order->shipping_country]; } // set order ID to order number $order_data['order_id'] = ltrim($order->get_order_number(), _x('#', 'hash before the order number', WC_Customer_Order_CSV_Export::TEXT_DOMAIN)); return $order_data; }
/** * Mark an order as voided. Because WC has no status for "void", we use * refunded. * * @since 3.1.0 * @param WC_Order $order order object */ public function mark_order_as_voided($order, $response) { $message = sprintf(_x('%s Void in the amount of %s approved.', 'Supports voids', $this->text_domain), $this->get_method_title(), wc_price($order->refund->amount, array('currency' => $order->get_order_currency()))); // adds the transaction id (if any) to the order note if ($response->get_transaction_id()) { $message .= ' ' . sprintf(_x('(Transaction ID %s)', 'Supports voids', $this->text_domain), $response->get_transaction_id()); } // mark order as cancelled, since no money was actually transferred if (!$order->has_status('cancelled')) { $this->voided_order_message = $message; // voids are fully "refunded" so cancel the voided order instead of marking as refunded if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_4()) { // filter in WC 2.4+ allows us to skip the "refunded" then "cancelled" transition add_filter('woocommerce_order_fully_refunded_status', array($this, 'maybe_cancel_voided_order'), 10, 2); } else { // WC 2.3/2.2 requires changing the order status to cancelled after it's already been changed to refunded ಠ_ಠ add_action('woocommerce_order_refunded', array($this, 'maybe_cancel_voided_order_2_3')); } } else { $order->add_order_note($message); } }