/** * Create new orders based on the parsed data */ private function process_orders() { global $wpdb; $this->imported = $this->merged = 0; // peforming a dry run? $dry_run = isset($_POST['dry_run']) && $_POST['dry_run'] ? true : false; $this->log->add('---'); $this->log->add(__('Processing orders.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN)); foreach ($this->posts as $post) { // orders with custom order order numbers can be checked for existance, otherwise there's not much we can do if (!empty($post['order_number_formatted']) && isset($this->processed_posts[$post['order_number_formatted']])) { $this->skipped++; $this->log->add(sprintf(__('> Order %s already processed. Skipping.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $post['order_number_formatted']), true); continue; } // see class-wc-checkout.php for reference $order_data = array('post_date' => date('Y-m-d H:i:s', $post['date']), 'post_type' => 'shop_order', 'post_title' => 'Order – ' . date('F j, Y @ h:i A', $post['date']), 'post_status' => 'publish', 'ping_status' => 'closed', 'post_excerpt' => $post['order_comments'], 'post_author' => 1, 'post_password' => uniqid('order_')); if (!$dry_run) { // track whether download permissions need to be granted $add_download_permissions = false; $order_id = wp_insert_post($order_data); if (is_wp_error($order_id)) { $this->errored++; $this->log->add(sprintf(__('> Error inserting %s: %s', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $post['order_number_formatted'], $order_id->get_error_message()), true); } // empty update to bump up the post_modified date to today's date (otherwise it would match the post_date, which isn't quite right) wp_update_post(array('ID' => $order_id)); // set order status wp_set_object_terms($order_id, $post['status'], 'shop_order_status'); // handle special meta fields update_post_meta($order_id, '_order_key', apply_filters('woocommerce_generate_order_key', uniqid('order_'))); update_post_meta($order_id, '_order_currency', get_woocommerce_currency()); // TODO: fine to use store default? if (!SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { update_post_meta($order_id, '_order_taxes', array()); // pre-2.1 } update_post_meta($order_id, '_prices_include_tax', get_option('woocommerce_prices_include_tax')); // add order postmeta foreach ($post['postmeta'] as $meta) { $meta_processed = false; // we don't set the "download permissions granted" meta, we call the woocommerce function to take care of this for us if (('Download Permissions Granted' == $meta['key'] || '_download_permissions_granted' == $meta['key']) && $meta['value']) { $add_download_permissions = true; $meta_processed = true; } if (!$meta_processed) { update_post_meta($order_id, $meta['key'], $meta['value']); } // set the paying customer flag on the user meta if applicable if ('_customer_user' == $meta['key'] && $meta['value'] && in_array($post['status'], array('processing', 'completed', 'refunded'))) { update_user_meta($meta['value'], "paying_customer", 1); } } // handle order items $order_items = array(); $order_item_meta = null; foreach ($post['order_items'] as $item) { $product = null; $variation_item_meta = array(); // if there's a product_id then we've already determined during parsing that this product exists if ($item['product_id']) { $product = get_product($item['product_id']); // handle variations if (($product->is_type('variable') || $product->is_type('variation') || $product->is_type('subscription_variation')) && method_exists($product, 'get_variation_id')) { foreach ($product->get_variation_attributes() as $key => $value) { $variation_item_meta[] = array('meta_name' => esc_attr(substr($key, 10)), 'meta_value' => $value); // remove the leading 'attribute_' from the name to get 'pa_color' for instance } } } // order item $order_items[] = array('order_item_name' => $product ? $product->get_title() : __('Unknown Product', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), 'order_item_type' => 'line_item'); // standard order item meta $_order_item_meta = array('_qty' => (int) $item['qty'], '_tax_class' => '', '_product_id' => $item['product_id'], '_variation_id' => $product && method_exists($product, 'get_variation_id') ? $product->get_variation_id() : 0, '_line_subtotal' => number_format((double) $item['total'], 2, '.', ''), '_line_subtotal_tax' => 0, '_line_total' => number_format((double) $item['total'], 2, '.', ''), '_line_tax' => 0); // add any product variation meta foreach ($variation_item_meta as $meta) { $_order_item_meta[$meta['meta_name']] = $meta['meta_value']; } // include any arbitrary order item meta $_order_item_meta = array_merge($_order_item_meta, $item['meta']); $order_item_meta[] = $_order_item_meta; } foreach ($order_items as $key => $order_item) { $order_item_id = woocommerce_add_order_item($order_id, $order_item); if ($order_item_id) { foreach ($order_item_meta[$key] as $meta_key => $meta_value) { if (strpos($meta_value, ':{i')) { $meta_value = unserialize(stripslashes($meta_value)); //'a:1:{i:0;s:19:"2014-05-01 08:00:00";}'; } woocommerce_add_order_item_meta($order_item_id, $meta_key, $meta_value); } } } // create the shipping order items (WC 2.1+) foreach ($post['order_shipping'] as $order_shipping) { $shipping_order_item = array('order_item_name' => $order_shipping['title'], 'order_item_type' => 'shipping'); $shipping_order_item_id = woocommerce_add_order_item($order_id, $shipping_order_item); if ($shipping_order_item_id) { woocommerce_add_order_item_meta($shipping_order_item_id, 'method_id', $order_shipping['method_id']); woocommerce_add_order_item_meta($shipping_order_item_id, 'cost', $order_shipping['cost']); } } // create the tax order items (WC 2.1+) foreach ($post['tax_items'] as $tax_item) { $tax_order_item = array('order_item_name' => $tax_item['title'], 'order_item_type' => 'tax'); $tax_order_item_id = woocommerce_add_order_item($order_id, $tax_order_item); if ($tax_order_item_id) { woocommerce_add_order_item_meta($tax_order_item_id, 'rate_id', $tax_item['rate_id']); woocommerce_add_order_item_meta($tax_order_item_id, 'label', $tax_item['label']); woocommerce_add_order_item_meta($tax_order_item_id, 'compound', $tax_item['compound']); woocommerce_add_order_item_meta($tax_order_item_id, 'tax_amount', $tax_item['tax_amount']); woocommerce_add_order_item_meta($tax_order_item_id, 'shipping_tax_amount', $tax_item['shipping_tax_amount']); } } // Grant downloadalbe product permissions if ($add_download_permissions) { woocommerce_downloadable_product_permissions($order_id); } // add order notes $order = new WC_Order($order_id); foreach ($post['notes'] as $order_note) { $order->add_order_note($order_note); } // record the product sales $order->record_product_sales(); } // ! dry run // was an original order number provided? if (!empty($post['order_number_formatted'])) { if (!$dry_run) { // do our best to provide some custom order number functionality while also allowing 3rd party plugins to provide their own custom order number facilities do_action('woocommerce_set_order_number', $order, $post['order_number'], $post['order_number_formatted']); $order->add_order_note(sprintf(__("Original order #%s", WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $post['order_number_formatted'])); // get the order so we can display the correct order number $order = new WC_Order($order_id); } $this->processed_posts[$post['order_number_formatted']] = $post['order_number_formatted']; } $this->imported++; $this->log->add(sprintf(__('> Finished importing order %s', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $dry_run ? "" : $order->get_order_number())); } $this->log->add(__('Finished processing orders.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN)); unset($this->posts); }
/** * Add any settings error * * @since 1.6 */ public function add_settings_errors() { global $wpdb; // nothing doing if (!isset($_POST['woocommerce_order_number_start'])) { return; } $newvalue = $_POST['woocommerce_order_number_start']; $oldvalue = get_option('woocommerce_order_number_start'); // no change to starting order number if ((int) $newvalue === (int) $oldvalue) { return; } if ($this->is_order_number_start_invalid($newvalue)) { // bad value if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { WC_Admin_Settings::add_error(__('Order Number Start must be a number greater than or equal to 0.', self::TEXT_DOMAIN)); } else { $this->errors = __('Order Number Start must be a number greater than or equal to 0.', self::TEXT_DOMAIN); } return; } if ($this->is_order_number_start_in_use($newvalue)) { // existing order number with a greater incrementing value $post_id = (int) $wpdb->get_var($wpdb->prepare("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_order_number' AND meta_value = %d", $this->get_max_order_number())); if (class_exists('WC_Order')) { $order = new WC_Order($post_id); $highest_order_number = $order->get_order_number(); } else { $highest_order_number = $post_id; } if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { WC_Admin_Settings::add_error(sprintf(__('There is an existing order (%s) with a number greater than or equal to %s. To set a new order number start please choose a higher number or permanently delete the relevant order(s).', self::TEXT_DOMAIN), $highest_order_number, (int) $newvalue)); } else { $this->errors = sprintf(__('There is an existing order (%s) with a number greater than or equal to %s. To set a new order number start please choose a higher number or permanently delete the relevant order(s).', self::TEXT_DOMAIN), $highest_order_number, (int) $newvalue); } return; } }
/** * Parse the order input file, building and returning an array of order data * to import into the database. * * The order data is broken into two portions: the couple of defined fields * that make up the wp_posts table, and then the name-value meta data that is * inserted into the wp_postmeta table. Within the meta data category, there * are known meta fields, such as 'billing_first_name' for instance, and then * arbitrary meta fields are allowed and identified by a CSV column title with * the prefix 'meta:'. * * @param array $parsed_data the raw data parsed from the CSV file * @param array $raw_headers the headers parsed from the CSV file * @param boolean $merging whether this is a straight import, or merge. For * the order import this will always be false. * @param int $record_offset number of records to skip before processing * * @return array associative array containing the key 'order' mapped to the parsed * data, and key 'skipped' with a count of the skipped rows */ private function parse_orders($parsed_data, $raw_headers, $merging, $record_offset) { global $WC_CSV_Import, $wpdb; $allow_unknown_products = isset($_POST['allow_unknown_products']) && $_POST['allow_unknown_products'] ? true : false; $results = array(); // Count row $row = 0; // Track skipped records $skipped = 0; // detect whether this is an import from the Customer/Order CSV Export plugin by checking for the required header names $csv_export_file = false; if (in_array('Item SKU', $raw_headers) && in_array('Item Name', $raw_headers) && in_array('Item Variation', $raw_headers) && in_array('Item Amount', $raw_headers) && in_array('Row Price', $raw_headers) && in_array('Order ID', $raw_headers)) { $csv_export_file = true; // Note: Although I would have liked to have first transformed the // Customer/Order CSV Export format into our standard format, then // all error reporting line numbers would be thrown off, so we'll // just deal with that other format in here } // get the known shipping methods and payment gateways once $available_methods = SV_WC_Plugin_Compatibility::WC()->shipping()->load_shipping_methods(); $available_gateways = SV_WC_Plugin_Compatibility::WC()->payment_gateways->payment_gateways(); $shop_order_status = (array) get_terms('shop_order_status', array('hide_empty' => 0, 'orderby' => 'id')); // get all defined taxes, keyed off of id $tax_rates = array(); foreach ($wpdb->get_results("SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates") as $_row) { $tax_rates[$_row->tax_rate_id] = $_row; } // Format order data foreach ($parsed_data as $item) { $row++; // skip record? if ($row <= $record_offset) { $WC_CSV_Import->log->add(sprintf(__('> Row %s - skipped due to record offset.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $row)); continue; } $postmeta = $order = array(); if (!$csv_export_file) { // standard format: optional integer order number and formatted order number $order_number = !empty($item['order_number']) ? $item['order_number'] : null; $order_number_formatted = !empty($item['order_number_formatted']) ? $item['order_number_formatted'] : $order_number; $WC_CSV_Import->log->add(sprintf(__('> Row %s - preparing for import.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $row)); // validate the supplied formatted order number/order number if (is_numeric($order_number_formatted) && !$order_number) { $order_number = $order_number_formatted; } // use formatted for underlying order number if possible if ($order_number && !is_numeric($order_number)) { $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Order number field must be an integer: %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $order_number)); $skipped++; continue; } if ($order_number_formatted && !$order_number) { $WC_CSV_Import->log->add(__('> > Skipped. Formatted order number provided but no numerical order number, see the documentation for further details.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN)); $skipped++; continue; } } else { // Customer/Order CSV Export plugin format. If the Sequential // Order Numbers Pro plugin is installed, order_number will be // available, if the Order ID is numeric use that, but otherwise // we have no idea what the underlying sequential order number might be $order_number_formatted = $item['order_id']; $order_number = !empty($item['order_number']) ? $item['order_number'] : (is_numeric($order_number_formatted) ? $order_number_formatted : 0); } // obviously we can't set the order's post_id, so to have the ability to set order numbers // we do the best that we can and make sure things work even better when a compatible // plugin like the Sequential Order Number plugin is installed if ($order_number_formatted) { // verify that this order number isn't already in use // we'll give 3rd party plugins two chances to hook in their custom order number facilities: // first by performing a simple search using the order meta field name used by both this and the // Sequential Order Number Pro plugin, allowing other plugins to filter over it if needed, // while still providing this plugin with some base functionality $query_args = array('numberposts' => 1, 'meta_key' => apply_filters('woocommerce_order_number_formatted_meta_name', '_order_number_formatted'), 'meta_value' => $order_number_formatted, 'post_type' => 'shop_order', 'post_status' => 'publish', 'fields' => 'ids'); $order_id = 0; $orders = get_posts($query_args); if (!empty($orders)) { list($order_id) = get_posts($query_args); } // and secondly allowing other plugins to return an entirely different order number if the simple search above doesn't do it for them $order_id = apply_filters('woocommerce_find_order_by_order_number', $order_id, $order_number_formatted); if ($order_id) { $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Order %s already exists.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $order_number_formatted)); $skipped++; continue; } } // handle the special (optional) customer_user field if (isset($item['customer_user']) && $item['customer_user']) { // attempt to find the customer user $found_customer = false; if (is_int($item['customer_user'])) { $found_customer = get_user_by('ID', $item['customer_user']); if (!$found_customer) { $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Cannot find customer with id %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $item['customer_user'])); $skipped++; continue; } } elseif (is_email($item['customer_user'])) { // check by email $found_customer = email_exists($item['customer_user']); } if (!$found_customer) { // still haven't found the customer, check by username $found_customer = username_exists($item['customer_user']); } if (!$found_customer) { // no sign of this customer $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Cannot find customer with email/username %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $item['customer_user'])); $skipped++; continue; } else { $item['customer_user'] = $found_customer; // user id } } elseif ($csv_export_file && isset($item['billing_email']) && $item['billing_email']) { // see if we can link the user by billing email $found_customer = email_exists($item['billing_email']); if ($found_customer) { $item['customer_user'] = $found_customer; } else { $item['customer_user'] = 0; } // guest checkout } else { // guest checkout $item['customer_user'] = 0; } if (!empty($item['status'])) { // check order status value $found_status = false; $available_statuses = array(); foreach ($shop_order_status as $status) { if (0 == strcasecmp($status->slug, $item['status'])) { $found_status = true; } $available_statuses[] = $status->slug; } if (!$found_status) { // unknown order status $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Unknown order status %s (%s).', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $item['status'], implode($available_statuses, ', '))); $skipped++; continue; } } else { $item['status'] = 'processing'; // default } if (!empty($item['date'])) { if (false === ($item['date'] = strtotime($item['date']))) { // invalid date format $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Invalid date format %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $item['date'])); $skipped++; continue; } } else { $item['date'] = time(); } $order_notes = array(); if (!empty($item['order_notes'])) { $order_notes = explode("|", $item['order_notes']); } // build the order data object $order['status'] = $item['status']; $order['date'] = $item['date']; $order['order_comments'] = !empty($item['customer_note']) ? $item['customer_note'] : null; $order['notes'] = $order_notes; if (!is_null($order_number)) { $order['order_number'] = $order_number; } // optional order number, for convenience if ($order_number_formatted) { $order['order_number_formatted'] = $order_number_formatted; } // totals $order_tax = $order_shipping_tax = null; // Get any known order meta fields, and default any missing ones to 0/null // the provided shipping/payment method will be used as-is, and if found in the list of available ones, the respective titles will also be set foreach ($this->order_meta_fields as $column) { switch ($column) { // this will be cased for pre WC 2.1 only case 'shipping_method': $value = isset($item[$column]) ? $item[$column] : ''; // look up shipping method by id or title $shipping_method = isset($available_methods[$value]) ? $value : null; if (!$shipping_method) { // try by title foreach ($available_methods as $method) { if (0 === strcasecmp($method->title, $value)) { $shipping_method = $method->id; break; // go with the first one we find } } } if ($shipping_method) { // known shipping method found $postmeta[] = array('key' => '_shipping_method', 'value' => $shipping_method); $postmeta[] = array('key' => '_shipping_method_title', 'value' => $available_methods[$shipping_method]->title); } elseif ($csv_export_file && $value) { // Customer/Order CSV Export format, shipping method title with no corresponding shipping method type found, so just use the title $postmeta[] = array('key' => '_shipping_method', 'value' => ''); $postmeta[] = array('key' => '_shipping_method_title', 'value' => $value); } elseif ($value) { // Standard format, shipping method but no title $postmeta[] = array('key' => '_shipping_method', 'value' => $value); $postmeta[] = array('key' => '_shipping_method_title', 'value' => ''); } else { // none $postmeta[] = array('key' => '_shipping_method', 'value' => ''); $postmeta[] = array('key' => '_shipping_method_title', 'value' => ''); } break; case 'payment_method': $value = isset($item[$column]) ? $item[$column] : ''; // look up shipping method by id or title $payment_method = isset($available_gateways[$value]) ? $value : null; if (!$payment_method) { // try by title foreach ($available_gateways as $method) { if (0 === strcasecmp($method->title, $value)) { $payment_method = $method->id; break; // go with the first one we find } } } if ($payment_method) { // known payment method found $postmeta[] = array('key' => '_payment_method', 'value' => $payment_method); $postmeta[] = array('key' => '_payment_method_title', 'value' => $available_gateways[$payment_method]->title); } elseif ($csv_export_file && $value) { // Customer/Order CSV Export format, payment method title with no corresponding payments method type found, so just use the title $postmeta[] = array('key' => '_payment_method', 'value' => ''); $postmeta[] = array('key' => '_payment_method_title', 'value' => $value); } elseif ($value) { // Standard format, payment method but no title $postmeta[] = array('key' => '_payment_method', 'value' => $value); $postmeta[] = array('key' => '_payment_method_title', 'value' => ''); } else { // none $postmeta[] = array('key' => '_payment_method', 'value' => ''); $postmeta[] = array('key' => '_payment_method_title', 'value' => ''); } break; // handle numerics // handle numerics case 'order_shipping': // legacy // legacy case 'shipping_total': $order_shipping = isset($item[$column]) ? $item[$column] : 0; // save the order shipping total for later use $postmeta[] = array('key' => '_order_shipping', 'value' => number_format((double) $order_shipping, 2, '.', '')); break; case 'order_shipping_tax': // legacy // legacy case 'shipping_tax_total': // ignore blanks but allow zeroes if (isset($item[$column]) && is_numeric($item[$column])) { $order_shipping_tax = $item[$column]; } break; case 'order_tax': // legacy // legacy case 'tax_total': // ignore blanks but allow zeroes if (isset($item[$column]) && is_numeric($item[$column])) { $order_tax = $item[$column]; } break; case 'order_discount': case 'cart_discount': case 'order_total': $value = isset($item[$column]) ? $item[$column] : 0; $postmeta[] = array('key' => '_' . $column, 'value' => number_format((double) $value, 2, '.', '')); break; case 'billing_country': case 'shipping_country': $value = isset($item[$column]) ? $item[$column] : ''; // support country name or code by converting to code $country_code = array_search($value, SV_WC_Plugin_Compatibility::WC()->countries->countries); if ($country_code) { $value = $country_code; } $postmeta[] = array('key' => '_' . $column, 'value' => $value); break; case 'Download Permissions Granted': case 'download_permissions_granted': if (isset($item['download_permissions_granted'])) { if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { $postmeta[] = array('key' => '_download_permissions_granted', 'value' => $item['download_permissions_granted']); } else { $postmeta[] = array('key' => 'Download Permissions Granted', 'value' => $item['download_permissions_granted']); } } break; default: $postmeta[] = array('key' => '_' . $column, 'value' => isset($item[$column]) ? $item[$column] : ""); } } // Get any custom meta fields foreach ($item as $key => $value) { if (!$value) { continue; } // Handle meta: columns - import as custom fields if (strstr($key, 'meta:')) { // Get meta key name $meta_key = isset($raw_headers[$key]) ? $raw_headers[$key] : $key; $meta_key = trim(str_replace('meta:', '', $meta_key)); // Add to postmeta array $postmeta[] = array('key' => esc_attr($meta_key), 'value' => $value); } } $order_shipping_methods = array(); $_shipping_methods = array(); if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { // pre WC 2.1 format of a single shipping method if (isset($item['shipping_method']) && $item['shipping_method']) { // collect the shipping method id/cost $_shipping_methods[] = array($item['shipping_method'], isset($item['shipping_cost']) ? $item['shipping_cost'] : null); } // collect any additional shipping methods $i = null; if (isset($item['shipping_method_1'])) { $i = 1; } elseif (isset($item['shipping_method_2'])) { $i = 2; } if (!is_null($i)) { while (!empty($item['shipping_method_' . $i])) { $_shipping_methods[] = array($item['shipping_method_' . $i], isset($item['shipping_cost_' . $i]) ? $item['shipping_cost_' . $i] : null); $i++; } } // if the order shipping total wasn't set, calculate it if (!isset($order_shipping)) { $order_shipping = 0; foreach ($_shipping_methods as $_shipping_method) { $order_shipping += $_shipping_method[1]; } $postmeta[] = array('key' => '_order_shipping' . $column, 'value' => number_format((double) $order_shipping, 2, '.', '')); } elseif (isset($order_shipping) && 1 == count($_shipping_methods) && is_null($_shipping_methods[0][1])) { // special case: if there was a total order shipping but no cost for the single shipping method, use the total shipping for the order shipping line item $_shipping_methods[0][1] = $order_shipping; } foreach ($_shipping_methods as $_shipping_method) { // look up shipping method by id or title $shipping_method = isset($available_methods[$_shipping_method[0]]) ? $_shipping_method[0] : null; if (!$shipping_method) { // try by title foreach ($available_methods as $method) { if (0 === strcasecmp($method->title, $_shipping_method[0])) { $shipping_method = $method->id; break; // go with the first one we find } } } if ($shipping_method) { // known shipping method found $order_shipping_methods[] = array('method_id' => $shipping_method, 'cost' => $_shipping_method[1], 'title' => $available_methods[$shipping_method]->title); } elseif ($csv_export_file && $_shipping_method[0]) { // Customer/Order CSV Export format, shipping method title with no corresponding shipping method type found, so just use the title $order_shipping_methods[] = array('method_id' => '', 'cost' => $_shipping_method[1], 'title' => $_shipping_method[0]); } elseif ($_shipping_method[0]) { // Standard format, shipping method but no title $order_shipping_methods[] = array('method_id' => $_shipping_method[0], 'cost' => $_shipping_method[1], 'title' => ''); } } } $order_items = array(); if (!$csv_export_file) { // standard format if (!empty($item['order_item_1'])) { // one or more order items $i = 1; while (!empty($item['order_item_' . $i])) { // split on non-escaped pipes // http://stackoverflow.com/questions/6243778/split-string-by-delimiter-but-not-if-it-is-escaped $_item_meta = preg_split("~\\\\.(*SKIP)(*FAIL)|\\|~s", $item['order_item_' . $i]); // fallback: try a simple explode, since the above apparently doesn't always work if ($item['order_item_' . $i] && empty($_item_meta)) { $_item_meta = explode('|', $item['order_item_' . $i]); } // pop off the special sku, qty and total values $product_identifier = array_shift($_item_meta); // sku or product_id:id $qty = array_shift($_item_meta); $total = array_shift($_item_meta); if (!$product_identifier || !$qty || !$total) { // invalid item $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Missing SKU, quantity or total for %s on row %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), 'order_item_' . $i, $row)); $skipped++; continue 2; // break outer loop } // product_id or sku if (false !== strpos($product_identifier, 'product_id:')) { // product by product_id $product_id = substr($product_identifier, 11); // not a product if ('product' != get_post_type($product_id)) { $product_id = ''; } } else { // find by sku $product_id = $wpdb->get_var($wpdb->prepare("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_sku' AND meta_value=%s LIMIT 1", $product_identifier)); } if (!$allow_unknown_products && !$product_id) { // unknown product $WC_CSV_Import->log->add(sprintf(__('> > Skipped. Unknown order item: %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $product_identifier)); $skipped++; continue 2; // break outer loop } // get any additional item meta $item_meta = array(); foreach ($_item_meta as $pair) { // replace any escaped pipes $pair = str_replace('\\|', '|', $pair); // find the first ':' and split into name-value $split = strpos($pair, ':'); $name = substr($pair, 0, $split); $value = substr($pair, $split + 1); $item_meta[$name] = $value; } $order_items[] = array('product_id' => $product_id, 'qty' => $qty, 'total' => $total, 'meta' => $item_meta); $i++; } } } else { // CSV Customer/Order Export format $sku = $item['item_sku']; $qty = $item['item_amount']; $total = $item['row_price']; if (!$sku || !$qty || !$total) { // invalid item $WC_CSV_Import->log->add(sprintf(__('> > Row %d - %s - skipped. Missing SKU, quantity or total', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $row, $item['order_id'])); $skipped++; continue; // break outer loop } // find by sku $product_id = $wpdb->get_var($wpdb->prepare("SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_sku' AND meta_value=%s LIMIT 1", $sku)); if (!$product_id) { // unknown product $WC_CSV_Import->log->add(sprintf(__('> > Row %d - %s - skipped. Unknown order item: %s.', WC_Customer_CSV_Import_Suite::TEXT_DOMAIN), $row, $item['order_id'], $sku)); $skipped++; continue; // break outer loop } $order_items[] = array('product_id' => $product_id, 'qty' => $qty, 'total' => $total); } $tax_items = array(); // standard tax item format which supports multiple tax items in numbered columns containing a pipe-delimated, colon-labeled format if (!empty($item['tax_item_1']) || !empty($item['tax_item'])) { // one or more order tax items // get the first tax item $tax_item = !empty($item['tax_item_1']) ? $item['tax_item_1'] : $item['tax_item']; $i = 1; $tax_amount_sum = $shipping_tax_amount_sum = 0; while ($tax_item) { $tax_item_data = array(); // turn "label: Tax | tax_amount: 10" into an associative array foreach (explode('|', $tax_item) as $piece) { list($name, $value) = explode(':', $piece); $tax_item_data[trim($name)] = trim($value); } // default rate id to 0 if not set if (!isset($tax_item_data['rate_id'])) { $tax_item_data['rate_id'] = 0; } // have a tax amount or shipping tax amount if (isset($tax_item_data['tax_amount']) || isset($tax_item_data['shipping_tax_amount'])) { // try and look up rate id by label if needed if (isset($tax_item_data['label']) && $tax_item_data['label'] && !$tax_item_data['rate_id']) { foreach ($tax_rates as $tax_rate) { if (0 === strcasecmp($tax_rate->tax_rate_name, $tax_item_data['label'])) { // found the tax by label $tax_item_data['rate_id'] = $tax_rate->tax_rate_id; break; } } } // check for a rate being specified which does not exist, and clear it out (technically an error?) if ($tax_item_data['rate_id'] && !isset($tax_rates[$tax_item_data['rate_id']])) { $tax_item_data['rate_id'] = 0; } // default label of 'Tax' if not provided if (!isset($tax_item_data['label']) || !$tax_item_data['label']) { $tax_item_data['label'] = 'Tax'; } // default tax amounts to 0 if not set if (!isset($tax_item_data['tax_amount'])) { $tax_item_data['tax_amount'] = 0; } if (!isset($tax_item_data['shipping_tax_amount'])) { $tax_item_data['shipping_tax_amount'] = 0; } // handle compound flag by using the defined tax rate value (if any) if (!isset($tax_item_data['tax_rate_compound'])) { $tax_item_data['tax_rate_compound'] = ''; if ($tax_item_data['rate_id']) { $tax_item_data['tax_rate_compound'] = $tax_rates[$tax_item_data['rate_id']]->tax_rate_compound; } } $tax_items[] = array('title' => '', 'rate_id' => $tax_item_data['rate_id'], 'label' => $tax_item_data['label'], 'compound' => $tax_item_data['tax_rate_compound'], 'tax_amount' => $tax_item_data['tax_amount'], 'shipping_tax_amount' => $tax_item_data['shipping_tax_amount']); // sum up the order totals, in case it wasn't part of the import $tax_amount_sum += $tax_item_data['tax_amount']; $shipping_tax_amount_sum += $tax_item_data['shipping_tax_amount']; } // get the next tax item (if any) $i++; $tax_item = isset($item['tax_item_' . $i]) ? $item['tax_item_' . $i] : null; } if (!is_numeric($order_tax)) { $order_tax = $tax_amount_sum; } if (!is_numeric($order_shipping_tax)) { $order_shipping_tax = $shipping_tax_amount_sum; } } // default to zero if not set if (!is_numeric($order_tax)) { $order_tax = 0; } if (!is_numeric($order_shipping_tax)) { $order_shipping_tax = 0; } // no tax items specified, so create a default one using the tax totals if (0 == count($tax_items)) { $tax_items[] = array('title' => '', 'rate_id' => 0, 'label' => 'Tax', 'compound' => '', 'tax_amount' => $order_tax, 'shipping_tax_amount' => $order_shipping_tax); } // add the order tax totals to the order meta $postmeta[] = array('key' => '_order_tax', 'value' => number_format((double) $order_tax, 2, '.', '')); $postmeta[] = array('key' => '_order_shipping_tax', 'value' => number_format((double) $order_shipping_tax, 2, '.', '')); // Customer/Order CSV Export format has orders broken up onto multiple lines, one per order item // so detect whether we are continuing an existing order if ($csv_export_file) { $ix = count($results); if ($ix > 0 && $results[$ix - 1]['order_number_formatted'] == $order['order_number_formatted']) { // continuing an existing order, add the current order item $results[$ix - 1]['order_items'][] = $order_items[0]; $order = null; } } if ($order) { $order['postmeta'] = $postmeta; $order['order_items'] = $order_items; $order['order_shipping'] = $order_shipping_methods; // WC 2.1+ $order['tax_items'] = $tax_items; // the order array will now contain the necessary name-value pairs for the wp_posts table, and also any meta data in the 'postmeta' array $results[] = $order; } } // Result return array($this->type => $results, 'skipped' => $skipped); }
/** * Capture a credit card charge for a prior authorization if this payment * method was used for the given order, the charge hasn't already been * captured, and the gateway supports issuing a capture request * * @since 1.0 * @param \WC_Order|int $order the order identifier or order object */ public function maybe_capture_charge($order) { if (!is_object($order)) { $order = new WC_Order($order); } // bail if the order wasn't paid for with this gateway if (!$this->has_gateway($order->payment_method)) { return; } $gateway = $this->get_gateway($order->payment_method); // ensure that it supports captures if (!$this->can_capture_charge($gateway)) { return; } // ensure the authorization is still valid for capture if (!$gateway->authorization_valid_for_capture($order)) { return; } // remove order status change actions, otherwise we get a whole bunch of capture calls and errors remove_action('woocommerce_order_action_wc_' . $this->get_id() . '_capture_charge', array($this, 'maybe_capture_charge')); // since a capture results in an update to the post object (by updating // the paid date) we need to unhook the save_post action, otherwise we // can get boomeranged and change the status back to on-hold if (SV_WC_Plugin_Compatibility::is_wc_version_gte_2_1()) { // WC 2.1+ remove_action('woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40, 2); } else { // WC 2.0 remove_action('woocommerce_process_shop_order_meta', 'woocommerce_process_shop_order_meta', 10, 2); } // perform the capture $gateway->do_credit_card_capture($order); }