[TOC]
- doc-wordpress
- Abréviations
- Must used plugins
- CPT(Custom Post Types) et CT (Custom Taxonomies)
- Template hierarchy
- Taxonomies
- template-parts
- Post formats
- Sanitization
- Escaping (output)
- Localization
- Javascript dependencies
- Template tags
- Search
- Sécurité
- Configuration
- Debugging
- Maintenance
- Settings API : build your own administrative interfaces the core way
- Plugins recommandés
- Optimiser Wordpress (scalability et performances)
- Ressources
- CPT: Custom Post Type
- CT : Custom Taxonomy
Dans le dossier wp-content/mu-plugins
- toujours actifs
- utilisateurs ne peuvent pas les désactiver par accident
- chargés avant les plugins normaux
Problèmes
- ils n'apparaissent pas les notifs de mise a jour. C'est à nous de penser à les update
- les hooks d'activation et de desactivation ne sont pas executés pour les must used plugins
En gros, à utiliser avec parcimonie. Il vaut mieux y mettre des plugins qui bougent pas trop, autonomes et isolés, surtout pour monitorer le site je dirai.
Les CPT et CT sont de préférence déclarés dans un plugin. Je préfère les mettre dans mu-plugins
pour être sûr qu'ils ne soient pas désactivés par erreur par l'admin.
Hyper important !
La hierarchie des templates wordpress, et leur fallbacks (quel template est appelé si celui demandé n'est pas trouvé).
Pour le lire, a gauche c'est l'input, par la qu'arrive la requete en fonction de l'url (modèle MVC). C'est la couleur gris foncé sur le diagramme. Et on descend vers la droite jusqu'à trouver le bon template. Jusqu'a index.php
si on en trouve aucun.
J'appelle front
tout template qui se trouve en première ligne de la requête. Ces templates ne sont les fallbacks d'aucun autre template, ils sont en première ligne.
- sert de fallback à : tout
- fallback : aucun
Pas toujours nécessaire, peut-être redondant avec index.php.
Pour un single post, page, single attachment. Utile si un post ou une page ont le même template (car sert de fallback aux deux)
Les archives n'utilisent pas singular.php
- sert de fallback à : page, single, attachment
- fallback : index.php
Template important. pour afficher un seul post
ou CPT.
- sert de fallback à :
attachment
,single-post
,single-${posttype}
- fallback : singular.php
Même que single, mais seulement pour le post par défaut de wordpress (pas de CPT)
Template pour afficher un seul post
.
- sert de fallback à :
- fallback : single.php
Même que single, mais seulement pour le CPT posttype. Template pour afficher un seul post
.
- sert de fallback à :
single-${posttype}-${slug}
- fallback : single
- si on choisit pour la front page 'Your latest posts' on utilise
home.php
- si on choisit une page statique, on peut créer une page avec le slug 'blog' listant tous les posts (
mon-site/blog
), et l'affecter à la page 'Posts page'.
En gros c'est pas compliqué : par défaut Wordpress nous met en home la liste des posts (/
). Dans ce cas c'est home.php
qui est appelée. Si on choisit de mettre une page static à la place, il faut aussi choisir une page pour la liste des posts. Et c'est toujours le template home.php
qui s'en charge.
- sert de fallback à : front-page
- fallback : index
Voir ici l'explication détaillée
- sert de fallback à : aucun (front)
- fallback : custom si existe, sinon
page-${slug}
jusqu'àpage
.
Une archive est une collection d'un type de données. Catégorie archive, tag archive, author archive, CPT archive etc. Assez générique.
Trop générique.
- sert de fallback à :
- fallback : index
Une archive pour la catégorie Catégorie (catégorie built in)
- sert de fallback à : aucun (front)
- fallback : archive
Attachment se traduit par fichier joint.
Tout ce qui est attaché à un post depuis le dossier medias
(image, video, PDF etc..). C'est un type de post fondamentalement.
- sert de fallback à : mimetype
- fallback : single
Template pour un type de donnée spécifique (image, vidéo, PDF). Par exemple
-
text.php pour le texte
-
video.php pour un attachment video
-
image.php pour un attachment image
-
sert de fallback à : ${subtype}
-
fallback : attachment
Controle les pages statiques (les posts de type page)
- sert de fallback à : page-${slug}
- fallback : singular
Peut être utile pour tout post (post, page, CPT). Ne sera pas pris en compte par la hierarchie par défaut.
Il faut rajouter dans le header de la page (tout en haut)
<?php
// Template Name: Ma page
?>
Wordpress va scanner ce header, et va le rendre accessible via l'admin.
Pour rendre un template custom utilisable pour un post, une page et un CPT on peut aussi mettre
<?php
// Template Name: Ma page
// Template Post Type: post, page, custom-post-type
?>
Il faut ensuite dire à la page ou au post explicitement d'utiliser ce template au lieu du template par défaut envoyé par la hierarchie.
- sert de fallback à : aucun (front)
- fallback : singular
Rendre ludique le fait d'avoir échoué à trouver une ressource. Et redemander à l'user ce qu'il cherchait.
On peut afficher un formulaire de recherche
echo get_search_form();
La recherche sur Wordpress par défaut a pour forme {domain}/?s=motRecherche
. Template utilisé si le paramètre s est présent dans la query string.
- sert de fallback à : aucun (front).
- fallback : index
Archive accessible à l'url /${posttype}
.
Il faut s'assurer à la registration que le CPT a bien has_archive
à true
, sinon la hierarchie retombera sur archive
.
Même si on réécrit le slug du CPT lorsqu'on l'enregistre avec register_post_type
$args = array(
'rewrite' => array(
'slug' => 'projets',
'with_front' => true,
),
);
register_post_type( 'projet', $args );
l'archive pour le CPT projet restera archive-projet.php
. Le rewrite
ne réécrit que le slug
dans les permaliens, la hierarchie des templates n'est pas concernée. Ainsi, pour appeler le template archive-projet.php
je pourrais demander l'url {mon-domain}/projets
.
function mondomaine_register_cpt_monposttype() {
/**
* Post Type: mon-posttype.
*/
$labels = array(
...
);
$args = array(
...
'has_archive' => true,
...
);
register_post_type( 'mon-posttype', $args );
}
add_action( 'init', 'mondomaine_register_cpt_monposttype' );
- sert de fallback à : aucun (front).
- fallback : archive
Template pour servir un simple CPT.
- sert de fallback à :
single-${posttype}-${slug}
pour personalise la vue à un CPT en particulier, puiscustom
s'il match - fallback : single
Les template de taxonomy vont être par essence des templates de listing (archives).
Template pour liste de tous les items de toutes les CT
- sert de fallback à :
taxonomy-${taxonomy}-${term}
(template spécifique à un terme) puistaxonomy-${taxonomy}
- fallback : archive
Template pour liste de tous les items d'une CT.
- sert de fallback à :
taxonomy-${taxonomy}-${term}
- fallback : taxonomy
- ne jamais utiliser
*-${id}
comme template ! Sauf en cas de force majeure. Trop dépendant de la bdd et peu explicite.
Les taxonomies sont des catégories qui permettent de regrouper les posts et de créer des liens entre eux.
Par défaut, un post standard aura accès à deux types de taxonomy (builtin wp) Categories
(hierarchical cad que les terms peuvent avoir aussi des enfants) and Tags
(non hierarchical cad que les terms ne peuvent pas avoir d'enfants).
Ces deux taxos sont incluses dans Wordpress par défaut. Mais on peut les supprimer si on veut. Y'en a 4 en tout si on compte les Link Categories
(catégorie pour les liens) et les Post_Format Categories
(pour les types de post en fonction de leur mimetype).
Les taxonomies sont les parents, les terms
sont les enfants.
A lire du haut vers le bas pour voir le flow du templating. Valable pour les taxonomies par défaut (voir la hierarchie général des templates)
- taxonomy-{taxonomy}-{term}.php
- taxonomy-{taxonomy}.php
- tag-{slug}.php
- tag-{id}.php
- category-{slug}.php
- category-{ID}.php
Si on l'applique à la taxonomy Categories
(slug category
)
- category-{slug}.php
- category-{id}.php
- category.php
- archive.php
- index.php
Si on l'applique à la taxonomy tags
(slug tag
)
- tag-{slug}.php
- tag-{id}.php
- tag.php
- archive.php
- index.php
On peut créer nos propres taxonomies avec register_taxonomy( '${slug taxonomy}', array( 'posttype' ), $args )
.
Si on applique la hierarchie à la taxonomy custom
- taxonomy-{taxonomy}-{term}.php
- taxonomy-{taxonomy}.php
- taxonomy.php
- archive.php
- index.php
Prendre des précautions lorsqu'on choisit un nom pour la taxonomy, qu'elle ne rentre pas en conflit avec des `noms reservés par Wordpress. Par exemple ne pas utiliser le mot action.
On a par défaut les taxonomies Catégories
(slug category
) et Etiquettes
(slug tag
) pour les articles (slug post
). Par défaut on a un term "Uncategorized" pour la taxonomie category
et un term "Tags" pour la taxonomie tag
.
Donc par défaut pour accéder a la liste des posts uncategorized
l'url sera
{`mon-domaine}/category/uncategoried`
De même, pour accéder a la liste des posts étiquettés Tags
l'url sera
{`mon-domaine}/tag/tags`
On peut changer le slug category
et tag
par défaut par un autre dans Settings/Permalinks/Optional
.
Pour les taxonomies custom. Imaginons on a créé un CPT projet
et une CT savoir-faire
avec un terme 'ebenisterie'. Comment lister tous les projets d'ébenisterie ?
{`mon-domaine}/savoir-faire/ebenisterie`
Le permalink dans Wordpress c'est l'URL entière d'une page (incluant le protocole, le nom de domain).
Le SEO ou Ranking de vos contenus parmi les résulats retournés par votre navigateur est hyper important si vous voulez être trouvé. Qui va sur la 2eme page de Google resultats ? (Montrer stat)
Le SEO c'est souvent de l'arnaque et de la magie noire (ca depend par exemple de code propriétaire du moteur de Google et les règles peuvent changer très vite) mais c'est important. Et il y a quand même des règles stables dans le temps.
Essayer de prévoir le SEO avant
de commencer le développement ou la mise en prod de votre site. Cela vous aidera a la structuration de vos pages. Le faire après peut devenir vite compliqué voire impossible (croyez moi).
Les slug sont la dernière partie de l'url, par exemple dans example.com/about-us
le slug est about-us
. Dans example.com/?p=356
il n'y a pas de
Toujours utiliser Post name
: plus comphrénsible, SEO friendly
-
search engines use your URL to determine what your page or content page is all about. And if they found your permalink related to the content or page, the search engines find it genuine and legitimate to give them priority in their search engine rankings.
-
Using SEO keywords in your URL can help you rank for your target keywords. Google uses the URL as a factor in ranking your page, so if the URL slug includes your keywords, then Google will be more likely to rank it.
- use HTTPS
- nom de domaine clair et unique
- keep it short and precise (concis). Le poids des derniers mots pour les bots crawlers est plus faible
- lisible pour les robots et pour les humains
- utiliser des sous-domaine:
sous-domaine.mon-domaine.com
- placer des mots clefs qui décrivent le contenu de la page
- eviter de mettre une année dans l'URL
- eviter la duplication d'URL sur votre site
- The best time to change a permalink is when you’re originally creating the post content
before
the post is published. Why? If you change a post’s slug after it has already been published, beware that you’re also changing the post’s URL, so the permalink change may create a 404 page. Any links that have been shared online using the old URL will not work any longer and will need to have redirects set up to the new URL. By customizing your post slug prior to publishing the content, you’ll have an optimized permalink the second your hit the publish button. - consistent structure sur tout le site
- plugin Yoast, un des plugins les plus installés et maintenu de l'écosystème de Wordpress. Gratuit possible, user friendly pour vos administrateurs de contenus (clients). Ne pas hésiter à l'installer.
paginate_links()
: on a la main sur le nombre de pages de résultats et le formattage du markup de balisation- le nombre de posts par page est reglé depuis l'admin dans Settings/Reading
- si on veut faire des choses plus contrôlés on passe par une WP_Query
hierarchical
:true
, traite la taxo comme une catégorie (liste d'items prédéfinis),false
traite la taxo comme un tag. On rentre des termes séparés par des virgules sur chaque post à la volée.
Si l'on ne souhaite pas avoir une taxonomie hierarchique et que l'on désire quand même avoir les checkboxs (c'est quand même plus cool) on peut utiliser le paramètre meta_box_cb
$args = array(
'hierarchical' => false,
// utilise les metabox de post comme pour la taxonomy hierarchique 'category'
'meta_box_cb' => 'post_categories_meta_box',
);
register_taxonomy( 'action', array( 'projet' ), $args );
On crée en général un dossier template-parts
à la racine du thème. On y stocke tout template html de contenu. Par exemple
template-parts/content.php
pour le contenu d'un posttemplate-parts/content-page.php
pour le contenu d'une page- etc...
Par exemple, le contenu de content.php
<?php
/**
* Contenu d'un post
*/
?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<?php the_title('<h1>', '</h1>'); ?>
</header>
<div class="entry-content">
<?php the_content(); ?>
</div>
</article>
On l'appelle ensuite avec la fonction get_template_part({path du fichier content},{ce qui suit le -}
dans la main loop. Par exemple
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<?php get_template_part('template-parts/content','page') ?>
<?php endwhile; else : ?>
<?php get_template_part('template-parts/content', 'none') ?>
<?php endif; ?>
Pour présenter différents types de posts de manière différente. Les formats sont des sortes de métadonnées sur les posts. Par défaut un post a le format standard
. Utile pour utiliser des template-parts en fonction d'une méta (ex: vidéo, comment etc...)
Les noms des formats match les noms des dashicons.
if ( has_post_format( 'video' )) {
echo 'this is the video format';
}
echo get_post_format($post_id);
Les post-formats ont un template dans la hierarchie du type taxonomy-post_format-post-format-{format}.php
.
Mais à utiliser davantage pour le style que pour du templating. On peut par exemple définir un content-{format}.php
dans nos template-parts
et charger un template différent en fonction du format.
A utiliser pour le format quote
pour les testimonials
par exemple, au lieu d'un CPT !
Process of cleaning or filtering your input data. On parle aussi de sanitazing, validating, filtering, cleaning... Même si on peut dire qu'il y a des différences entre ces termes globalement ils veulent tous dire "preventing invalid from entering your application.
Il y a plusieurs approches pour sanitize les données en entrée et certaines sont plus sécurisées que d'autres.
La meilleure approche est de concevoir ce processus comme un processus d'inspection. N'essaie pas de corriger des données invalides, force les utilisateurs à jouer selon tes règles. L'histoire a montré que la tentative de corriger des données invalides a mené à des vulnérabilités.
Avoir une whitelist approach : on fait l'hypothèse que toute donnée qui arrive est invalide sauf si on peut prouver qu'elle ne l'est pas. Ainsi, l'erreur qu'on risque de faire c'est d'invalider une donnée valide. Ce qui est facheux mais moins que l'inverse.
Un ensemble de données $data
arrive.
- crée un tableau $clean vide. Ce tableau ne contiendra que des données dont on est sûrs qu'elles ont été validées.
- creer un ensemble de regles de validation que $data doit passer avant de se retrouver dans $clean.
- privilégier des fonctions builtin quand c'est possible (plus sur que votre code)
- identifier l'input (donnée considérée comme invalide et sale)
- valider l'input
- distinguer entre donnée sale et donné valide
Sanitize strips ou modifie les data, c'est a dire qu'il modifie l'entrée. C'est donc du filtre qui peut ouvrir vers des vulnérabilités. Il faut faire confiance à une fonction maintenue par une communauté depuis 20ans.
- sanitize_text_field
- sanitize_title
- sanitize_email
- sanitize_html_class
- esc_url_raw : enleve caracteres invalides dans une url
- sanitize_user
- sanitize_option
- sanitize_meta
Escaping output : échaper ou encoder des caractères pour être sûrs que leur signification originale est preservée, et eviter d'envoyer un résultat dangereux à un système exterieur à votre site/application (navigateur, base de données, url).
Pareil que pour la sanitization, on fait l'assomption que toute donnée peut etre malveillante (meme si on a validé).
Escaping output est aussi en 3 étaps:
- identifier les output
- échapper les outputs
- distinguer entre donnée échappée et non échappéé
htmlentities
est la meilleure fonction. Prend en entrée une string et retourne une version modifiée de telle sorte que certains caractères ne seront pas intérpretés par le navigateur (balises html ou scrit, "<" ). htmlentities accepte des arguments qui permettent de définir les règles d'échappement et le character set qui doit correspondre à celui indiqué dans le header Content-Type de votre requête réponse au client.
En gros utilisez toujours htmlentities($donnée_a_echaper, ENT_QUOTES, 'UTF_8')
.
Un exemple
//identification de notre sortie
$html=array();
//echappement
$html['username'] = htmlentities($clean['username'], ENT_QUOTES, 'UTF_8');
//servi au client
echo "<p>Welcome back, {$html['username']}.</p>"
- utiliser une fonction native à votre base de données (implementée pour elle)
- mysqli::real_escape_string
- PDO, nouveau standard dans PHP. Pour des requetes préparées.
Wordpress met à disposition tout un tas de fonctions pour l'échappement. Vous n'aurez surement jamais besoin d'utiliser htmlentities directement.
Output shoudl occured as late as possible. Après que tous les filtres WP aient été appliquées. En gros, juste avant de l'echo sur la sortie standard.
Les fonctions strip aussi les données (modifient)
- esc_html : a utiliser à la place de htmlentities
- esc_url: converse caracteres dans encoding url
- esc_js: rend <script></script> inofensif
- esc_attr: clean les attributs d'element html
- esc_textarea: pareil que esc_html mais dans un text area
Avant de renvoyer une donnée dans un script php sur la sortie standard, on veut être sûr de pas envoyer du code malfaisant au client, et éviter du cross-site-scripting (XSS). Une liste de fonctions utils
esc_html()
: a utiliser pour recuperer des données dans un élément html
Voir ici
_e(text, domain)
, print une string traduite__(text,domain)
, renvoie une string traduite sans la print
Pour échapper ces fonctions de traductions on fait soit par exemple esc_html(__(text,domain))
. Soit en plus court (donc à privilégier) esc_html_e(text, domain))
.
esc_html_e( 'Hello World', 'text_domain' );
// same as
echo esc_html( __( 'Hello World', 'text_domain' ) );
Pour la traduction.
- esc_html_e : escape html, translate puis echo. A utiliser de préférence tout le temps !
- esc_html__ : escape html; translate and return string
- esc_attr_e : escape attribute, translate puis echo
- _e : translate and echo (pas d'escape)
- __ : translate et retourne (pas d'escape)
Toutes ces fonctions prennent deux arguments
_e('text a traduire', 'nom-**unique**-de-domaine');
nom **unique** de domaine
vient du Text Domain
de notre thème, définit obligatoirement dans le style.css
de notre thème, avec toutes les autres métas du theme.
Voir ce lien pour comprendre comment fonctionne en pratique la localization.
Si on regarde la page du codex de la fonction wp_enqueue_scripts on peut voir toutes les dépendences JS du coeur de Wordpress (utilisé côté admin).
Si on a besoin de l'une de ces librairies on a pas besoin de les importer explicitement dans notre projet. Il suffit de copier leur slug dans le tableau $deps
passé en argument de wp_enqueue_scripts
.
Si on veut par exemple embarquer jquery
sur nos pages webs servies au client
wp_enqueue_script( 'main-script', get_stylesheet_directory_uri() . '/js/scripts.min.js', array( 'jquery' ), $js_version, true );
Dans notre fichier main-script.js
si on veut utiliser jquery, il ne faut pas utiliser le signe $ car il peut rentrer en conflit avec d'autres library.
Donc au lieu d'écrire
$('.single-projet)
on écrira
jQuery('.single-projet)
Les [template tags
] sont des fonctions spéciales de wordpress qui permettent d'accéder au contenu/données wordpress (par ex get_the_title()
, the_title()
) . Elles passent aussi par des filtres (hook filter) add_filter('the_title', callback)
.
La plupart de ces fonctions acceptent des paramètres.
the_title('<h1>','</h1>');
- get_header()
- get_footer()
- get_sidebar()
- get_template_part()
- get_search_form()
- comments_template()
- wp_loginout() : pour fair un bouton de connexion/deconnexion facilement. Si on veut être redirigé vers la même page où est présent le bouton on peut faire
<?php wp_loginout( get_permalink() ?>
. La fonction prend en argument une url pour leredirect
après login ou logout. - wp_logout_url()
- wp_login_url()
- wp_login_form()
- wp_lostpassword_url()
- wp_register()
- is_user_logged_in()
- bloginfo(), voir tous les paramètres
Pour les listings.
- single_post_title(), outside of the loop
- post_type_archive()
- single_cat_title()
- single_tag_title()
- single_term_title()
- wp_get_archives() : très utile pour faire des listings par date, par postype très facilement (pareil que le widget, editable par l'user)
Par exemple, créer une liste de liens de poste projets par année, 3 max en affichant le nombre de posts par année
<?php
$args = array(
'type' => 'yearly',
'limit' => 3,
'show_post_count' => true,
'post_type' => 'projet',
);
wp_get_archives( $args );
?>
- get_calendar() (pareil que le widget, editable par l'user)
- calendar_week_mod()
- delete_get_calendar_cache()
- wp_meta()
- get_current_blog_id() (pour le multisites)
- wp_title (deprecated, auto display a present)
- allowed_tags()
- wp_enqueue_scripts()
- wp_nav_menu: display a menu on the page. Utiliser tous les args pour les wrapper au lieu de faire du markup HTML a la main autour.
- register_nav_menu: ajouter des locations de menu
- walk_nav_menu_tree (used internally by the core) utilisé pour controler comment les menus sont construits et affichés
- the_permalink(): a connaitre
- get_permalink(): a connaitre
- get_post_permalink()
- get_page_link()
- get_attachment_link()
- edit_post_link()
- get_edit_post_link()
- get_delete_post_link()
- edit_comment_link()
- edit_tag_link()
- home_url()
- site_url()
- get_search_url($terme_recherche): Retrieves the permalink for a search.
- get_search_query()
- the_feed_link()
2 notations équivalentes dans l'url
{mon-domain}/?s=terme-recherche
ou
{mon-domain}/search/terme-recherche
****### En bref
- rester à jour (core, plugins, themes)
- modifier le prefixe des tables de wordpress, ne pas utiliser
wp_
- limiter les tentatives de login
- faire une whitelist des IP qui peuvent se connecter en tant qu'admin (dans le .htaccess)
- utiliser des mots de passe forts
- déplacer le fichier de configuration
wp-config.php
en dehors de l'install de wordpress. On peut le placer en dehors du root de Wordpress. Wordpress va checker automatiquement le dossier parent s'il trouve pas le wp-config dans son dossier d'install. Sur Apache, pour eviter qu'on puisse acceder auwp-config.php
en plain/text (suite a un bug) on peut rajouter la regle<FileMatch ^wp-config.php$>deny from all</FilesMatch>
au.htaccess
- deplacer le repertoire wp-content ailleurs que dans l'install de wordpress. Cela ne rend pas le site plus secure mais ça permet d'échapper à bcp de scripts d'attaque car la structure ne sera plus standard. Ajouter dans le
wp-config.php
define('WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/mon-site/wp-content'; define('WP_CONTENT_URL', 'http://example/com/mon-site/wp-content'); define('WP_PLUGIN_URL', 'http://example.com/mon-site/wp-content/plugins');
- utiliser les rôles
- utiliser les salts
- forcer le SSL au login et aux admins avec
define('FORCE_SSL_LOGIN', true);
etdefine('FORCE_SSL_ADMIN', true);
- utiliser un plugin spécialisé en sécurtié comme BulletProof Security (BPS), Wordfrence Security
// Active/Desactive toutes les maj auto (core, themes, plugins, translations).
define( 'AUTOMATIC_UPDATED_DISABLED', false );
// Maj auto pour le core de wp seulement: false, true ou minor.
define( 'WP_AUTO_UPDATE_CORE', 'minor' );
// Limite le nombre de revisions max pour un post.
define( 'WP_POST_REVISIONS', 5 );
// Limite l'intervalle entre 2 autosave en secondes. Par défaut 60s.
define( 'AUTOSAVE_INTERNAL', 120 );
// Memoire RAM max alloué à Wordpress par le serveur (si le host est d'accord)
define('WP_MEMORY_LIMIT', '64MB');
// Active le cache, utile pour certains plugins pour optimiser le site
define('WP_CACHE', true);
//Forcer le SSL pour le login
define('FORCE_SSL_LOGIN', true);
//Forcer le SSL pour servir toutes les pages d'admin (/wp-admin). Ralentit le chargement des pages d'admin mais assure l'encryption de toutes les données.
define('FORCE_SSL_ADMIN', true);
Le fichier .htaccess
est un fichier de configuration d'Apache. On peut y définir des règles et c'est là que réside la magie de wordpress
RewriteCond ${REQUEST_FILENAME} !-f
RewriteCond ${REQUEST_FILENAME} !-d
RewriteRule . /index.php [ L]
Ce .htaccess
dit si la requete ne demande ni un fichier, ni un repertoire qui existe sur le path alors renvoie la requete vers le index.php, ce dernier lancant tout le core de wordpress.
On peut se servir du .htaccess pour rajouter de la config
- redirections permanentes:
301 redirect 301 {ancienne-url} {nouvelle-url}
- mémoire allouée à PHP :
php_value memory_limit 64M
- taille des uploads max:
php_value upload_max_filesize 20M
etphp_value post_max_size 20M
- sécurtié, restreindre l'accès à des IP.
Bonne pratique : restreindre l'accès au dossier wp-admin
a une liste blanche d'ip. Pour cela, creer un .htaccess
dans le dossier wp-admin
avec le code suivant
AuthType Basic
order deny,allow
deny from all
#IP adress to whitelist
allow from xxx.xxx.xxx.xxx.
Ainsi, seule une liste blanche d'IP d'amin peuvent se connecter en tant qu'admin au Wordpress. Si l'admin a une nouvelle IP il suffit de l'ajouter à la liste.
Configuration de log d'erreur pour un site en production directement dans le .htaccess
php_flag display_startup_errors off
php_flag display_errors off
php_flag html_errors off
php_flag log_errors on
php_value error_log /public_html/php_errors.log
où error_log
est une valeur relative au document root du web server (du Virtual Host) et non la racine de l'install de Wordpress.
// Active le mode debug - equivalent à php_flag log_errors on
define( 'WP_DEBUG', true );
// Ne pas afficher les messages d'erreur sur la sortie standard.
define( 'WP_DEBUG_DISPLAY', false );
// Log les erreurs dans le fichier debug.log.
define( 'WP_DEBUG_LOG', true );
// Log toutes les requetes vers la db dans un tableau,
define( 'SAVEQUERIES', true );
// Pour afficher le tableau de la requête
global $wpdb;
print_r( $wpdb->queries );
//Afficher toutes les constantes PHP disponibles
print_r( get_defined_constants() );
Il y a un mode maintenance natif à Wordpress. Il suffit de créer un fichier .maintenance
à la racine de l'installation de wordpress et y mettre le code suivant
<?php
$upgrading = time();
?>
On peut customiser davantage cette page en créant un fichier maintenance.php
dans le wp-content
. Celui-ci prend la priorité sur le fichier .maintenance
Utiliser pour développer des plugins et des pages d'administration/d'options custom contenant des formulaires. On peut faire vraiment tout ce qu'on veut avec et le fait d'utiliser cette méthode plutot que des hacks (notamment via des pages d'options ACF (PRO ONLY!)) : donnent plusieurs avantages
- visual consistency
- robustness and future proof
- less work : handling form submission, security nonce, sanitizing data for free (une fois qu'on a compris comment ça marchait)
Cette API est très bien mais elle est vraiment pas intuitive et demande pas mal de code boilerplate pour être effective. Cela dit elle est native, supportée et encouragée pour développer des plugins, des pages d'options d'un thème de manière professionnelle.
Il y'a d'autres alternatives, comme des frameworks qui encapsulent tout ça mais comme tout usage de code tiers il faut faire attention à ce que ces frameworks soient bien maintenus dans le temps et soient capables d'accomoder les éventuelles évolutions de la Settings API. Voir des ressources ici.
Dans tous les cas, avant d'utiliser des outils pour vous épargner beaucoup de travail redondant il est bien de le faire au moins une fois pour savoir qu'est ce qui vous ne plait pas pour trouver éventuellement le bon outil pour vous facilier la vie, ou créer le votre.
//creation d'un menu custom
add_action('admin_menu','domain_create_menu');
function domain_create_menu(){
//creation d'un menu dans le menu de premier niveau 'Settings'
add_options_page('Mon plugin','Mon plugin','manage_options','domain-options','domain_settings_page');
//appeler la fonction pour enregistrer les settings
add_action('admin_init','domain_settings_page_html');
}
function domain_settings_page(){
register_setting(
'domain-settings-group',
'domain_options',
array(
//La valeur par défaut, tres important au premier lancement quand l'option n'existe
//pas encore en base, dit ce que get_option() doit retourner par défaut
'default' => array(
'domain_setting_tel' => '02 02 02 02 02',
),
// La fonction appelée après la soumission du formulaire, ici on clean et on valide
'sanitize_callback' => function ( array $input ) {
//Sanitize ici chaque champ
//$input['domain_setting_tel'] = filtrer($input)
return $input;
},
)
);
}
register_settings
dit à l'Options API qu'elle va enregistrer quelque chose sous la clef domain_options
. Ces options seront enregistrées dans la table wp_options
sous la forme de paires clé valeur.
On va avoir plusieurs champs, plusieurs options à définir mais on va les enregistrer sous la même clef dans la table sous forme de tableau serialisé. Le premier paramètre est l'options group name
, il permet d'identifier toutes les options. Le deuxième paramètre est important car c'est la clef sous laquelle sera enregistrée les options dans la table. Il doit être unique. Le troisième paramètre est la callback pour sanitizer les valeurs de nos options.
function domain_settings_page(){
add_settings_section(
'domain-section-section1',
'Site',
'domain_setting_section1_html',
'domain-options'
);
}
On enregistre une section sur la page domain-options
(id de la page). La callback domain_setting_section1_html
sert à afficher du markup supplémentaire pour notre section (en général de l'aide).
Pour boucler le tout on doit dire à la section quels settings (champs) elle va présenter. On le fait avec add_settings_field
. On enregistre ici un champ de numéro de téléphone que l'on appelera domain_setting_tel
. Il sera enregistré comme une clef de l'option domain_options
, enregistrée en base, qui est, je vous le rappelle, un tableau sérialisé.
add_settings_field(
'mon_champ_id',
'Numéro de telephone',
'domain_settings_tel_callback',
'domain-options',
'domain-section',
array(
'label_for' => '',
'class' => '',
)
);
Ok, je vous laisse consulter la doc pour savoir ce que tout ces arguments signifient. En gros, on dit j'enregistre un champ avec
mon_champ_id
comme id du champ (pas vraiment important)Numéro de telephone
qui sera affiché comme label pour la champdomain_settings_tel_callback
, la callback pour ecrire notre inputdomain-options
, l'id ou slug de la page sur laquelle on enregistre le champdomain-section
, l'id ou slug de la section sur laquelle on enregistre le champarray()
, le dernier argument est en option, pour passer des trucs en plus à notre callback
Vous remarquerez qu'on a pas utilisé ici le nom du champ domain_setting_tel
. C'est parce que c'est une clef de l'option domain_options
. Donc c'est à nous de joueur un peu avec la Settings API. Je sais c'est pas l'API la plus simple de Wordpress...
Bientôt fini, écrivons le markup de notre champ en définissant notre fonction domain_settings_tel_callback
.
/**
* Construit l'input domain_setting_tel
*
* @param array $input Données injectées via add_settings_field.
*/
function adb_setting_nb_max_featured_projects_callback( $input ) {
//On récuperer la valeur en base. Si elle existe pas encore, on récupere la valeur renseignée sous la clef 'default' dans `register_setting`
$values = get_option( 'domain_options' );
$value = $values['domain_setting_tel'];
?>
<input
id="domain_setting_tel_id"
type="text"
name="<?php echo esc_attr( 'domain_options[domain_setting_tel]' ); ?>"
value="<?php echo esc_attr( $value ); ?>"
>
<?php
}
Voilà ! Quand on postera le formulaire, tout devrait bien se passer. La callback de sanitization devrait être appelée et ensuite l'option mise à jour en base. C'est la Settings API qui se charge de tout ça. Nous on doit juste bien cabler le tout.
Il ne reste qu'à construire la page d'options à proprement dit.
On définit notre fonction domain_settings_page_html
qui va construire notre page d'options (markup)
function domain_settings_page_html(){
?>
<div class="wrap">
<h2>Mon plugin</h2>
<form action="options.php" method="post">
// securité : creation auto de nonce par wordpress pour le groupe d'options
<?php settings_fields( 'domain-settings-group' ); ?>
// génération des sections présentes sur la page
<?php do_settings_sections( 'domain-options' ); ?>
// soumettre le formulaire
<p class="submit">
<?php submit_button( 'Enregistrer les changements' ); ?>
</p>
</form>
</div>
}
<?php
}
De la magie a lieu ici sur plusieurs lignes
- do_settings_sections( 'domain-options' ) : la Settings API va se charger de rendre tout le markup des sections et des champs qui y sont enregistrés.
- settings_fields('domain-settings-group') : va intégrer des inputs cachés, des nonces, pour valider l'origine de la soumission du formulaire et nous protéger d'attaque CSRF. Rien à faire de plus de notre part.
Quand on soumet, la Settings API se charge de
- valider le nonce
- valider/sanitizer chaque champ avec la déclaration de notre sanitize_callback
- mettre à jour l'option en base et sauvegarder les inputs
Voilà, un aperçu de la Settings API.
Comme vous pouvez le voir c'est assez puissant mais c'est hyper verbeux et c'est facile de se perdre car elle est pas si intuitive que ça. Je recommande donc après l'avoir fait une fois de manière vanilla de s'écrire un petit plugin ou une petite fonction utils pour nous aider à préparer tout ça de manière beaucoup plus simple et efficace.
- W3 Total Cache : permet de mettre en cache les pages générés par Wordpress. Crée un dossier wp-content/cache et y stocke du html static généré par les requetes précédentes. La prochaine fois qu'une page est appelée, au lieu d'executer la loop et de faire des requetes a la base, wordpress sert la page static déjà générée. Ameliore le SEO, Lazy Image etc...
- BPS Security : sécurité de Wordpress (alternative solide à Wordfence)
- ACF, la base pour manipuler les post metas
- hebergeur adapté
- mettre en place une mise en cache
- ne pas uploader vidéos/audio sur le site. Mettre sur youtube, soundcloud et utiliser le lien plutot. Ca augmente la taille des backups, ca coute cher en bande passante
- reduire les appels à la db (mieux vaut remonter plus d'infos en une fois que une info puis une info puis une info)
- limite le nb de revisions de posts. Dans le
wp-config.php
:define( 'WP_POST_REVISIONS', 3 );
- disable hotlinking and leaching of your content
#disable hotlinking of images with forbidden or custom image option
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?{domaine} [NC]
RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?google.com [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ – [NC,F,L]
- limiter la taille des postes/pages
- afficher l'excerpt plutot que tout le post dans les listings
- reduire nb de plugins
- au lieu de link vers des CDN (font, icons, js lib) les mettre directement sur le site
- optimisez les images pour le web (compression, taille)
- lazy loading
- configurer PHP via le
php.ini
, desactiver les extensions inutiles pour wordpress
;security
expose_php = Off
;performence
register_argc_argv = Off
;upload
file_uploads = On
memory_limit = 128M
upload_max_filesize = 10M
post_max_size = 48M
max_execution_time = 600
max_input_vars = 1000
;Opcache
opcache.memory_consumption=64
opcache.interned_string_buffers=16
opcache.max_accelerated_files=600
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.fast_shutdown=1
- mettre la table de
wp_comments
en InnoDB (adapté pour lecture de grands volumes) si MySql ou utiliser MariaDB
Très bien faite, mais peut parfois demander un peu d'experience pour s'y retrouver
- Codex
- Wordpress hierarchy
- Wordpress coding standards
- Template tags
- Using Permalinks
- Data Sanitization/Escaping
- Localization
Ces hebergeurs spécialisés peuvent être utiles si vous êtes pro et cherchez des solutions pour des clients prêt à payer. Sinon ne dépensez pas votre argent inutilement et utilisez l'hebergeur le moins cher possible !
- siteground. Le mieux on dirait car on a pas de restrictions sur nos acces (on a un ssh) et c'est optimisé (php, plugin cache custom installé par défaut), pas trop cher
- bluehost
- liquidweb
- Professional WordPress: Design and Development de Brad Williams et David Damstra, Edition Wrox, 3rd Edition, 2015
- Professional WordPress Plugin Development de Brad Williams et Justin Taldock, Edition Wrox, 2nd Edition, 2020
- Essential PHP Security, Chris Shiflett, Edition O'REILLY, 2006 (Attention certaines fonctions PHP sont deprecated mais sinon la philosophie et les conseils sont toujours pertinents et bien expliqués. Lecture 'attentive').
- Modern PHP: new features and good practices, Josh Lochart, Edition O'REILLY, 2015
- https://phproundtable.com/episode/all-things-wordpress
- https://podcast.htmlallthethings.com/e/the-thing-about-wordpress/
- Plugingplate : starterpack
- Introduction
A vos risques et périls
- 5 Ways to Create a WordPress Plugin Settings Page
- WordPress Settings Framework
- Creating A WordPress Settings Page Using the WordPress REST API
- wp-optionskit
- Settings API Code Generator
- ACF, classic et solide. Mais payant pour avoir accès a des features hyper utiles comme des champs repeater ou des pages d'options
- Carbon, meilleure alternative à ACF. Complètement gratuit, solide et robuste. Champs repeaters et gallery gratuit !
- Cours wordpress.org
- Building websites with WordPress
- Learn Wordpress
- Wordpress for beginners training
- How to Learn WordPress for Free in a Week (or Less)
- Wordpress tutorials
- How to, video
- Our wordpress library
- Wordpress freecodecamp
- Building PHP MVC Framework from Scratch, The Codeholic. Pas sur Wordpress mais montre les fonctionnalités de base d'un framework à implémenter
- Become a WordPress Developer: Unlocking Power With Code, pas mal pour apprendre le processus de la création de thème dans son ensemble