Proces obejmuje 2 kroki:
- pokaż formularz frontendowy
- zapisz dane po przesłaniu
Są trzy różne podejścia, które przychodzą mi na myśl, aby pokazać frontend:
- użyj wbudowanego formularza rejestracyjnego, stylów edycji itp., aby uczynić go bardziej „frontendowym”
- użyj strony / postu WordPress i wyświetl formularz za pomocą krótkiego kodu
- użyj dedykowanego szablonu niezwiązanego z żadną stroną / postem, ale wywołanego określonym adresem URL
Do tej odpowiedzi użyję tego drugiego. Powody są następujące:
- użycie wbudowanego formularza rejestracyjnego może być dobrym pomysłem, głębokie dostosowania mogą być bardzo trudne przy użyciu wbudowanego formularza, a jeśli ktoś chce również dostosować pola formularza, wzrost bólu
- użycie strony WordPress w połączeniu z krótkim kodem, nie jest tak niezawodne, a także uważam, że shorcodes nie należy używać do funkcji, tylko do formatowania i tak dalej
1: Zbuduj adres URL
Wszyscy wiemy, że domyślna forma rejestracji witryny WordPress jest często celem spamerów. Korzystanie z niestandardowego adresu URL pomaga rozwiązać ten problem. Ponadto chcę również użyć zmiennego adresu URL, tzn. Adres URL formularza rejestracji nie powinien być zawsze taki sam, co utrudnia spamerom życie. Trik odbywa się za pomocą nonce w adresie 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' )
);
}
Korzystanie z tych funkcji jest łatwe do wyświetlenia w szablonach link do formularza rejestracyjnego, nawet jeśli jest dynamiczny.
2: Rozpoznaj adres URL, pierwszy odcinek Custom_Reg\Custom_Reg
klasy
Teraz musimy rozpoznać adres URL. W tym celu zacznę pisać klasę, która zakończy się w dalszej części odpowiedzi:
<?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];
}
}
}
Funkcja wygląda na pierwszą część adresu URL później home_url()
, a jeśli pasuje do naszej wartości jednorazowej, zwraca PRAWDA. Ta funkcja będzie używana do sprawdzenia naszego żądania i wykonania niezbędnych działań w celu wyświetlenia naszego formularza.
3: Custom_Reg\Form
Klasa
Napiszę teraz klasę, która będzie odpowiedzialna za generowanie znaczników formularza. Użyję go również do przechowywania we właściwości ścieżki pliku szablonu, która powinna zostać użyta do wyświetlenia formularza.
<?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;
}
}
Klasa generuje formularze zapętlając wszystkie pola dodając create
metodę wywoływania na każdym z nich. Każde pole musi być instancją Custom_Reg\FieldInterface
. Dodano dodatkowe ukryte pole do weryfikacji nonce. Metodą formularza jest domyślnie „POST”, ale można ją ustawić na „GET” przy użyciu setVerb
metody Po utworzeniu znacznik jest zapisywany we $form
właściwości obiektu, która jest powtarzana przez output()
metodę, zaczepiona w 'custom_registration_form'
hook: w szablonie formularza wystarczy wywołaćdo_action( 'custom_registration_form' )
wyświetli formularz.
4: szablon domyślny
Jak powiedziałem, szablon formularza można łatwo zastąpić, jednak potrzebujemy podstawowego szablonu na wypadek awarii. Napisałem tutaj bardzo szorstki szablon, bardziej dowód koncepcji niż prawdziwy szablon.
<?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: Custom_Reg\FieldInterface
interfejs
Każde pole powinno być obiektem, który implementuje następujący interfejs
<?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 = '');
}
Myślę, że komentarze wyjaśniają, co powinny robić klasy implementujące ten interfejs.
6: Dodanie niektórych pól
Teraz potrzebujemy kilku pól. Możemy stworzyć plik o nazwie „fields.php”, w którym zdefiniujemy klasy pól:
<?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' );
}
}
Użyłem klasy bazowej do zdefiniowania domyślnej implementacji interfejsu, jednak można dodać bardzo spersonalizowane pola bezpośrednio implementujące interfejs lub rozszerzające klasę podstawową i zastępujące niektóre metody.
W tym momencie mamy wszystko do wyświetlenia formularza, teraz potrzebujemy czegoś, aby zweryfikować i zapisać pola.
7: Custom_Reg\Saver
Klasa
<?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;
}
}
Ta klasa ma 2 główne metody, jedna ( validate
), która zapętla pola, sprawdza je i zapisuje dobre dane w tablicy, druga ( save
) zapisuje wszystkie dane w bazie danych i wysyła hasło pocztą elektroniczną do nowego użytkownika.
8: Korzystanie ze zdefiniowanych klas: zakończenie Custom_Reg
zajęć
Teraz możemy znów pracować nad Custom_Reg
klasą, dodając metody, które „kleją” zdefiniowany obiekt i sprawiają, że działają
<?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];
}
}
}
Konstruktor klasy akceptuje instancję Form
i jedną z Saver
.
init()
metoda (za pomocą checkUrl()
) spójrz na pierwszą część adresu URL po home_url()
, a jeśli pasuje do właściwej wartości jednorazowej, sprawdza, czy formularz został już przesłany, jeśli tak, używając Saver
obiektu, sprawdza poprawność i zapisuje dane użytkownika, w przeciwnym razie po prostu wydrukuj formularz .
init()
Metoda uruchamia także hak akcji, 'custom_reg_form_init'
przekazując instancję formularza jako argument: tego haka należy użyć do dodania pól, ustawienia niestandardowego szablonu, a także do dostosowania metody formularza.
9: Składanie rzeczy
Teraz musimy napisać główny plik wtyczki, w którym możemy
- wymagają wszystkich plików
- wczytaj domenę tekstową
- uruchom cały proces za pomocą
Custom_Reg
klasy instancji i połączeniainit()
na niej metodę za pomocą odpowiednio wczesnego przechwytywania
- użyj „custom_reg_form_init”, aby dodać pola w celu utworzenia klasy
Więc:
<?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: Brakujące zadania
Teraz wszystko jest dość skończone. Musimy tylko dostosować szablon, prawdopodobnie dodając niestandardowy plik szablonu w naszym motywie.
W ten sposób możemy dodawać określone style i skrypty tylko do niestandardowej strony rejestracji
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( ... );
}
});
Za pomocą tej metody możemy kolejkować niektóre skrypty js do obsługi sprawdzania poprawności po stronie klienta, np. Ten . Znaczniki potrzebne do działania skryptu można łatwo obsłużyć edytując Custom_Reg\BaseField
klasę.
Jeśli chcemy dostosować e-mail rejestracyjny, możemy skorzystać ze standardowej metody i mając niestandardowe dane zapisane w meta, możemy z nich skorzystać w e-mailu.
Ostatnim zadaniem, które prawdopodobnie chcemy wdrożyć, jest zapobieganie prośbie o domyślny formularz rejestracyjny, tak prosty jak:
add_action( 'login_form_register', function() { exit(); } );
Wszystkie pliki można znaleźć w Gist tutaj .