Quá trình này bao gồm 2 bước:
- hiển thị các hình thức frontend
- lưu dữ liệu khi nộp
Có 3 cách tiếp cận khác nhau xuất hiện trong đầu tôi để hiển thị frontend:
- sử dụng biểu mẫu đăng ký tích hợp, kiểu chỉnh sửa, v.v để làm cho nó trở nên "giống như"
- sử dụng trang / bài đăng trên WordPress và hiển thị biểu mẫu bằng cách sử dụng một mã ngắn
- sử dụng một mẫu dành riêng không được kết nối với bất kỳ trang / bài đăng nào, nhưng được gọi bởi một url cụ thể
Đối với câu trả lời này, tôi sẽ sử dụng cái sau. Những lý do là:
- sử dụng biểu mẫu đăng ký tích hợp có thể là một ý tưởng hay, các tùy chỉnh sâu có thể rất khó sử dụng biểu mẫu tích hợp và nếu ai đó cũng muốn các trường biểu mẫu tùy chỉnh thì nỗi đau sẽ tăng lên
- sử dụng trang WordPress kết hợp với shortcode, không đáng tin cậy và tôi cũng nghĩ rằng không nên sử dụng mã shorode cho chức năng, chỉ để định dạng và như vậy
1: Xây dựng url
Tất cả chúng ta đều biết rằng hình thức đăng ký mặc định của một trang web WordPress thường là mục tiêu cho những kẻ gửi thư rác. Sử dụng một url tùy chỉnh là một trợ giúp để giải quyết vấn đề này. Ngoài ra, tôi cũng muốn sử dụng một url biến , tức là url mẫu đăng ký không nên luôn giống nhau, điều này làm cho cuộc sống của những kẻ gửi thư rác trở nên khó khăn hơn. Thủ thuật được thực hiện bằng cách sử dụng một nonce trong 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' )
);
}
Sử dụng các chức năng này rất dễ dàng để hiển thị trong các mẫu một liên kết đến biểu mẫu đăng ký ngay cả khi nó động.
2: Nhận ra url, sơ khai đầu tiên của Custom_Reg\Custom_Reg
lớp
Bây giờ chúng ta cần nhận ra url. Đối với mục đích, tôi sẽ bắt đầu viết một lớp, nó sẽ được hoàn thành sau trong câu trả lời:
<?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];
}
}
}
Hàm nhìn vào phần đầu tiên của url sau home_url()
và nếu nó khớp với phần mở rộng của chúng ta, nó sẽ trả về TRUE. chức năng này sẽ được sử dụng để kiểm tra yêu cầu của chúng tôi và thực hiện hành động cần thiết để hiển thị biểu mẫu của chúng tôi.
3: Custom_Reg\Form
Lớp
Bây giờ tôi sẽ viết một lớp, sẽ chịu trách nhiệm tạo ra đánh dấu biểu mẫu. Tôi cũng sẽ sử dụng nó để lưu trữ trong một thuộc tính đường dẫn tệp mẫu nên được sử dụng để hiển thị biểu mẫu.
<?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;
}
}
Lớp tạo đánh dấu biểu mẫu lặp tất cả các trường được thêm create
phương thức gọi trên mỗi trường. Mỗi lĩnh vực phải là ví dụ của Custom_Reg\FieldInterface
. Một trường ẩn bổ sung được thêm vào để xác minh nonce. Phương thức biểu mẫu là 'POST' theo mặc định, nhưng nó có thể được giải quyết thành 'GET' bằng setVerb
phương thức. Sau khi tạo, đánh dấu được lưu bên trong thuộc tính $form
đối tượng được lặp lại theo output()
phương thức, được nối vào 'custom_registration_form'
hook: trong mẫu biểu mẫu, chỉ cần gọi do_action( 'custom_registration_form' )
sẽ xuất ra biểu mẫu.
4: Mẫu mặc định
Như tôi đã nói, mẫu cho biểu mẫu có thể dễ dàng được ghi đè, tuy nhiên chúng ta cần một mẫu cơ bản làm dự phòng. Tôi sẽ viết ở đây một mẫu rất thô, nhiều bằng chứng về khái niệm hơn là một mẫu thực.
<?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
Giao diện
Mỗi trường phải là một đối tượng thực hiện giao diện sau
<?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 = '');
}
Tôi nghĩ rằng các ý kiến giải thích những gì các lớp thực hiện giao diện này nên làm.
6: Thêm một số lĩnh vực
Bây giờ chúng ta cần một số lĩnh vực. Chúng ta có thể tạo một tệp có tên 'Field.php' trong đó chúng ta xác định các lớp trường:
<?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' );
}
}
Tôi đã sử dụng một lớp cơ sở để xác định hàm giả giao diện mặc định, tuy nhiên, người ta có thể thêm các trường rất tùy chỉnh trực tiếp thực hiện giao diện hoặc mở rộng lớp cơ sở và ghi đè một số phương thức.
Tại thời điểm này, chúng tôi có mọi thứ để hiển thị biểu mẫu, bây giờ chúng tôi cần một cái gì đó để xác nhận và lưu các trường.
7: Custom_Reg\Saver
Lớp học
<?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;
}
}
Lớp đó, có 2 phương thức chính, một ( validate
) lặp các trường, xác thực chúng và lưu dữ liệu tốt vào một mảng, thứ hai ( save
) lưu tất cả dữ liệu trong cơ sở dữ liệu và gửi mật khẩu qua email cho người dùng mới.
8: Sử dụng các lớp được xác định: hoàn thành Custom_Reg
lớp
Bây giờ chúng ta có thể làm việc lại trên Custom_Reg
lớp, thêm các phương thức "dán" đối tượng được xác định và làm cho chúng hoạt động
<?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];
}
}
}
Hàm tạo của lớp chấp nhận một thể hiện Form
và một trong số đó Saver
.
init()
Phương thức (sử dụng checkUrl()
) xem phần đầu tiên của url sau home_url()
và nếu nó khớp với phần mở rộng bên phải, nó sẽ kiểm tra xem biểu mẫu đã được gửi chưa, nếu sử dụng Saver
đối tượng, nó xác nhận và lưu userdata, nếu không thì chỉ cần in biểu mẫu .
init()
phương thức cũng kích hoạt hook hành động 'custom_reg_form_init'
chuyển qua thể hiện của biểu mẫu làm đối số: hook này nên được sử dụng để thêm các trường, để thiết lập mẫu tùy chỉnh và cũng để tùy chỉnh phương thức biểu mẫu.
9: Đặt mọi thứ lại với nhau
Bây giờ chúng ta cần viết tệp plugin chính, nơi chúng ta có thể
- yêu cầu tất cả các tập tin
- tải miền văn bản
- khởi động toàn bộ quá trình bằng cách sử dụng
Custom_Reg
lớp khởi tạo và gọi init()
phương thức trên nó bằng cách sử dụng một hook hợp lý sớm
- sử dụng 'custom_reg_form_init' để thêm các trường để tạo thành lớp
Vì thế:
<?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: Mất nhiệm vụ
Bây giờ everithing là khá tốt. Chúng tôi chỉ cần tùy chỉnh mẫu, có thể thêm tệp mẫu tùy chỉnh trong chủ đề của chúng tôi.
Chúng tôi chỉ có thể thêm các kiểu và tập lệnh cụ thể vào trang đăng ký tùy chỉnh theo cách này
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( ... );
}
});
Sử dụng phương pháp đó, chúng ta có thể liệt kê một số tập lệnh js để xử lý xác nhận phía máy khách, ví dụ: tập lệnh này . Đánh dấu cần thiết để làm cho kịch bản đó hoạt động có thể dễ dàng xử lý chỉnh sửa Custom_Reg\BaseField
lớp.
Nếu chúng tôi muốn tùy chỉnh email đăng ký, chúng tôi có thể sử dụng phương pháp tiêu chuẩn và lưu dữ liệu tùy chỉnh trên meta, chúng tôi có thể sử dụng chúng trong email.
Nhiệm vụ cuối cùng có lẽ chúng tôi muốn thực hiện là ngăn chặn yêu cầu đối với biểu mẫu đăng ký mặc định, dễ dàng như:
add_action( 'login_form_register', function() { exit(); } );
Tất cả các tệp có thể được tìm thấy trong một Gist ở đây .