Le processus comprend 2 étapes:
- afficher le formulaire frontal
- enregistrer les données lors de la soumission
Il y a 3 approches différentes qui me viennent à l'esprit pour montrer le frontend:
- utilisez le formulaire d'enregistrement intégré, les styles d'édition, etc. pour le rendre plus "comme le frontend"
- utiliser une page / publication WordPress et afficher un formulaire à l'aide d'un shortcode
- utiliser un modèle dédié non connecté à une page / publication, mais appelé par une URL spécifique
Pour cette réponse, je vais utiliser ce dernier. Les raisons sont les suivantes:
- utiliser le formulaire d'enregistrement intégré peut être une bonne idée, les personnalisations approfondies peuvent être très difficiles à utiliser avec le formulaire intégré, et si l'on veut également personnaliser les champs du formulaire, la douleur augmente
- utiliser une page WordPress en combinaison avec un shortcode, n'est pas si fiable, et je pense aussi que les shorcodes ne devraient pas être utilisés pour la fonctionnalité, juste pour le formatage et autres
1: Créez l'URL
Nous savons tous que le formulaire d'inscription par défaut d'un site WordPress est souvent une cible pour les spammeurs. L'utilisation d'une URL personnalisée est une aide pour résoudre ce problème. De plus, je veux également utiliser une URL variable , c'est-à-dire que l'URL du formulaire d'enregistrement ne doit pas toujours être la même, cela rend la vie des spammeurs plus difficile. L'astuce se fait en utilisant un nonce dans l'url:
/**
* Generate dynamic registration url
*/
function custom_registration_url() {
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
return home_url( $nonce );
}
/**
* Generate dynamic registration link
*/
function custom_registration_link() {
$format = '<a href="%s">%s</a>';
printf(
$format,
custom_registration_url(), __( 'Register', 'custom_reg_form' )
);
}
L'utilisation de ces fonctions permet d'afficher facilement dans les modèles un lien vers le formulaire d'inscription même s'il est dynamique.
2: Reconnaître l'url, premier bout de Custom_Reg\Custom_Reg
classe
Maintenant, nous devons reconnaître l'URL. Pour le but, je vais commencer à écrire une classe, qui sera terminée plus tard dans la réponse:
<?php
// don't save, just a stub
namespace Custom_Reg;
class Custom_Reg {
function checkUrl() {
$url_part = $this->getUrl();
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
if ( ( $url_part === $nonce ) ) {
// do nothing if registration is not allowed or user logged
if ( is_user_logged_in() || ! get_option('users_can_register') ) {
wp_safe_redirect( home_url() );
exit();
}
return TRUE;
}
}
protected function getUrl() {
$home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
$relative = trim(str_replace($home_path, '', esc_url(add_query_arg(array()))), '/');
$parts = explode( '/', $relative );
if ( ! empty( $parts ) && ! isset( $parts[1] ) ) {
return $parts[0];
}
}
}
La fonction regarde la première partie de l'url après home_url()
, et si elle correspond à notre nonce, elle retourne TRUE. cette fonction sera utilisée pour vérifier notre demande et effectuer les actions nécessaires pour afficher notre formulaire.
3: La Custom_Reg\Form
classe
Je vais maintenant écrire une classe, qui sera chargée de générer le balisage du formulaire. Je vais également l'utiliser pour stocker dans une propriété le chemin du fichier modèle qui devrait être utilisé pour afficher le formulaire.
<?php
// file: Form.php
namespace Custom_Reg;
class Form {
protected $fields;
protected $verb = 'POST';
protected $template;
protected $form;
public function __construct() {
$this->fields = new \ArrayIterator();
}
public function create() {
do_action( 'custom_reg_form_create', $this );
$form = $this->open();
$it = $this->getFields();
$it->rewind();
while( $it->valid() ) {
$field = $it->current();
if ( ! $field instanceof FieldInterface ) {
throw new \DomainException( "Invalid field" );
}
$form .= $field->create() . PHP_EOL;
$it->next();
}
do_action( 'custom_reg_form_after_fields', $this );
$form .= $this->close();
$this->form = $form;
add_action( 'custom_registration_form', array( $this, 'output' ), 0 );
}
public function output() {
unset( $GLOBALS['wp_filters']['custom_registration_form'] );
if ( ! empty( $this->form ) ) {
echo $this->form;
}
}
public function getTemplate() {
return $this->template;
}
public function setTemplate( $template ) {
if ( ! is_string( $template ) ) {
throw new \InvalidArgumentException( "Invalid template" );
}
$this->template = $template;
}
public function addField( FieldInterface $field ) {
$hook = 'custom_reg_form_create';
if ( did_action( $hook ) && current_filter() !== $hook ) {
throw new \BadMethodCallException( "Add fields before {$hook} is fired" );
}
$this->getFields()->append( $field );
}
public function getFields() {
return $this->fields;
}
public function getVerb() {
return $this->verb;
}
public function setVerb( $verb ) {
if ( ! is_string( $verb) ) {
throw new \InvalidArgumentException( "Invalid verb" );
}
$verb = strtoupper($verb);
if ( in_array($verb, array( 'GET', 'POST' ) ) ) $this->verb = $verb;
}
protected function open() {
$out = sprintf( '<form id="custom_reg_form" method="%s">', $this->verb ) . PHP_EOL;
$nonce = '<input type="hidden" name="_n" value="%s" />';
$out .= sprintf( $nonce, wp_create_nonce( 'custom_reg_form_nonce' ) ) . PHP_EOL;
$identity = '<input type="hidden" name="custom_reg_form" value="%s" />';
$out .= sprintf( $identity, __CLASS__ ) . PHP_EOL;
return $out;
}
protected function close() {
$submit = __('Register', 'custom_reg_form');
$out = sprintf( '<input type="submit" value="%s" />', $submit );
$out .= '</form>';
return $out;
}
}
La classe génère un balisage de formulaire en boucle tous les champs a ajouté la create
méthode d' appel sur chacun d'eux. Chaque champ doit être une instance de Custom_Reg\FieldInterface
. Un champ caché supplémentaire est ajouté pour la vérification nonce. La méthode du formulaire est 'POST' par défaut, mais elle peut être réglée sur 'GET' en utilisant la setVerb
méthode. Une fois créé, le balisage est enregistré dans la $form
propriété d'objet qui est répercutée par la output()
méthode, accrochée au 'custom_registration_form'
crochet: dans le modèle de formulaire, il suffit d'appeler do_action( 'custom_registration_form' )
pour afficher le formulaire.
4: Le modèle par défaut
Comme je l'ai dit, le modèle de formulaire peut être facilement remplacé, mais nous avons besoin d'un modèle de base comme solution de rechange. J'écrirai ici un modèle très approximatif, plus une preuve de concept qu'un vrai modèle.
<?php
// file: default_form_template.php
get_header();
global $custom_reg_form_done, $custom_reg_form_error;
if ( isset( $custom_reg_form_done ) && $custom_reg_form_done ) {
echo '<p class="success">';
_e(
'Thank you, your registration was submitted, check your email.',
'custom_reg_form'
);
echo '</p>';
} else {
if ( $custom_reg_form_error ) {
echo '<p class="error">' . $custom_reg_form_error . '</p>';
}
do_action( 'custom_registration_form' );
}
get_footer();
5: L' Custom_Reg\FieldInterface
interface
Chaque champ doit être un objet qui implémente l'interface suivante
<?php
// file: FieldInterface.php
namespace Custom_Reg;
interface FieldInterface {
/**
* Return the field id, used to name the request value and for the 'name' param of
* html input field
*/
public function getId();
/**
* Return the filter constant that must be used with
* filter_input so get the value from request
*/
public function getFilter();
/**
* Return true if the used value passed as argument should be accepted, false if not
*/
public function isValid( $value = NULL );
/**
* Return true if field is required, false if not
*/
public function isRequired();
/**
* Return the field input markup. The 'name' param must be output
* according to getId()
*/
public function create( $value = '');
}
Je pense que les commentaires expliquent ce que les classes implémentant cette interface devraient faire.
6: Ajout de certains champs
Maintenant, nous avons besoin de quelques champs. Nous pouvons créer un fichier appelé 'fields.php' où nous définissons les classes de champs:
<?php
// file: fields.php
namespace Custom_Reg;
abstract class BaseField implements FieldInterface {
protected function getType() {
return isset( $this->type ) ? $this->type : 'text';
}
protected function getClass() {
$type = $this->getType();
if ( ! empty($type) ) return "{$type}-field";
}
public function getFilter() {
return FILTER_SANITIZE_STRING;
}
public function isRequired() {
return isset( $this->required ) ? $this->required : FALSE;
}
public function isValid( $value = NULL ) {
if ( $this->isRequired() ) {
return $value != '';
}
return TRUE;
}
public function create( $value = '' ) {
$label = '<p><label>' . $this->getLabel() . '</label>';
$format = '<input type="%s" name="%s" value="%s" class="%s"%s /></p>';
$required = $this->isRequired() ? ' required' : '';
return $label . sprintf(
$format,
$this->getType(), $this->getId(), $value, $this->getClass(), $required
);
}
abstract function getLabel();
}
class FullName extends BaseField {
protected $required = TRUE;
public function getID() {
return 'fullname';
}
public function getLabel() {
return __( 'Full Name', 'custom_reg_form' );
}
}
class Login extends BaseField {
protected $required = TRUE;
public function getID() {
return 'login';
}
public function getLabel() {
return __( 'Username', 'custom_reg_form' );
}
}
class Email extends BaseField {
protected $type = 'email';
public function getID() {
return 'email';
}
public function getLabel() {
return __( 'Email', 'custom_reg_form' );
}
public function isValid( $value = NULL ) {
return ! empty( $value ) && filter_var( $value, FILTER_VALIDATE_EMAIL );
}
}
class Country extends BaseField {
protected $required = FALSE;
public function getID() {
return 'country';
}
public function getLabel() {
return __( 'Country', 'custom_reg_form' );
}
}
J'ai utilisé une classe de base pour définir l'implémentation d'interface par défaut, cependant, on peut ajouter des champs très personnalisés implémentant directement l'interface ou étendant la classe de base et remplaçant certaines méthodes.
À ce stade, nous avons tout pour afficher le formulaire, maintenant nous avons besoin de quelque chose pour valider et enregistrer les champs.
7: La Custom_Reg\Saver
classe
<?php
// file: Saver.php
namespace Custom_Reg;
class Saver {
protected $fields;
protected $user = array( 'user_login' => NULL, 'user_email' => NULL );
protected $meta = array();
protected $error;
public function setFields( \ArrayIterator $fields ) {
$this->fields = $fields;
}
/**
* validate all the fields
*/
public function validate() {
// if registration is not allowed return false
if ( ! get_option('users_can_register') ) return FALSE;
// if no fields are setted return FALSE
if ( ! $this->getFields() instanceof \ArrayIterator ) return FALSE;
// first check nonce
$nonce = $this->getValue( '_n' );
if ( $nonce !== wp_create_nonce( 'custom_reg_form_nonce' ) ) return FALSE;
// then check all fields
$it = $this->getFields();
while( $it->valid() ) {
$field = $it->current();
$key = $field->getID();
if ( ! $field instanceof FieldInterface ) {
throw new \DomainException( "Invalid field" );
}
$value = $this->getValue( $key, $field->getFilter() );
if ( $field->isRequired() && empty($value) ) {
$this->error = sprintf( __('%s is required', 'custom_reg_form' ), $key );
return FALSE;
}
if ( ! $field->isValid( $value ) ) {
$this->error = sprintf( __('%s is not valid', 'custom_reg_form' ), $key );
return FALSE;
}
if ( in_array( "user_{$key}", array_keys($this->user) ) ) {
$this->user["user_{$key}"] = $value;
} else {
$this->meta[$key] = $value;
}
$it->next();
}
return TRUE;
}
/**
* Save the user using core register_new_user that handle username and email check
* and also sending email to new user
* in addition save all other custom data in user meta
*
* @see register_new_user()
*/
public function save() {
// if registration is not allowed return false
if ( ! get_option('users_can_register') ) return FALSE;
// check mandatory fields
if ( ! isset($this->user['user_login']) || ! isset($this->user['user_email']) ) {
return false;
}
$user = register_new_user( $this->user['user_login'], $this->user['user_email'] );
if ( is_numeric($user) ) {
if ( ! update_user_meta( $user, 'custom_data', $this->meta ) ) {
wp_delete_user($user);
return FALSE;
}
return TRUE;
} elseif ( is_wp_error( $user ) ) {
$this->error = $user->get_error_message();
}
return FALSE;
}
public function getValue( $var, $filter = FILTER_SANITIZE_STRING ) {
if ( ! is_string($var) ) {
throw new \InvalidArgumentException( "Invalid value" );
}
$method = strtoupper( filter_input( INPUT_SERVER, 'REQUEST_METHOD' ) );
$type = $method === 'GET' ? INPUT_GET : INPUT_POST;
$val = filter_input( $type, $var, $filter );
return $val;
}
public function getFields() {
return $this->fields;
}
public function getErrorMessage() {
return $this->error;
}
}
Cette classe a 2 méthodes principales, une ( validate
) qui boucle les champs, les valide et enregistre les bonnes données dans un tableau, la seconde ( save
) enregistre toutes les données dans la base de données et envoie le mot de passe par e-mail au nouvel utilisateur.
8: Utilisation de classes définies: terminer la Custom_Reg
classe
Maintenant, nous pouvons travailler à nouveau sur la Custom_Reg
classe, en ajoutant les méthodes qui "collent" l'objet défini et les font fonctionner
<?php
// file Custom_Reg.php
namespace Custom_Reg;
class Custom_Reg {
protected $form;
protected $saver;
function __construct( Form $form, Saver $saver ) {
$this->form = $form;
$this->saver = $saver;
}
/**
* Check if the url to recognize is the one for the registration form page
*/
function checkUrl() {
$url_part = $this->getUrl();
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
if ( ( $url_part === $nonce ) ) {
// do nothing if registration is not allowed or user logged
if ( is_user_logged_in() || ! get_option('users_can_register') ) {
wp_safe_redirect( home_url() );
exit();
}
return TRUE;
}
}
/**
* Init the form, if submitted validate and save, if not just display it
*/
function init() {
if ( $this->checkUrl() !== TRUE ) return;
do_action( 'custom_reg_form_init', $this->form );
if ( $this->isSubmitted() ) {
$this->save();
}
// don't need to create form if already saved
if ( ! isset( $custom_reg_form_done ) || ! $custom_reg_form_done ) {
$this->form->create();
}
load_template( $this->getTemplate() );
exit();
}
protected function save() {
global $custom_reg_form_error;
$this->saver->setFields( $this->form->getFields() );
if ( $this->saver->validate() === TRUE ) { // validate?
if ( $this->saver->save() ) { // saved?
global $custom_reg_form_done;
$custom_reg_form_done = TRUE;
} else { // saving error
$err = $this->saver->getErrorMessage();
$custom_reg_form_error = $err ? : __( 'Error on save.', 'custom_reg_form' );
}
} else { // validation error
$custom_reg_form_error = $this->saver->getErrorMessage();
}
}
protected function isSubmitted() {
$type = $this->form->getVerb() === 'GET' ? INPUT_GET : INPUT_POST;
$sub = filter_input( $type, 'custom_reg_form', FILTER_SANITIZE_STRING );
return ( ! empty( $sub ) && $sub === get_class( $this->form ) );
}
protected function getTemplate() {
$base = $this->form->getTemplate() ? : FALSE;
$template = FALSE;
$default = dirname( __FILE__ ) . '/default_form_template.php';
if ( ! empty( $base ) ) {
$template = locate_template( $base );
}
return $template ? : $default;
}
protected function getUrl() {
$home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
$relative = trim( str_replace( $home_path, '', add_query_arg( array() ) ), '/' );
$parts = explode( '/', $relative );
if ( ! empty( $parts ) && ! isset( $parts[1] ) ) {
return $parts[0];
}
}
}
Le constructeur de la classe accepte une instance de Form
et une de Saver
.
init()
La méthode (en utilisant checkUrl()
) regarde la première partie de l'URL après home_url()
, et si elle correspond avec le bon nonce, elle vérifie si le formulaire a déjà été soumis, si c'est le cas en utilisant l' Saver
objet, elle valide et enregistre les données utilisateur, sinon il suffit d'imprimer le formulaire .
init()
La méthode déclenche également le hook d'action en 'custom_reg_form_init'
passant l'instance de formulaire comme argument: ce hook doit être utilisé pour ajouter des champs, pour configurer le modèle personnalisé et également pour personnaliser la méthode du formulaire.
9: Assembler les choses
Maintenant, nous devons écrire le fichier du plugin principal, où nous pouvons
- exiger tous les fichiers
- charger le domaine de texte
- démarrage du processus entier en utilisant la
Custom_Reg
classe d' instanciation et la init()
méthode d' appel à l'aide d'un hook raisonnablement précoce
- utilisez le 'custom_reg_form_init' pour ajouter les champs à la classe de formulaire
Alors:
<?php
/**
* Plugin Name: Custom Registration Form
* Description: Just a rough plugin example to answer a WPSE question
* Plugin URI: https://wordpress.stackexchange.com/questions/10309/
* Author: G. M.
* Author URI: https://wordpress.stackexchange.com/users/35541/g-m
*
*/
if ( is_admin() ) return; // this plugin is all about frontend
load_plugin_textdomain(
'custom_reg_form',
FALSE,
plugin_dir_path( __FILE__ ) . 'langs'
);
require_once plugin_dir_path( __FILE__ ) . 'FieldInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'fields.php';
require_once plugin_dir_path( __FILE__ ) . 'Form.php';
require_once plugin_dir_path( __FILE__ ) . 'Saver.php';
require_once plugin_dir_path( __FILE__ ) . 'CustomReg.php';
/**
* Generate dynamic registration url
*/
function custom_registration_url() {
$nonce = urlencode( wp_create_nonce( 'registration_url' ) );
return home_url( $nonce );
}
/**
* Generate dynamic registration link
*/
function custom_registration_link() {
$format = '<a href="%s">%s</a>';
printf(
$format,
custom_registration_url(), __( 'Register', 'custom_reg_form' )
);
}
/**
* Setup, show and save the form
*/
add_action( 'wp_loaded', function() {
try {
$form = new Custom_Reg\Form;
$saver = new Custom_Reg\Saver;
$custom_reg = new Custom_Reg\Custom_Reg( $form, $saver );
$custom_reg->init();
} catch ( Exception $e ) {
if ( defined('WP_DEBUG') && WP_DEBUG ) {
$msg = 'Exception on ' . __FUNCTION__;
$msg .= ', Type: ' . get_class( $e ) . ', Message: ';
$msg .= $e->getMessage() ? : 'Unknown error';
error_log( $msg );
}
wp_safe_redirect( home_url() );
}
}, 0 );
/**
* Add fields to form
*/
add_action( 'custom_reg_form_init', function( $form ) {
$classes = array(
'Custom_Reg\FullName',
'Custom_Reg\Login',
'Custom_Reg\Email',
'Custom_Reg\Country'
);
foreach ( $classes as $class ) {
$form->addField( new $class );
}
}, 1 );
10: Tâches manquantes
Maintenant, tout est assez bien fait. Nous avons juste à personnaliser le modèle, en ajoutant probablement un fichier de modèle personnalisé dans notre thème.
Nous pouvons ajouter des styles et des scripts spécifiques uniquement à la page d'inscription personnalisée de cette manière
add_action( 'wp_enqueue_scripts', function() {
// if not on custom registration form do nothing
if ( did_action('custom_reg_form_init') ) {
wp_enqueue_style( ... );
wp_enqueue_script( ... );
}
});
En utilisant cette méthode, nous pouvons mettre en file d'attente certains scripts js pour gérer la validation côté client, par exemple celle-ci . Le balisage nécessaire pour faire fonctionner ce script peut être facilement géré lors de l'édition de la Custom_Reg\BaseField
classe.
Si nous voulons personnaliser l'e-mail d'inscription, nous pouvons utiliser la méthode standard et avoir des données personnalisées enregistrées sur méta, nous pouvons les utiliser dans l'e-mail.
La dernière tâche que nous voulons probablement mettre en œuvre est d'empêcher la demande de formulaire d'inscription par défaut, aussi simple que:
add_action( 'login_form_register', function() { exit(); } );
Tous les fichiers peuvent être trouvés dans un Gist ici .