BEAR - WooCommerce Bulk Editor and Products Manager Professional

Benutzerdefinierte Produktattribute in WooCommerce in globale umwandeln

Aus einer CSV-Datei importierte oder von Shopify übernommene Produkte erhalten Size, Color oder Material oft als benutzerdefinierte Produktattribute: schlichten Text, der in jedes Produkt einzeln getippt wurde, statt Werten eines gemeinsamen Attributs. Sie erscheinen auf der Produktseite, aber weder WooCommerce noch BEAR können sie filtern, umbenennen oder produktübergreifend bearbeiten. Die Lösung ist, das benutzerdefinierte Produktattribut in ein globales umzuwandeln. Im Folgenden steht, wie Sie die beiden Arten erkennen, was BEAR mit benutzerdefinierten Attributen kann und was nicht, wie Sie sie in Masse umwandeln, ohne Variationen zu verlieren, und wie Sie die Reste löschen.

Globale und benutzerdefinierte (lokale) Attribute in WooCommerce

WooCommerce kennt zwei Arten von Produktattributen, und sie werden unterschiedlich gespeichert.

  • Globale Attribute werden einmal unter Produkte → Attribute erstellt. Jedes ist eine Taxonomie mit dem Namen pa_ plus seinem Slug (pa_size, pa_color), und seine Werte sind Begriffe dieser Taxonomie, die sich alle Produkte teilen, die sie verwenden. Benennen Sie den Begriff XL einmal um, und jedes Produkt zeigt den neuen Namen.
  • Benutzerdefinierte Attribute, auch lokale oder Text-Attribute genannt, existieren nur innerhalb eines einzelnen Produkts. Name und Werte sind Text, der im Meta-Feld _product_attributes dieses Produkts gespeichert wird, zum Beispiel S | M | L. Zwei Produkte mit einem benutzerdefinierten Attribut namens Size teilen nur die Schreibweise.

Im Produkteditor zeigt ein globales Attribut im Tab Attribute seinen Namen als festes Label und seine Werte als Auswahl von Begriffen. Ein benutzerdefiniertes Attribut zeigt den Namen als bearbeitbares Textfeld und die Werte in einem Textfeld, getrennt durch |. Die WooCommerce-Anleitung zu Produkt-Taxonomien und Attributen zeigt beide.

Importe sind die übliche Quelle benutzerdefinierter Attribute. Der WooCommerce-Produktimporter erstellt ein globales Attribut nur dann, wenn die Spalte Attribute 1 global (2, 3 ...) den Wert 1 hat; bei 0 oder ohne die Spalte erhält jedes Produkt sein eigenes benutzerdefiniertes Attribut, selbst wenn bereits ein globales Attribut mit demselben Namen existiert. Deshalb kann ein Shop zwei „Size“-Attribute an einem Produkt haben. Migrationswerkzeuge von Shopify und anderen Plattformen machen oft dasselbe. Damit es nicht wieder passiert, setzen Sie die Spalte global in Ihren Importdateien für jedes Attribut auf 1.

Was BEAR mit benutzerdefinierten Attributen kann und was nicht

BEAR bearbeitet Attribute als Taxonomien. Jedes globale Attribut ist eine Spalte, die Sie im Einstellungen-Tab einschalten können, ein Filter im Filter-Tab, eine Zeile im Massenbearbeitung-Tab und eine Auswahl in den Erweiterten Massenvorgängen für Variationen (Variations Advanced Bulk Operations). Benutzerdefinierte Attribute sind nichts davon, denn es gibt keine Liste von ihnen: Jedes Produkt hält seinen eigenen, privaten Text. In der Praxis heißt das:

  • Keine Spalte und kein Filter für ein benutzerdefiniertes Attribut, Sie können also nicht „alle Produkte mit Material = Cotton“ auflisten.
  • Keine Massenänderung ihrer Namen, Werte oder Sichtbarkeit und kein Massenlöschen.
  • Variationen, die auf einem benutzerdefinierten Attribut beruhen, lassen sich weiterhin in der Tabelle bearbeiten (Preise, Bestand, SKUs), aber nicht nach Attributwert auswählen. Das Filtern oder Bepreisen von Variationen nach Größe oder Farbe, wie unter Variationen nach Attribut filtern beschrieben, erfordert ein globales Attribut.
  • Das Erzeugen von Variationen aus Attributkombinationen, beschrieben unter Variationen in Masse erstellen, verwendet nur globale Attribute. Fügen Sie Produkten, deren Variationen ein benutzerdefiniertes Size verwenden, ein globales Size hinzu, erhalten sie ein zweites, separates Size-Feld statt eines neuen Wertes im bestehenden.

Dieselben Grenzen gelten auch an anderer Stelle in WooCommerce: Attributfilter auf der Shop-Seite und Attribut-Archivseiten brauchen ebenfalls globale Attribute. Allein aus diesen Gründen lohnt sich die Umwandlung.

Benutzerdefinierte Produktattribute in WooCommerce in Masse in globale umwandeln

Produkte ohne Variationen: zwei Läufe

Ist das benutzerdefinierte Attribut nur Information (Material, Brand, Pattern) und gehört sein Wert zu etwas, das BEAR filtern kann, etwa einer Kategorie, kann BEAR den größten Teil der Arbeit erledigen. Der benutzerdefinierte Wert selbst lässt sich nicht filtern, deshalb braucht dieser Weg diese Verknüpfung.

  1. Erstellen Sie das globale Attribut und seine Begriffe unter Produkte → Attribute.
  2. Wählen Sie die Produkte in BEAR aus, zum Beispiel nach Kategorie, und fügen Sie das globale Attribut mit dem richtigen Begriff hinzu, wie unter Attribute in Masse zu Produkten hinzufügen beschrieben. Produkte mit unterschiedlichen Werten brauchen einen Lauf pro Wert, jeweils mit eigenem Filter.
  3. Entfernen Sie das alte benutzerdefinierte Attribut mit dem zweiten Codebeispiel unten.

Unterscheiden sich die Werte von Produkt zu Produkt oder beruhen Variationen auf dem Attribut, verwenden Sie stattdessen den Umwandlungscode: Er liest die eigenen Werte jedes Produkts.

Variationen oder unterschiedliche Werte: an Ort und Stelle umwandeln

Eine Variation verweist nicht auf das Attribut; sie speichert ihren Wert unter einem Schlüssel. Bei einem benutzerdefinierten Size lautet der Schlüssel attribute_size und der Wert ist der Text, etwa Small. Bei einem globalen Size lautet der Schlüssel attribute_pa_size und der Wert ist der Slug des Begriffs, small. Die Umwandlung bedeutet also, das übergeordnete Produkt und jede Variation neu zu schreiben. Die Variationen zu löschen und neu zu erzeugen funktioniert ebenfalls, aber die neuen Variationen erhalten neue IDs, und ihre Preise, Bestände, SKUs und Bilder müssen erneut festgelegt werden.

Der folgende Code wandelt ein benutzerdefiniertes Attribut an Ort und Stelle um. Für jedes Produkt, das es hat, erstellt der Code fehlende Begriffe aus den Werten des Produkts, ersetzt das benutzerdefinierte Attribut an derselben Position und mit denselben Markierungen Sichtbar auf der Produktseite (Visible on the product page) und Für Varianten verwendet (Used for variations) durch das globale, überträgt die Standardwerte des Formulars und schreibt den Wert jeder Variation neu. Variations-IDs, Preise, Bestand und SKUs bleiben unverändert.

/*
 * 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
    ));
});

So führen Sie ihn aus:

  1. Erstellen Sie eine vollständige Datenbanksicherung. Der Code ändert Produkte direkt; der Verlauf von BEAR erfasst ihn nicht und kann ihn nicht rückgängig machen.
  2. Erstellen Sie das globale Attribut unter Produkte → Attribute mit demselben Namen und notieren Sie seinen Slug. Die Begriffe erstellt der Code.
  3. Setzen Sie $local_name auf den Namen des benutzerdefinierten Attributs genau so, wie er an den Produkten steht, und $taxonomy auf pa_ plus den Slug.
  4. Öffnen Sie, als Administrator angemeldet, /wp-admin/?convert_local_attribute=1. Jede Seite verarbeitet 50 Produkte und zeigt einen Link zur nächsten; folgen Sie ihm, bis die Meldung sagt, dass der Vorgang abgeschlossen ist.
  5. Prüfen Sie einige Produkte, einfache und variable, im Frontend und im Produkteditor. Entfernen Sie danach den Code.

Produkte, die bereits sowohl das benutzerdefinierte als auch das globale Attribut haben, werden übersprungen und gezählt, denn das Zusammenführen beider erfordert eine Entscheidung, welche Werte richtig sind. Korrigieren Sie diese von Hand oder entfernen Sie das benutzerdefinierte Duplikat mit dem nächsten Beispiel. Bei WPML oder Polylang tragen übersetzte Produkte eigene Attributnamen; führen Sie die Umwandlung für jeden Namen aus und übersetzen Sie die neuen Begriffe. Bei mehreren Attributen (Size und Color) führen Sie sie einmal pro Attribut aus.

Übrig gebliebene benutzerdefinierte Attribute in Masse löschen

Sind die Variationen eines Produkts auf globale Attribute umgebaut oder hat ein Import Duplikate erzeugt, bleiben die alten benutzerdefinierten Attribute an den Produkten. Dieser Code entfernt die genannten benutzerdefinierten Attribute von allen Produkten. Globale Attribute mit demselben Namen bleiben unberührt, und ein benutzerdefiniertes Attribut, auf dem bestehende Variationen noch beruhen, bleibt erhalten, sodass keine Variation ihre Option verliert.

/*
 * 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));
});

Sichern Sie zuerst, listen Sie die Namen in $names auf, öffnen Sie dann als Administrator /wp-admin/?delete_local_attributes=1 und folgen Sie den Links wie oben. Hat ein Produkt nach dem Lauf noch das benutzerdefinierte Attribut, beruhen Variationen darauf; wandeln Sie es um oder löschen Sie zuerst diese Variationen. Um stattdessen ein globales Attribut von Produkten zu entfernen, siehe ein Attribut vollständig von einem Produkt lösen.

Nach der Umwandlung

  • Öffnen Sie ein umgewandeltes variables Produkt im Editor: Das Attribut erscheint als Begriffsauswahl, und jede Variation zeigt ihren Wert statt Beliebig (Any).
  • Schalten Sie in BEAR die Spalte des Attributs im Einstellungen-Tab ein und filtern Sie nach einem seiner Begriffe: Die umgewandelten Produkte erscheinen.
  • Benennen Sie Werte einmal um oder führen Sie sie zusammen, unter Produkte → Attribute → Begriffe konfigurieren (Configure terms), statt an jedem Produkt.
  • Variationstitel werden beim Speichern jeder Variation durch den Code neu aufgebaut, nun aus den Namen der Begriffe; Variationstitel mit Attributen erklärt, wie sie aufgebaut werden.
  • Leeren Sie den Seiten-Cache, falls der Shop noch die alten Attributtabellen zeigt.

Wandelt sich ein Produkt nicht so um, wie Sie es erwarten, posten Sie im BEAR-Support-Forum sein Attribut-Setup und die Meldung, die der Code ausgegeben hat.