]*>\s*~i', '', $in_html); $html = preg_replace('/(<[^>]+) style=".*?"/i', '$1', $html); $html = preg_replace('/(<[^>]+) class=".*?"/i', '$1', $html); $html = preg_replace('/(<[^>]+) width=".*?"/i', '$1', $html); $html = preg_replace('/(<[^>]+) height=".*?"/i', '$1', $html); $html = preg_replace('/(<[^>]+) alt=".*?"/i', '$1', $html); $html = preg_replace('/^/', '$1', str_replace(array('', '', '', ''), '', $html)); $html = preg_replace("/<\/?div[^>]*\>/i", "", $html); $html = preg_replace('#().*?()#', '$1$2', $html); $html = preg_replace('/]*>(.*)<\/a>/iU', '', $html); $html = preg_replace("/<\/?h1[^>]*\>/i", "", $html); $html = preg_replace("/<\/?strong[^>]*\>/i", "", $html); $html = preg_replace("/<\/?span[^>]*\>/i", "", $html); //$html = str_replace('   ', '', $html); $html = str_replace(' ', ' ', $html); $html = str_replace('\t', ' ', $html); $html = str_replace(' ', ' ', $html); $html = preg_replace("/http:\/\/g(\d+)\.a\./i", "https://ae$1.", $html); $pattern = "/<[^\/>]*>([\s]?)*<\/[^>]*>/"; $html = preg_replace($pattern, '', $html); $html = str_replace(array('prefix}woocommerce_attribute_taxonomies wat " . "ON(CONCAT('pa_',wat.attribute_name)=tt.taxonomy) " . "WHERE tr.object_id = %s AND tt.taxonomy NOT IN (". implode(',', $escAttrs) .")"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($wpdb->prepare($sql, $product_id)); } } public function set_woocommerce_attributes($post_id, $attributes = array()) { global $wpdb; global $woocommerce; // convert Amazon attributes into woocommerce attributes $_product_attributes = array(); $position = 0; foreach ($attributes as $attr) { $key = $attr['name']; $value = $attr['value']; if (!is_object($value)) { // change dimension name as woocommerce attribute name $attribute_name = $this->cleanTaxonomyName(strtolower($key)); // Clean if (is_array($value)) { foreach($value as $k=>$v){ $value[$k] = $this->cleanValue($v); } }else{ $value = $this->cleanValue($value); } // if is empty attribute don't import if (empty($value)){ continue; } $_product_attributes[$attribute_name] = array( 'name' => $attribute_name, 'value' => is_array($value)?implode(", ", $value):$value, 'position' => $position++, 'is_visible' => 1, 'is_variation' => 0, 'is_taxonomy' => 1 ); $this->add_attribute($post_id, $key, $value); } } // update product attribute update_post_meta($post_id, '_product_attributes', $_product_attributes); $this->attrclean_clean_all('array'); // delete duplicate attributes // refresh attribute cache //$dmtransient_name = 'wc_attribute_taxonomies'; //$dmattribute_taxonomies = $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "woocommerce_attribute_taxonomies"); //set_transient($dmtransient_name, $dmattribute_taxonomies); flush_rewrite_rules(); a2wl_delete_transient('wc_attribute_taxonomies'); } public function add_attribute($post_id, $key, $value): void { global $wpdb; // avoid object to be inserted in terms if (is_object($value)) { return; } // Empty attribute values are expected for sold-out/disabled products (the attribute // itself is still registered in advance). Not a bug; keep as a case to verify with // a dedicated test in the future. if (empty($value) || (is_array($value) && count(array_filter($value)) === 0)) { a2wl_info_log(sprintf( '[add_attribute] Empty value detected for product_id=%d, key=%s', $post_id, $key )); } $taxonomy = $this->cleanTaxonomyName($key); $attribute_label = $key; //attribute name should be the same as taxonomy just without pa_ $attribute_name = str_replace('pa_', '', $taxonomy); $attribute_type = 'select'; $attribute_taxonomies = $wpdb->get_var( "SELECT * FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name = '" . esc_sql($attribute_name) . "'" ); if ($attribute_taxonomies) { // Update existing global attribute // Set type to 'select' since this attribute is used for variations $wpdb->update( $wpdb->prefix . 'woocommerce_attribute_taxonomies', [ 'attribute_type' => $attribute_type ], ['attribute_name' => $attribute_name] ); } else { $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', [ 'attribute_label' => $attribute_label, 'attribute_name' => $attribute_name, 'attribute_type' => $attribute_type, 'attribute_orderby' => 'name' ] ); } // add attribute values if not exist $values = is_array($value) ? $value : [$value]; // check taxonomy if (!taxonomy_exists($taxonomy)) { // add attribute value foreach ($values as $attribute_value) { $attribute_value = (string) $attribute_value; if (is_string($attribute_value)) { // add term $name = $this->cleanValue($attribute_value); $slug = sanitize_title($name); if (!term_exists($name)) { $term_taxonomy_id = $this->createTermAndLinkTaxonomy($name, $taxonomy, $slug); } else { $term_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM {$wpdb->terms} WHERE name = %s", $name ) ); $existing_tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE term_id = %d AND taxonomy = %s", $term_id, $taxonomy ) ); if ($existing_tt_id) { //term is connected to the taxonomy, do nothing $term_taxonomy_id = $existing_tt_id; } else { //need create a new token and connect it to the taxonomy $term_taxonomy_id = $this->createTermAndLinkTaxonomy($name, $taxonomy, $slug); } } } } } if (!empty($values)) { $attribute_slugs = array(); $terms = $this->load_terms($taxonomy); foreach ($terms as $term) { $attribute_slugs[] = strval($term->slug); } // Add terms foreach ($values as $attribute_value) { $attribute_value = $this->cleanValue((string) $attribute_value); $attribute_slug = sanitize_title(htmlspecialchars($attribute_value, ENT_NOQUOTES)); if (!in_array(strval($attribute_slug), $attribute_slugs, true)) { wp_insert_term($attribute_value, $taxonomy, array('slug'=>$attribute_slug)); } } } // wp_term_relationships (object_id to term_taxonomy_id) if (!empty($values)) { foreach ($values as $term) { if (!is_array($term) && !is_object($term)) { $term = sanitize_title(htmlspecialchars($term, ENT_NOQUOTES)); $term_taxonomy_id = $wpdb->get_var("SELECT tt.term_taxonomy_id FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} as tt ON tt.term_id = t.term_id WHERE t.slug = '" . esc_sql($term) . "' AND tt.taxonomy = '" . esc_sql($taxonomy) . "'"); if ($term_taxonomy_id) { $checkSql = "SELECT * FROM {$wpdb->term_relationships} WHERE object_id = {$post_id} AND term_taxonomy_id = {$term_taxonomy_id}"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if (!$wpdb->get_var($checkSql)) { $wpdb->insert( $wpdb->term_relationships, array('object_id' => $post_id, 'term_taxonomy_id' => $term_taxonomy_id) ); } } } } } // Update WPML translates global $woocommerce_wpml; if (isset($woocommerce_wpml) && property_exists($woocommerce_wpml, 'attributes') && $woocommerce_wpml->attributes) { $woocommerce_wpml->attributes->set_attribute_config_in_settings( $taxonomy, 1 ); } } public function attrclean_clean_all($retType = 'die') { // :: get duplicates list $duplicates = $this->attrclean_getDuplicateList(); if (empty($duplicates) || !is_array($duplicates)) { $ret['status'] = 'valid'; $ret['msg_html'] = 'no duplicate terms found!'; if ($retType == 'die') die(wp_json_encode($ret)); else return $ret; } // html message $__duplicates = array(); $__duplicates[] = '0 : name, slug, term_taxonomy_id, taxonomy, count'; foreach ($duplicates as $key => $value) { $__duplicates[] = $value->name . ' : ' . implode(', ', (array) $value); } $ret['status'] = 'valid'; $ret['msg_html'] = implode('
', $__duplicates); // if ( $retType == 'die' ) die(wp_json_encode($ret)); // else return $ret; // :: get terms per duplicate $__removeStat = array(); $__terms = array(); $__terms[] = '0 : term_id, name, slug, term_taxonomy_id, taxonomy, count'; foreach ($duplicates as $key => $value) { $terms = $this->attrclean_getTermPerDuplicate($value->name, $value->taxonomy); if (empty($terms) || !is_array($terms) || count($terms) < 2) continue 1; $first_term = array_shift($terms); // html message foreach ($terms as $k => $v) { $__terms[] = $key . ' : ' . implode(', ', (array) $v); } // :: remove duplicate term $removeStat = $this->attrclean_removeDuplicate($first_term->term_id, $terms, false); // html message $__removeStat[] = '-------------------------------------- ' . $key; $__removeStat[] = '---- term kept'; $__removeStat[] = 'term_id, term_taxonomy_id'; $__removeStat[] = $first_term->term_id . ', ' . $first_term->term_taxonomy_id; foreach ($removeStat as $k => $v) { $__removeStat[] = '---- ' . $k; if (!empty($v) && is_array($v)) { foreach ($v as $k2 => $v2) { $__removeStat[] = implode(', ', (array) $v2); } } else if (!is_array($v)) { $__removeStat[] = (int) $v; } else { $__removeStat[] = 'empty!'; } } } $ret['status'] = 'valid'; $ret['msg_html'] = implode('
', $__removeStat); if ($retType == 'die') die(wp_json_encode($ret)); else return $ret; } /** * Attributes clean duplicate */ public function attrclean_getDuplicateList() { global $wpdb; // $q = "SELECT COUNT(a.term_id) AS nb, a.name, a.slug FROM {$wpdb->terms} AS a WHERE 1=1 GROUP BY a.name HAVING nb > 1;"; $q = "SELECT COUNT(a.term_id) AS nb, a.name, a.slug, b.term_taxonomy_id, b.taxonomy, b.count FROM {$wpdb->terms} AS a " . "LEFT JOIN {$wpdb->term_taxonomy} AS b ON a.term_id = b.term_id " . "WHERE b.taxonomy REGEXP '^pa_' GROUP BY a.name, b.taxonomy HAVING nb > 1 " . "ORDER BY a.name ASC"; $res = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q) ); if (!$res || !is_array($res)) return false; $ret = array(); foreach ($res as $key => $value) { $name = $value->name; $taxonomy = $value->taxonomy; $ret["$name@@$taxonomy"] = $value; } return $ret; } public function attrclean_getTermPerDuplicate($term_name, $taxonomy) { global $wpdb; $q = "SELECT a.term_id, a.name, a.slug, b.term_taxonomy_id, b.taxonomy, b.count FROM {$wpdb->terms} AS a " . "LEFT JOIN {$wpdb->term_taxonomy} AS b ON a.term_id = b.term_id " . "WHERE a.name=%s AND b.taxonomy=%s ORDER BY a.slug ASC"; $res = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q, $term_name, $taxonomy) ); if (!$res || !is_array($res)) return false; $ret = array(); foreach ($res as $key => $value) { $ret[$value->term_taxonomy_id] = $value; } return $ret; } public function attrclean_removeDuplicate($first_term, $terms = array(), $debug = false) { if (empty($terms) || !is_array($terms)) return false; $term_id = array(); $term_taxonomy_id = array(); foreach ($terms as $k => $v) { $term_id[] = $v->term_id; $term_taxonomy_id[] = $v->term_taxonomy_id; $taxonomy = $v->taxonomy; } // var_dump('
',$first_term, $term_id, $term_taxonomy_id, $taxonomy,'
'); $ret = array(); $ret['term_relationships'] = $this->attrclean_remove_term_relationships($first_term, $term_taxonomy_id, $debug); $ret['terms'] = $this->attrclean_remove_terms($term_id, $debug); $ret['term_taxonomy'] = $this->attrclean_remove_term_taxonomy($term_taxonomy_id, $taxonomy, $debug); // var_dump('
',$ret,'
'); return $ret; } /** * Create a new term and link it to the given taxonomy. * * Normally each taxonomy should have its own terms, even if names/slugs match. * This function uses wp_unique_term_slug() as a safeguard in case another process * already inserted the same slug for this taxonomy, ensuring uniqueness and avoiding conflicts. * * @param string $termName Term name (e.g. "white") * @param string $taxonomy Taxonomy name (e.g. "pa_color") * @param string $slug Base slug for the term * @return int|false term_taxonomy_id or false on failure */ private function createTermAndLinkTaxonomy($termName, $taxonomy, $slug) { global $wpdb; if (trim($slug) !== '' && trim($termName) !== '') { $term = (object) [ 'taxonomy' => $taxonomy, 'parent' => 0, 'term_id' => 0, ]; $unique_slug = wp_unique_term_slug($slug, $term); $this->db_custom_insert( $wpdb->terms, [ 'values' => ['name' => $termName, 'slug' => $unique_slug], 'format' => ['%s', '%s'] ], true ); $term_id = $wpdb->insert_id; $this->db_custom_insert( $wpdb->term_taxonomy, [ 'values' => ['term_id' => $term_id, 'taxonomy' => $taxonomy], 'format' => ['%d', '%s'] ], true ); return $wpdb->insert_id; } return false; } private function attrclean_remove_term_relationships($first_term, $term_taxonomy_id, $debug = false) { global $wpdb; $idList = (is_array($term_taxonomy_id) && count($term_taxonomy_id) > 0 ? implode(', ', array_map(array($this, 'prepareForInList'), $term_taxonomy_id)) : 0); if ($debug) { $q = "SELECT a.object_id, a.term_taxonomy_id FROM {$wpdb->term_relationships} AS a " . "WHERE a.term_taxonomy_id IN ({$idList}) ORDER BY a.object_id ASC, a.term_taxonomy_id"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $res = $wpdb->get_results($q); if (!$res || !is_array($res)) return false; $ret = array(); $ret[] = 'object_id, term_taxonomy_id'; foreach ($res as $key => $value) { $term_taxonomy_id = $value->term_taxonomy_id; $ret["$term_taxonomy_id"] = $value; } return $ret; } // execution/ update $q = "UPDATE {$wpdb->term_relationships} AS a SET a.term_taxonomy_id = %s " . "WHERE a.term_taxonomy_id IN ({$idList})"; $res = $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q, $first_term) ); $ret = $res; return $ret; } private function attrclean_remove_terms($term_id, $debug = false) { global $wpdb; $idList = (is_array($term_id) && count($term_id) > 0 ? implode(', ', array_map(array($this, 'prepareForInList'), $term_id)) : 0); if ($debug) { $q = "SELECT a.term_id, a.name FROM {$wpdb->terms} AS a " . "WHERE a.term_id IN (%s) ORDER BY a.name ASC"; $res = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q, $idList) ); if (!$res || !is_array($res)) return false; $ret = array(); $ret[] = 'term_id, name'; foreach ($res as $key => $value) { $term_id = $value->term_id; $ret["$term_id"] = $value; } return $ret; } // execution/ update $q = "DELETE FROM a USING {$wpdb->terms} as a WHERE a.term_id IN ({$idList})"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return $wpdb->query($q); } private function attrclean_remove_term_taxonomy($term_taxonomy_id, $taxonomy, $debug = false) { global $wpdb; $idList = ( is_array($term_taxonomy_id) && count($term_taxonomy_id) > 0 ? implode(', ', array_map(array($this, 'prepareForInList'), $term_taxonomy_id)) : 0 ); if ($debug) { $q = "SELECT a.term_id, a.taxonomy, a.term_taxonomy_id FROM {$wpdb->term_taxonomy} AS a " . "WHERE a.term_taxonomy_id IN ({$idList}) AND a.taxonomy = %s ORDER BY a.term_taxonomy_id ASC"; $res = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q, esc_sql($taxonomy)) ); if (!$res || !is_array($res)) return false; $ret = array(); $ret[] = 'term_id, taxonomy, term_taxonomy_id'; foreach ($res as $key => $value) { $term_taxonomy_id = $value->term_taxonomy_id; $ret["$term_taxonomy_id"] = $value; } return $ret; } // execution/ update $q = "DELETE FROM a USING {$wpdb->term_taxonomy} as a WHERE a.term_taxonomy_id IN ({$idList}) AND a.taxonomy = %s"; return $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->prepare($q, esc_sql($taxonomy)) ); } public function load_terms(string $taxonomy) { global $wpdb; $query = "SELECT DISTINCT t.name, t.slug FROM {$wpdb->terms} AS t " . "INNER JOIN {$wpdb->term_taxonomy} as tt ON tt.term_id = t.term_id " . "WHERE tt.taxonomy = %s"; return $wpdb->get_results($wpdb->prepare($query, $taxonomy), OBJECT); } public function db_custom_insert(string $table, array $fields, bool $ignore = false, bool $wp_way = false): int { global $wpdb; if ($wp_way && !$ignore) { $wpdb->insert($table, $fields['values'], $fields['format']); } else { $columns = []; foreach ($fields['values'] as $k => $v) { $columns[] = $k; } $query = "INSERT " . ($ignore ? "IGNORE" : "") . " INTO $table (" . implode(', ', $columns) . ")" . " VALUES (" . implode(', ', $fields['format']) . ");"; $valuesForPrepare = array_values($fields['values']); $wpdb->query($wpdb->prepare($query, $valuesForPrepare)); } return $wpdb->insert_id; } public function prepareForInList($v): string { return "'" . $v . "'"; } public function cleanTaxonomyName(string $value, bool $withPrefix = true, bool $checkSize = true): string { // Sanitize taxonomy names. Slug format (no spaces, lowercase) - uses sanitize_title if ($withPrefix) { $ret = wc_attribute_taxonomy_name($value); // return 'pa_' . $value } else { $ret = wc_sanitize_taxonomy_name($value); // return $value } if ($checkSize) { // limit to 32 characters (database/ table wp_term_taxonomy/ field taxonomy/ is limited to varchar(32) ) if (wp_is_valid_utf8($ret)) { $limit_max = $withPrefix ? 18 : 15; // utf8: 3 + 29/2 if (function_exists('mb_substr')) { $ret = mb_substr($ret, 0, $limit_max); // Ensure generated name doesn't exceed byte length using strlen (because Woocommerce use strlen) while (strlen($ret) > ($withPrefix ? 32 : 28)) { $ret = mb_substr($ret, 0, -1, 'UTF-8'); // remove last character } } } else { $limit_max = $withPrefix ? 32 : 29; // 29 = 32 - strlen('pa_') $ret = substr($ret, 0, $limit_max); } } // IMPORTANT, if not sure do not need sanitize_title! return $ret; } public function cleanValue(string $value): string { // Format Camel Case //$value = trim( preg_replace('/([A-Z])/', ' $1', $value) ); // Clean / from value return trim(preg_replace('/(\/)/', '-', $value)); } public function multi_implode($array, $glue) { $ret = ''; foreach ($array as $item) { if (is_array($item)) { $ret .= $this->multi_implode($item, $glue) . $glue; } else { $ret .= $item . $glue; } } $ret = substr($ret, 0, 0 - strlen($glue)); return $ret; } }