- ❗Wymagania ⚙️
- 🧩 Krok 1 – Struktura wtyczki 📂
- 🔧 Krok 2 – Obsługa uploadu w frontendzie 🚀
- 🔧 Krok 3 – Implementacja w Add to Cart AJAX ⚡
- 🔧 Krok 4 – Dodawanie pól upload w panelu produktu (Admin) 🧰
- 🔧 Krok 5 – Zapis w metadanych zamówienia 🧾
- 🔧 Krok 6 – Upload + walidacja (backend) 🛡️
- 🔧 Krok 7 – Plik główny wtyczki 📦
- 🔧 Krok 8 – Wyświetlanie pól uploadu na froncie (Formularz produktu) 🧲
- 🏁 Podsumowanie 🎉
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
-
użytkownik wybiera plik
-
JS zbiera ten plik i pakuje w
FormData -
wysyła żądanie AJAX →
admin-ajax.php💥 -
backend zapisuje plik na serwerze
-
backend zwraca finalny URL
-
JS wstawia ten URL do ukrytego pola
-
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)
🎯 Zobacz repo GitHub z pełnym kodem źródłowym
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.
Sklepy internetowe Woocommerce
Sklepy internetowe Opencart
Sklepy internetowe Prestashop
Sklepy internetowe Magento
Strony internetowe Joomla!
Strony Internetowe Wordpress


