modul 00 / arhitectura WordPress pentru dezvoltatori
ce se întâmplă la o cerere WordPress
Ca dezvoltator WordPress, treci de la „folosesc dashboard-ul” la „înțeleg și modific ce se întâmplă dedesubt”. Acest curs urmează resursele oficiale developer.wordpress.org (Theme, Plugin & Block Editor Handbook). Presupune că știi deja administrarea de bază (cursul WordPress Admin) — de aici construim partea de cod.
WordPress e PHP + MySQL
WordPress e o aplicație PHP care stochează conținutul într-o bază de date MySQL și generează HTML la fiecare cerere. Nu servește fișiere statice — construiește pagina dinamic de fiecare dată (sau din cache). Înțelegerea acestui flux e fundamentul a tot ce urmează.
ciclul unei cereri (request lifecycle)
- Vizitatorul cere o adresă (ex: /blog/articol-nou).
- Serverul rulează index.php → încarcă wp-load.php → wp-config.php (config + conexiunea DB).
- Se încarcă nucleul (core), apoi pluginurile active, apoi tema activă (functions.php).
- WP parsează URL-ul (rewrite rules) și îl transformă într-o interogare (query): ce conținut se cere?
- Rulează main query-ul către baza de date și încarcă postările potrivite.
- Template hierarchy (modulul 3) alege ce fișier de temă randează rezultatul.
- Tema generează HTML-ul, care se trimite browserului.
Pe tot parcursul, WordPress declanșează zeci de hooks (modulele 1–2) — puncte în care codul tău se poate „agăța” ca să modifice comportamentul. Aceasta e cheia extensibilității: nu editezi nucleul, ci te conectezi la el.
structura fișierelor
| Locație | Ce conține |
| wp-config.php | Configurarea: date DB, chei de securitate, constante (WP_DEBUG etc.) |
| wp-admin/ & wp-includes/ | Nucleul WordPress — NU se modifică niciodată (se suprascrie la update) |
| wp-content/themes/ | Temele (aspectul & template-urile) |
| wp-content/plugins/ | Pluginurile (funcționalitatea) |
| wp-content/uploads/ | Fișierele încărcate (media) |
regula de aur
Nu modifica niciodată nucleul (core) — wp-admin/ și wp-includes/ se suprascriu complet la fiecare actualizare, deci orice modificare acolo dispare (și e o gaură de securitate). Tot codul tău trăiește în teme (aspect) și pluginuri (funcționalitate), conectat prin hooks. Această disciplină e ce face WordPress mentenabil și actualizabil.
modul 00 / arhitectura WordPress pentru dezvoltatori
baza de date, temă vs plugin & mediul de dezvoltare
baza de date pe scurt
WordPress stochează totul în tabele MySQL (prefix implicit wp_):
| Tabel | Ce ține |
| wp_posts | Postări, pagini, și orice tip de conținut (inclusiv CPT-uri) + revizii, meniuri |
| wp_postmeta | Metadate pentru postări (câmpuri personalizate) — perechi cheie/valoare |
| wp_options | Setări globale ale site-ului (nume, URL, opțiuni de plugin) |
| wp_users / wp_usermeta | Utilizatorii și datele lor |
| wp_terms / wp_term_taxonomy / wp_term_relationships | Categorii, etichete, taxonomii și legăturile lor cu postările |
Rar scrii SQL direct — WordPress oferă funcții (API) care fac interogările pentru tine în siguranță (WP_Query, get_post_meta, get_option). Dar e util să știi unde trăiesc datele.
temă vs plugin — unde pui codul?
Întrebarea de arhitectură care apare mereu. Regula: aspectul în temă, funcționalitatea în plugin.
| Temă | Plugin |
| Rol | Cum ARATĂ site-ul (prezentare, template-uri) | Ce FACE site-ul (funcționalitate) |
| Exemple | Layout, CSS, template-uri de pagină, the loop | CPT-uri, shortcode-uri, integrări, logică de business |
| La schimbarea temei | Se pierde (e legat de temă) | Rămâne (independent de temă) |
Test practic: „dacă schimb tema, vreau să păstrez asta?” Dacă da (ex: un Custom Post Type „Produse”) → plugin. Dacă e pur vizual → temă. Greșeala clasică de începător: pui CPT-uri în functions.php-ul temei și le pierzi la schimbarea temei.
child themes — nu edita tema-părinte
Ca și cu nucleul: dacă modifici o temă cumpărată/oficială direct, pierzi schimbările la update. Soluția: un child theme — o temă mică ce moștenește tema-părinte și îi suprascrie doar ce vrei. Sigur la actualizări.
mediul de dezvoltare local
- Dezvolți local (pe calculatorul tău), nu direct în producție. Unelte: LocalWP, DevKinsta, XAMPP, Docker, sau wp-env (oficial).
- Activează WP_DEBUG în wp-config.php (define('WP_DEBUG', true);) — afișează erorile PHP, esențial în dezvoltare (dezactivat în producție!).
- Flux: dezvolți local → testezi → duci pe staging → apoi producție. Niciodată „coding live” pe site-ul clientului.
de reținut
Trei discipline definesc dezvoltatorul WP profesionist: nu atinge nucleul, nu edita direct teme-părinte (folosește child themes), și pune funcționalitatea în pluginuri, aspectul în teme. Plus: dezvoltă local cu WP_DEBUG pornit. Restul cursului îți dă uneltele (hooks, template hierarchy, WP_Query, CPT) — dar aceste principii sunt cadrul în care le folosești corect.
modul 01 / hooks: actions
ce sunt hooks & cum funcționează actions
Hooks sunt cel mai important concept din dezvoltarea WordPress. Toată extensibilitatea — pluginuri, teme, modificări — se bazează pe ele. Dacă înțelegi hooks, înțelegi WordPress.
ideea centrală
Pe măsură ce WordPress rulează, „strigă” în anumite momente: „acum inițializez”, „acum afișez head-ul”, „acum salvez o postare”. Aceste momente se numesc hooks. Codul tău poate „asculta” un hook și rula atunci — fără să modifici nucleul. E ca o extensie care se conectează în punctele prevăzute.
Există două tipuri:
- Actions — „când se întâmplă X, fă ceva” (execuți cod la un moment dat). Acest modul.
- Filters — „când WP procesează valoarea Y, modific-o înainte de folosire” (transformi date). Modulul următor.
add_action — conectarea la un action
add_action( 'nume_hook', 'functia_mea' );
function functia_mea() {
// codul care rulează când se declanșează hook-ul
}
add_action spune: „când WordPress ajunge la nume_hook, rulează functia_mea”. Funcția ta se numește callback. Pui asta în functions.php (temă) sau într-un plugin.
exemplu real
Adaugi Google Analytics în <head>-ul site-ului fără să editezi header.php:
add_action( 'wp_head', 'adauga_analytics' );
function adauga_analytics() {
echo '<script src="..."></script>';
}
Hook-ul wp_head se declanșează când WP construiește <head>. Codul tău se injectează exact acolo — curat, actualizabil, fără a atinge tema.
hook-uri de tip action frecvente
| Hook | Când se declanșează |
| init | WP e inițializat — locul pentru înregistrarea CPT-urilor, taxonomiilor |
| wp_enqueue_scripts | Momentul corect pentru a încărca CSS/JS pe front-end (modulul 3) |
| wp_head / wp_footer | Injectare în <head> / înainte de </body> |
| save_post | La salvarea unei postări (declanșezi acțiuni: notificări, cache) |
| admin_menu | Adăugarea de pagini în meniul de administrare |
| after_setup_theme | Configurarea suportului temei (add_theme_support) |
de reținut
Modelul mental: WordPress oferă puncte de conectare (hooks); tu îți atașezi cod la ele fără a schimba nucleul. Un plugin întreg e, în esență, o colecție de callback-uri agățate în hook-urile potrivite. Alegerea hook-ului corect (momentul corect din ciclul de viață) e jumătate din pricepere — de aceea tabelul de mai sus merită memorat.
modul 01 / hooks: actions
prioritate, argumente & sintaxa modernă
prioritatea — ordinea callback-urilor
Mai multe funcții se pot agăța de același hook. Prioritatea (număr, implicit 10) decide ordinea: numere mici rulează primele, mari — ultimele.
add_action( 'init', 'functia_mea', 5 ); // rulează mai devreme
add_action( 'init', 'alta_functie', 20 ); // rulează mai târziu
Util când ordinea contează (ex: vrei să rulezi după ce alt plugin s-a inițializat → prioritate mai mare).
argumentele — a patra valoare
Unele hook-uri trimit date către callback. Al patrulea parametru al add_action spune câte argumente primești (implicit 1).
add_action( 'save_post', 'la_salvare', 10, 3 );
// hook callback prio nr_args
function la_salvare( $post_id, $post, $update ) {
// primești ID-ul, obiectul postare, și dacă e update
}
Trebuie să ceri numărul corect de argumente ca să le primești — altfel funcția ta nu le vede. Documentația fiecărui hook îți spune ce argumente oferă.
funcții anonime & clase
// closure (funcție anonimă) — modern, compact:
add_action( 'init', function() {
// cod inline
} );
// metodă dintr-o clasă (în pluginuri OOP):
add_action( 'init', array( $this, 'metoda_mea' ) );
În cod modern (mai ales pluginuri organizate obiectual), vezi des closures și metode de clasă drept callback-uri. Principiul rămâne identic.
do_action — propriile tale hook-uri
Poți crea propriile puncte de extensie, ca alți dezvoltatori (sau tu însuți) să se conecteze la pluginul tău:
// în pluginul tău, unde vrei un punct de extensie:
do_action( 'plugin_meu_dupa_salvare', $date );
Așa devine codul tău extensibil — exact filozofia WordPress: nu modifici, te conectezi.
remove_action & depanare
remove_action( 'hook', 'functie', prioritate ) — dezactivează un callback (ex: dintr-un plugin/temă) — dar trebuie să potrivești exact hook-ul, funcția și prioritatea. Dacă un hook „nu funcționează”: verifică numele exact, prioritatea, numărul de argumente, și momentul (te agăți prea târziu, după ce hook-ul deja a rulat?). Cele mai multe bug-uri de hooks sunt probleme de timing sau de nume greșit.
modul 02 / hooks: filters
filters: modificarea valorilor
Dacă actions execută cod la un moment dat, filters modifică o valoare înainte ca WordPress s-o folosească. Regula de aur care le separă: un filtru primește o valoare, o schimbă și o returnează. Un action nu returnează nimic.
add_filter — sintaxa
add_filter( 'nume_filtru', 'functia_mea' );
function functia_mea( $valoare ) {
// modifici $valoare...
return $valoare; // OBLIGATORIU returnezi!
}
Greșeala #1 la filtre: să uiți return. Fără el, returnezi null și ștergi valoarea (titlul dispare, conținutul se golește). Un filtru trebuie mereu să returneze o valoare — modificată sau nu.
exemplu real
Adaugi un text după fiecare conținut de postare:
add_filter( 'the_content', 'adauga_semnatura' );
function adauga_semnatura( $content ) {
if ( is_single() ) {
$content .= '<p class="sig">Scris de echipa noastră.</p>';
}
return $content;
}
Filtrul the_content primește conținutul postării; îi adaugi ceva și-l returnezi. WordPress folosește versiunea ta la afișare. Nu ai atins niciun template.
action vs filter — comparație directă
| Action | Filter |
| Scop | Execută cod la un eveniment | Modifică o valoare |
| Primește date? | Uneori (context) | Mereu — valoarea de filtrat |
| Returnează? | Nu | DA — obligatoriu |
| Verb tipic | „fă / afișează / salvează” | „schimbă / ajustează / adaugă la” |
filtre comune
| Filtru | Ce modifică |
| the_content | Conținutul unei postări înainte de afișare |
| the_title | Titlul unei postări |
| excerpt_length / excerpt_more | Lungimea și textul „citește mai mult” al rezumatului |
| body_class | Clasele CSS adăugate pe <body> |
| wp_nav_menu_items | Elementele dintr-un meniu |
| upload_mimes | Tipurile de fișiere permise la încărcare |
de reținut
Testul mental pentru „action sau filter?”: întorc o valoare? Dacă WordPress îmi dă ceva de modificat și așteaptă înapoi rezultatul → filter (nu uita return!). Dacă vreau doar să rulez cod la un moment dat → action. Ambele folosesc același mecanism de hooks; diferă contractul (returnezi sau nu).
modul 02 / hooks: filters
prioritate, propriile filtre & aplicarea lor
prioritate & argumente (ca la actions)
Filtrele au aceiași parametri: prioritate și număr de argumente.
add_filter( 'the_title', 'scurteaza_titlu', 10, 2 );
function scurteaza_titlu( $title, $post_id ) {
// primești titlul ȘI ID-ul postării (2 argumente)
return $title;
}
Important: chiar dacă primești mai multe argumente, filtrezi și returnezi doar primul (valoarea principală). Celelalte sunt context (read-only). Aici, returnezi titlul, nu ID-ul.
crearea propriilor filtre — apply_filters
Ca și do_action, poți oferi puncte de personalizare în pluginul tău — dar pentru valori:
// în pluginul tău:
$mesaj = apply_filters( 'plugin_meu_mesaj', 'Mesaj implicit' );
echo $mesaj;
Alți dezvoltatori pot apoi schimba „Mesaj implicit” cu un add_filter( 'plugin_meu_mesaj', ... ), fără a-ți edita pluginul. Pluginurile bine făcute expun multe filtre — sunt „reglabile” fără hack-uri.
când folosești filtre vs când editezi template-uri
- Modifici o valoare generată de WP (titlu, conținut, clase) → filtru (curat, funcționează peste tot, supraviețuiește la update-ul temei).
- Schimbi structura HTML a unei pagini întregi → template în temă (modulul 3).
- Regula: preferă filtrele pentru ajustări punctuale; nu suprascrie un template întreg doar ca să schimbi o linie.
capcane frecvente
- Lipsa return — cea mai comună; golește valoarea.
- Efecte laterale grele într-un filtru (interogări lente la fiecare titlu) — filtrele rulează des; ține-le ușoare.
- Modificarea altui argument decât primul — degeaba; doar primul (returnat) contează.
- Filtru agățat prea târziu/devreme — verifică momentul în ciclul de viață.
actions + filters = WordPress
Împreună, cele două tipuri de hooks explică practic toată dezvoltarea WordPress. Actions îți dau momente în care să acționezi; filters îți dau valori pe care să le modelezi. Fiecare plugin și temă bine construite sunt colecții de callback-uri agățate inteligent. Odată ce gândești „la ce hook mă conectez și ce fac/modific acolo”, gândești ca un dezvoltator WordPress.
modul 03 / anatomia temei & template hierarchy
structura unei teme & template hierarchy
O temă WordPress controlează cum arată site-ul. E o colecție de fișiere PHP (template-uri) plus CSS/JS. Înțelegerea structurii și a template hierarchy e centrală pentru orice personalizare de aspect.
fișierele esențiale ale unei teme
| Fișier | Rol |
| style.css | Obligatoriu — conține antetul temei (nume, autor, versiune) în comentariu + CSS |
| index.php | Obligatoriu — template-ul de rezervă (fallback) pentru orice |
| functions.php | „Motorul” temei: hooks, suport de funcții, enqueue (nu e template, e cod) |
| header.php / footer.php | Partea de sus / jos, incluse în toate paginile |
| single.php | Afișarea unei singure postări |
| page.php | Afișarea unei pagini |
| archive.php | Liste (arhive de categorie, autor, dată) |
| 404.php | Pagina „nu s-a găsit” |
Antetul din style.css e ce face WordPress să recunoască folderul drept temă:
/*
Theme Name: Tema Mea
Author: Romeo
Version: 1.0
*/
template hierarchy — cum alege WP fișierul
Când se cere o pagină, WordPress caută template-ul potrivit de la specific la general și îl folosește pe primul găsit. Exemple:
- O postare individuală: single-{tip}.php → single.php → singular.php → index.php.
- O pagină: page-{slug}.php → page-{id}.php → page.php → singular.php → index.php.
- O categorie: category-{slug}.php → category.php → archive.php → index.php.
De aceea index.php e obligatoriu: e plasa de siguranță finală. Iar puterea vine din specificitate: vrei un layout aparte doar pentru categoria „Știri”? Creezi category-stiri.php și WP îl folosește automat, doar acolo.
de reținut
Template hierarchy e „harta de decizie” a temei: pentru fiecare tip de cerere, WP coboară de la cel mai specific fișier la cel mai general. Nu memora toată diagrama — înțelege principiul (specific → general → index.php) și consultă diagrama oficială când ai nevoie. Odată ce o „vezi”, știi exact ce fișier să creezi pentru orice personalizare.
modul 03 / anatomia temei & template hierarchy
template tags, partials & enqueue corect
template tags — funcțiile temei
Template tags sunt funcții WordPress pe care le pui în template-uri ca să afișezi conținut dinamic:
the_title(); // afișează titlul postării curente
the_content(); // afișează conținutul
the_permalink(); // URL-ul postării
bloginfo('name'); // numele site-ului
get_header(); // include header.php
Convenție utilă: funcțiile the_* afișează (echo) direct; cele get_* returnează valoarea (ca s-o folosești în cod). Ex: the_title() afișează, get_the_title() returnează.
includerea partials (părți refolosibile)
get_header(); // header.php
get_footer(); // footer.php
get_sidebar(); // sidebar.php
get_template_part( 'template-parts/card' ); // partial propriu refolosibil
get_template_part e cheia pentru DRY în teme: extragi o bucată repetată (un card de articol) într-un fișier și o incluzi oriunde. Modificarea într-un loc se reflectă peste tot.
conditional tags — logică în template
if ( is_single() ) { ... } // suntem pe o postare?
if ( is_home() ) { ... } // pagina de blog?
if ( is_page('contact') ) { ... } // pagina „contact”?
if ( is_archive() ) { ... } // o listă/arhivă?
Îți permit să afișezi lucruri diferite după context, într-un template comun.
enqueue — modul CORECT de a încărca CSS & JS
Greșeala clasică: să pui <link> și <script> direct în header.php. Modul corect e prin wp_enqueue, în functions.php, agățat de hook-ul wp_enqueue_scripts:
add_action( 'wp_enqueue_scripts', 'incarca_assets' );
function incarca_assets() {
wp_enqueue_style( 'tema-stil', get_stylesheet_uri() );
wp_enqueue_script( 'tema-js',
get_template_directory_uri() . '/js/app.js',
array('jquery'), '1.0', true ); // dependențe, versiune, în footer
}
- WordPress gestionează dependențele (încarcă jQuery înainte de scriptul tău), evită dublurile, și pune versiuni pentru cache-busting.
- Punând scriptul „în footer” (ultimul parametru true) — pagina se randează mai repede.
- Alte pluginuri/teme pot depinde de sau modifica ce ai încărcat — imposibil cu <script> hardcodat.
de reținut
Nu hardcoda niciodată assets în template. Folosește wp_enqueue_style/script pe hook-ul wp_enqueue_scripts — e standardul care face temele compatibile, cache-abile și fără conflicte. Combinat cu template tags (afișare dinamică), template parts (DRY) și conditional tags (logică pe context), ai tot ce trebuie ca să construiești o temă profesionistă.
modul 04 / The Loop & interogări WP_Query
The Loop — inima afișării
The Loop (bucla) e mecanismul prin care WordPress parcurge postările găsite de interogare și le afișează. Aproape orice template conține o buclă. E conceptul care leagă baza de date de HTML-ul afișat.
anatomia buclei standard
if ( have_posts() ) :
while ( have_posts() ) : the_post();
// pentru FIECARE postare, afișezi:
the_title();
the_content();
endwhile;
else :
echo 'Nicio postare găsită.';
endif;
- have_posts() — mai sunt postări de afișat? (condiția buclei)
- the_post() — trece la postarea următoare și o pregătește ca „postarea curentă” (setează datele pentru template tags).
- În interiorul buclei, template tags precum the_title() se referă automat la postarea curentă.
main query — interogarea automată
WordPress rulează automat o interogare principală (main query) pe baza URL-ului: pe o categorie, aduce postările acelei categorii; pe pagina de blog, ultimele postări. Bucla standard din template afișează exact rezultatele acestei interogări — nu trebuie să o construiești tu, doar s-o parcurgi.
de ce contează
- Fiecare template de listă/singular conține o buclă — e tiparul universal de afișare.
- Înțelegând bucla, poți controla exact ce și cum se afișează pentru fiecare postare.
- Cazurile avansate (liste multiple pe o pagină, conținut personalizat) cer bucle personalizate — lecția următoare.
de reținut
Bucla e puntea dintre date și afișare: interogarea (main query sau una proprie) găsește postările, iar bucla le parcurge una câte una, folosind template tags pe „postarea curentă”. Odată ce recunoști tiparul if have_posts → while have_posts → the_post, îl vei vedea în orice temă și îl vei putea adapta.
modul 04 / The Loop & interogări WP_Query
WP_Query & pre_get_posts
Când vrei alte postări decât cele din main query — o listă de „articole recomandate”, produse dintr-o categorie, un slider — construiești o interogare personalizată cu WP_Query.
WP_Query — o buclă secundară
$q = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 5,
'category_name' => 'stiri',
'orderby' => 'date',
'order' => 'DESC',
) );
if ( $q->have_posts() ) :
while ( $q->have_posts() ) : $q->the_post();
the_title();
endwhile;
wp_reset_postdata(); // OBLIGATORIU după o buclă proprie!
endif;
- Îi dai un array de argumente (ce postări vrei) și obții un obiect cu propriile have_posts() / the_post().
- WP_Query acceptă zeci de parametri: tip, taxonomie, meta (câmpuri personalizate), autor, dată, număr, ordonare, paginare.
wp_reset_postdata() — nu-l uita
După o buclă WP_Query proprie, trebuie să apelezi wp_reset_postdata(). Altfel, „postarea curentă” rămâne setată greșit, iar restul paginii (main query) se strică. Cea mai frecventă cauză de „de ce s-a stricat pagina după bucla mea” — ai uitat reset-ul.
capcană: NU folosi query_posts()
Vei vedea în tutoriale vechi query_posts() — evită-l. Strică main query-ul și paginarea. Pentru bucle secundare folosește WP_Query; pentru a modifica main query-ul folosește pre_get_posts (mai jos). Niciodată query_posts.
pre_get_posts — modificarea main query corect
Vrei să schimbi interogarea principală (ex: „arată 12 postări pe pagina de blog, nu 10” sau „exclude o categorie din arhive”)? Nu construiești un WP_Query nou — modifici main query-ul înainte să ruleze, printr-un hook:
add_action( 'pre_get_posts', 'modifica_query' );
function modifica_query( $query ) {
// doar pe front-end și doar main query:
if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) {
$query->set( 'posts_per_page', 12 );
}
}
- Verificările ! is_admin() și is_main_query() sunt esențiale — altfel modifici și interogările din admin sau bucle secundare, stricând lucruri.
- E metoda corectă și performantă: modifici interogarea existentă, nu rulezi una în plus.
ghid de decizie
Afișezi rezultatele paginii curente? → bucla standard (main query). Vrei o listă suplimentară (recomandate, slider)? → WP_Query nou (+ wp_reset_postdata). Vrei să schimbi ce afișează pagina însăși? → pre_get_posts (nu WP_Query, nu query_posts). Aceste trei căi acoperă aproape orice nevoie de interogare — alegerea corectă e ce separă codul performant de cel fragil.
modul 05 / Custom Post Types & taxonomii
Custom Post Types (CPT)
WordPress vine cu tipuri de conținut predefinite (postări, pagini, atașamente). Dar rareori vrei „postări” pentru tot. Un magazin are Produse, un portofoliu are Proiecte, un restaurant are Preparate. Acestea sunt Custom Post Types (CPT) — tipuri de conținut proprii, cu propriul loc în admin și propriile reguli.
de ce CPT-uri
- Organizare clară: „Proiecte” separate de „Articole” în dashboard.
- Câmpuri și taxonomii proprii (un Produs are preț și categorie de produs, nu categorii de blog).
- Template-uri dedicate (single-produs.php prin template hierarchy).
- Structură de date curată — nu îndeși totul în „postări”.
register_post_type — pe hook-ul init
add_action( 'init', 'inreg_produse' );
function inreg_produse() {
register_post_type( 'produs', array(
'labels' => array(
'name' => 'Produse',
'singular_name' => 'Produs',
),
'public' => true,
'has_archive' => true, // pagină de arhivă /produs/
'menu_icon' => 'dashicons-cart',
'supports' => array('title','editor','thumbnail','custom-fields'),
'show_in_rest' => true, // apare în editorul bloc & REST API
) );
}
argumente cheie
| Argument | Rol |
| public | Vizibil pe front-end și în admin |
| has_archive | Creează o pagină de listă (arhivă) pentru tip |
| supports | Ce funcții are (titlu, editor, imagine reprezentativă, câmpuri) |
| show_in_rest | Îl expune în editorul bloc (Gutenberg) și în REST API — pune-l true |
| rewrite | Structura URL-ului (slug-ul) |
capcane clasice
1) Pune CPT-ul într-un PLUGIN, nu în functions.php-ul temei (altfel îl pierzi la schimbarea temei — vezi modulul 0). 2) După înregistrare, mergi la Settings → Permalinks și dă „Save” (flush rewrite rules) — altfel URL-urile CPT-ului dau 404. 3) Nu folosi slug-uri rezervate (ca „post”, „page”). Aceste trei greșeli prind aproape orice începător.
modul 05 / Custom Post Types & taxonomii
taxonomii personalizate
Taxonomiile sunt sistemele de clasificare a conținutului. WordPress are din start două: categorii (ierarhice) și etichete/tags (plate). Dar pentru CPT-uri proprii ai adesea nevoie de clasificări proprii — taxonomii personalizate.
exemplu
Un CPT „Produs” ar putea avea:
- Taxonomia „Categorie de produs” (ierarhică, ca și categoriile: Electronice → Telefoane).
- Taxonomia „Brand” (plată, ca etichetele).
ierarhice vs plate
| Ierarhică (ca categoriile) | Plată (ca etichetele) |
| Structură | Părinte → copil (subcategorii) | Listă simplă, fără ierarhie |
| Interfață admin | Checkbox-uri | Câmp de introducere liberă |
| Exemplu | Categorie de produs, Departament | Brand, Ingredient, Culoare |
register_taxonomy — tot pe init
add_action( 'init', 'inreg_cat_produs' );
function inreg_cat_produs() {
register_taxonomy( 'cat_produs', 'produs', array(
'labels' => array( 'name' => 'Categorii de produs' ),
'hierarchical' => true, // true = ca categoriile; false = ca etichetele
'public' => true,
'show_in_rest' => true,
) );
}
- Al doilea argument ('produs') leagă taxonomia de CPT-ul respectiv (poate fi și un array de tipuri).
- 'hierarchical' => true = comportament de categorie; false = comportament de etichetă.
- La fel ca la CPT: înregistrează pe init, pune în plugin, și flush permalinks după.
interogarea după taxonomie
Odată create, poți filtra cu WP_Query (modulul 4):
$q = new WP_Query( array(
'post_type' => 'produs',
'tax_query' => array( array(
'taxonomy' => 'cat_produs',
'field' => 'slug',
'terms' => 'telefoane',
) ),
) );
de reținut
CPT + taxonomii transformă WordPress dintr-un simplu blog într-un CMS pentru orice tip de date: magazine, portofolii, directoare, cataloage, evenimente. Modelul mental: CPT = tipul de lucru (Produs, Proiect, Eveniment); taxonomia = felul în care le clasifici (categorie, brand, locație). Împreună cu câmpurile personalizate (modulul următor), ai un model de date complet, adaptat exact nevoii.
modul 06 / metadata & câmpuri personalizate
post meta & câmpuri personalizate
Titlul și conținutul nu sunt de ajuns. Un Produs are preț, SKU, dimensiuni; un Eveniment are dată și locație; un membru al echipei are funcție și LinkedIn. Aceste date suplimentare se stochează ca metadata (post meta) — perechi cheie/valoare atașate unei postări (în tabelul wp_postmeta).
funcțiile de bază
// salvezi / actualizezi:
update_post_meta( $post_id, 'pret', '199' );
// citești:
$pret = get_post_meta( $post_id, 'pret', true );
// true = o singură valoare (nu array)
// ștergi:
delete_post_meta( $post_id, 'pret' );
- Al treilea parametru true la get_post_meta = „vreau o singură valoare” (fără el, primești un array — sursă frecventă de confuzie).
- Cheia ('pret') e numele câmpului tău; valoarea poate fi text, număr, serializat.
afișarea în template
$pret = get_post_meta( get_the_ID(), 'pret', true );
if ( $pret ) {
echo '<span class="pret">' . esc_html( $pret ) . ' lei</span>';
}
Observă esc_html() — escapezi mereu la afișare (modulul 8, securitate). Verifici și că valoarea există înainte de a o afișa.
meta keys „ascunse” (cu _ la început)
O cheie care începe cu underscore (_pret) e protejată: nu apare în interfața implicită „Custom Fields” a WordPress. Se folosește pentru date gestionate de cod/meta boxes, nu editate manual de utilizator în câmpul brut.
de reținut
Post meta e cum extinzi orice postare/CPT cu date proprii. Modelul: update_post_meta (scrii) / get_post_meta (citești), pe chei cu nume. Combinat cu CPT (tipul) și taxonomii (clasificarea), ai acum al treilea element al unui model de date complet: atributele fiecărui element. Rămâne întrebarea: cum introduce utilizatorul aceste valori? — meta boxes sau ACF, mai jos.
modul 06 / metadata & câmpuri personalizate
meta boxes & abordarea ACF
Datele meta trebuie introduse de undeva. Ai două căi: să construiești o interfață manual (meta box) sau să folosești un plugin care o generează (ACF — abordarea practică dominantă în industrie).
meta box manual — cum funcționează (conceptual)
Un meta box e o casetă în ecranul de editare a postării, unde adaugi câmpuri proprii. Necesită trei pași:
- Înregistrezi caseta cu add_meta_box() pe hook-ul add_meta_boxes.
- Randezi HTML-ul câmpurilor (un input pentru „preț” etc.), incluzând un nonce pentru securitate.
- Salvezi valorile pe hook-ul save_post: verifici nonce-ul, sanitizezi input-ul, apoi update_post_meta.
add_action( 'save_post', 'salveaza_pret' );
function salveaza_pret( $post_id ) {
// verifici nonce + permisiuni (modulul 8)...
if ( isset( $_POST['pret'] ) ) {
update_post_meta( $post_id, '_pret',
sanitize_text_field( $_POST['pret'] ) );
}
}
Meta box-ul manual îți dă control total, dar e cod repetitiv (boilerplate) și trebuie făcut cu grijă la securitate.
ACF — Advanced Custom Fields (calea practică)
În practica reală, majoritatea dezvoltatorilor folosesc ACF, cel mai popular plugin pentru câmpuri personalizate. Îți dă o interfață vizuală pentru a defini câmpuri, fără boilerplate.
- Definești câmpuri vizual: creezi un „field group”, adaugi câmpuri (text, imagine, dată, repeater, relație) și le legi de un CPT / pagină.
- ACF generează interfața de editare automat, curată și prietenoasă pentru client.
- Citești în cod simplu:
$pret = get_field( 'pret' ); // valoarea câmpului postării curente
the_field( 'pret' ); // o afișează direct
$img = get_field( 'imagine' ); // tipuri complexe: array cu date
- Tipuri de câmpuri bogate: text, textarea, imagine, fișier, dată, culoare, select, repeater (liste de subcâmpuri), relationship (legături între postări), grup, flexible content.
- Economisește enorm timp și oferă clientului o interfață clară — de aceea e standard de facto.
meta box manual vs ACF
| Meta box manual | ACF |
| Efort | Mult boilerplate, atenție la securitate | Vizual, rapid |
| Control | Total | Foarte mare, dar prin plugin |
| Interfață client | O construiești tu | Generată, prietenoasă |
| Când | Cazuri foarte specifice / fără dependențe | Majoritatea proiectelor reale |
de reținut
Trebuie să înțelegi post meta și meta box-urile la nivel de concept (ce se întâmplă dedesubt, securitatea la salvare), dar în practică vei folosi aproape sigur ACF pentru viteză și pentru o interfață curată pentru client. Cunoașterea mecanismului brut te ajută să depanezi și să decizi corect; ACF îți dă productivitatea. Ambele stau pe același fundament: metadata atașată conținutului.
modul 07 / dezvoltarea pluginurilor
anatomia unui plugin
Un plugin e modul standard de a adăuga funcționalitate în WordPress. Vestea bună: un plugin poate fi incredibil de simplu — un singur fișier PHP cu un antet special. De acolo crește cât ai nevoie.
plugin minimal
Un plugin e un folder în wp-content/plugins/ cu un fișier PHP care are un antet-comentariu special:
<?php
/*
Plugin Name: Portofoliu Romeo
Description: Adaugă tipul de conținut Proiecte.
Version: 1.0
Author: Romeo
*/
// codul pluginului aici (hooks, funcții)...
Antetul Plugin Name e ce face WordPress să-l recunoască și să-l afișeze în lista de pluginuri, gata de activat. Atât — ai un plugin funcțional.
structura unui plugin mai mare
portofoliu-romeo/
portofoliu-romeo.php // fișierul principal (antetul + bootstrap)
includes/ // clase și logică
admin/ // cod pentru zona de administrare
assets/ // CSS, JS, imagini
languages/ // traduceri
hook-uri de activare & dezactivare
Uneori vrei să faci ceva o singură dată, la activarea pluginului (ex: creezi tabele, setezi opțiuni implicite, dai flush la permalinks după înregistrarea unui CPT):
register_activation_hook( __FILE__, 'la_activare' );
function la_activare() {
// rulează O DATĂ, la activare
flush_rewrite_rules(); // ex: după register_post_type
}
register_deactivation_hook( __FILE__, 'la_dezactivare' );
// curățenie la dezactivare
Acestea rezolvă capcana „URL-urile CPT dau 404”: înregistrezi CPT-ul pe init ȘI faci flush_rewrite_rules() la activarea pluginului.
bune practici de organizare
- Prefix unic pentru funcții/clase (ex: roro_) — eviți coliziuni cu alte pluginuri (nu există namespace implicit în WP procedural).
- Pluginuri OOP: încapsulezi în clase, eviți poluarea spațiului global.
- Verifică defined('ABSPATH') || exit; la începutul fișierelor — împiedică accesul direct la fișier.
- Un plugin = o responsabilitate clară; nu îngrămădi zece funcții fără legătură.
de reținut
Bariera de intrare pentru un plugin e surprinzător de joasă: un fișier, un antet, gata. Complexitatea crește organic pe măsura nevoii. Cheia e disciplina: prefixe unice (fără coliziuni), organizare pe foldere, hook-uri de activare pentru setup, și tot codul conectat prin hooks. Un plugin bine făcut e doar o colecție ordonată de callback-uri cu un scop clar.
modul 07 / dezvoltarea pluginurilor
shortcode-uri & unde pui codul
shortcode-uri — inserarea de conținut dinamic
Un shortcode e o etichetă simplă pe care utilizatorul o pune în conținut, iar WordPress o înlocuiește cu output generat de codul tău. Ex: [portofoliu categorie="web"] → afișează o listă de proiecte.
add_shortcode( 'salut', 'shortcode_salut' );
function shortcode_salut( $atribute ) {
$a = shortcode_atts( array(
'nume' => 'vizitator', // valoare implicită
), $atribute );
return 'Salut, ' . esc_html( $a['nume'] ) . '!';
}
// utilizare în conținut: [salut nume="Ana"]
- Regula de aur a shortcode-urilor: return, nu echo! Un shortcode trebuie să returneze string-ul, nu să-l afișeze — altfel apare în locul greșit pe pagină.
- shortcode_atts — combină atributele date de utilizator cu valorile implicite (elegant și sigur).
- Escapezi output-ul (esc_html) — datele din atribute vin de la utilizator.
alte moduri de a expune funcționalitate
- Widget-uri — blocuri pentru zonele de widget (sidebar, footer); clasă care extinde WP_Widget.
- Blocuri Gutenberg — abordarea modernă pentru editorul bloc (necesită JS/React; noțiuni în modulul 8).
- Pagini de admin & setări — add_menu_page / Settings API pentru configurare.
- Shortcode — cel mai simplu pentru „inserează asta undeva în conținut”.
marea întrebare: temă sau plugin?
Revenim la principiul din modulul 0, acum cu instrumente concrete. Pune într-un plugin:
- CPT-uri și taxonomii (datele trebuie să supraviețuiască schimbării temei).
- Shortcode-uri și funcționalitate de business.
- Integrări (API-uri externe, formulare, logică).
Pune în temă (functions.php):
- Enqueue de assets ale temei, suport de temă (add_theme_support), zone de meniu/widget.
- Ajustări pur vizuale legate de acea temă.
de reținut
Shortcode-urile (return, nu echo!) sunt cel mai accesibil mod de a livra funcționalitate pe care utilizatorul o plasează unde vrea. Iar decizia temă vs plugin — pe care acum o poți lua cu instrumente reale — e semnătura unui dezvoltator matur: datele și funcționalitatea în pluginuri (portabile), aspectul în teme (înlocuibile). Această separare face proiectele mentenabile pe termen lung.
modul 08 / securitate, REST API & WP-CLI
securitate WordPress: sanitizare, escaping, nonces & capabilities
Securitatea nu e opțională — un plugin sau o temă vulnerabilă poate compromite tot site-ul. Vestea bună: WordPress oferă funcții pentru fiecare situație. Trebuie doar să le folosești consecvent. Reține patru piloni.
1. sanitizarea input-ului (la intrare)
Regula: nu ai încredere niciodată în datele care intră — din formulare, URL-uri, orice sursă externă. Le cureți înainte de a le folosi/salva.
$nume = sanitize_text_field( $_POST['nume'] );
$email = sanitize_email( $_POST['email'] );
$url = esc_url_raw( $_POST['url'] );
$nr = absint( $_POST['cantitate'] ); // întreg pozitiv
$html = wp_kses_post( $_POST['continut'] ); // permite doar HTML sigur
2. escaping-ul output-ului (la ieșire)
Regula: escapează totul chiar înainte de afișare, mai ales datele dinamice. Previne XSS (injectarea de cod în pagină).
echo esc_html( $text ); // text în HTML
echo esc_attr( $valoare ); // valoare într-un atribut
echo esc_url( $link ); // un URL
echo wp_kses_post( $html ); // HTML cu doar taguri permise
Principiul general: escape late — escapezi cât mai aproape de punctul de afișare, cu funcția potrivită contextului (html / attr / url).
3. nonces — protecție împotriva CSRF
Un nonce („number used once”) e un token care confirmă că o acțiune (trimiterea unui formular, un click) vine chiar de la utilizatorul legitim, nu dintr-un atac fals (CSRF).
// în formular:
wp_nonce_field( 'salveaza_produs', 'produs_nonce' );
// la procesare, verifici:
if ( ! isset( $_POST['produs_nonce'] ) ||
! wp_verify_nonce( $_POST['produs_nonce'], 'salveaza_produs' ) ) {
return; // nonce invalid — oprește
}
4. capabilities — verifică permisiunile
Înainte de o acțiune sensibilă, verifică dacă utilizatorul are dreptul:
if ( ! current_user_can( 'edit_posts' ) ) {
return; // nu are permisiunea
}
Nu te baza pe „ascunderea” unui buton — verifică mereu permisiunea pe server, la procesare.
formula securității
Sanitizează la intrare, escapează la ieșire, verifică nonce + capabilities la acțiuni. Aceste patru reflexe, aplicate consecvent, previn marea majoritate a vulnerabilităților WordPress (XSS, CSRF, escaladare de privilegii). Nu sunt opționale și nu sunt „pentru mai târziu” — sunt parte din fiecare formular și fiecare afișare de date dinamice pe care le scrii.
modul 08 / securitate, REST API & WP-CLI
REST API & WP-CLI
WP REST API — WordPress ca back-end de date
REST API expune conținutul WordPress ca JSON, accesibil prin cereri HTTP. Astfel, WordPress poate alimenta cu date orice: o aplicație JavaScript, o aplicație mobilă, un site „headless” (front-end separat), integrări externe.
GET /wp-json/wp/v2/posts // toate postările, ca JSON
GET /wp-json/wp/v2/posts/42 // postarea cu ID 42
GET /wp-json/wp/v2/produs // CPT-ul tău (dacă show_in_rest = true)
- Fiecare tip de conținut cu show_in_rest => true apare automat în API — de aceea îl setezi la CPT-uri.
- Poți adăuga rute proprii cu register_rest_route() pe hook-ul rest_api_init, pentru endpoint-uri personalizate.
- Editorul bloc (Gutenberg) folosește intern REST API — de aici importanța lui în WP modern.
add_action( 'rest_api_init', function() {
register_rest_route( 'app/v1', '/status', array(
'methods' => 'GET',
'callback' => 'raspuns_status',
'permission_callback' => '__return_true',
) );
} );
Atenție la securitate: pentru date sensibile sau scriere, folosește permission_callback ca să verifici autentificarea/permisiunile — nu lăsa endpoint-uri deschise care expun sau modifică date.
WP-CLI — WordPress din linia de comandă
WP-CLI îți permite să administrezi WordPress din terminal, fără dashboard. Indispensabil pentru dezvoltatori și automatizare.
wp plugin list // listează pluginurile
wp plugin activate acf // activează un plugin
wp core update // actualizează nucleul
wp db export backup.sql // exportă baza de date
wp search-replace 'vechi.md' 'nou.md' // înlocuire în DB (ex: la migrare)
wp user create ana ana@x.md --role=editor
- Rapid & automatizabil: ce ar dura minute prin dashboard se face într-o comandă; se poate pune în scripturi.
- Esențial la migrări: search-replace schimbă corect URL-urile în toată baza de date (inclusiv date serializate) când muți site-ul.
- Operațiuni în masă, mentenanță, debugging, deployment — toate mai rapide din CLI.
de reținut
REST API face din WordPress o sursă de date pentru orice front-end (aplicații JS, mobile, headless) — direcția modernă a platformei. WP-CLI îți dă viteză și automatizare ca dezvoltator (migrări, mentenanță, deployment). Amândouă marchează trecerea de la „utilizator de dashboard” la „dezvoltator care controlează WordPress programatic” — exact saltul pe care îl face acest curs.
modul 09 / block editor, proiect & test final
editorul bloc (Gutenberg) & bune practici
WordPress modern se construiește în jurul editorului bloc (Gutenberg). Ca dezvoltator, trebuie să înțelegi peisajul, chiar dacă dezvoltarea de blocuri avansate (cu React) e un subiect mare în sine.
ce sunt blocurile
În loc de un câmp mare de conținut (editorul clasic), pagina se compune din blocuri individuale: paragraf, titlu, imagine, galerie, buton, coloane etc. Fiecare bloc e o unitate independentă, configurabilă.
căile de a lucra cu blocuri
| Abordare | Ce presupune |
| Block patterns | Aranjamente pre-făcute de blocuri existente — fără cod, refolosibile |
| Blocuri native + ACF Blocks | Creezi blocuri proprii cu ACF, folosind PHP/template (fără React) — practic și popular |
| Blocuri custom (nativ) | Dezvoltare cu JavaScript/React și build tools — control total, dar complex |
| Full Site Editing (FSE) | Teme „block”: tot site-ul (header, footer, template-uri) editat cu blocuri, definit în theme.json |
show_in_rest => true la CPT-uri e ce le face să funcționeze în editorul bloc — de aceea l-am subliniat la modulul 5.
direcția platformei
- WordPress se mută spre Full Site Editing și teme block, cu theme.json pentru configurarea globală (culori, tipografie, spațieri).
- Editorul clasic rămâne disponibil (plugin Classic Editor), dar noile proiecte merg pe blocuri.
- Pentru majoritatea nevoilor practice, block patterns + ACF Blocks acoperă foarte mult, fără a intra în React.
bune practici — recapitulare de dezvoltator profesionist
- Nu atinge nucleul; folosește child themes; funcționalitatea în pluginuri, aspectul în teme.
- Tot ce faci, prin hooks (actions & filters); nu suprascrie template-uri întregi pentru ajustări mici.
- Enqueue corect assets; nu hardcoda scripturi/stiluri.
- Securitate mereu: sanitizează, escapează, nonces, capabilities.
- CPT/taxonomii/meta în pluginuri; show_in_rest true; flush permalinks la activare.
- Dezvoltă local cu WP_DEBUG; folosește WP-CLI și controlul versiunilor (Git); prefixe unice.
- Cod lizibil, prefixat, documentat — urmează standardele de codare WordPress.
de reținut
Nu trebuie să stăpânești dezvoltarea de blocuri React ca să fii un dezvoltator WordPress valoros — dar trebuie să înțelegi peisajul (blocuri, patterns, ACF Blocks, FSE, theme.json) și să știi când folosești fiecare. Combinat cu fundamentele solide din tot cursul — hooks, template hierarchy, WP_Query, CPT, meta, pluginuri, securitate — ai profilul complet al unui dezvoltator care poate construi aproape orice pe WordPress.
modul 09 / block editor, proiect & test final
proiect ghidat: plugin de portofoliu
Pui tot cursul la lucru construind un plugin complet și funcțional — exact ce ai face pentru un client. Lucrează local, într-un WordPress de dezvoltare, pas cu pas.
ce construiești: pluginul „Portofoliu”
Un plugin care adaugă un tip de conținut „Proiecte” cu detalii proprii și un mod de a le afișa oriunde pe site.
cerințe funcționale
- Un CPT „Proiect” cu titlu, conținut, imagine reprezentativă.
- O taxonomie „Tehnologie” (plată, ca etichetele) pentru a clasifica proiectele.
- Câmpuri personalizate (cu ACF sau meta box): URL-ul proiectului, clientul, anul.
- Un shortcode [portofoliu] care afișează o listă de proiecte (opțional filtrată după tehnologie).
- Datele apar și în REST API (show_in_rest).
planul, pe modulele cursului
- Structura pluginului (mod. 0, 7): folder în plugins/, fișier principal cu antet, prefix unic, defined('ABSPATH') || exit;.
- CPT & taxonomie (mod. 5): register_post_type('proiect', ...) și register_taxonomy('tehnologie', 'proiect', ...) pe hook-ul init, cu show_in_rest => true.
- Activare (mod. 7): register_activation_hook → flush_rewrite_rules() (ca URL-urile să nu dea 404).
- Câmpuri (mod. 6): definești cu ACF (URL, client, an) sau un meta box manual cu nonce + sanitizare la save_post.
- Shortcode (mod. 7): add_shortcode('portofoliu', ...) care rulează un WP_Query (mod. 4) pe CPT-ul „proiect”, parcurge bucla, afișează titlu + câmpuri, și return string-ul (nu echo!) + wp_reset_postdata().
- Afișare & securitate (mod. 3, 8): escapezi tot output-ul (esc_html, esc_url); dacă încarci CSS propriu, prin wp_enqueue_style pe wp_enqueue_scripts.
definiția lui „gata”
- Pluginul se activează fără erori (WP_DEBUG pornit, zero notice-uri).
- „Proiectele” apar în admin, cu taxonomia și câmpurile lor; URL-urile CPT nu dau 404.
- Shortcode-ul [portofoliu] afișează corect proiectele pe orice pagină.
- Tot output-ul e escapat; salvarea câmpurilor manuale are nonce + sanitizare.
- La schimbarea temei, totul rămâne funcțional (fiindcă e în plugin).
- Bonus: filtrare în shortcode [portofoliu tehnologie="wordpress"]; un template single-proiect.php în temă; expunerea unei rute REST proprii.
felicitări
Ai parcurs dezvoltarea WordPress de la arhitectură la un plugin real: hooks (actions & filters), template hierarchy, The Loop & WP_Query, Custom Post Types & taxonomii, metadata & câmpuri, structura pluginurilor, securitate, REST API și WP-CLI — și le-ai combinat într-un livrabil complet. Ai trecut de la „administrez WordPress” la „construiesc pe WordPress”. Urmează testul final: 20 de întrebări din tot cursul. Prag: 70%. Succes!