Der Prozess umfasst 2 Schritte:
- zeige das Frontend Formular
- Speichern Sie die Daten bei der Übermittlung
Es gibt 3 verschiedene Ansätze, die mir einfallen, um das Frontend zu zeigen:
- Verwenden Sie das eingebaute Registrierungsformular, Bearbeitungsstile usw., um es "frontendartiger" zu gestalten.
- Verwenden Sie eine WordPress-Seite / einen WordPress-Beitrag und zeigen Sie das Formular mit einem Shortcode an
- Verwenden Sie eine dedizierte Vorlage, die nicht mit einer Seite / einem Beitrag verknüpft ist, sondern von einer bestimmten URL aufgerufen wird
Für diese Antwort verwende ich letzteres. Die Gründe sind:
- Die Verwendung des eingebauten Registrierungsformulars kann eine gute Idee sein, tiefe Anpassungen können mit dem eingebauten Formular sehr schwierig sein, und wenn man auch Formularfelder anpassen möchte, erhöht sich der Schmerz
- Verwenden Sie eine WordPress-Seite in Kombination mit einem Shortcode, ist nicht so zuverlässig, und ich denke, dass Shorcodes nicht für die Funktionalität verwendet werden sollten, nur für die Formatierung und so
1: Erstellen Sie die URL
Wir alle wissen, dass die Standardregistrierungsform einer WordPress-Site häufig ein Ziel für Spammer ist. Die Verwendung einer benutzerdefinierten URL ist eine Hilfe zur Lösung dieses Problems. Außerdem möchte ich auch eine variable URL verwenden, dh die Registrierungsformular-URL sollte nicht immer gleich sein, dies macht Spammern das Leben schwerer. Der Trick wird mit einem Nonce in der URL gemacht:
/**
* 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' )
);
}
Die Verwendung dieser Funktionen ist einfach, in Vorlagen einen Link zum Registrierungsformular anzuzeigen, auch wenn es dynamisch ist.
2: Erkennen Sie die URL, den ersten Stub der Custom_Reg\Custom_Reg
Klasse
Jetzt müssen wir die URL erkennen. Zum Zweck beginne ich mit dem Schreiben einer Klasse, die später in der Antwort beendet wird:
<?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];
}
}
}
Die Funktion betrachtet den ersten Teil der URL danach home_url()
und gibt TRUE zurück , wenn sie mit unserer Nonce übereinstimmt. Diese Funktion wird verwendet, um unsere Anfrage zu überprüfen und die erforderlichen Aktionen auszuführen, um unser Formular anzuzeigen.
3: Die Custom_Reg\Form
Klasse
Ich werde jetzt eine Klasse schreiben, die für die Generierung des Formular-Markups verantwortlich ist. Ich werde es auch verwenden, um in einer Eigenschaft den Pfad der Vorlagendatei zu speichern, der zum Anzeigen des Formulars verwendet werden soll.
<?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;
}
}
Die Klasse generiert eine Formular-Markup-Schleife, in der alle Felder der jeweils hinzugefügten aufrufenden create
Methode aufgeführt werden. Jedes Feld muss eine Instanz von sein Custom_Reg\FieldInterface
. Ein zusätzliches ausgeblendetes Feld wird zur Überprüfung hinzugefügt. Die Formularmethode ist standardmäßig 'POST', kann aber mit setVerb
method auf 'GET' gesetzt werden . Nach der Erstellung der Auszeichnungs innerhalb der gespeicherten $form
Objekteigenschaft, die durch Echo wird output()
in Verfahren, gehakt 'custom_registration_form'
Hook: in der Formularvorlage, rufen Sie einfach do_action( 'custom_registration_form' )
ausgeben wird die Form.
4: Die Standardvorlage
Wie gesagt, die Vorlage für das Formular kann leicht überschrieben werden, wir benötigen jedoch eine grundlegende Vorlage als Fallback. Ich werde hier eine sehr grobe Vorlage schreiben, eher ein Proof of Concept als eine echte Vorlage.
<?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: Die Custom_Reg\FieldInterface
Schnittstelle
Jedes Feld sollte ein Objekt sein, das die folgende Schnittstelle implementiert
<?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 = '');
}
Ich denke, dass Kommentare erklären, welche Klassen diese Schnittstelle implementieren sollten.
6: Hinzufügen einiger Felder
Jetzt brauchen wir ein paar Felder. Wir können eine Datei namens 'fields.php' erstellen, in der wir die Feldklassen definieren:
<?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' );
}
}
Ich habe eine Basisklasse verwendet, um die Standard-Schnittstellenimplementierung zu definieren. Man kann jedoch sehr angepasste Felder hinzufügen, die die Schnittstelle direkt implementieren oder die Basisklasse erweitern und einige Methoden überschreiben.
An diesem Punkt haben wir alles, um das Formular anzuzeigen. Jetzt brauchen wir etwas, um die Felder zu validieren und zu speichern.
7: Die Custom_Reg\Saver
Klasse
<?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;
}
}
Diese Klasse hat 2 Hauptmethoden, eine ( validate
), die die Felder schleifen, validieren und gute Daten in einem Array save
speichern , die zweite ( ), alle Daten in der Datenbank speichern und ein Passwort per E-Mail an einen neuen Benutzer senden.
8: Verwenden definierter Klassen: Beenden der Custom_Reg
Klasse
Jetzt können wir wieder an der Custom_Reg
Klasse arbeiten und die Methoden hinzufügen, die das definierte Objekt "zusammenkleben" und zum Funktionieren bringen
<?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];
}
}
}
Der Konstruktor der Klasse akzeptiert eine Instanz von Form
und eine von Saver
.
init()
method (using checkUrl()
) schaut sich den ersten Teil der URL danach home_url()
an und überprüft, ob das Formular bereits Saver
gesendet wurde , wenn dies mit dem Objekt der Fall ist, überprüft und speichert es die Benutzerdaten, andernfalls druckt es einfach das Formular aus .
init()
method löst auch den Aktions-Hook aus 'custom_reg_form_init'
, der die Formularinstanz als Argument übergibt: Dieser Hook sollte zum Hinzufügen von Feldern, zum Einrichten der benutzerdefinierten Vorlage und zum Anpassen der Formularmethode verwendet werden.
9: Dinge zusammenfügen
Jetzt müssen wir die Haupt-Plugin-Datei schreiben, wo wir können
- Benötigen Sie alle Dateien
- Laden Sie die Textdomain
- Starten Sie den gesamten Prozess mithilfe der Instanziierungsklasse
Custom_Reg
und der Aufrufmethode init()
unter Verwendung eines relativ frühen Hooks
- Verwenden Sie 'custom_reg_form_init', um die Felder zur Formularklasse hinzuzufügen
So:
<?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: Fehlende Aufgaben
Jetzt ist alles ziemlich erledigt. Wir müssen nur die Vorlage anpassen und fügen wahrscheinlich eine benutzerdefinierte Vorlagendatei in unser Design ein.
Auf diese Weise können wir der benutzerdefinierten Registrierungsseite nur bestimmte Stile und Skripte hinzufügen
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( ... );
}
});
Mit dieser Methode können wir einige js-Skripte in die Warteschlange stellen, um die clientseitige Validierung durchzuführen, z . B. dieses . Das für die Ausführung des Skripts erforderliche Markup kann problemlos zum Bearbeiten der Custom_Reg\BaseField
Klasse verwendet werden.
Wenn wir die Registrierungs-E-Mail anpassen möchten, können wir die Standardmethode verwenden und benutzerdefinierte Daten auf Meta speichern und in der E-Mail verwenden.
Die letzte Aufgabe, die wir wahrscheinlich implementieren möchten, ist das Verhindern der Anforderung eines Standardregistrierungsformulars, so einfach wie:
add_action( 'login_form_register', function() { exit(); } );
Alle Dateien können in einem Gist finden hier .