Convertir des attributs personnalisés en attributs globaux dans WooCommerce
Les produits importés depuis un fichier CSV ou migrés depuis Shopify reçoivent souvent leur Taille, Couleur ou Matière sous forme d'attributs de produit personnalisés : du texte simple saisi dans chaque produit, au lieu de valeurs d'un attribut partagé. Ils s'affichent sur la page du produit, mais ni WooCommerce ni BEAR ne peuvent les filtrer, les renommer ou les modifier d'un produit à l'autre. La solution consiste à convertir l'attribut de produit personnalisé en attribut global. Vous verrez ci-dessous comment reconnaître les deux types, ce que BEAR peut et ne peut pas faire avec les attributs personnalisés, comment les convertir en masse sans perdre les variations, et comment supprimer les restes.
Attributs globaux et personnalisés (locaux) dans WooCommerce
WooCommerce a deux types d'attributs de produit, et ils sont stockés de façons différentes.
- Les attributs globaux sont créés une fois sous Produits → Attributs. Chacun est une taxonomie nommée
pa_plus son slug (pa_size,pa_color), et ses valeurs sont des termes de cette taxonomie, partagés par tous les produits qui les utilisent. Renommez le terme XL une fois et tous les produits affichent le nouveau nom. - Les attributs personnalisés, aussi appelés attributs locaux ou textuels, n'existent qu'à l'intérieur d'un seul produit. Le nom et les valeurs sont du texte enregistré dans la meta
_product_attributesde ce produit, par exempleS | M | L. Deux produits dotés d'un attribut personnalisé nommé Taille ne partagent rien d'autre que l'orthographe.
Dans l'éditeur de produit, sous l'onglet Attributs, un attribut global affiche son nom comme une étiquette fixe et ses valeurs comme un sélecteur de termes. Un attribut personnalisé affiche son nom dans un champ de texte modifiable et ses valeurs dans une zone de texte séparées par |. Le guide WooCommerce sur les taxonomies et attributs de produit montre les deux.
Les imports sont la source habituelle des attributs personnalisés. L'importateur de produits de WooCommerce ne crée un attribut global que si la colonne Attribute 1 global (2, 3...) vaut 1 ; avec 0 ou sans cette colonne, chaque produit reçoit son propre attribut personnalisé, même lorsqu'un attribut global du même nom existe déjà. C'est pourquoi une boutique peut se retrouver avec deux attributs « Taille » sur un même produit. Les outils de migration depuis Shopify et d'autres plateformes font souvent la même chose. Pour éviter que cela se reproduise, mettez la colonne global à 1 pour chaque attribut dans vos fichiers d'import.
Ce que BEAR peut et ne peut pas faire avec les attributs personnalisés
BEAR modifie les attributs comme des taxonomies. Chaque attribut global est une colonne que vous pouvez activer dans l'onglet Paramètres (Settings), un filtre dans l'onglet Filtres (Filters), une ligne dans l'onglet Modification groupée (Bulk Edit) et un choix dans Opérations groupées avancées sur les variations (Variations Advanced Bulk Operations). Les attributs personnalisés ne sont rien de tout cela, car il n'en existe aucune liste : chaque produit détient son propre texte privé. En pratique :
- Pas de colonne ni de filtre pour un attribut personnalisé, donc vous ne pouvez pas lister « tous les produits avec Matière = Coton ».
- Pas de modification en masse de leurs noms, valeurs ou visibilité, et pas de suppression en masse.
- Les variations construites sur un attribut personnalisé peuvent toujours être modifiées dans le tableau (prix, stock, UGS), mais elles ne peuvent pas être sélectionnées par valeur d'attribut. Filtrer ou fixer le prix des variations selon la taille ou la couleur, comme décrit dans le filtrage des variations par attribut, demande un attribut global.
- La génération de variations à partir de combinaisons d'attributs, décrite dans la création de variations en masse, n'utilise que des attributs globaux. Ajouter une Taille globale à des produits dont les variations utilisent une Taille personnalisée leur donne une seconde case Taille distincte, et non une nouvelle valeur dans la case existante.
Les mêmes limites s'appliquent ailleurs dans WooCommerce : les filtres d'attributs de la page boutique et les pages d'archive d'attributs demandent eux aussi des attributs globaux. Ces seules raisons justifient la conversion.
Convertir en masse les attributs de produit personnalisés en attributs globaux
Produits sans variations : deux exécutions
Lorsque l'attribut personnalisé n'est qu'une information (Matière, Marque, Motif) et que sa valeur va avec quelque chose que BEAR sait filtrer, comme une catégorie, BEAR peut faire l'essentiel du travail. La valeur personnalisée elle-même ne peut pas être filtrée, donc cette voie a besoin de ce lien.
- Créez l'attribut global et ses termes sous Produits → Attributs.
- Sélectionnez les produits dans BEAR, par exemple par catégorie, et ajoutez l'attribut global avec le bon terme, comme décrit dans l'ajout d'attributs aux produits en masse. Les produits ayant des valeurs différentes demandent une exécution par valeur, chacune avec son propre filtre.
- Supprimez l'ancien attribut personnalisé avec le second exemple de code ci-dessous.
Si les valeurs diffèrent d'un produit à l'autre, ou si des variations sont construites sur l'attribut, utilisez plutôt le code de conversion : il lit les valeurs propres à chaque produit.
Variations ou valeurs différentes : convertir sur place
Une variation ne pointe pas vers l'attribut ; elle stocke sa valeur sous une clé. Pour une Taille personnalisée, la clé est attribute_size et la valeur est le texte, par exemple Small. Pour une Taille globale, la clé est attribute_pa_size et la valeur est le slug du terme, small. Convertir signifie donc réécrire le produit parent et chaque variation. Supprimer les variations et les générer de nouveau fonctionne aussi, mais les nouvelles variations reçoivent de nouveaux ID, et leurs prix, stock, UGS et images doivent être redéfinis.
Le code ci-dessous convertit un attribut personnalisé sur place. Pour chaque produit qui le possède, le code crée les termes manquants à partir des valeurs du produit, remplace l'attribut personnalisé par l'attribut global à la même position et avec les mêmes indicateurs Visible sur la page produit (Visible on the product page) et Utilisé pour les variations (Used for variations), déplace les valeurs par défaut du formulaire et réécrit la valeur de chaque variation. Les ID des variations, leurs prix, leur stock et leurs UGS restent tels quels.
/*
* Convert one custom (local) product attribute into a global attribute,
* variations included. Child theme functions.php or a code snippets plugin.
* Create the global attribute in Products > Attributes first, back up the
* database, then open /wp-admin/?convert_local_attribute=1 as an administrator.
* Remove the code when it reports that it has finished.
*/
add_action('admin_init', function () {
if (empty($_GET['convert_local_attribute']) || !current_user_can('manage_woocommerce')) {
return;
}
$local_name = 'Size'; // the custom attribute name as typed on the products
$taxonomy = 'pa_size'; // the global attribute: pa_ + its slug
$per_page = 50;
if (!taxonomy_exists($taxonomy)) {
wp_die('Create the global attribute first: ' . esc_html($taxonomy));
}
$local_key = sanitize_title($local_name);
$page = max(1, (int) $_GET['convert_local_attribute']);
$converted = 0;
$skipped = 0;
// Finds or creates the term for a value, returns the term object.
$get_term = function ($value) use ($taxonomy) {
$found = term_exists($value, $taxonomy);
if (!$found) {
$found = wp_insert_term($value, $taxonomy);
}
return is_wp_error($found) ? null : get_term((int) $found['term_id'], $taxonomy);
};
$result = wc_get_products(array(
'status' => array('publish', 'private', 'draft', 'pending', 'future'),
'limit' => $per_page,
'page' => $page,
'orderby' => 'ID',
'order' => 'ASC',
'paginate' => true,
));
foreach ($result->products as $product) {
$attributes = $product->get_attributes();
if (!isset($attributes[$local_key]) || $attributes[$local_key]->is_taxonomy()) {
continue;
}
if (isset($attributes[$taxonomy])) {
$skipped++; // has both: fix this product by hand
continue;
}
$local = $attributes[$local_key];
$term_ids = array();
foreach ($local->get_options() as $value) {
$term = $get_term($value);
if ($term) {
$term_ids[] = (int) $term->term_id;
}
}
$global = new WC_Product_Attribute();
$global->set_id(wc_attribute_taxonomy_id_by_name($taxonomy));
$global->set_name($taxonomy);
$global->set_options($term_ids);
$global->set_position($local->get_position());
$global->set_visible($local->get_visible());
$global->set_variation($local->get_variation());
unset($attributes[$local_key]);
$attributes[$taxonomy] = $global;
$product->set_attributes($attributes);
// The default form values of a variable product use the same keys.
$defaults = $product->get_default_attributes();
if (isset($defaults[$local_key])) {
$term = $get_term($defaults[$local_key]);
unset($defaults[$local_key]);
if ($term) {
$defaults[$taxonomy] = $term->slug;
}
$product->set_default_attributes($defaults);
}
$product->save();
// Every variation stores its own value: text before, term slug now.
foreach ($product->is_type('variable') ? $product->get_children() : array() as $variation_id) {
$variation = wc_get_product($variation_id);
$values = $variation ? $variation->get_attributes() : array();
if (!array_key_exists($local_key, $values)) {
continue;
}
$value = $values[$local_key];
unset($values[$local_key]);
$term = ('' === $value) ? null : $get_term($value);
$values[$taxonomy] = $term ? $term->slug : ''; // empty = "Any"
$variation->set_attributes($values);
$variation->save();
}
$converted++;
}
$next = $page < $result->max_num_pages
? '<a href="' . esc_url(admin_url('?convert_local_attribute=' . ($page + 1))) . '">Next page</a>'
: 'Finished. Remove this code.';
wp_die(sprintf(
'Page %d of %d: %d products converted, %d skipped (they already have %s). %s',
$page,
max(1, $result->max_num_pages),
$converted,
$skipped,
esc_html($taxonomy),
$next
));
});
Mode d'emploi :
- Faites une sauvegarde complète de la base de données. Le code modifie directement les produits ; l'Historique (History) de BEAR ne l'enregistre pas et ne peut pas l'annuler.
- Créez l'attribut global sous Produits → Attributs, avec le même nom, et notez son slug. Les termes sont créés par le code.
- Réglez
$local_namesur le nom de l'attribut personnalisé exactement tel qu'il figure sur les produits, et$taxonomysurpa_plus le slug. - Connecté en tant qu'administrateur, ouvrez
/wp-admin/?convert_local_attribute=1. Chaque page traite 50 produits et affiche un lien vers la suivante ; suivez-le jusqu'à ce que le message indique que c'est terminé. - Vérifiez quelques produits, simples et variables, dans la boutique et dans l'éditeur de produit. Retirez ensuite le code.
Les produits qui ont déjà à la fois l'attribut personnalisé et l'attribut global sont ignorés et comptés, car les fusionner demande de décider quelles valeurs sont les bonnes. Corrigez-les à la main ou supprimez le doublon personnalisé avec l'exemple suivant. Avec WPML ou Polylang, les produits traduits portent leurs propres noms d'attributs ; lancez la conversion pour chaque nom et traduisez les nouveaux termes. Pour plusieurs attributs (Taille et Couleur), lancez-la une fois par attribut.
Supprimer en masse les attributs personnalisés restants
Une fois les variations d'un produit reconstruites sur des attributs globaux, ou après qu'un import a créé des doublons, les anciens attributs personnalisés restent sur les produits. Ce code supprime les attributs personnalisés nommés de tous les produits. Les attributs globaux du même nom ne sont pas touchés, et un attribut personnalisé sur lequel des variations existantes sont encore construites est conservé, de sorte qu'aucune variation ne perd son option.
/*
* Remove custom (local) attributes by name from all products.
* Attributes that existing variations are built on are kept.
* Child theme functions.php or a code snippets plugin. Back up the database,
* then open /wp-admin/?delete_local_attributes=1 as an administrator.
*/
add_action('admin_init', function () {
if (empty($_GET['delete_local_attributes']) || !current_user_can('manage_woocommerce')) {
return;
}
$names = array_map('sanitize_title', array('Size', 'Color')); // custom attribute names
$page = max(1, (int) $_GET['delete_local_attributes']);
$cleaned = 0;
$result = wc_get_products(array(
'status' => array('publish', 'private', 'draft', 'pending', 'future'),
'limit' => 50,
'page' => $page,
'orderby' => 'ID',
'order' => 'ASC',
'paginate' => true,
));
foreach ($result->products as $product) {
$attributes = $product->get_attributes();
$changed = false;
foreach ($attributes as $key => $attribute) {
if ($attribute->is_taxonomy() || !in_array(sanitize_title($attribute->get_name()), $names, true)) {
continue;
}
if ($attribute->get_variation() && $product->is_type('variable') && $product->get_children()) {
continue; // variations still use it
}
unset($attributes[$key]);
$changed = true;
}
if ($changed) {
$product->set_attributes($attributes);
$product->save();
$cleaned++;
}
}
$next = $page < $result->max_num_pages
? '<a href="' . esc_url(admin_url('?delete_local_attributes=' . ($page + 1))) . '">Next page</a>'
: 'Finished. Remove this code.';
wp_die(sprintf('Page %d of %d: %d products cleaned. %s', $page, max(1, $result->max_num_pages), $cleaned, $next));
});
Sauvegardez d'abord, listez les noms dans $names, puis ouvrez /wp-admin/?delete_local_attributes=1 en tant qu'administrateur et suivez les liens comme ci-dessus. Un produit qui a encore l'attribut personnalisé après l'exécution a des variations construites dessus ; convertissez-le, ou supprimez d'abord ces variations. Pour retirer plutôt un attribut global des produits, voir comment détacher complètement un attribut.
Après la conversion
- Ouvrez un produit variable converti dans l'éditeur : l'attribut s'affiche comme un sélecteur de termes, et chaque variation montre sa valeur au lieu de N'importe lequel (Any).
- Dans BEAR, activez la colonne de l'attribut dans l'onglet Paramètres (Settings) et filtrez par l'un de ses termes : les produits convertis apparaissent.
- Renommez ou fusionnez les valeurs une seule fois, sous Produits → Attributs → Configurer les termes (Configure terms), plutôt que sur chaque produit.
- Les titres des variations sont reconstruits lorsque le code enregistre chaque variation, désormais à partir des noms des termes ; les titres de variations avec attributs explique comment ils sont construits.
- Videz le cache des pages si la boutique affiche encore les anciens tableaux d'attributs.
Si un produit ne se convertit pas comme prévu, publiez sa configuration d'attributs et le message affiché par le code sur le forum d'assistance de BEAR.