Google Vehicle Listings

Ivan Belovari

Google Vehicle Listings ist eine von Google angebotene Plattform bzw. Funktion, die es Autohändlern ermöglicht, ihre Fahrzeugangebote direkt in den Google-Suchergebnissen und auf Google Maps zu präsentieren. Das Ziel ist es, die Sichtbarkeit von Fahrzeugen im Internet zu erhöhen und potenziellen Kunden eine einfache Möglichkeit zu bieten, nach Autos zu suchen und diese zu vergleichen.

Inhaltsverzeichnis

Erstellung von einem Google Vehicle Listings Konto

Diese Anleitung beschreibt die Schritte zur Einrichtung eines Google Vehicle Listings-Kontos für ein Autohaus, einschließlich der Verwaltung von Fahrzeugdaten und der Einhaltung von Google-Richtlinien.

Voraussetzungen

Bevor du dein Google Vehicle Listings-Konto einrichtest, stelle sicher, dass folgende Voraussetzungen erfüllt sind:

  • Google-Konto: Du benötigst ein aktives Google-Konto, um dich bei https://carcenter.webapps.google.com/ anzumelden.

  • Google My Business: Dein Autohaus sollte in Google My Business registriert sein und als „Car Dealership“ kategorisiert werden. Dabei ist keine direkte Eingabe von Zugangsdaten zu Google My Business erforderlich.

  • Place ID: Stelle sicher, dass du die richtige Place ID deines Autohauses hast. Diese wird benötigt, um dein Unternehmen mit Google Vehicle Listings zu verknüpfen. Wie du die Place ID findest, erfährst du im Abschnitt „Plugin Backend“.

  • Geschäftsdaten: Alle relevanten Informationen zu deinem Autohaus und deinem Fahrzeugbestand sollten bereitliegen, da du diese während des Registrierungsprozesses eingeben musst.

  • Richtlinienverständnis: Mache dich mit den Richtlinien und Anforderungen von Google Vehicle Listings vertraut, um sicherzustellen, dass du die Daten korrekt und vollständig eingibst.

Konto erstellen

  • Registrierung:
    Besuche https://carcenter.webapps.google.com/ und melde dich mit deinem bestehenden Google-Konto an oder erstelle ein neues.

  • Daten eingeben:
    Nach der Anmeldung wirst du aufgefordert, alle erforderlichen Daten zu deinem Autohaus und den Fahrzeugen vollständig und korrekt einzugeben. Achte darauf, dass du alle Felder ordentlich ausfüllst und die vorgegebenen Richtlinien beachtest.
  •  

Übermittlung von Daten von Rosebud an GVL

Das von mir entwickelte Plugin dient als Schnittstelle zwischen unserem internen Rosebud-System, in dem Fahrzeugdaten verwaltet werden, und Google Vehicle Listings (GVL). Es sorgt dafür, dass die aktuellsten Fahrzeuginformationen automatisch und reibungslos an GVL übermittelt werden.

Wie funktioniert das Plugin?

  1. Datenextraktion aus Rosebud:
    Das Plugin greift auf die Fahrzeugdaten in unserem Rosebud-System zu. Diese Daten umfassen alle relevanten Informationen zu Fahrzeugen wie Modell, Baujahr, Preis und Bilder.

  2. Datenaufbereitung:
    Nachdem das Plugin die Daten aus Rosebud abgerufen hat, bereitet es diese in dem Format auf, das von Google Vehicle Listings benötigt wird. Dies kann bedeuten, die Daten in eine bestimmte CSV Struktur zu bringen und sie an die Vorgaben von GVL anzupassen.

  3. Übertragung via FTP:
    Die aufbereiteten Daten werden anschließen automatisch in einen festgelegten FTP-Ordner hochgeladen. Dieser Ordner dient als Übergabepunkt für Google Vehicle Listings. GVL greift regelmäßig auf diesen Ordner zu, um die neuesten Daten abzurufen und die Fahrzeuglisten entsprechend zu aktualisieren.

Das Plugin verbindet unser internes Rosebud-System mit Google Vehicle Listings durch einen automatisierten, täglichen Datentransfer um 8 Uhr morgens. Es extrahiert, bereitet auf und überträgt die Fahrzeugdaten zuverlässig über einen FTP-Ordner an GVL. Dadurch bleiben die Fahrzeuglisten stets aktuell, was die Sichtbarkeit unserer Angebote erhöht und den Prozess für uns und für potenzielle Käufer optimiert.

Plugin Backend

Google Place IDs

Die Place IDs sind für die Vehicle Listings unbedingt erforderlich,
herausfinden kann man sie über
https://developers.google.com/maps/documentation/javascript/examples/places-placeid-finder

Die Standorte werden automatisch aus dem Rosebud System importiert, es ist möglich,
für mehrere Standorte die gleiche Place ID zu benutzen

Stand Januar 2024 sind werden nur die Meilen als Einheit für den Kilometerstand angenommen

SFTP Server Daten:
Findet man unter https://carcenter.webapps.google.com/feed > Feed Credentials

Automatisches Hochladen zum FTP

Der automatische Upload funktioniert über 
ein Cronjob.
Diese funktion kann hier eingeschaltet werden.

Ziemlich selbsterklärend.

Die Struktur der CSV Datei

Die einzelnen Felder im CSV-Header für Google Vehicle Listings folgen den Vorgaben der Feed-Spezifikation von Google Vehicle Listings. Im Folgenden erkläre ich jedes Feld aus deinem Header, was es bedeutet und in welchem Format bzw. Datentyp es in der Regel erwartet wird. Da die Spezifikationen Änderungen unterliegen können, dienen diese Angaben als allgemeine Richtlinie basierend auf den bekannten Anforderungen. Für die aktuellsten Details sollte immer die offizielle Dokumentation konsultiert werden.

Hier ist eine tabellarische Darstellung der Felder, ihrer Bedeutung und des erwarteten Formats gemäß den Spezifikationen von Google Vehicle Listings:

Attribut Erforderlich Datentyp Beschreibung Anmerkungen
vin Erforderlich String Fahrzeugidentifikationsnummer, eine eindeutige alphanumerische Kennzeichnung für das jeweilige Fahrzeug.
Hinweis: Muss dem NHTSA-Standard entsprechen.
Wird auf Google angezeigt.
id Optional String Eine interne Artikelnummer, die für jedes Fahrzeug eindeutig ist. Nicht bei Google angezeigt, dient zur Verbesserung der Datenqualität.
store_code Erforderlich String Eine interne Kennung, die für jeden Autohändler eindeutig ist. Nicht bei Google angezeigt, dient zur Verbesserung der Datenqualität.
place_id Optional String Die Orts-ID des Unternehmensprofils des Autohauses. Nicht bei Google angezeigt, dient zur Verbesserung der Datenqualität.
maps_url Optional URL Google Maps-URL des Unternehmensprofils des Autohauses. Nicht bei Google angezeigt, dient zur Verbesserung der Datenqualität.
dealership_name Erforderlich String Vollständiger Name des Autohauses. Wird auf Google angezeigt.
dealership_address Erforderlich String Vollständige Adresse des Autohauses, inkl. Stadt, Postleitzahl und Land. Wird auf Google angezeigt.
tracking_phone_number Optional Telefonnummer Für die Fahrzeugeinträge verwendete Telefonnummer. Wird auf Google angezeigt.
image_link Empfohlen URL Das Hauptbild des Fahrzeugs. Wird auf Google angezeigt.
additional_image_link Empfohlen URL, wiederholt Zusätzliche Bilder für das Fahrzeug. Wird auf Google angezeigt.
link Empfohlen URL Link zur Fahrzeugdetailseite (VDP). Wird auf Google angezeigt.
date_in_stock Optional Datum Datum, an dem das Fahrzeug zum ersten Mal zum Verkauf stand (ISO 8601, z. B. 2020-01-31). Nicht bei Google angezeigt, dient zur Verbesserung der Datenqualität.
price Erforderlich Preis Der endgültige Sonderangebotspreis für das Fahrzeug (ISO 4217, z. B. 10499 USD). Wird auf Google angezeigt.
vehicle_msrp Empfohlen Preis UVP für ein neues Fahrzeug in der aktuellen Konfiguration (ISO 4217). Wird auf Google angezeigt.
condition Erforderlich String Zustand des Fahrzeugs: neu oder gebraucht.
Erlaubte Werte: n/new (Neu), u/used (Gebraucht).
Wird auf Google angezeigt.
certified_pre_owned Optional Boolesch Gibt an, ob das Fahrzeug zertifiziert gebraucht ist.
Erlaubte Werte: 1/y/yes, 0/n/no.
Wird auf Google angezeigt (nur für Gebrauchtwagen).
make Erforderlich String Hersteller des Fahrzeugs, z. B. Toyota. Wird auf Google angezeigt.
model Erforderlich String Modell des Fahrzeugs, z. B. Civic. Wird auf Google angezeigt.
trim Erforderlich, sofern verfügbar String Ausstattungsvariante des Modells, z. B. S, SV oder SL. Wird auf Google angezeigt.
year Erforderlich Int Modelljahr im Format YYYY. Wird auf Google angezeigt.
mileage Erforderlich für gebrauchte Fahrzeuge Ganzzahl + Einheit Laufleistung des Fahrzeugs (z. B. 2333 oder 52,777 miles). Wird auf Google angezeigt. Standard: Meilen.
exterior_color Empfohlen String Vom OEM angegebene Außenfarbe, z. B. White, Platinum. Wird auf Google angezeigt.
				
					<?php

/**
 * Plugin Name: Autoactiva Vehicle Feed
 * Plugin URI:  https://autoactiva.de/
 * Description: Erstellt einen WooCommerce-Fahrzeug-Export als CSV und lädt ihn optional per SFTP (cURL) hoch. 
 *              Zusätzlich können Google Place IDs für verschiedene Autohäuser im Backend verwaltet werden.
 * Version:     1.3.0
 * Author:      Ivan Belovari
 * Author URI:  https://autoactiva.de/
 * License:     GPL2
 * Text Domain: autoactiva-vehicle-feed
 */

if (! defined('ABSPATH')) {
    exit; // Kein direkter Zugriff
}

/**
 * ==========================
 * 1) Einstellungen registrieren
 * ==========================
 */
function autoactiva_vehicle_feed_register_settings()
{
    // Bisherige SFTP-Optionen
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_sftp_server');
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_sftp_port');
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_sftp_user');
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_sftp_password');
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_auto_upload_enabled');

    // Option für Google Place IDs
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_place_ids');

    // Neue Option für Maßeinheit (Kilometer oder Meilen) mit Standard "miles"
    register_setting('autoactiva_vehicle_feed_settings_group', 'autoactiva_vehicle_feed_unit', array(
        'default' => 'miles'
    ));
}
add_action('admin_init', 'autoactiva_vehicle_feed_register_settings');

/**
 * ==========================
 * 2) Menüpunkt unter "Einstellungen"
 * ==========================
 */
function autoactiva_vehicle_feed_add_admin_menu()
{
    add_options_page(
        'Autoactiva Vehicle Feed',
        'Autoactiva Vehicle Feed',
        'manage_options',
        'autoactiva-vehicle-feed',
        'autoactiva_vehicle_feed_settings_page'
    );
}
add_action('admin_menu', 'autoactiva_vehicle_feed_add_admin_menu');

/**
 * ==========================
 * 3) Rendering der Einstellungsseite
 * ==========================
 */
function autoactiva_vehicle_feed_settings_page()
{
    if (! current_user_can('manage_options')) {
        return;
    }

    // Hole den aktuellen Wert der Checkbox (Default 'no')
    $auto_upload_enabled = get_option('autoactiva_vehicle_feed_auto_upload_enabled', 'no');
    // Hole die bevorzugte Einheit (Default 'miles')
    $unit = get_option('autoactiva_vehicle_feed_unit', 'miles');

    // Aktion auswerten (CSV oder CSV + SFTP)
    if (isset($_POST['autoactiva_action']) && check_admin_referer('autoactiva_vehicle_feed_action', 'autoactiva_vehicle_feed_nonce')) {
        $action_type = sanitize_text_field($_POST['autoactiva_action']);

        if ($action_type === 'create_csv') {
            // Nur CSV
            $export_result = autoactiva_vehicle_feed_run_export(false);
            if ($export_result === true) {
                echo '<div class="notice notice-success"><p>CSV wurde erfolgreich erstellt.</p></div>';
            } else {
                echo '<div class="notice notice-error"><p>Fehler beim Erstellen der CSV: ' . esc_html($export_result) . '</p></div>';
            }
        } elseif ($action_type === 'export_vehicle_listings') {
            // CSV + SFTP
            $export_result = autoactiva_vehicle_feed_run_export(true);
            if ($export_result === true) {
                echo '<div class="notice notice-success"><p>Export zu Vehicle Listings (SFTP) wurde erfolgreich durchgeführt.</p></div>';
            } else {
                echo '<div class="notice notice-error"><p>Fehler beim Export zu Vehicle Listings: ' . esc_html($export_result) . '</p></div>';
            }
        }
    }
    ?>
    <div class="wrap">
        <h1>Autoactiva Vehicle Feed – Einstellungen</h1>

        <form method="post" action="options.php">
            <?php
            // Bindet die Settings-Fields etc. ein
            settings_fields('autoactiva_vehicle_feed_settings_group');
            do_settings_sections('autoactiva_vehicle_feed_settings_group');
            ?>

            <table class="form-table">
                <tr>
                    <th scope="row"><label for="autoactiva_vehicle_feed_sftp_server">SFTP-Server</label></th>
                    <td>
                        <input type="text" name="autoactiva_vehicle_feed_sftp_server" id="autoactiva_vehicle_feed_sftp_server"
                            value="<?php echo esc_attr(get_option('autoactiva_vehicle_feed_sftp_server')); ?>"
                            style="width: 300px;">
                    </td>
                </tr>
                <tr>
                    <th scope="row"><label for="autoactiva_vehicle_feed_sftp_port">SFTP-Port</label></th>
                    <td>
                        <input type="number" name="autoactiva_vehicle_feed_sftp_port" id="autoactiva_vehicle_feed_sftp_port"
                            value="<?php echo esc_attr(get_option('autoactiva_vehicle_feed_sftp_port', 22)); ?>"
                            style="width: 100px;">
                        <p class="description">
                            Standard für SFTP ist 22. Dein Hoster/Server kann aber auch andere Ports nutzen.
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><label for="autoactiva_vehicle_feed_sftp_user">SFTP-Benutzer</label></th>
                    <td>
                        <input type="text" name="autoactiva_vehicle_feed_sftp_user" id="autoactiva_vehicle_feed_sftp_user"
                            value="<?php echo esc_attr(get_option('autoactiva_vehicle_feed_sftp_user')); ?>"
                            style="width: 300px;">
                    </td>
                </tr>
                <tr>
                    <th scope="row"><label for="autoactiva_vehicle_feed_sftp_password">SFTP-Passwort</label></th>
                    <td>
                        <input type="password" name="autoactiva_vehicle_feed_sftp_password" id="autoactiva_vehicle_feed_sftp_password"
                            value="<?php echo esc_attr(get_option('autoactiva_vehicle_feed_sftp_password')); ?>"
                            style="width: 300px;">
                    </td>
                </tr>
            </table>

            <hr>
            <h2>Google Place IDs pro Autohaus</h2>
            <p>Trage hier die Google Place IDs für deine Autohäuser ein. Diese werden später im CSV automatisch zugeordnet.</p>
            <?php
            // Gespeicherte Werte holen (Array)
            $saved_place_ids = get_option('autoactiva_vehicle_feed_place_ids', array());
            if (! is_array($saved_place_ids)) {
                $saved_place_ids = array();
            }

            // Alle Begriffe aus WooCommerce-Attribut "pa_autohaus" holen
            $autohaus_terms = get_terms(array(
                'taxonomy'   => 'pa_autohaus',  // Taxonomie-Name bei WooCommerce-Attribut "autohaus"
                'hide_empty' => false,
            ));

            if (! is_wp_error($autohaus_terms) && ! empty($autohaus_terms)) {
                echo '<table class="form-table"><tbody>';
                foreach ($autohaus_terms as $term) {
                    $autohaus_name = $term->name;
                    $value = isset($saved_place_ids[$autohaus_name]) ? $saved_place_ids[$autohaus_name] : '';
                    ?>
                    <tr>
                        <th scope="row">
                            <?php echo esc_html($autohaus_name); ?>
                        </th>
                        <td>
                            <input type="text"
                                name="autoactiva_vehicle_feed_place_ids[<?php echo esc_attr($autohaus_name); ?>]"
                                value="<?php echo esc_attr($value); ?>"
                                style="width: 400px;" />
                            <br><small>Place ID für <?php echo esc_html($autohaus_name); ?></small>
                        </td>
                    </tr>
                    <?php
                }
                echo '</tbody></table>';
            } else {
                echo '<p><em>Keine Autohaus-Begriffe gefunden (pa_autohaus leer?).</em></p>';
            }
            ?>

            <hr>
            <h2>Maßeinheit für den Kilometerstand</h2>
            <table class="form-table">
                <tr>
                    <th scope="row">Bevorzugte Einheit</th>
                    <td>
                        <label>
                            <input type="radio" name="autoactiva_vehicle_feed_unit" value="miles" <?php checked($unit, 'miles'); ?>>
                            Meilen
                        </label><br>
                        <label>
                            <input type="radio" name="autoactiva_vehicle_feed_unit" value="km" <?php checked($unit, 'km'); ?>>
                            Kilometer
                        </label>
                        <p class="description">
                            Wähle die Maßeinheit, die im CSV-Export verwendet werden soll.
                        </p>
                    </td>
                </tr>
            </table>

            <hr>
            <h2>Automatischer Upload</h2>
            <table class="form-table">
                <tr>
                    <th scope="row">Fahrzeuge automatisch hochladen</th>
                    <td>
                        <label for="autoactiva_vehicle_feed_auto_upload_enabled">
                            <input type="checkbox"
                                name="autoactiva_vehicle_feed_auto_upload_enabled"
                                id="autoactiva_vehicle_feed_auto_upload_enabled"
                                value="yes"
                                <?php checked($auto_upload_enabled, 'yes'); ?> />
                            Fahrzeuge täglich um 08:00 Uhr hochladen
                        </label>
                        <p class="description">
                            Wenn aktiviert, wird jeden Tag um 08:00 Uhr automatisch eine CSV erstellt und via SFTP hochgeladen.
                        </p>
                    </td>
                </tr>
            </table>

            <?php submit_button('SFTP- und Place-ID-Daten speichern'); ?>
        </form>

        <hr>

        <h2>Manueller Export</h2>
        <p>
            <strong>CSV erstellen:</strong> Erzeugt nur die CSV im <code>uploads</code>-Ordner.<br>
            <strong>Export zu Vehicle Listings durchführen:</strong> Erzeugt die CSV <em>und</em> lädt sie via SFTP hoch (mit den oben gespeicherten Zugangsdaten).
        </p>

        <form method="post">
            <?php wp_nonce_field('autoactiva_vehicle_feed_action', 'autoactiva_vehicle_feed_nonce'); ?>

            <!-- Button: CSV erstellen -->
            <button type="submit" name="autoactiva_action" value="create_csv" class="button button-secondary">
                CSV erstellen
            </button>

            <!-- Button: Export zu Vehicle Listings (SFTP) -->
            <button type="submit" name="autoactiva_action" value="export_vehicle_listings" class="button button-primary">
                Export zu Vehicle Listings durchführen (SFTP)
            </button>
        </form>
    </div>
    <?php
}

/**
 * ==========================
 * 4) Helper-Funktion: Hole Place ID aus gespeicherten Daten
 * ==========================
 *
 * @param string $dealership_name z.B. "Toyota Offenbach"
 * @return string (z.B. "ChIJlzW6MgYSvUcR4gBn6foFnlM") oder leer
 */
function get_place_id_by_dealership($dealership_name)
{
    $all_place_ids = get_option('autoactiva_vehicle_feed_place_ids', array());
    if (! is_array($all_place_ids)) {
        $all_place_ids = array();
    }
    return isset($all_place_ids[$dealership_name]) ? $all_place_ids[$dealership_name] : '';
}

/**
 * ==========================
 * 5) Export-Funktion (CSV + optionaler SFTP-Upload)
 * ==========================
 *
 * @param bool $do_sftp Gibt an, ob SFTP-Upload durchgeführt werden soll.
 * @return true|string  true = Erfolg, string = Fehlermeldung
 */
function autoactiva_vehicle_feed_run_export($do_sftp = false)
{
    // WooCommerce-Check
    if (! function_exists('wc_get_products')) {
        return 'WooCommerce ist nicht aktiv oder nicht verfügbar.';
    }

    // SFTP-Daten aus den Settings
    $sftp_server   = get_option('autoactiva_vehicle_feed_sftp_server', '');
    $sftp_port     = get_option('autoactiva_vehicle_feed_sftp_port', 22);
    $sftp_user     = get_option('autoactiva_vehicle_feed_sftp_user', '');
    $sftp_password = get_option('autoactiva_vehicle_feed_sftp_password', '');

    // Pfad zur CSV im Uploads-Verzeichnis
    $upload_dir      = wp_upload_dir();
    $local_file_path = trailingslashit($upload_dir['basedir']) . 'woocommerce_export.csv';

    // Produkte abrufen (nur veröffentlichte)
    $args = array(
        'status' => array('publish'),
        'limit'  => -1,
    );
    $products = wc_get_products($args);
    if (! $products) {
        return 'Keine Produkte gefunden.';
    }

    // CSV-Header
    $header = array(
        'store_code',
        'place_id',
        'id',
        'post_id',
        'VIN',
        'year',
        'make',
        'model',
        'trim',
        'exterior_color',
        'interior_color',
        'mileage',
        'fuel_efficiency',
        'fuel',
        'ev_range',
        'ev_battery',
        'co2_emission',
        'condition',
        'price',
        'dealership_name',
        'dealership_address',
        'link',
        'image_link',
        'additional_image_link',
        'vehicle_option',
        'vehicle_fulfillment',
        'legal_disclaimer'
    );

    // CSV-Datei erstellen
    $fp = fopen($local_file_path, 'w');
    if (! $fp) {
        return "Konnte CSV nicht erstellen unter: $local_file_path";
    }

    // Kopfzeile in CSV
    fputcsv($fp, $header);

    // Beispiel-Store-Code (ggf. dynamisch anpassbar)
    $store_code = '1';

    // Einheitseinstellung abfragen
    $unit = get_option('autoactiva_vehicle_feed_unit', 'miles');

    // Produkte durchgehen
    foreach ($products as $product) {
        $product_id = $product->get_id();

        // Attribute abfragen
        $pa_vinfull         = $product->get_attribute('pa_vinfull');
        $pa_erstzulassung   = $product->get_attribute('pa_erstzulassung');
        $pa_marke           = $product->get_attribute('pa_marke');
        $pa_modell          = $product->get_attribute('pa_modell');
        $pa_trim            = $product->get_attribute('pa_trim');
        $pa_farbe           = $product->get_attribute('pa_farbe');
        $pa_innenfarbe      = $product->get_attribute('pa_innenfarbe');
        $pa_kilometerstand  = $product->get_attribute('pa_kilometerstand');
        $pa_kraftstoff      = $product->get_attribute('pa_kraftstoff');
        $pa_fahrzeugzustand = $product->get_attribute('pa_fahrzeugzustand');
        $pa_autohaus        = $product->get_attribute('pa_autohaus');
        $pa_adresse         = $product->get_attribute('pa_adresse');

        // Optionale Attribute
        $pa_vehicle_option      = $product->get_attribute('pa_vehicle_option');
        $pa_vehicle_fulfillment = $product->get_attribute('pa_vehicle_fulfillment');
        $pa_legal_disclaimer    = $product->get_attribute('pa_legal_disclaimer');

        // VIN
        $vin = $pa_vinfull ? $pa_vinfull : '';

        // Jahr aus "08.2021" extrahieren
        $year = '';
        if (!empty($pa_erstzulassung)) {
            $year = substr($pa_erstzulassung, -4);
        }

        // Kilometerstand bearbeiten
        $mileage = '';
        if (!empty($pa_kilometerstand)) {
            // Entfernung von Einheiten (z.B. " km")
            $pa_kilometerstand = str_ireplace(array(' km', 'km'), '', $pa_kilometerstand);
            
            // Als Zahl parsen
            $km_value = floatval(trim($pa_kilometerstand));
            
            if ($unit === 'miles') {
                // Umrechnung in Meilen
                $value = $km_value * 0.621371;
                $value = round($value, 2);
                $mileage = $value . ' miles';
            } else {
                // Kilometer beibehalten
                $value = round($km_value, 2);
                $mileage = $value . ' km';
            }
        }

        // Fahrzeugzustand
        $condition = 'used';
        if (strtolower($pa_fahrzeugzustand) === 'neuwagen') {
            $condition = 'new';
        }

        // Preis
        $price = $product->get_price() . ' ' . get_woocommerce_currency();

        // Fuel, EV-Daten, CO2 etc. (nicht genutzt)
        $fuel_efficiency = '';
        $ev_range        = '';
        $ev_battery      = '';
        $co2_emission    = '';
        $fuel           = $pa_kraftstoff;

        // Place ID zum Autohaus-Namen
        $place_id = get_place_id_by_dealership($pa_autohaus);

        // Links
        $link = get_permalink($product_id);

        // Bilder
        $image_id   = $product->get_image_id();
        $image_link = $image_id ? wp_get_attachment_url($image_id) : '';

        $gallery_ids = $product->get_gallery_image_ids();
        $additional_image_link = array();
        if (! empty($gallery_ids)) {
            foreach ($gallery_ids as $gid) {
                $additional_image_link[] = wp_get_attachment_url($gid);
            }
        }
        $additional_image_link_str = implode(',', $additional_image_link);

        // CSV-Zeile
        $data = array(
            $store_code,
            $place_id,
            $product_id,
            $product_id,
            $vin,
            $year,
            $pa_marke,
            $pa_modell,
            $pa_trim,
            $pa_farbe,
            $pa_innenfarbe,
            $mileage,
            $fuel_efficiency,
            $fuel,
            $ev_range,
            $ev_battery,
            $co2_emission,
            $condition,
            $price,
            $pa_autohaus,
            $pa_adresse,
            $link,
            $image_link,
            $additional_image_link_str,
            $pa_vehicle_option,
            $pa_vehicle_fulfillment,
            $pa_legal_disclaimer
        );

        fputcsv($fp, $data);
    }

    fclose($fp);

    // Wenn kein SFTP gewünscht, fertig
    if (! $do_sftp) {
        return true;
    }

    // SFTP nur, wenn Daten vorhanden
    if (empty($sftp_server) || empty($sftp_user) || empty($sftp_password)) {
        return 'SFTP-Daten sind unvollständig. Kein Upload.';
    }

    // Ziel-Dateiname auf dem SFTP-Server
    $remote_file_path = 'woocommerce_export.csv';

    // cURL-Upload
    $upload_res = autoactiva_vehicle_feed_sftp_upload(
        $sftp_server,
        (int) $sftp_port,
        $sftp_user,
        $sftp_password,
        $local_file_path,
        $remote_file_path
    );

    if ($upload_res === true) {
        return true;
    } else {
        return $upload_res; // Fehlermeldung
    }
}

/**
 * ==========================
 * 6) SFTP-Upload mit cURL
 * ==========================
 *
 * @param string $server  SFTP-Server
 * @param int    $port    Port
 * @param string $user    Benutzername
 * @param string $pass    Passwort
 * @param string $local   Pfad zur lokalen Datei
 * @param string $remote  Zielpfad auf dem SFTP-Server
 *
 * @return true|string  true = Erfolg, string = Fehlermeldung
 */
function autoactiva_vehicle_feed_sftp_upload($server, $port, $user, $pass, $local, $remote)
{
    if (! function_exists('curl_init')) {
        return 'cURL ist nicht verfügbar oder nicht aktiviert.';
    }

    // Lokale Datei öffnen
    $fp = @fopen($local, 'r');
    if (! $fp) {
        return "Konnte lokale Datei nicht öffnen: $local";
    }

    // SFTP-URL
    $url = "sftp://{$server}:{$port}/{$remote}";

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_USERPWD, "{$user}:{$pass}");
    curl_setopt($ch, CURLOPT_UPLOAD, 1);
    curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_SFTP); // SFTP-Protokoll aktivieren
    curl_setopt($ch, CURLOPT_INFILE, $fp);
    curl_setopt($ch, CURLOPT_INFILESIZE, filesize($local));

    // Timeouts
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_TIMEOUT, 300);

    // SSL-Überprüfung ggf. anpassen
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    // Upload ausführen
    $result  = curl_exec($ch);
    $err_no  = curl_errno($ch);
    $err_msg = curl_error($ch);

    curl_close($ch);
    fclose($fp);

    if ($result === false || $err_no) {
        return "Fehler beim SFTP-Upload: [{$err_no}] {$err_msg}";
    }

    // Upload erfolgreich
    return true;
}

// ---(C) Cron-Intervall definieren + Hook auf Option-Update---
function autoactiva_vehicle_feed_add_cron_schedules($schedules)
{
    if (!isset($schedules['every_24_hours'])) {
        $schedules['every_24_hours'] = array(
            'interval' => 24 * 60 * 60,  // 24 Stunden
            'display'  => __('Alle 24 Stunden (täglich)'),
        );
    }
    return $schedules;
}
add_filter('cron_schedules', 'autoactiva_vehicle_feed_add_cron_schedules');

add_action('update_option_autoactiva_vehicle_feed_auto_upload_enabled', 'autoactiva_vehicle_feed_update_auto_upload_schedule', 10, 2);

function autoactiva_vehicle_feed_update_auto_upload_schedule()
{
    $enabled = get_option('autoactiva_vehicle_feed_auto_upload_enabled', 'no');

    if ($enabled === 'yes') {
        // Einplanen, falls nicht bereits geplant
        if (!wp_next_scheduled('autoactiva_vehicle_feed_daily_cron_event')) {
            // Zeitpunkt: heute um 08:00 oder morgen um 08:00 (falls heute schon vorbei ist)
            $today_8am = strtotime(date('Y-m-d') . ' 08:00:00');
            if (time() > $today_8am) {
                $scheduled_time = strtotime('tomorrow 08:00');
            } else {
                $scheduled_time = $today_8am;
            }
            wp_schedule_event($scheduled_time, 'every_24_hours', 'autoactiva_vehicle_feed_daily_cron_event');
        }
    } else {
        // Deaktivieren: Cron-Event löschen
        $timestamp = wp_next_scheduled('autoactiva_vehicle_feed_daily_cron_event');
        if ($timestamp) {
            wp_unschedule_event($timestamp, 'autoactiva_vehicle_feed_daily_cron_event');
        }
    }
}

// ---(D) Die eigentliche Cron-Callback-Funktion---
add_action('autoactiva_vehicle_feed_daily_cron_event', 'autoactiva_vehicle_feed_daily_cron_callback');
function autoactiva_vehicle_feed_daily_cron_callback()
{
    // Automatisch SFTP-Upload ausführen
    autoactiva_vehicle_feed_run_export(true);
}