Jak dodać upload plików do formularza produktu w WooCommerce?

Jak dodać upload plików do formularza produktu w WooCommerce?

Wielu właścicieli sklepów internetowych staje przed prostym problemem: chcą, aby klient mógł wysłać plik razem z zamówieniem. Niestety — darmowa wersja Fluent Forms nie wspiera uploadu plików.

Dlatego stworzyliśmy rozszerzenie do naszej wtyczki DC Product Form, które w prosty sposób dodaje możliwość wysyłania plików na karcie produktu, zapisuje je do zamówienia i pozwala sprzedawcy pobrać je w panelu. Takie rozszerzenia to codzienność przy tworzeniu sklepu internetowego dopasowanego do produktów wymagających personalizacji, plików i dodatkowych danych od klienta.

W tym tutorialu pokażemy dokładnie, jak to zrobić — krok po kroku. 

 

❗Wymagania ⚙️

Zanim zaczniemy tworzyć rozszerzenie krok-po-kroku, upewnij się, że masz przygotowane podstawy. Dzięki temu od razu będziesz mógł testować upload w praktyce. 👇

🔹 1) Zainstalowana wtyczka DC Product Form

Bez niej nie ma sensu tworzyć uploadu – bo upload ma rozszerzać właśnie formularz produktu.
Jeśli jej jeszcze nie masz – pobierz ją tutaj i aktywuj, zanim ruszysz dalej.

🔹 2) WooCommerce już skonfigurowane

Czyli działa sklep, można dodać produkt do koszyka 🛒
To jest miejsce, gdzie upload faktycznie będzie wykorzystywany.

🔹 3) Podstawowa znajomość PHP + JS

Nic zaawansowanego – ale tworzymy własny plugin, więc będziemy:

  • tworzyć strukturę plików 📂

  • pisać PHP hooki ⚡

  • obsługiwać AJAX 🚀

  • ładować JS pod frontend

I tyle — to naprawdę wystarczy 👍

💬 Jeśli masz to wszystko – możemy zaczynać budowę wtyczki od zera.

 

🧩 Krok 1 – Struktura wtyczki 📂

Zanim napiszemy pierwszą linię kodu, musimy przygotować szkielet całej wtyczki.
To bardzo ważne – bo dzięki temu wszystko będzie później uporządkowane, a my będziemy mogli swobodnie rozwijać moduł o kolejne funkcje. 💡

📁 Tak wygląda struktura katalogu rozszerzenia:

dc-woo-file-upload/
├─ assets
│   ├─ dc-upload-attach.js
│   └─ dc-file-upload.js
├─ includes
│   ├─ dc-file-upload-uploader.php
│   ├─ dc-file-upload-handler.php
│   ├─ dc-file-upload-admin_form.php
│   └─ dc-file-upload-frontend.php
└─ dc-woo-file-upload.php

🔍 Co w tych folderach?

📦 assets/

Tutaj trzymamy wszystkie skrypty JS obsługujące upload plików w przeglądarce.
W tym:

  • obsługa wysyłania pliku AJAXem

  • dołączanie danych uploadu do koszyka

⚙️ includes/

Logika backendowa (PHP):

  • obsługa uploadu (AJAX)

  • zapis do zamówienia

  • wyświetlanie w adminie

  • integracja z frontendem formularza

🏁 plik główny: dc-woo-file-upload.php

Tutaj rejestrujemy plugin, wczytujemy wszystkie require/include oraz dodajemy hakami funkcje.

💬 W kolejnych krokach przejdziemy przez te pliki po kolei i dodamy do nich kod.

 

🔧 Krok 2 – Obsługa uploadu w frontendzie 🚀

W tym kroku zajmiemy się pierwszym kluczowym elementem całej wtyczki — obsługą przesyłania plików po stronie użytkownika (frontend).
To tutaj klient wybiera plik, a my wysyłamy go asynchronicznie do WordPressa, a następnie podmieniamy pole hidden w formularzu, aby WooCommerce wiedziało, jaki plik został dodany. 📁➡️🌐

 

🎯 Co robi ten skrypt?

Plik: assets/dc-file-upload.js

Ten skrypt:

  • wykrywa moment, kiedy użytkownik wybierze plik w polu typu file 👇

  • przekazuje go AJAX-em do admin-ajax.php

  • odbiera URL przesłanego pliku w odpowiedzi

  • zapisuje ten URL w hidden input

  • pokazuje miniaturę / podgląd na stronie ⭐

Czyli dokładnie to, czego potrzebujemy, żeby proces uploadu był intuicyjny i natychmiastowy.

 

📌 Fragment kodu odpowiedzialny za upload i miniaturę:

jQuery(function($){

    $(document).on('change', '.dc-upload-input', function(e){

        let file = this.files[0];
        let field = $(this);

        if(!file) return;

        let form = new FormData();
        form.append('action', 'dc_upload_file');
        form.append('file', file);

        $.ajax({
            url: dc_ajax.url,
            type: 'POST',
            data: form,
            processData: false,
            contentType: false,
            success(res){

                if(!res.success) {
                    alert("Upload failed");
                    return;
                }

                let url = res.data.url;
                let wrapper = field.closest('.dc-upload-field');
                wrapper.find('input[type="hidden"]').val(url);

                wrapper.find('.dc-upload-thumb').html(
                    '<img src="'+url+'" style="max-width:120px; margin-top:8px; border:1px solid #ccc; border-radius:4px;">'
                )
            }
        });
    });

});

 

🧠 Jak to działa krok po kroku

  1. użytkownik wybiera plik

  2. JS zbiera ten plik i pakuje w FormData

  3. wysyła żądanie AJAX → admin-ajax.php 💥

  4. backend zapisuje plik na serwerze

  5. backend zwraca finalny URL

  6. JS wstawia ten URL do ukrytego pola

  7. dodatkowo pokazuje miniaturkę 🖼️

💬 Dzięki temu klient od razu widzi, że plik został poprawnie przesłany, i mamy pewność, że URL trafi później do zamówienia.

 

🔧 Krok 3 – Implementacja w Add to Cart AJAX ⚡

Samo przesłanie pliku to jeszcze nie wszystko — teraz musimy zadbać o to, aby WooCommerce faktycznie otrzymało informację o przesłanych plikach w momencie dodawania produktu do koszyka.
Bez tego cały upload byłby tylko „wizualny”. 🚫

Dlatego potrzebujemy drugiego skryptu, który:

🎯 Cel:

  • przechwytuje akcję add to cart

  • dokleja do requestu JSON z przesłanymi plikami

  • a przy tradycyjnym submit → dodaje hidden <input> z JSON-em

Plik odpowiedzialny za to:

👉 assets/dc-upload-attach.js

 

🛠️ Jak działa ten skrypt?

  • monitoruje każde zapytanie AJAX

  • sprawdza, czy to request z DC Product Form

  • pobiera listę przesłanych plików

  • serializuje ją

  • dokleja do requestu

  • a w przypadku standardowego submitu — dodaje ukryte pole

Dzięki temu WooCommerce NIE MA prawa pominąć przesłanych plików ✔️

 

📌 Fragment kodu, który „podczepia” pliki pod Add to Cart:

jQuery(function($){

    function collectUploadedFiles(){
        var out = {};
        $('[name^="dc_upload_url_"]').each(function(){
            var $input = $(this);
            var index = $input.attr('name').replace('dc_upload_url_',''); 
            var val = $input.val();
            
            if(val){
                var label = $input.data('label') || (index); 
                out[label] = val; 
            }
        });
        return out;
    }

    $(document).ajaxSend(function(event, xhr, settings){

        if(!settings.data) return;

        let dataStr = '';

        if(typeof settings.data === 'string'){
            dataStr = settings.data;
        } else if(typeof settings.data === 'object' && !(settings.data instanceof FormData)){
            dataStr = new URLSearchParams(settings.data).toString();
        } else {
            return;
        }

        if(dataStr.indexOf('dcpf_add_to_cart') === -1){
            return;
        }

        let uploads = collectUploadedFiles();
        if($.isEmptyObject(uploads)) return; 

        let extra = '&uploaded_files=' + encodeURIComponent(JSON.stringify(uploads));

        settings.data = dataStr + extra;
    });

    $('form.cart').on('submit', function(){
        var uploads = collectUploadedFiles();
        if($.isEmptyObject(uploads)) return;

        $('<input>',{
            type:'hidden',
            name:'dc_uploaded_files_json',
            value: JSON.stringify(uploads)
        }).appendTo(this);
    });

});

 

🚦 Dlaczego to jest kluczowy krok?

Bo:

  • WooCommerce domyślnie nie wysyła żadnych danych z plikami

  • AJAX Add to Cart działa poza formularzem

  • upload nie jest automatycznie dołączony do koszyka

Gdybyśmy tego nie zrobili — pliki zniknęłyby na starcie.

 

🧠 W skrócie (mega ważne):

  • przechwytuje dcpf_add_to_cart

  • pobiera informacje o uploadzie

  • zamienia w JSON

  • dokleja do requestu AJAX

  • a przy klasycznym submit → dodaje hidden input

💬 Po tym kroku WooCommerce będzie otrzymywać informację o przesłanych plikach — zarówno przez AJAX, jak i przez zwykły formularz.

 

 

🔧 Krok 4 – Dodawanie pól upload w panelu produktu (Admin) 🧰

Po stronie administratora potrzebujemy miejsca, w którym zdefiniujemy pola uploadu, które będą wyświetlane w formularzu na karcie produktu.
I właśnie za to odpowiada plik:

includes/dc-file-upload-admin_form.php ⚙️

 

🎯 Co ten plik robi?

Ten plik:

  • dodaje specjalną sekcję w edycji produktu w WooCommerce

  • pozwala administratorowi dodawać własne pola uploadu

  • każde pole może mieć:

    • nazwę 🏷️

    • opis 💬

    • dozwolone rozszerzenia 📂

    • maksymalny rozmiar pliku (w MB) 🧱

  • umożliwia dodawanie wielu pól

  • umożliwia ich usuwanie

  • zapisuje te pola do meta produktu

Super ważne:
Każdy produkt może mieć inne pola upload 🚀

 

🏗️ Jak wygląda struktura pól?

Każde pole posiada:

parametr czego dotyczy
label nazwa pola w formularzu
desc tekst pomocniczy dla klienta
extensions lista rozdzielona przecinkami
max max MB

 

📝 Jak działa interfejs?

  • tabela renderowana w backendzie

  • każda kolumna = jedno ustawienie pola

  • przycisk „Dodaj pole upload”

  • JS dynamicznie dopisuje wiersz

  • przycisk „Usuń” kasuje konkretne pole

Czyli zero ograniczeń 🔥

 

🔍 Kompletny kod dla dc-file-upload-admin_form.php

<?php
/**
 * Form admina dla upload fields (na stronie produktu)
 */

// pobieramy wartość z meta
$fields = get_post_meta($post->ID, '_dc_upload_fields', true);

if (!is_array($fields)) {
    $fields = [];
}
?>

<div id="dc-upload-fields-wrapper">

    <h3>Pola upload plików</h3>

    <table class="widefat dc-upload-fields-table">
        <thead>
            <tr>
                <th>Nazwa pola</th>
                <th>Opis</th>
                <th>Rozszerzenia (np: jpg,png,pdf)</th>
                <th>Maks. wielkość (MB)</th>
                <th></th>
            </tr>
        </thead>

        <tbody id="dc-upload-fields-body">
        <?php foreach ($fields as $index => $field): ?>
            <tr>
                <td>
                    <input type="text"
                           name="dc_upload_fields[<?php echo $index; ?>][label]"
                           value="<?php echo esc_attr($field['label'] ?? ''); ?>"
                           placeholder="Nazwa pola"
                           class="regular-text" />
                </td>

                <td>
                    <input type="text"
                           name="dc_upload_fields[<?php echo $index; ?>][desc]"
                           value="<?php echo esc_attr($field['desc'] ?? ''); ?>"
                           placeholder="Opis pola"
                           class="regular-text" />
                </td>

                <td>
                    <input type="text"
                           name="dc_upload_fields[<?php echo $index; ?>][extensions]"
                           value="<?php echo esc_attr($field['extensions'] ?? ''); ?>"
                           placeholder="jpg,png,pdf"
                           class="regular-text" />
                </td>

                <td>
                    <input type="number"
                           min="0"
                           step="0.1"
                           name="dc_upload_fields[<?php echo $index; ?>][max]"
                           value="<?php echo esc_attr($field['max'] ?? ''); ?>"
                           placeholder="5"
                           class="small-text" /> MB
                </td>

                <td>
                    <button type="button" class="button dc-remove-row">Usuń</button>
                </td>
            </tr>
        <?php endforeach; ?>
        </tbody>

    </table>


    <br>

    <button type="button" class="button button-primary" id="dc-add-upload-field">
        Dodaj pole upload
    </button>

</div>


<script>
    (function($){

        $('#dc-add-upload-field').on('click', function(){

            let tbody = $('#dc-upload-fields-body');

            let count = tbody.find('tr').length;
            let row = `
            <tr>
                <td><input type="text" name="dc_upload_fields[`+count+`][label]" placeholder="Nazwa pola" class="regular-text"></td>
                <td><input type="text" name="dc_upload_fields[`+count+`][desc]" placeholder="Opis pola" class="regular-text"></td>
                <td><input type="text" name="dc_upload_fields[`+count+`][extensions]" placeholder="jpg,png,pdf" class="small-text"></td>
                <td><input type="number" name="dc_upload_fields[`+count+`][max]" placeholder="5" step="0.1" class="small-text"> MB</td>
                <td><button type="button" class="button dc-remove-row">Usuń</button></td>
            </tr>`;

            tbody.append(row);
        });

        $(document).on('click', '.dc-remove-row', function(){
            $(this).closest('tr').remove();
        });

    })(jQuery);
</script>

 

🧠 Co jest najważniejsze z perspektywy działania pluginu?

➡️ ten plik jest miejscem, gdzie tworzymy konfigurację pól uploadu
➡️ te ustawienia potem wyświetli frontend
➡️ te same ustawienia frontend wykorzysta do walidacji
➡️ backend wykorzysta do ograniczeń uploadu

Czyli tu zaczyna się logika uploadu.

 

💬 Po tym kroku:

Admin może dodać:

✔ Pole „Zdjęcie projektu klienta”
✔ Pole „Skany dokumentów”
✔ Pole „Projekt w PDF”

z limitami, z opisami, z rozszerzeniami

i to wszystko zapisane jest w meta produktu.

 

🔧 Krok 5 – Zapis w metadanych zamówienia 🧾

Kiedy plik zostanie przesłany oraz przekazany do koszyka – musimy upewnić się, że informacja o nim zostanie zapisana w zamówieniu.
Jeśli tego nie zrobimy, uploader „działa”, ale po złożeniu zamówienia wszystko przepada. ❌

Dlatego w tym kroku podpinamy się pod WooCommerce i dopisujemy załączniki bezpośrednio do danych pozycji zamówienia (line item).

Dzięki temu:

  • każdy produkt w zamówieniu może mieć własne pliki 💡

  • nie mieszamy uploadów między produktami

  • pliki przypisane są 1:1 do konkretnej pozycji zamówienia

  • można je wyświetlać w panelu i w e-mailach

 

📍 Plik odpowiedzialny za zapis

includes/dc-file-upload-handler.php

Ten plik:

  • rejestruje klasę odpowiedzialną za obsługę zapisów,

  • podpina się pod event tworzenia pozycji zamówienia,

  • pobiera dane uploadu z koszyka,

  • zapisuje je jako meta danej pozycji.

 

🧠 Hook użyty w tym kroku

woocommerce_checkout_create_order_line_item

To właśnie moment, w którym WooCommerce zamienia koszyk w zamówienie.
Idealny czas, żeby dorzucić nasze dane. ⚙️

 

📌 Kluczowy fragment kodu

Poniżej znajduje się kod obsługujący cały proces zapisu:

<?php

if(!defined('ABSPATH')) exit;

class DC_File_Upload_Handler {

    public function __construct() {

        add_action('woocommerce_checkout_create_order_line_item', [$this, 'save_to_order'], 10, 4);
    }


    public function save_to_order($item, $cart_item_key, $values, $order) {

        if (empty($values['dc_uploaded_files'])) {
            return;
        }

        $item->add_meta_data(
            'dc_uploaded_files',
            $values['dc_uploaded_files'],
            true
        );
    }

}

 

🧩 Co tu się dokładnie dzieje?

  • podczas tworzenia pozycji zamówienia sprawdzamy, czy produkt ma upload

  • jeśli nie → wychodzimy

  • jeśli tak → zapisujemy JSON z listą plików jako meta

  • meta zostanie automatycznie:

    • zapisana,

    • widoczna tylko po stronie admina,

    • pobrana wraz z zamówieniem.

 

🎉 Efekt po tym kroku

📦 pliki są trwale przypisane do zamówienia
📦 WooCommerce niczego nie zgubi
📦 możesz je później wyświetlać gdzie chcesz: admin, maile, PDF, REST API

 

 

🔧 Krok 6 – Upload + walidacja (backend) 🛡️

Frontend już działa. Klient wybiera plik, skrypt wykrywa zmianę, wysyła go AJAX-em i wyświetla miniaturkę.
Ale to wszystko na razie tylko w przeglądarce.

Teraz czas na rdzeń uploadu po stronie serwera – czyli miejsce, gdzie WordPress powinien:

  • przyjąć przesyłany plik 📥

  • sprawdzić, czy w ogóle istnieje ⚠️

  • wygenerować dla niego bezpieczną nazwę 🏷️

  • zapisać go w odpowiednim katalogu 💾

  • zwrócić do JS finalny URL 🔗

  • obsłużyć błędy ❌

i zrobić to w pełni zgodnie ze standardem WP.

Za to właśnie odpowiada:

📍 Plik: includes/dc-file-upload-uploader.php

 

🎯 Co dokładnie robimy w tym kroku?

W tej klasie implementujemy:

🗂️ upload_dir()

Ustalamy miejsce zapisu — specjalny katalog:

/wp-content/uploads/dc-uploads/

(dzięki temu użytkownik nie miesza się z medialibrary)

💾 wp_handle_upload()

Obsługujemy zapis na dysk + walidację

(jednak w tym konkretnym kodzie używamy manualnego move_uploaded_file – i to OK)

🔗 zwrot URL

Backend odsyła do JS wyliczony publiczny link

⚠ obsługa błędów

jeśli pliku brak → zwrot error
jeśli zapis się nie uda → zwrot error

 

📌 Kod obsługi uploadu

<?php

if (!defined('ABSPATH')) exit;

class DC_File_Upload_Uploader {

    public function __construct() {
        add_action('wp_ajax_dc_upload_file', [$this, 'upload']);
        add_action('wp_ajax_nopriv_dc_upload_file', [$this, 'upload']);
    }

    public function upload() {
        $i18n_error = esc_html__('Brak pliku', 'dc-woo-file-upload');
        $i18n_save_error = esc_html__('Nie udało się zapisać pliku', 'dc-woo-file-upload');

        if (empty($_FILES['file'])) {
            wp_send_json_error(['msg' => $i18n_error]);
        }

        $field_key = isset($_POST['field_key'])
            ? sanitize_text_field( wp_unslash( $_POST['field_key'] ) )
            : '';

        $file = $_FILES['file'];

        $upload_dir = wp_upload_dir();
        $target_dir = $upload_dir['basedir'] . '/dc-uploads/';

        if (! file_exists($target_dir)) {
            wp_mkdir_p($target_dir);
        }

        $date_prefix = date('YmdHis'); 

        $random_suffix = substr(md5(uniqid(rand(), true)), 0, 8); 
        
        $file_info = pathinfo(sanitize_file_name($file['name']));
        $extension = $file_info['extension'] ?? '';
        
        $new_name = $date_prefix . '_' . $random_suffix;
        if (!empty($extension)) {
             $new_name .= '.' . strtolower($extension);
        }

        $target_path = $target_dir . $new_name;

        if (! move_uploaded_file($file['tmp_name'], $target_path)) {
            wp_send_json_error(['msg' => $i18n_save_error]);
        }

        $url = $upload_dir['baseurl'] . '/dc-uploads/' . $new_name;

        wp_send_json_success([
            'url' => $url
        ]);
    }

}

 

🧠 Dlaczego to rozwiązanie działa świetnie?

  • pliki nie wpadają do biblioteki WP 📦

  • struktura katalogów jest bezpieczna

  • nazwy są unikalne

  • mamy kontrolę

  • klienci nie nadpiszą sobie plików

  • błędy są zwracane natychmiast

  • działa dla zalogowanych i niezalogowanych

 

🎉 Po tym kroku:

✔ klient wysyła pliki
✔ backend je przyjmuje
✔ pliki trafiają w bezpieczne miejsce
✔ JS dostaje finalny URL
✔ można kontynuować zamówienie

 

🔧 Krok 7 – Plik główny wtyczki 📦

(bootstrap całego rozszerzenia)

To jest absolutne serce całego projektu 💛
Bez tego pliku cała reszta nie istnieje, nie ładuje się i nie działa.

Dlatego właśnie ten plik jest fundamentem – oraz faktycznie powinien być Krokiem 1 w całym tutorialu.
(w pełni się zgadzam)

 

🎯 Za co odpowiada plik dc-woo-file-upload.php?

Ten plik:

  • deklaruje nagłówek wtyczki (WordPress musi go widzieć) ⚙️

  • definiuje ścieżkę pluginu 📂

  • rejestruje metabox w edycji produktu 🧱

  • zapisuje pola uploadu do meta 💾

  • ładuje wszystkie pozostałe klasy (require once) 🚚

  • instancjonuje:

    • uploader backend

    • handler zapisu do zamówienia

    • frontend renderer

  • podczepia wszystkie filtry WooCommerce potrzebne do:

    • dodania uploadów do koszyka,

    • przeniesienia uploadów do zamówienia,

    • wyświetlania linków w panelu admina.

Czyli:

💡 ten plik jest klejem całego modułu

 

🧠 Kluczowe funkcjonalności

🟦 bootstrap pluginu

– rejestruje klasę DC_Woo_File_Upload

🟦 ładuje pod-klasy

– File Upload Uploader
– File Upload Handler
– Frontend renderer

🟦 rejestruje metabox

→ gdzie admin dodaje pola

🟦 rejestruje zapis meta

→ aby pola się zapisały

🟦 interceptuje dodanie produktu do koszyka

→ aby uploady trafiły w koszyk

🟦 intercept checkout

→ aby uploady trafiły w zamówienie

🟦 renderuje linki w panelu admina

→ „pobierz plik”

 

📌 Kod pliku dc-woo-file-upload.php

<?php
/**
 * Plugin Name: DC Woo File Upload
 * Description: Pozwala na dodawanie pól uploadu w produktach WooCommerce.
 * Author: Design Cart
 * Version: 1.0.0
 */

if ( ! defined('ABSPATH') ) exit;

define( 'DC_FILE_UPLOAD_PATH', plugin_dir_path(__FILE__) );

class DC_Woo_File_Upload {

    public function __construct() {

        add_action('add_meta_boxes', [$this, 'register_metabox']);

        add_action('save_post_product', [$this, 'save_fields']);

        require_once DC_FILE_UPLOAD_PATH.'includes/dc-file-upload-uploader.php';
        new DC_File_Upload_Uploader();

        require_once DC_FILE_UPLOAD_PATH.'includes/dc-file-upload-handler.php';
        new DC_File_Upload_Handler();

        add_action('wp', [$this, 'load_frontend']);
    }

    public function register_metabox() {

        add_meta_box(
            'dc_woo_file_upload_box',
            'DC – Pliki przesyłane przez klienta',
            [$this,'render_metabox'],
            'product',
            'normal',
            'default'
        );

    }

    public function load_frontend() {

        if ( !is_product() )
            return;

        if ( did_action('dc_file_upload_frontend_loaded') ) return;
        require_once DC_FILE_UPLOAD_PATH . 'includes/dc-file-upload-frontend.php';

        new DC_File_Upload_Frontend();

        do_action('dc_file_upload_frontend_loaded');
    }

    
    public function render_metabox($post) {

        include DC_FILE_UPLOAD_PATH . 'includes/dc-file-upload-admin_form.php';

    }

    public function save_fields($post_id) {

        if (!isset($_POST['dc_upload_fields']))
            return;

        $fields = $_POST['dc_upload_fields'];

        if (!is_array($fields))
            $fields = [];

        foreach ($fields as $k => $f)
        {
            $fields[$k]['label']      = sanitize_text_field($f['label'] ?? '');
            $fields[$k]['desc']       = sanitize_text_field($f['desc'] ?? '');
            $fields[$k]['extensions'] = sanitize_text_field($f['extensions'] ?? '');
            $fields[$k]['max']        = sanitize_text_field($f['max'] ?? '');
        }

        update_post_meta($post_id, '_dc_upload_fields', $fields);
    }


}

add_filter('woocommerce_add_cart_item_data', function($cart_item_data,$product_id,$variation_id){
    
    $uploads_json = $_POST['dc_uploaded_files_json'] ?? '';
    
    if (empty($uploads_json)) {
        $uploads_json = $_POST['uploaded_files'] ?? '';
    }

    if ($uploads_json) {
        
        $uploads_data = json_decode(stripslashes($uploads_json), true);

        $safe_uploads = [];

        if (is_array($uploads_data)) {
            foreach ($uploads_data as $index => $url) {
                $safe_uploads[wc_clean($index)] = esc_url_raw($url); 
            }
        }

        if (!empty($safe_uploads)) {
            $cart_item_data['dc_uploaded_files'] = $safe_uploads;
        }
    }

    return $cart_item_data;

},10,3);

add_action('woocommerce_checkout_create_order_line_item',function($item,$cart_item_key,$values,$order){

    if( empty($values['dc_uploaded_files']) ) return;

    foreach($values['dc_uploaded_files'] as $index=>$url){
        $item->add_meta_data('Plik '.$index,$url); 
    }

},10,4);

add_filter('woocommerce_order_item_display_meta_value', function($value, $meta, $item) {
    $upload_path_segment = '/dc-uploads/';
    
    if (is_string($value) && strpos($value, $upload_path_segment) !== false) {
        
        if (strpos($value, 'http') === 0) {
            $url = $value;
        } else {
            return $value; 
        }

        $link_text = esc_html__('Pobierz plik', 'dc-woo-file-upload');
        
        $link_html = '<a href="' . esc_url($url) . '" target="_blank" download>' . $link_text . '</a>';

        return $link_html;
    }
    
    return $value;

}, 10, 3);

new DC_Woo_File_Upload;

 

 

🎉 Po tym kroku

Wtyczka:

✔ istnieje
✔ uruchamia się
✔ ładuje wszystkie moduły
✔ obsługuje backend
✔ obsługuje frontend
✔ obsługuje koszyk
✔ obsługuje zamówienie
✔ obsługuje admin
✔ obsługuje linki do pobierania

pełny cykl 🔥

 

🔧 Krok 8 – Wyświetlanie pól uploadu na froncie (Formularz produktu) 🧲

Na tym etapie mamy już:
✔ upload po stronie przeglądarki,
✔ obsługę AJAX backendu,
✔ przekazanie plików do koszyka,
✔ zapis w zamówieniu.

Teraz czas na krok, który jest absolutnie widoczny dla klienta — czyli wyrenderowanie pól uploadu na stronie produktu WooCommerce.
Bez tego użytkownik w ogóle nie zobaczy, że może przesłać plik. 📁🙂

I właśnie temu służy plik:

📍 includes/dc-file-upload-frontend.php

 

🎯 Za co odpowiada ten komponent?

Ta klasa:

  • pobiera z meta dane wprowadzone w panelu admina (Krok 6)

  • renderuje każde pole uploadu w formularzu produktu

  • ustawia:

    • nazwy pól 🏷️

    • opisy 💬

    • dozwolone formaty 🔠

    • maksymalny rozmiar 🚫 MB

  • wstawia input <input type="file">

  • dodaje hidden <input type="hidden"> dla URL

  • generuje kontener miniatury

  • załącza skrypty uploadu ⚙️

Mówiąc krótko:

👉 to tu „materializujemy” pola, które admin skonfigurował wcześniej! 🔥

 

🧰 Hook integracyjny

Klasa korzysta z akcji:

add_action('dc_upload_fields', …)

co oznacza, że render pojawia się w konkretnym miejscu, wywołanym przez DC Product Form.

 

📦 Kod pliku dc-file-upload-frontend.php

<?php

if (!defined('ABSPATH')) exit;

class DC_File_Upload_Frontend {

    public function __construct() {
        add_action('dc_upload_fields', [$this,'render_fields'], 1);
        add_action('wp_enqueue_scripts', [$this, 'scripts']);
    }


    public function scripts() {

        wp_enqueue_script(
            'dc-upload-js',
            plugin_dir_url(__FILE__) . '../assets/js/dc-file-upload.js',
            ['jquery'],
            false,
            true
        );

        wp_enqueue_script(
            'dc-attach-js',
            plugins_url('../assets/js/dc-upload-attach.js', __FILE__),
            ['jquery'],
            false,
            true
        );

        wp_localize_script(
            'dc-upload-js',
            'dc_ajax',
            ['url' => admin_url('admin-ajax.php')]
        );
    }

    public function render_fields() {
        global $product;

        if ( ! $product || ! $product instanceof WC_Product ) {
            return;
        }

        $fields = get_post_meta($product->get_id(), '_dc_upload_fields', true);

        if ( ! $fields || ! is_array($fields) ) {
            return;
        }

        echo '<div id="dc-product-upload-fields" style="margin-bottom:20px;">';

        foreach ($fields as $index => $field) {

            $label      = isset($field['label'])      ? $field['label']      : '';
            $desc       = isset($field['desc'])       ? $field['desc']       : '';
            $extensions = isset($field['extensions']) ? $field['extensions'] : '';
            $max        = isset($field['max'])        ? $field['max']        : '';

            $accept_attr = '';
            if (!empty($extensions)) {
                $exts = array_filter(array_map('trim', explode(',', $extensions)));
                if (!empty($exts)) {
                    $accept_attr = ' accept=".' . esc_attr(implode(',.', $exts)) . '"';
                }
            }

            ?>
            <div class="dc-upload-field" data-index="<?php echo esc_attr($index); ?>" style="margin-bottom:15px;">

                <?php if (!empty($label)) : ?>
                    <label>
                        <strong><?php echo esc_html($label); ?></strong>
                    </label><br>
                <?php endif; ?>

                <?php if (!empty($desc)) : ?>
                    <p class="description" style="margin: 0 0 5px;">
                        <?php echo esc_html($desc); ?>
                    </p>
                <?php endif; ?>

                <input
                    type="file"
                    name="dc_upload_field_<?php echo esc_attr($index); ?>"
                    class="dc-upload-input"
                    <?php echo $accept_attr; ?>
                    data-max="<?php echo esc_attr($max); ?>"
                >

                <input
                    type="hidden"
                    name="dc_upload_url_<?php echo esc_attr($index); ?>"
                    data-label="<?php if(!empty($label)){ echo $label; } ?>" 
                >

                <div class="dc-upload-thumb"></div>

                <?php if (!empty($extensions) || !empty($max)) : ?>
                    <small class="dc-upload-hint" style="display:block; margin-top:3px; color:#666;">
                        <?php if (!empty($extensions)) : ?>
                            Dozwolone rozszerzenia: <?php echo esc_html($extensions); ?>.
                        <?php endif; ?>
                        <?php if (!empty($max)) : ?>
                            Maksymalny rozmiar: <?php echo esc_html($max); ?> MB.
                        <?php endif; ?>
                    </small>
                <?php endif; ?>

            </div>
            <?php
        }

        echo '</div>';
    }

}

 

🧠 Jak to wygląda w praktyce na froncie?

Po tym kroku na karcie produktu pojawia się sekcja z uploadami, np.:

Prześlij swoje pliki:

  • [ Wybierz JPG ]

  • opis pod spodem

  • miniaturka po uploadzie

klient widzi wszystko od razu, przed koszykiem ✔️

 

🎉 Co osiągnęliśmy po tym kroku?

✔ pola upload widoczne dla klienta
✔ dynamiczne opisy i limity
✔ obsługa wielu pól
✔ miniatury
✔ spójność z konfiguracją produktu

Teraz frontend jest gotowy.

 

🏁 Podsumowanie 🎉

Udało nam się przejść przez całą drogę – od pustego folderu, po w pełni gotowe rozszerzenie, które realnie rozwiązuje brak uploadu plików w formularzu produktu WooCommerce.

Dzięki krokom z tego tutoriala stworzyliśmy własną wtyczkę, która:

  • dodaje pola uploadu w edycji produktu 🛠️

  • pozwala klientowi przesłać plik bez przeładowania strony ⚡

  • zapisuje pliki w bezpiecznym katalogu /uploads/dc-uploads/

  • podłącza upload do koszyka WooCommerce 🛒

  • przenosi go do zamówienia, jako meta produktu 📦

  • wyświetla klikane linki „Pobierz plik” w panelu zamówienia 🔗

Co ważne — wszystko działa bez płatnych dodatków i ograniczeń.

Zbudowałeś kompletny, profesjonalny mechanizm uploadu na froncie WooCommerce.
Od strony UX, UI i backendu.

 

💡 Co możesz teraz zrobić?

  • pobrać gotową paczkę ZIP

  • zainstalować ją i porównać z własną implementacją

  • używać jej produkcyjnie, bez kodowania

  • przeanalizować strukturę i rozszerzyć ją pod swoje potrzeby

 

⬇ Linki do pobrania wtyczki

🎯 Pobierz gotowe rozszerzenie DC Woo File Upload (ZIP)
Pobierz z Design Cart

🎯 Zobacz repo GitHub z pełnym kodem źródłowym
Pobierz z Github

A jeśli chcesz iść dalej — możesz rozwinąć tę wtyczkę o:

  • wielokrotne uploady per jedno pole

  • walidację typów MIME

  • podgląd PDF

  • autousuwanie plików po X dniach

  • upload w koszyku

  • upload w checkout

  • upload w panelu klienta

🔗 możliwości są nieograniczone.