/** * Takes a heading and normalizes it based on the current importer type * * @since 1.0.0 * @param string $heading * @return string */ public static function normalize_heading($heading) { $s_heading = trim($heading); // lowercase and replace space with underscores if not a custom meta value if (!SV_WC_Helper::str_starts_with($s_heading, 'meta:')) { $s_heading = strtolower($heading); $s_heading = str_replace(' ', '_', $s_heading); } return $s_heading; }
/** * Maybe force TLS v1.2 requests. * * @since 4.4.0 */ public function set_tls_1_2_request($handle, $r, $url) { if (!SV_WC_Helper::str_starts_with($url, 'https://')) { return; } $versions = curl_version(); $curl_version = $versions['version']; // Get the SSL details list($ssl_type, $ssl_version) = explode('/', $versions['ssl_version']); $ssl_version = substr($ssl_version, 0, -1); // If cURL and/or OpenSSL aren't up to the challenge, bail if (!version_compare($curl_version, '7.34.0', '>=') || 'OpenSSL' === $ssl_type && !version_compare($ssl_version, '1.0.1', '>=')) { return; } curl_setopt($handle, CURLOPT_SSLVERSION, 6); }
/** * Locates the WooCommerce template files from our templates directory * * @since 1.0.0 * @param string $template Already found template * @param string $template_name Searchable template name * @param string $template_path Template path * @return string Search result for the template */ public function locate_template($template, $template_name, $template_path) { // Only keep looking if no custom theme template was found or if // a default WooCommerce template was found. if (!$template || SV_WC_Helper::str_starts_with($template, WC()->plugin_path())) { // Set the path to our templates directory $plugin_path = $this->get_plugin_path() . '/templates/'; // If a template is found, make it so if (is_readable($plugin_path . $template_name)) { $template = $plugin_path . $template_name; } } return $template; }
/** * * @dataProvider provider_test_str_starts_with_false */ public function test_str_starts_with_false($haystack, $needle) { $this->assertFalse(\SV_WC_Helper::str_starts_with($haystack, $needle)); }
/** * Write the given row to the CSV * * This is abstracted so the provided data can be matched to the CSV headers set and the CSV delimiter and * enclosure can be controlled from a single method * * @since 3.0 * @param array $row */ private function write($row) { $data = array(); foreach ($this->headers as $header_key => $_) { if (!isset($row[$header_key])) { $row[$header_key] = ''; } // strict string comparison, as values like '0' are valid $value = '' !== $row[$header_key] ? $row[$header_key] : ''; // escape leading equals sign character with a single quote to prevent CSV injections, see http://www.contextis.com/resources/blog/comma-separated-vulnerabilities/ if (SV_WC_Helper::str_starts_with($value, '=')) { $value = "'" . $value; } $data[] = $value; } fputcsv($this->stream, $data, $this->delimiter, $this->enclosure); }
/** * Parse raw coupon data * * @since 3.0.0 * @param array $item Raw coupon data from CSV * @param array $options Optional. Options * @param array $raw_headers Optional. Raw headers * @throws \WC_CSV_Import_Suite_Import_Exception validation, parsing errors * @return array|bool Parsed coupon data or false on failure */ protected function parse_item($item, $options = array(), $raw_headers = array()) { $coupon_code = isset($item['code']) ? $item['code'] : null; $merging = $options['merge']; $insert_non_matching = isset($options['insert_non_matching']) && $options['insert_non_matching']; /* translators: Placeholders: %s - row number */ $preparing = $merging ? __('> Row %s - preparing for merge.', 'woocommerce-csv-import-suite') : __('> Row %s - preparing for import.', 'woocommerce-csv-import-suite'); wc_csv_import_suite()->log('---'); wc_csv_import_suite()->log(sprintf($preparing, $this->line_num)); // prepare coupon & postmeta for import $coupon = $postmeta = $terms = array(); // cannot merge or insert without coupon code if (!$coupon_code) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_missing_coupon_code', __('Missing coupon code.', 'woocommerce-csv-import-suite')); } global $wpdb; // check for existing coupons $found_coupon = $wpdb->get_var($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_type = 'shop_coupon' AND post_status = 'publish' AND post_title = %s", $coupon_code)); // prepare for merging if ($merging) { // no coupon found if (!$found_coupon) { if ($insert_non_matching) { wc_csv_import_suite()->log(sprintf(__('> > Skipped. Cannot find coupon with code %s. Importing instead.', 'woocommerce-csv-import-suite'), esc_html($coupon_code))); $merging = false; } else { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_cannot_find_coupon', sprintf(__('Cannot find coupon with code %s.', 'woocommerce-csv-import-suite'), esc_html($coupon_code))); } } else { /* translators: Placeholders: %s - coupon code */ wc_csv_import_suite()->log(sprintf(__("> > Found coupon with code '%s'.", 'woocommerce-csv-import-suite'), esc_html($coupon_code))); // record the coupon ID $coupon['id'] = $found_coupon; } } // prepare for importing if (!$merging) { // coupon already exists if ($found_coupon) { /* translators: Placeholders: %s - coupon code */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_coupon_code_already_exists', sprintf(__("Coupon code '%s' already exists.", 'woocommerce-csv-import-suite'), esc_html($coupon_code))); } // check required fields if (!isset($item['type']) || !$item['type']) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_missing_coupon_type', __("Missing coupon discount type.", 'woocommerce-csv-import-suite')); } } // get the set of possible coupon discount types $discount_types = wc_get_coupon_types(); // check for the discount type validity both by key and value (ie either 'fixed_cart' or 'Cart Discount' if (isset($item['type']) && $item['type']) { $discount_type_is_valid = false; foreach ($discount_types as $key => $value) { if (0 === strcasecmp($key, $item['type']) || 0 === strcasecmp($value, __($item['type'], 'woocommerce-csv-import-suite'))) { $discount_type_is_valid = true; $coupon['type'] = $key; break; } } if (!$discount_type_is_valid) { /* translators: Placeholders: %s - discount type name */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_unknown_coupon_type', sprintf(__("Unknown discount type '%s'.", 'woocommerce-csv-import-suite'), esc_html($item['type']))); } } // build the coupon data object $coupon['code'] = $item['code']; $coupon['description'] = isset($item['description']) ? $item['description'] : ''; // get any known coupon data fields foreach ($this->coupon_data_fields as $column) { switch ($column) { case 'products': // handle products: look up by sku // handle products: look up by sku case 'exclude_products': $val = isset($item[$column]) ? $item[$column] : ''; $skus = array_filter(array_map('trim', explode(',', $val))); $val = array(); foreach ($skus as $sku) { // 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)); // no product found if (!$product_id) { /* translators: Placeholders: %s - product SKU */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_unknown_product_sku', sprintf(__('Unknown product sku: %s.', 'woocommerce-csv-import-suite'), esc_html($sku))); } $val[] = $product_id; } // map to standard column name $column = 'products' == $column ? 'product_ids' : 'exclude_product_ids'; break; case 'product_categories': case 'exclude_product_categories': $val = isset($item[$column]) ? $item[$column] : ''; $product_cats = array_filter(array_map('trim', explode(',', $val))); $val = array(); foreach ($product_cats as $product_cat) { // validate product category $term = term_exists($product_cat, 'product_cat'); // unknown category if (!$term) { /* translators: Placeholders: %s - product category name */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_unknown_product_category', sprintf(__('Unknown product category: %s.', 'woocommerce-csv-import-suite'), esc_html($product_cat))); } $val[] = $term['term_id']; } // map to standard column name $column = 'product_categories' == $column ? 'product_category_ids' : 'exclude_product_category_ids'; break; case 'customer_emails': $val = isset($item[$column]) ? $item[$column] : ''; $emails = array_filter(array_map('trim', explode(',', $val))); $val = array(); foreach ($emails as $email) { // invalid email if (!is_email($email)) { /* translators: Placeholders: %s - email address */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_email', sprintf(__('Invalid email: %s.', 'woocommerce-csv-import-suite'), esc_html($email))); } $val[] = $email; } break; case 'enable_free_shipping': // handle booleans, defaulting to 'no' on import (not merge) // handle booleans, defaulting to 'no' on import (not merge) case 'individual_use': case 'exclude_sale_items': $val = isset($item[$column]) && $item[$column] ? strtolower($item[$column]) : ($merging ? '' : 'no'); if ($val && 'yes' != $val && 'no' != $val) { /* translators: Placeholders: %s - column name */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_value', sprintf(__("Column '%s' must be 'yes' or 'no'.", 'woocommerce-csv-import-suite'), esc_html($column))); } // transform into true boolean, so that the format matches with // WC_API_Coupons $val = 'yes' === $val; break; case 'expiry_date': $val = isset($item[$column]) ? $item[$column] : ''; // invalid date format if ($val && false === strtotime($val)) { /* translators: Placeholders: %s - a date in invalid format */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_date_format', sprintf(__("Invalid date format '%s'", 'woocommerce-csv-import-suite'), esc_html($item[$column]))); } break; case 'usage_limit': // handle integers // handle integers case 'usage_count': case 'usage_limit_per_user': case 'limit_usage_to_x_items': $val = isset($item[$column]) ? $item[$column] : ''; // invalid integer value if (!empty($val) && !is_numeric($val)) { /* translators: Placeholders: %1$s - column title, %2$s - column value */ throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_value', sprintf(__('Invalid %1$s \'%2$s\'.', 'woocommerce-csv-import-suite'), esc_html($column), esc_html($val))); } break; default: $val = isset($item[$column]) ? $item[$column] : ''; } // only use non-empty values (zeroes are fine, but empty strings are not) if (is_numeric($val) || !empty($val)) { $coupon[$column] = $val; } } // get any custom meta fields foreach ($item as $key => $value) { if (!$value) { continue; } // handle meta: columns - import as custom fields if (SV_WC_Helper::str_starts_with($key, 'meta:')) { // get meta key name $meta_key = trim(str_replace('meta:', '', $key)); // skip known meta fields if (in_array($meta_key, $this->coupon_meta_fields)) { continue; } // add to postmeta array $postmeta[$meta_key] = $value; } elseif (SV_WC_Helper::str_starts_with($key, 'tax:')) { $results = $this->parse_taxonomy_terms($key, $value); if (!$results) { continue; } // add to array $terms[] = array('taxonomy' => $results[0], 'terms' => $results[1]); } } $coupon['coupon_meta'] = $postmeta; $coupon['terms'] = $terms; /** * Filter parsed coupon data * * Gives a chance for 3rd parties to parse data from custom columns * * @since 3.0.0 * @param array $coupon Parsed coupon data * @param array $data Raw coupon data from CSV * @param array $options Import options * @param array $raw_headers Raw CSV headers */ return apply_filters('wc_csv_import_suite_parsed_coupon_data', $coupon, $item, $options, $raw_headers); }
/** * Parse raw customer data * * @since 3.0.0 * @param array $item Raw customer data from CSV * @param array $options Optional. Options * @param array $raw_headers Optional. Raw headers * @throws \WC_CSV_Import_Suite_Import_Exception validation, parsing errors * @return array|bool Parsed customer data or false on failure */ protected function parse_item($item, $options = array(), $raw_headers = array()) { $customer_id = !empty($item['id']) ? $item['id'] : 0; $username = isset($item['username']) && $item['username'] ? sanitize_user($item['username']) : null; $email = isset($item['email']) && $item['email'] ? $item['email'] : null; $merging = $options['merge']; $insert_non_matching = isset($options['insert_non_matching']) && $options['insert_non_matching']; /* translators: Placeholders: %s - row number */ $preparing = $merging ? __('> Row %s - preparing for merge.', 'woocommerce-csv-import-suite') : __('> Row %s - preparing for import.', 'woocommerce-csv-import-suite'); wc_csv_import_suite()->log('---'); wc_csv_import_suite()->log(sprintf($preparing, $this->line_num)); // prepare for merging if ($merging) { // check if user exists $found_customer = false; // check that at least one required field for merging is provided if (!$customer_id && !$username && !$email) { wc_csv_import_suite()->log(__('> > Cannot merge without id, email or username. Importing instead.', 'woocommerce-csv-import-suite')); $merging = false; } // 1. try matching on user ID if ($customer_id) { $found_customer = get_user_by('id', $customer_id); if (!$found_customer) { // no other fields to match on if (!$username && !$email) { if ($insert_non_matching) { wc_csv_import_suite()->log(sprintf(__('> > Skipped. Cannot find customer with id %s. Importing instead.', 'woocommerce-csv-import-suite'), $customer_id)); $merging = false; } else { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_cannot_find_customer', sprintf(__('Cannot find customer with id %s.', 'woocommerce-csv-import-suite'), $customer_id)); } } else { // we can keep trying with username and/or email wc_csv_import_suite()->log(sprintf(__('> > Cannot find customer with id %s.', 'woocommerce-csv-import-suite'), $customer_id)); } } else { wc_csv_import_suite()->log(sprintf(__('> > Found user with id %s.', 'woocommerce-csv-import-suite'), $customer_id)); } } // 2. try matching on username if (!$found_customer && $username) { // check by username $found_customer = username_exists($username); if (!$found_customer) { if (!$email) { if ($insert_non_matching) { wc_csv_import_suite()->log(sprintf(__('> > Skipped. Cannot find customer with username %s. Importing instead.', 'woocommerce-csv-import-suite'), $username)); $merging = false; } else { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_cannot_find_customer', sprintf(__('Cannot find customer with username %s.', 'woocommerce-csv-import-suite'), $username)); } } else { // We can keep trying with email wc_csv_import_suite()->log(sprintf(__('> > Cannot find customer with username %s.', 'woocommerce-csv-import-suite'), $username)); } } else { wc_csv_import_suite()->log(sprintf(__('> > Found user with username %s.', 'woocommerce-csv-import-suite'), $username)); $customer_id = $found_customer; } } // 3. try matching on email if (!$found_customer && $email) { // check by email $found_customer = email_exists($email); if (!$found_customer) { if ($insert_non_matching) { wc_csv_import_suite()->log(sprintf(__('> > Skipped. Cannot find customer with email %s. Importing instead.', 'woocommerce-csv-import-suite'), $email)); $merging = false; } else { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_cannot_find_customer', sprintf(__('Cannot find customer with email %s.', 'woocommerce-csv-import-suite'), $email)); } } else { wc_csv_import_suite()->log(sprintf(__('> > Found user with email %s.', 'woocommerce-csv-import-suite'), $email)); $customer_id = $found_customer; } } } // prepare for importing if (!$merging) { // Required fields. although login (user_login) is technically also required, we can use email for that if (!$email) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_missing_customer_email', __('No email set for new customer.', 'woocommerce-csv-import-suite')); } // Check if user already exists $user_exists = $username && username_exists($username) || $email && email_exists($email); if ($user_exists) { $identifier = esc_html($username ? $username : $email); throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_customer_already_exists', sprintf(__('Customer %s already exists.', 'woocommerce-csv-import-suite'), $identifier)); } } // validate username if ($username && !validate_username($username)) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_username', sprintf(__('Invalid username: %s', 'woocommerce-csv-import-suite'), esc_html($username))); } // validate email if ($email && !is_email($email)) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_email', sprintf(__('Invalid email: %s', 'woocommerce-csv-import-suite'), esc_html($email))); } // verify the role: allow by slug or by name. skip if not found (TODO is this too harsh? {IT 2016-04-09}) if (isset($item['role']) && $item['role']) { global $wp_roles; if (!isset($wp_roles->role_names[$item['role']])) { $found_role_by_name = false; // fallback to first role by name foreach ($wp_roles->role_names as $slug => $name) { if ($name == $item['role']) { $item['role'] = $slug; $found_role_by_name = true; break; } } if (!$found_role_by_name) { throw new WC_CSV_Import_Suite_Import_Exception('wc_csv_import_suite_invalid_role', sprintf(__('Role "%s" not found.', 'woocommerce-csv-import-suite'), $item['role'])); } } } // prepare user & usermeta for import $user = $usermeta = array(); // if merging, set user ID if ($merging && $customer_id) { $user['id'] = $customer_id; } // email $user['email'] = $email; // ensure username is set (required) $user['username'] = $username ? $username : sanitize_user($email); // password if (isset($item['password'])) { $user['password'] = $item['password']; } // user role, defaults to customer if not merging if (isset($item['role']) && $item['role']) { $user['role'] = $item['role']; } if (isset($item['first_name']) && $item['first_name']) { $user['first_name'] = $item['first_name']; } if (isset($item['last_name']) && $item['last_name']) { $user['last_name'] = $item['last_name']; } if (isset($item['date_registered']) && $item['date_registered']) { $user['date_registered'] = $item['date_registered']; } $user['billing_address'] = $user['shipping_address'] = array(); // get any known customer data (meta) fields foreach ($this->customer_data_fields as $column) { switch ($column) { // normalize customer addresses, to match the WC_API/CLI formats case 'billing_address': case 'shipping_address': $type = substr($column, 0, strpos($column, '_')); $address_fields = $this->address_fields; if ('shipping' == $type) { unset($address_fields['phone']); unset($address_fields['email']); } foreach ($address_fields as $key) { $meta_key = $type . '_' . $key; // on insert use all columns, on merge only use if there is a value. if (isset($item[$meta_key]) && (!$merging || $item[$meta_key])) { $user[$column][$key] = $item[$meta_key]; } // on create default wp user first/last name to billing first/last if (!$merging) { if ('billing_first_name' == $meta_key && !empty($item[$meta_key]) && empty($user['first_name'])) { $user['first_name'] = $item[$meta_key]; } elseif ('billing_last_name' == $meta_key && !empty($item[$meta_key]) && empty($user['last_name'])) { $user['last_name'] = $item[$meta_key]; } } } break; // normalize the paying customer field // normalize the paying customer field case 'paying_customer': if (isset($item[$column]) && (!$merging || $item[$column])) { $usermeta[$column] = $item[$column]; } break; } } // handle the billing/shipping address defaults as needed $copy_billing_to_shipping = isset($options['billing_address_for_shipping_address']) && $options['billing_address_for_shipping_address']; if ($copy_billing_to_shipping && !empty($user['billing_address'])) { foreach ($user['billing_address'] as $key => $value) { // if the shipping address field is set, use that. Otherwise, copy the // value from billing address if (!isset($user['shipping_address'][$key]) || !$user['shipping_address'][$key]) { $user['shipping_address'][$key] = $value; } } } // get any custom meta fields foreach ($item as $key => $value) { if (!$value) { continue; } // handle meta: columns - import as custom fields if (SV_WC_Helper::str_starts_with($key, 'meta:')) { // get meta key name $meta_key = trim(str_replace('meta:', '', $key)); // skip known meta fields if (in_array($meta_key, $this->customer_data_fields)) { continue; } // add to usermeta array $usermeta[$meta_key] = $value; } } $user['user_meta'] = $usermeta; /** * Filter parsed customer data * * Gives a chance for 3rd parties to parse data from custom columns * * @since 3.0.0 * @param array $customer Parsed customer data * @param array $data Raw customer data from CSV * @param array $options Import options * @param array $raw_headers Raw CSV headers */ return apply_filters('wc_csv_import_suite_parsed_customer_data', $user, $item, $options, $raw_headers); }
/** * Render column mapper for CSV import * * @since 3.0.0 * @param array $input * @param array $options * @param array $raw_headers * @return string HTML for column mapper */ protected function render_column_mapper($data, $options, $raw_headers) { $headers = array_keys($data[2]); // data always starts from 2nd line $columns = array(); $sample_size = count($data); foreach ($headers as $heading) { $importer = sanitize_key($_GET['import']); // determine default mapping for heading $mapping = WC_CSV_Import_Suite_Parser::normalize_heading($heading); if (SV_WC_Helper::str_starts_with($heading, 'meta:')) { $mapping = 'import_as_meta'; } if (SV_WC_Helper::str_starts_with($heading, 'tax:')) { $mapping = 'import_as_taxonomy'; } /** * Filter default CSV column <-> field mapping * * @since 3.0.0 * @param string $map_to Field to map the column to. Defaults to column name * @param string $column Column name from CSV file */ $default_mapping = apply_filters("wc_csv_import_suite_{$importer}_column_default_mapping", $mapping, $heading); $columns[$heading] = array('default_mapping' => $default_mapping, 'sample_values' => array()); foreach ($data as $row) { $columns[$heading]['sample_values'][] = isset($row[$heading]) ? $row[$heading] : ''; } } /** * Filter column mapping options * * @since 3.0.0 * @param array $mapping_options Associative array of column mapping options * @param string $importer Importer type * @param array $headers Normalized headers * @param array $raw_headers Raw headers from CSV file * @param array $columns Associative array as 'column' => 'default mapping' */ $mapping_options = apply_filters('wc_csv_import_suite_column_mapping_options', $this->get_column_mapping_options(), $importer, $headers, $raw_headers, $columns); $mapping_options['import_as_meta'] = __('Custom Field with column name', 'woocommerce-csv-import-suite'); $mapping_options['import_as_taxonomy'] = __('Taxonomy with column name', 'woocommerce-csv-import-suite'); // give this instance a descriptive name to be used in the view template $csv_importer = $this; include 'admin/views/html-import-column-mapper.php'; }
/** * Check if input string is possibly a JSON array * * @since 3.0.0 * @param string $string * @return bool True if string is possible JSON, false otherwise */ private function is_possibly_json_array($string) { return '[]' == $string || SV_WC_Helper::str_starts_with($string, '[{') && SV_WC_Helper::str_ends_with($string, '}]'); }