Kompleksowy formularz wiadomości WooCommerce – wszystko w jednym miejscu
Ten rozbudowany formularz wiadomości WooCommerce to coś więcej niż prosty formularz. To potężne narzędzie, które stworzyłem, by zautomatyzować kontakt z klientami i jednocześnie ułatwić zarządzanie zamówieniami oraz stanem magazynowym wariantów produktów. Całość działa jako shortcode, więc mogę go dodać w dowolnym miejscu na stronie – np. na stronie w panelu admina lub jako dedykowaną podstronę tylko dla mnie.
Shortcode, który należy wstawić to:
[customers_table_and_form]
Najlepszym miejscem na dodanie kodu nie jest plik functions.php
, tylko wtyczka Code Snippets. Dlaczego? Bo dzięki niej mogę łatwo zarządzać fragmentami kodu, włączać lub wyłączać je bez ryzyka zepsucia całej strony.
Jak działa i co oferuje formularz
Po pierwsze – wyciągam listę klientów, którzy w danym miesiącu kupili konkretny produkt. Ich dane, cena zakupu, data oraz adres e-mail są prezentowane w tabeli. Id produktu, który ma być filtrowany, mogę łatwo zmienić w tej linii kodu:
$product_id = 6257;
Wystarczy podmienić 6257 na dowolne ID mojego produktu w WooCommerce.
Po drugie – mam formularz do masowej wysyłki wiadomości e-mail. Mogę wysłać wiadomość do wszystkich klientów lub tylko do wybranych. Co więcej – formularz umożliwia dołączanie wielu załączników (max 5 MB każdy), co pozwala np. na wysłanie materiałów PDF lub grafik promocyjnych.
Całość zawiera także opcję testowego e-maila, który mogę wysłać do siebie, zanim wyślę do klientów. To pozwala mi sprawdzić formatowanie, poprawność treści i załączniki.
To właśnie dlatego ten formularz wiadomości WooCommerce jest tak funkcjonalny – bo nie muszę korzystać z zewnętrznych wtyczek mailingowych, żeby mieć podstawowy kontakt z klientem.
Obsługa stanów magazynowych produktów z wariantami
Po prawej stronie formularza znajduje się narzędzie do edycji stanów magazynowych. Mogę wybrać konkretny wariant (w tym przykładzie ID 7762 i 7763), wpisać nowy stan i zapisać. Jeśli dany produkt nie zarządza stanem magazynowym, otrzymam komunikat z błędem.
Kod pozwala zatem nie tylko komunikować się z klientami, ale też dynamicznie zarządzać wariantami produktów, co jest ogromnym ułatwieniem. Warianty można zmienić w tej sekcji:
echo '<option value="7762">Pakiet podstawowy</option>'; echo '<option value="7763">Pakiet rozszerzony</option>';
Po prostu podmieniam ID i nazwę zgodnie z moimi produktami.
Podsumowanie możliwości – co mogę dzięki temu zrobić
Ten formularz wiadomości WooCommerce daje mi pełną kontrolę nad:
- filtrowaniem zamówień z danego miesiąca,
- listą klientów z ich danymi,
- masową lub selektywną wysyłką maili,
- dodawaniem załączników,
- testowaniem wiadomości,
- edytowaniem stanów magazynowych wariantów,
- podglądem całkowitej wartości zamówień danego produktu.
Dzięki temu wszystko mam w jednym miejscu. Nie muszę przechodzić między różnymi zakładkami WooCommerce. Wszystko mogę obsłużyć z jednego shortcode’u – szybko i wygodnie.
Ten kod idealnie sprawdza się dla sprzedawców kursów, pakietów, konsultacji lub produktów sezonowych. Ułatwia kontakt z klientami i oszczędza mnóstwo czasu.
Dodaję go przez Code Snippets, włączam, wstawiam shortcode na stronie i gotowe. Tak właśnie działa dobry formularz wiadomości WooCommerce.
function woocommerce_customers_table_and_form_shortcode() { ob_start(); $message = ''; // Zmienna na komunikaty // ID produktu do sprawdzenia (główny produkt) $product_id = 6257; $current_month_start = date('Y-m-01 00:00:00'); $current_month_end = date('Y-m-t 23:59:59'); // Pobieranie zamówień z bieżącego miesiąca dla produktu o ID 6257 $args = array( 'limit' => -1, 'status' => 'completed', 'date_completed' => $current_month_start . '...' . $current_month_end, ); $orders = wc_get_orders($args); $customers = []; $total_value = 0; $lp = 1; // Zmienna do numeracji LP foreach ($orders as $order) { foreach ($order->get_items() as $item) { if ($item->get_product_id() == $product_id) { $customer = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(); $date = $order->get_date_completed()->date('Y-m-d'); $price = $item->get_total(); $total_value += $price; $customers[] = [ 'lp' => $lp, 'name' => $customer, 'date' => $date, 'price' => number_format($price, 2, ',', ' ') . ' zł', 'email' => $order->get_billing_email(), 'order_id' => $order->get_id() // ID zamówienia do identyfikacji ]; $lp++; } } } // Obsługa wysyłania formularza wiadomości (bez zmian) if (isset($_POST['submit_message'])) { $subject = sanitize_text_field($_POST['message_subject']); $content = wp_kses_post($_POST['message_content']); $send_option = $_POST['send_option']; $emails = []; if ($send_option === 'all') { foreach ($customers as $customer) { $emails[] = $customer['email']; } $message = '<div class="message-success"><span>Wiadomość została wysłana do wszystkich klientów!</span></div>'; } elseif ($send_option === 'specific') { $send_to = isset($_POST['send_to']) ? $_POST['send_to'] : []; foreach ($customers as $customer) { if (in_array($customer['order_id'], $send_to)) { $emails[] = $customer['email']; } } $email_count = count($emails); if ($email_count > 0) { $message = '<div class="message-success"><span>Wiadomość została wysłana do ' . $email_count . ' klientów!</span></div>'; } else { $message = '<div class="message-error"><span>Brak wybranych klientów do wysłania wiadomości.</span></div>'; } } if (!empty($emails)) { $attachments = []; if (!empty($_FILES['message_attachments'])) { foreach ($_FILES['message_attachments']['tmp_name'] as $key => $tmp_name) { if ($_FILES['message_attachments']['size'][$key] <= 5242880) { // Max 5MB $uploaded = wp_upload_bits($_FILES['message_attachments']['name'][$key], null, file_get_contents($tmp_name)); if (!$uploaded['error']) { $attachments[] = $uploaded['file']; } } } } $headers = array('Content-Type: text/html; charset=UTF-8'); foreach ($emails as $email) { wp_mail($email, $subject, $content, $headers, $attachments); } } } // Wyświetlanie komunikatu nad formularzem i tabelą klientów echo $message; echo '<div class="caly-formularz" style="display: flex; justify-content: space-between;">'; // Formularz po lewej stronie (bez zmian) echo '<div class="lewa-strona" style="flex: 1; padding-right: 20px;">'; echo '<div class="wiadomosc-testowa">'; echo '<div class="tytulik"><span class="tu-jest-naglowek">Test wysyłania wiadomości</span></div>'; echo '<form id="test-email-form">'; echo '<div class="do-testowania"><label class="do-testu" for="test_email">Adres email do testu:</label><br></div>'; echo '<input type="email" id="test_email" name="test_email" required><br>'; echo '<button type="button" id="send-test-email">Wyślij test</button>'; echo '</form>'; echo '</div>'; echo '<hr>'; ?> <div class="formularz"> <div class="naglowek-formularza"> <span class="tu-jest-naglowek">Formularz dla GLOW UP</span> </div> <form id="send-message-form" method="post" enctype="multipart/form-data"> <div class="do-testowania"> <label class="do-testu" for="test_email">Temat wiadomości:</label><br> </div> <input type="text" id="message_subject" name="message_subject" required><br><br> <div class="linia-oddzielajaca"> <hr class="linijka" style="border: 0; height: 1px; background-color: #ddd; width: 450px; margin: 0 auto;"><br> </div> <div class="do-testowania"> <label for="message_content">Treść wiadomości:</label><br> </div> <div class="tresc-wysylanej-wiadomosci"> <?php $content = ''; wp_editor($content, 'message_content', array('textarea_rows' => 6)); ?> <br> </div> <div class="linia-oddzielajaca"> <hr class="linijka" style="border: 0; height: 1px; background-color: #ddd; width: 450px; margin: 0 auto;"><br> </div> <div class="do-testowania"> <label for="send_option">Wyślij do:</label><br> <select id="send_option" name="send_option"> <option value="all">Wyślij do wszystkich</option> <option value="specific">Wyślij do pojedynczych klientów</option> </select><br><br> </div> <div id="customer_list" style="display:none;"> <div class="prawa-strona"> <div class="tabela-z-klientami"> <table border="1" cellpadding="10"> <?php if (!empty($customers)) : ?> <?php foreach ($customers as $customer) : ?> <tr class="zawartosc-komorek"> <td><?php echo esc_html($customer['lp']); ?></td> <td><?php echo esc_html($customer['name']); ?></td> <td><input type="checkbox" class="customer-checkbox" name="send_to[]" value="<?php echo esc_html($customer['order_id']); ?>"></td> </tr> <?php endforeach; ?> <?php else : ?> <tr> <td colspan="5">Aktualnie brak klientów do wyświetlenia.</td> </tr> <?php endif; ?> </table> </div> </div> </div> <div class="linia-oddzielajaca"> <hr class="linijka" style="border: 0; height: 1px; background-color: #ddd; width: 450px; margin: 0 auto;"><br> </div> <div class="do-testowania"> <label for="message_attachments">Załączniki (maksymalnie 5, max 5MB każdy):</label> </div> <input type="file" name="message_attachments[]" multiple accept=".jpg,.jpeg,.png,.pdf,.docx,.xlsx,.txt,.zip,.rar"><br><br> <ul id="attachment-list"></ul> <input class="guziczek-formularza" type="submit" name="submit_message" value="Wyślij wiadomość"> </form> </div> <script> // Obsługa załączników - wyświetlanie nazw plików document.querySelector('input[type="file"]').addEventListener('change', function() { const fileList = this.files; const attachmentList = document.getElementById('attachment-list'); attachmentList.innerHTML = ''; for (let i = 0; i < fileList.length; i++) { const fileItem = document.createElement('li'); fileItem.textContent = fileList[i].name; attachmentList.appendChild(fileItem); } }); // Pokazywanie/ukrywanie listy klientów w zależności od wyboru w polu select document.getElementById('send_option').addEventListener('change', function() { const customerList = document.getElementById('customer_list'); customerList.style.display = (this.value === 'specific') ? 'block' : 'none'; }); // AJAX do testowego wysyłania wiadomości document.getElementById('send-test-email').addEventListener('click', function() { const email = document.getElementById('test_email').value; const subject = document.getElementById('message_subject').value; const content = tinyMCE.get('message_content').getContent(); const formData = new FormData(); formData.append('action', 'send_test_email'); formData.append('email', email); formData.append('subject', subject); formData.append('content', content); const files = document.querySelector('input[name="message_attachments[]"]').files; for (let i = 0; i < files.length; i++) { formData.append('attachments[]', files[i]); } fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', body: formData }) .then(response => response.text()) .then(data => { alert('Wiadomość testowa została wysłana prawidłowo!'); }) .catch(error => { alert('Błąd przy wysyłaniu wiadomości testowej.'); }); }); </script> <?php echo '</div>'; // Koniec lewego kontenera // Ustawienie lokalizacji na polski setlocale(LC_TIME, 'pl_PL.UTF-8'); // Prawa strona – obsługa zmiany stanu magazynowego dla wariantów echo '<div class="prawa-strona" style="flex: 1;">'; $message = ''; // Obsługa formularza zmiany stanu magazynowego if (isset($_POST['new_stock']) && is_numeric($_POST['new_stock'])) { $new_stock = intval($_POST['new_stock']); if (isset($_POST['selected_variant']) && !empty($_POST['selected_variant'])) { $selected_variant_id = intval($_POST['selected_variant']); $variation = wc_get_product($selected_variant_id); if ($variation && $variation->managing_stock()) { $variation->set_stock_quantity($new_stock); $variation->save(); $message = '<div class="message-success"><span>Stan magazynowy dla wariantu ' . esc_html(get_the_title($selected_variant_id)) . ' został zaktualizowany!</span></div>'; } else { $message = '<div class="message-error"><span>Nie znaleziono wariantu lub nie zarządza on stanem magazynowym.</span></div>'; } } else { $message = '<div class="message-error"><span>Proszę wybrać wariant.</span></div>'; } } echo $message; // Formularz aktualizacji stanu magazynowego z wyborem wariantu echo '<div class="zmiana-stanu-magazynowego">'; echo '<form method="post">'; echo '<div class="wybierz-wariant">'; echo '<label for="selected_variant">Wybierz wariant:</label><br>'; echo '<select id="selected_variant" name="selected_variant">'; echo '</div>'; echo '<option value="">-- Wybierz wariant --</option>'; echo '<option value="7762">Pakiet podstawowy</option>'; echo '<option value="7763">Pakiet rozszerzony</option>'; echo '</select><br><br>'; // Pole do wpisania nowej wartości – ukryte do momentu wyboru wariantu echo '<div id="variant_update_fields" style="display:none;">'; echo '<label class="stany-magazynowe" for="new_stock">Wpisz nowy stan magazynowy:</label><br>'; echo '<input type="number" id="new_stock" name="new_stock" min="0" required><br>'; echo '<button class="guzik-do-stanow" type="submit">Zmień stan</button>'; echo '</div>'; echo '</form>'; // Skrypt JS pokazujący pole aktualizacji po wybraniu wariantu echo '<script> document.getElementById("selected_variant").addEventListener("change", function() { var fields = document.getElementById("variant_update_fields"); if (this.value !== "") { fields.style.display = "block"; } else { fields.style.display = "none"; } }); </script>'; // Wyświetlanie aktualnych stanów magazynowych dla obu wariantów $variant1 = wc_get_product(7762); $variant2 = wc_get_product(7763); $stock1 = $variant1 ? $variant1->get_stock_quantity() : 'N/A'; $stock2 = $variant2 ? $variant2->get_stock_quantity() : 'N/A'; echo '<p class="aktualny-stan-magazynowy">Aktualny stan magazynowy:</p>'; echo '<ul>'; echo 'Pakiet podstawowy: ' . esc_html($stock1) . ' miejsc<br>'; echo 'Pakiet rozszerzony: ' . esc_html($stock2) . ' miejsc'; echo '</ul>'; echo '</div>'; // Koniec prawego kontenera echo '<div class="informacja-o-miesiacu">'; echo '<span class="lista-klientow-tytul">Lista klientów, którzy kupili produkt w miesiącu:</span><b><span class="miesiac"> ' . strftime('%B') . '</b></span>'; echo '</div>'; if (!empty($customers)) { echo '<div class="tabela-z-klientami">'; echo '<table border="1" cellpadding="10">'; echo '<tr class="nazwy-komorek"><th>LP</th><th>Imię i Nazwisko</th><th>Data Zakupu</th><th>Cena</th></tr>'; foreach ($customers as $customer) { echo '<tr class="zawartosc-komorek">'; echo '<td>' . esc_html($customer['lp']) . '</td>'; echo '<td>' . esc_html($customer['name']) . '</td>'; echo '<td>' . esc_html($customer['date']) . '</td>'; echo '<td>' . esc_html($customer['price']) . '</td>'; echo '</tr>'; } echo '<tr class="podsumowanie">'; echo '<td colspan="3"><strong>Łączna wartość zamówień:</strong></td>'; echo '<td><strong>' . number_format($total_value, 2, ',', ' ') . ' zł</strong></td>'; echo '</tr>'; echo '</table>'; } else { echo '<p>Aktualnie brak sprzedaży.</p>'; } echo '</div>'; echo '</div>'; // Koniec kontenera flex return ob_get_clean(); } // AJAX do wysyłania testowego emaila (bez zmian) function send_test_email_ajax() { $email = sanitize_email($_POST['email']); $subject = sanitize_text_field($_POST['subject']); $content = wp_kses_post($_POST['content']); $attachments = []; if (!empty($_FILES['attachments'])) { foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) { if ($_FILES['attachments']['size'][$key] <= 5242880) { // Max 5MB $uploaded = wp_upload_bits($_FILES['attachments']['name'][$key], null, file_get_contents($tmp_name)); if (!$uploaded['error']) { $attachments[] = $uploaded['file']; } } } } $headers = array('Content-Type: text/html; charset=UTF-8'); wp_mail($email, $subject, $content, $headers, $attachments); echo 'success'; wp_die(); } add_action('wp_ajax_send_test_email', 'send_test_email_ajax'); add_action('wp_ajax_nopriv_send_test_email', 'send_test_email_ajax'); add_shortcode('customers_table_and_form', 'woocommerce_customers_table_and_form_shortcode');