Dobre Praktyki Programowania Wtyczek WordPress

Pisanie wtyczek do WordPressa i WooCommerce wymaga zachowania rygorystycznych standardów inżynieryjnych. Nieprzemyślany kod może prowadzić do konfliktów z innymi wtyczkami, podatności bezpieczeństwa (SQLi, XSS, CSRF), a także drastycznego obciążenia bazy danych i pamięci RAM serwera.

W tym przewodniku omawiamy dobre praktyki programistyczne, standardy architektury PHP (PSR-12) oraz wzorce projektowe stosowane w ekosystemie Pluger i Entriso.


1. Architektura i Separacja Odpowiedzialności (Clean Architecture)

Jednym z najczęstszych błędów początkujących deweloperów WordPressa jest umieszczanie całej logiki biznesowej, rejestracji hooków, kodu HTML i zapytań SQL w jednym, gigantycznym pliku głównym (monolithic plugin file).

Rekomendowana struktura modułowa

Terminal / Kod
my-custom-plugin/
├── assets/                  # Zasoby frontendowe (CSS, JS, obrazy)
│   ├── css/
│   └── js/
├── includes/                # Logika biznesowa i klasy PHP
│   ├── Admin/               # Panele administracyjne, ustawienia WP Admin
│   │   ├── SettingsPage.php
│   │   └── AdminNotices.php
│   ├── Api/                 # Punkty końcowe REST API i webhooki
│   │   └── RestEndpoints.php
│   ├── Core/                # Inicjalizacja, ładowanie hooków, kontener DI
│   │   ├── Plugin.php
│   │   └── Activator.php
│   └── Services/            # Usługi biznesowe (np. integracja z Pluger)
│       └── LicensingService.php
├── languages/               # Pliki tłumaczeń (.pot, .po, .mo)
├── templates/               # Czyste szablony widoków (HTML/PHP)
├── composer.json            # Autoloader PSR-4
├── my-custom-plugin.php     # Plik wejściowy (bootstrap)
└── readme.txt               # Specyfikacja WordPress.org

Wykorzystanie Autoloadera PSR-4

Zamiast dziesiątek instrukcji require_once, używaj standardu Composer PSR-4:

Terminal / Kod
{
  "name": "vendor/my-custom-plugin",
  "autoload": {
    "psr-4": {
      "MyVendor\\MyPlugin\\": "includes/"
    }
  }
}

2. Bezpieczeństwo: Walidacja, Sanityzacja i Escaping Danych

Zasada numer jeden bezpiecznego programowania w WordPressie brzmi: Nigdy nie ufaj danym wejściowym i zawsze zabezpieczaj dane wyjściowe.

A. Sanityzacja danych wejściowych (Input Sanitization)

Przed zapisaniem jakichkolwiek wartości z $_POST, $_GET lub $_REQUEST:

Terminal / Kod
// 1. Zawsze zdejmuj automatyczne slashe WordPressa (wp_unslash)
$raw_email = isset($_POST['user_email']) ? wp_unslash($_POST['user_email']) : '';

// 2. Sanityzuj odpowiednio do typu danych
$clean_email = sanitize_email($raw_email);
$clean_text  = sanitize_text_field(wp_unslash($_POST['user_name'] ?? ''));
$clean_int   = absint($_POST['item_id'] ?? 0);
$clean_html  = wp_kses_post(wp_unslash($_POST['rich_content'] ?? ''));

B. Weryfikacja Uprawnień i Nonce (CSRF Protection)

Każdy formularz i żądanie AJAX musi weryfikować token bezpieczeństwa (Nonce) oraz uprawnienia zalogowanego użytkownika (capabilities):

Terminal / Kod
public function handle_save_settings(): void {
    // 1. Sprawdź Nonce
    if (!isset($_POST['my_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['my_nonce'])), 'my_save_action')) {
        wp_die(__('Błąd autoryzacji tokenu bezpieczeństwa.', 'my-plugin'), 403);
    }

    // 2. Sprawdź uprawnienia użytkownika
    if (!current_user_can('manage_options')) {
        wp_die(__('Brak wystarczających uprawnień administracyjnych.', 'my-plugin'), 403);
    }

    // 3. Bezpieczne wykonanie logiki
    update_option('my_plugin_option', sanitize_text_field(wp_unslash($_POST['setting_val'] ?? '')));
}

C. Escaping danych wyjściowych (Output Escaping)

Każda zmienna drukowana do kodu HTML musi być przefiltrowana w momencie wyświetlania:

  • esc_html($text) — dla zwykłego tekstu wewnątrz znaczników HTML.
  • esc_attr($attr) — dla wartości atrybutów HTML (np. value="", placeholder="").
  • esc_url($url) — dla adresów URL w atrybutach href lub src.
  • esc_js($script) — dla inline JavaScript.

3. Optymalizacja Wydajności i Pamięci RAM

Częstym problemem wtyczek jest spowalnianie czasu ładowania strony (TTFB) oraz psucie wskaźników Core Web Vitals (LCP, INP, CLS).

A. Dynamiczne kolejkowanie skryptów i styli (Conditional Enqueueing)

Nigdy nie ładuj skryptów CSS i JS na każdej podstronie, jeśli wtyczka jest używana tylko na wybranych podstronach (np. tylko w koszyku WooCommerce lub na dedykowanej stronie w WP Admin):

Terminal / Kod
add_action('wp_enqueue_scripts', function () {
    // Ładuj skrypty tylko na stronach produktów WooCommerce
    if (is_product()) {
        wp_enqueue_script(
            'my-plugin-script',
            plugin_dir_url(__FILE__) . 'assets/js/product-feature.js',
            ['jquery'],
            '1.0.0',
            true // ładuj w stopce (defer/async)
        );
        wp_enqueue_style(
            'my-plugin-style',
            plugin_dir_url(__FILE__) . 'assets/css/product-feature.css',
            [],
            '1.0.0'
        );
    }
});

B. Wykorzystanie Transients API (Buforowanie Zapytań i API)

Gdy wtyczka odpytuje zewnętrzny serwer (np. serwer licencji Pluger) lub wykonuje ciężkie kalkulacje, wynik należy zawsze buforować:

Terminal / Kod
public function get_external_data(): array {
    $cache_key = 'my_plugin_cached_payload';
    $cached = get_transient($cache_key);

    if (false !== $cached) {
        return $cached;
    }

    // Wykonaj zapytanie tylko gdy brak cache
    $response = wp_remote_get('https://api.pluger.pl/v1/resource');
    if (is_wp_error($response)) {
        return [];
    }

    $data = json_decode(wp_remote_retrieve_body($response), true);

    // Zapisz w pamięci podręcznej na 6 godzin
    set_transient($cache_key, $data, 6 * HOUR_IN_SECONDS);

    return $data;
}

4. Hooki i Filtry: Kiedy i jak rozszerzać WordPressa

WordPress opiera się na wzorcu Publish-Subscribe (Event-Driven Architecture).

Typ HookaFunkcjaPrzeznaczenie
Action Hookadd_action($tag, $callback)Wykonanie logiki w określonym momencie cyklu życia WP (np. init, template_redirect, admin_menu).
Filter Hookadd_filter($tag, $callback)Modyfikacja i zwrócenie przekazanej wartości (np. the_content, woocommerce_product_price).

Złota zasada filtrów: Filtr zawsze musi zwracać wartość (return $value;). Przerwanie łańcucha filtra bez return uszkodzi działanie WordPressa lub innych wtyczek.


5. Dobre praktyki licencjonowania i automatycznych aktualizacji

Tworząc wtyczki komercyjne PRO, kluczowe jest wdrożenie bezpiecznego i bezawaryjnego systemu aktualizacji:

  1. Brak twardych blokad: Błąd połączenia z serwerem licencji nie może wyłączać działania sklepu klienta (Grace Period / Cache statusu licencji na min. 12-24h).
  2. Standardowy Auto-Updater: Wtyczka powinna integrować się z natywnym filtrem site_transient_update_plugins, aby administrator mógł aktualizować ją jednym kliknięciem z poziomu kokpitu WP.
  3. One-Click Connect: Zamiast uciążliwego przepisywania kluczy, udostępnij autoryzację jednym kliknięciem z platformą Pluger.pl.

Podsumowanie i Checklist Inżynieryjny

  • Używam przestrzeni nazw (Namespaces) i standardu PSR-12.
  • Wszystkie zmienne wejściowe są przepuszczane przez wp_unslash() oraz odpowiedni sanitize_*().
  • Każde żądanie formularza i AJAX jest zabezpieczone przez wp_verify_nonce() oraz current_user_can().
  • Wszystkie dane wyjściowe są zabezpieczane przez esc_html(), esc_attr() lub esc_url().
  • Skrypty JS i style CSS ładują się tylko na tych stronach, na których są faktycznie niezbędne.
  • Ciężkie zapytania i odwołania HTTP są buforowane przez Transients API.

Szukasz gotowej infrastruktury dla swojej wtyczki? Zapoznaj się z architekturą licencjonowania Pluger lub dołącz do programu współpracy deweloperskiej.