mirror of
https://github.com/10h30/ultimatemember.git
synced 2026-06-05 15:09:37 +09:00
a102d22ce1
Added `strip_shortcodes` across various sanitization routines to ensure user inputs do not execute shortcodes. This enhances security by blocking unintended shortcode processing in fields such as text, email, URLs, and form descriptions.
1209 lines
38 KiB
PHP
1209 lines
38 KiB
PHP
<?php
|
|
namespace um\core;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! class_exists( 'um\core\Form' ) ) {
|
|
|
|
/**
|
|
* Class Form
|
|
* @package um\core
|
|
*/
|
|
class Form {
|
|
|
|
/**
|
|
* @var
|
|
*/
|
|
public $form_data;
|
|
|
|
public $form_suffix = null;
|
|
|
|
/**
|
|
* @var
|
|
*/
|
|
public $form_id;
|
|
|
|
/**
|
|
* @var
|
|
*/
|
|
public $form_status;
|
|
|
|
/**
|
|
* @var null
|
|
*/
|
|
public $post_form = null;
|
|
|
|
/**
|
|
* @var null
|
|
*/
|
|
public $nonce = null;
|
|
|
|
/**
|
|
* @var null|array
|
|
*/
|
|
public $errors = null;
|
|
|
|
/**
|
|
* @var null
|
|
*/
|
|
public $processing = null;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
public $all_fields = array();
|
|
|
|
/**
|
|
* Whitelisted usermeta that can be stored when UM Form is submitted.
|
|
*
|
|
* @since 2.6.7
|
|
*
|
|
* @var array
|
|
*/
|
|
public $usermeta_whitelist = array();
|
|
|
|
/**
|
|
* Hook for singleton
|
|
* @since 2.8.0
|
|
*/
|
|
public function hooks() {
|
|
add_action( 'template_redirect', array( &$this, 'form_init' ), 2 );
|
|
add_action( 'init', array( &$this, 'field_declare' ) );
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public function ajax_muted_action() {
|
|
UM()->check_ajax_nonce();
|
|
|
|
// phpcs:disable WordPress.Security.NonceVerification
|
|
if ( ! isset( $_REQUEST['hook'] ) ) {
|
|
die( esc_html__( 'Invalid hook', 'ultimate-member' ) );
|
|
}
|
|
|
|
if ( isset( $_REQUEST['user_id'] ) ) {
|
|
$user_id = absint( $_REQUEST['user_id'] );
|
|
}
|
|
if ( ! isset( $user_id ) || ! UM()->roles()->um_current_user_can( 'edit', $user_id ) ) {
|
|
die( esc_html__( 'You can not edit this user.', 'ultimate-member' ) );
|
|
}
|
|
|
|
$hook = sanitize_key( $_REQUEST['hook'] );
|
|
/**
|
|
* Fires on AJAX muted action.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_run_ajax_function__{$hook}
|
|
*
|
|
* @param {array} $request Request.
|
|
*
|
|
* @example <caption>Make any custom action on AJAX muted action.</caption>
|
|
* function my_run_ajax_function( $request ) {
|
|
* // your code here
|
|
* }
|
|
* add_action( 'um_run_ajax_function__{$hook}', 'my_run_ajax_function', 10, 1 );
|
|
*/
|
|
do_action( "um_run_ajax_function__{$hook}", $_REQUEST );
|
|
// phpcs:enable WordPress.Security.NonceVerification
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public function ajax_select_options() {
|
|
UM()->check_ajax_nonce();
|
|
|
|
// phpcs:disable WordPress.Security.NonceVerification
|
|
|
|
$arr_options = array();
|
|
$arr_options['status'] = 'success';
|
|
$arr_options['post'] = $_POST;
|
|
|
|
// Callback validation
|
|
if ( empty( $_POST['child_callback'] ) ) {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'Wrong callback.', 'ultimate-member' );
|
|
|
|
wp_send_json( $arr_options );
|
|
}
|
|
|
|
$ajax_source_func = sanitize_text_field( $_POST['child_callback'] );
|
|
|
|
if ( ! function_exists( $ajax_source_func ) ) {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'Wrong callback.', 'ultimate-member' );
|
|
|
|
wp_send_json( $arr_options );
|
|
}
|
|
|
|
$allowed_callbacks = UM()->options()->get( 'allowed_choice_callbacks' );
|
|
|
|
if ( empty( $allowed_callbacks ) ) {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'This is not possible for security reasons.', 'ultimate-member' );
|
|
wp_send_json( $arr_options );
|
|
}
|
|
|
|
$allowed_callbacks = array_map( 'rtrim', explode( "\n", wp_unslash( $allowed_callbacks ) ) );
|
|
|
|
if ( ! in_array( $ajax_source_func, $allowed_callbacks, true ) ) {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'This is not possible for security reasons.', 'ultimate-member' );
|
|
|
|
wp_send_json( $arr_options );
|
|
}
|
|
|
|
if ( UM()->fields()->is_source_blacklisted( $ajax_source_func ) ) {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'This is not possible for security reasons.', 'ultimate-member' );
|
|
|
|
wp_send_json( $arr_options );
|
|
}
|
|
|
|
if ( isset( $_POST['form_id'] ) ) {
|
|
UM()->fields()->set_id = absint( $_POST['form_id'] );
|
|
}
|
|
UM()->fields()->set_mode = 'profile';
|
|
$form_fields = UM()->fields()->get_fields();
|
|
$arr_options['fields'] = $form_fields;
|
|
|
|
if ( isset( $arr_options['post']['members_directory'] ) && 'yes' === $arr_options['post']['members_directory'] ) {
|
|
global $wpdb;
|
|
|
|
$values_array = $wpdb->get_col(
|
|
$wpdb->prepare(
|
|
"SELECT DISTINCT meta_value
|
|
FROM $wpdb->usermeta
|
|
WHERE meta_key = %s AND
|
|
meta_value != ''",
|
|
$arr_options['post']['child_name']
|
|
)
|
|
);
|
|
|
|
if ( ! empty( $values_array ) ) {
|
|
$parent_dropdown = isset( $arr_options['post']['parent_option_name'] ) ? $arr_options['post']['parent_option_name'] : '';
|
|
$arr_options['items'] = call_user_func( $ajax_source_func, $parent_dropdown );
|
|
|
|
if ( array_keys( $arr_options['items'] ) !== range( 0, count( $arr_options['items'] ) - 1 ) ) {
|
|
// array with dropdown items is associative
|
|
$arr_options['items'] = array_intersect_key( array_map( 'trim', $arr_options['items'] ), array_flip( $values_array ) );
|
|
} else {
|
|
// array with dropdown items has sequential numeric keys, starting from 0 and there are intersected values with $values_array
|
|
$arr_options['items'] = array_intersect( $arr_options['items'], $values_array );
|
|
}
|
|
} else {
|
|
$arr_options['items'] = array();
|
|
}
|
|
|
|
wp_send_json( $arr_options );
|
|
} else {
|
|
/**
|
|
* UM hook
|
|
*
|
|
* @type filter
|
|
* @title um_ajax_select_options__debug_mode
|
|
* @description Activate debug mode for AJAX select options
|
|
* @input_vars
|
|
* [{"var":"$debug_mode","type":"bool","desc":"Enable Debug mode"}]
|
|
* @change_log
|
|
* ["Since: 2.0"]
|
|
* @usage
|
|
* <?php add_filter( 'um_ajax_select_options__debug_mode', 'function_name', 10, 1 ); ?>
|
|
* @example
|
|
* <?php
|
|
* add_filter( 'um_ajax_select_options__debug_mode', 'my_ajax_select_options__debug_mode', 10, 1 );
|
|
* function my_ajax_select_options__debug_mode( $debug_mode ) {
|
|
* // your code here
|
|
* return $debug_mode;
|
|
* }
|
|
* ?>
|
|
*/
|
|
$debug = apply_filters( 'um_ajax_select_options__debug_mode', false );
|
|
if ( $debug ) {
|
|
$arr_options['debug'] = array(
|
|
$_POST,
|
|
$form_fields,
|
|
);
|
|
}
|
|
|
|
if ( ! empty( $_POST['child_callback'] ) && isset( $form_fields[ $_POST['child_name'] ] ) ) {
|
|
// If the requested callback function is added in the form or added in the field option, execute it with call_user_func.
|
|
if ( isset( $form_fields[ $_POST['child_name'] ]['custom_dropdown_options_source'] ) &&
|
|
! empty( $form_fields[ $_POST['child_name'] ]['custom_dropdown_options_source'] ) &&
|
|
$form_fields[ $_POST['child_name'] ]['custom_dropdown_options_source'] === $ajax_source_func ) {
|
|
|
|
$arr_options['field'] = $form_fields[ $_POST['child_name'] ];
|
|
|
|
$arr_options['items'] = call_user_func( $ajax_source_func, $arr_options['field']['parent_dropdown_relationship'] );
|
|
} else {
|
|
$arr_options['status'] = 'error';
|
|
$arr_options['message'] = __( 'This is not possible for security reasons.', 'ultimate-member' );
|
|
}
|
|
}
|
|
|
|
// phpcs:enable WordPress.Security.NonceVerification
|
|
wp_send_json( $arr_options );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Count the form errors.
|
|
* @return integer
|
|
*/
|
|
public function count_errors() {
|
|
$errors = $this->errors;
|
|
|
|
if ( $errors && is_array( $errors ) ) {
|
|
return count( $errors );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Appends field errors
|
|
*
|
|
* @param string $key
|
|
* @param string $error
|
|
*/
|
|
public function add_error( $key, $error ) {
|
|
if ( ! isset( $this->errors[ $key ] ) ) {
|
|
/**
|
|
* UM hook
|
|
*
|
|
* @type filter
|
|
* @title um_submit_form_error
|
|
* @description Change error text on submit form
|
|
* @input_vars
|
|
* [{"var":"$error","type":"string","desc":"Error String"},
|
|
* {"var":"$key","type":"string","desc":"Error Key"}]
|
|
* @change_log
|
|
* ["Since: 2.0"]
|
|
* @usage
|
|
* <?php add_filter( 'um_submit_form_error', 'function_name', 10, 2 ); ?>
|
|
* @example
|
|
* <?php
|
|
* add_filter( 'um_submit_form_error', 'my_submit_form_error', 10, 2 );
|
|
* function my_submit_form_error( $error, $key ) {
|
|
* // your code here
|
|
* return $error;
|
|
* }
|
|
* ?>
|
|
*/
|
|
$this->errors[ $key ] = apply_filters( 'um_submit_form_error', $error, $key );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends field notices
|
|
* @param string $key
|
|
* @param string $notice
|
|
*/
|
|
public function add_notice( $key, $notice ) {
|
|
if ( ! isset( $this->notices[ $key ] ) ) {
|
|
/**
|
|
* UM hook
|
|
*
|
|
* @type filter
|
|
* @title um_submit_form_notice
|
|
* @description Change notice text on submit form
|
|
* @input_vars
|
|
* [{"var":"$notice","type":"string","desc":"notice String"},
|
|
* {"var":"$key","type":"string","desc":"notice Key"}]
|
|
* @change_log
|
|
* ["Since: 2.0"]
|
|
* @usage
|
|
* <?php add_filter( 'um_submit_form_notice', 'function_name', 10, 2 ); ?>
|
|
* @example
|
|
* <?php
|
|
* add_filter( 'um_submit_form_notice', 'my_submit_form_notice', 10, 2 );
|
|
* function my_submit_form_notice( $notice, $key ) {
|
|
* // your code here
|
|
* return $notice;
|
|
* }
|
|
* ?>
|
|
*/
|
|
$this->notices[ $key ] = apply_filters( 'um_submit_form_notice', $notice, $key );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* If a form has errors
|
|
*
|
|
* @param string $key
|
|
* @return boolean
|
|
*/
|
|
public function has_error( $key ) {
|
|
if ( isset( $this->errors[ $key ] ) ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* If a form has notices/info
|
|
*
|
|
* @param string $key
|
|
* @return boolean
|
|
*/
|
|
public function has_notice( $key ) {
|
|
if ( isset( $this->notices[ $key ] ) ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the errors as a WordPress Error object
|
|
*
|
|
* @return \WP_Error
|
|
*/
|
|
function get_wp_error() {
|
|
$wp_error = new \WP_Error();
|
|
if ( $this->count_errors() > 0 ) {
|
|
foreach ( $this->errors as $key => $value ) {
|
|
$wp_error->add( $key, $value );
|
|
}
|
|
}
|
|
return $wp_error;
|
|
}
|
|
|
|
|
|
/**
|
|
* Declare all fields
|
|
*/
|
|
public function field_declare() {
|
|
if ( isset( UM()->builtin()->custom_fields ) ) {
|
|
$this->all_fields = UM()->builtin()->custom_fields;
|
|
} else {
|
|
$this->all_fields = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove banned wp_usermeta keys from submitted data.
|
|
*
|
|
* @since 2.6.5
|
|
* @param array $submitted
|
|
* @return array
|
|
*/
|
|
public function clean_submitted_data( $submitted ) {
|
|
foreach ( $submitted as $metakey => $value ) {
|
|
if ( UM()->user()->is_metakey_banned( $metakey/*, 'submission'*/ ) ) {
|
|
unset( $submitted[ $metakey ] );
|
|
}
|
|
}
|
|
|
|
return $submitted;
|
|
}
|
|
|
|
/**
|
|
* Validate form on submit
|
|
*/
|
|
public function form_init() {
|
|
if ( isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
|
$http_post = ( 'POST' === $_SERVER['REQUEST_METHOD'] );
|
|
} else {
|
|
$http_post = 'POST';
|
|
}
|
|
// Handles Register, Profile and Login forms.
|
|
if ( $http_post && ! is_admin() && isset( $_POST['form_id'] ) && is_numeric( $_POST['form_id'] ) ) {
|
|
|
|
$this->form_id = absint( $_POST['form_id'] );
|
|
if ( 'um_form' !== get_post_type( $this->form_id ) ) {
|
|
return;
|
|
}
|
|
|
|
$this->form_status = get_post_status( $this->form_id );
|
|
if ( 'publish' !== $this->form_status ) {
|
|
return;
|
|
}
|
|
|
|
// Verified that form_id is right and UM form is published. Then get form data.
|
|
|
|
$this->form_data = UM()->query()->post_data( $this->form_id );
|
|
|
|
// Checking the form custom fields. Form without custom fields is invalid.
|
|
if ( ! array_key_exists( 'mode', $this->form_data ) ) {
|
|
return;
|
|
}
|
|
|
|
// Checking the form custom fields. Form without custom fields is invalid.
|
|
if ( ! array_key_exists( 'custom_fields', $this->form_data ) ) {
|
|
return;
|
|
}
|
|
|
|
$custom_fields = maybe_unserialize( $this->form_data['custom_fields'] );
|
|
if ( ! is_array( $custom_fields ) || empty( $custom_fields ) ) {
|
|
return;
|
|
}
|
|
|
|
$ignore_keys = array();
|
|
|
|
$arr_restricted_fields = array();
|
|
if ( 'profile' === $this->form_data['mode'] ) {
|
|
$arr_restricted_fields = UM()->fields()->get_restricted_fields_for_edit();
|
|
}
|
|
|
|
$password_fields = array(
|
|
'user_password',
|
|
'confirm_user_password',
|
|
);
|
|
|
|
$field_types_without_metakey = UM()->builtin()->get_fields_without_metakey();
|
|
foreach ( $custom_fields as $cf_k => $cf_data ) {
|
|
if ( ! array_key_exists( 'type', $cf_data ) || in_array( $cf_data['type'], $field_types_without_metakey, true ) ) {
|
|
unset( $custom_fields[ $cf_k ] );
|
|
}
|
|
|
|
if ( array_key_exists( 'type', $cf_data ) && 'password' === $cf_data['type'] ) {
|
|
$ignore_keys[] = $cf_k;
|
|
$ignore_keys[] = 'confirm_' . $cf_k;
|
|
|
|
$password_fields[] = $cf_k;
|
|
$password_fields[] = 'confirm_' . $cf_k;
|
|
}
|
|
|
|
if ( 'profile' === $this->form_data['mode'] ) {
|
|
if ( ! empty( $cf_data['edit_forbidden'] ) ) {
|
|
$ignore_keys[] = $cf_k;
|
|
}
|
|
|
|
if ( ! um_can_edit_field( $cf_data ) || ! um_can_view_field( $cf_data ) ) {
|
|
$ignore_keys[] = $cf_k;
|
|
}
|
|
}
|
|
|
|
if ( ! array_key_exists( 'metakey', $cf_data ) || empty( $cf_data['metakey'] ) ) {
|
|
unset( $custom_fields[ $cf_k ] );
|
|
}
|
|
|
|
if ( isset( $cf_data['required_opt'] ) ) {
|
|
$opt = $cf_data['required_opt'];
|
|
if ( UM()->options()->get( $opt[0] ) !== $opt[1] ) {
|
|
$ignore_keys[] = $cf_k;
|
|
}
|
|
}
|
|
}
|
|
$cf_metakeys = array_column( $custom_fields, 'metakey' );
|
|
$all_cf_metakeys = $cf_metakeys;
|
|
|
|
// The '_um_last_login' cannot be updated through UM form.
|
|
$cf_metakeys = array_values( array_diff( $cf_metakeys, array( 'role_select', 'role_radio', 'role', '_um_last_login', 'user_pass', 'user_password', 'confirm_user_password' ) ) );
|
|
if ( ! empty( $ignore_keys ) ) {
|
|
$cf_metakeys = array_values( array_diff( $cf_metakeys, $ignore_keys ) );
|
|
}
|
|
// Remove restricted fields when edit profile.
|
|
if ( 'profile' === $this->form_data['mode'] ) {
|
|
// Column names from wp_users table.
|
|
$cf_metakeys = array_values( array_diff( $cf_metakeys, array( 'user_login' ) ) );
|
|
// Hidden for edit fields
|
|
$cf_metakeys = array_values( array_diff( $cf_metakeys, $arr_restricted_fields ) );
|
|
|
|
$cf_metakeys[] = 'profile_photo';
|
|
$cf_metakeys[] = 'cover_photo';
|
|
|
|
if ( ! empty( $this->form_data['use_custom_settings'] ) && ! empty( $this->form_data['show_bio'] ) ) {
|
|
$cf_metakeys[] = UM()->profile()->get_show_bio_key( $this->form_data );
|
|
} else {
|
|
if ( UM()->options()->get( 'profile_show_bio' ) ) {
|
|
$cf_metakeys[] = UM()->profile()->get_show_bio_key( $this->form_data );
|
|
}
|
|
}
|
|
}
|
|
// Add required usermeta for register.
|
|
if ( 'register' === $this->form_data['mode'] ) {
|
|
$cf_metakeys[] = 'form_id';
|
|
}
|
|
|
|
/**
|
|
* Filters whitelisted usermeta keys that can be stored inside DB after UM Form submission.
|
|
*
|
|
* @param {array} $whitelisted_metakeys Whitelisted usermeta keys.
|
|
* @param {array} $form_data UM form data.
|
|
*
|
|
* @return {array} Whitelisted usermeta keys.
|
|
*
|
|
* @since 2.6.7
|
|
* @hook um_whitelisted_metakeys
|
|
*
|
|
* @example <caption>Extends whitelisted usermeta keys.</caption>
|
|
* function my_um_whitelisted_metakeys( $metakeys, $form_data ) {
|
|
* $metakeys[] = 'some_key';
|
|
* return $metakeys;
|
|
* }
|
|
* add_filter( 'um_whitelisted_metakeys', 'my_um_whitelisted_metakeys', 10, 2 );
|
|
*/
|
|
$cf_metakeys = apply_filters( 'um_whitelisted_metakeys', $cf_metakeys, $this->form_data );
|
|
|
|
// Important variable to prevent save unnecessary data to wp_usermeta.
|
|
$this->usermeta_whitelist = $cf_metakeys;
|
|
|
|
/**
|
|
* Fires before UM login, registration or profile form submission.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_before_submit_form_post
|
|
*
|
|
* @param {array} $post $_POST submission array. Deprecated since 2.0.
|
|
* @param {object} $this UM()->form() class instance. Since 2.6.7
|
|
*
|
|
* @example <caption>Make any custom action before UM login, registration or profile form submission.</caption>
|
|
* function my_custom_before_submit_form_post( $um_form_obj ) {
|
|
* // your code here
|
|
* }
|
|
* add_action( 'um_before_submit_form_post', 'my_custom_before_submit_form_post' );
|
|
*/
|
|
do_action( 'um_before_submit_form_post', $this );
|
|
|
|
$formdata = wp_unslash( $_POST );
|
|
|
|
if ( isset( $formdata['form_id'] ) ) {
|
|
// Don't un-slash passwords in manner of WordPress native password field.
|
|
$form_id = absint( $formdata['form_id'] );
|
|
foreach ( $password_fields as &$password_field ) {
|
|
$password_field .= '-' . $form_id;
|
|
}
|
|
unset( $password_field );
|
|
$formdata = UM()->form()::ignore_formdata_unslash( $formdata, $password_fields );
|
|
}
|
|
|
|
/* save entire form as global */
|
|
/**
|
|
* Filters $_POST submitted data by the UM login, registration or profile form.
|
|
*
|
|
* @param {array} $_post Submitted data. Already un-slashed by `wp_unslash()`.
|
|
*
|
|
* @return {array} Submitted data.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_submit_post_form
|
|
*
|
|
* @example <caption>Extends $_POST data.</caption>
|
|
* function my_submit_post_form( $_post ) {
|
|
* $_post['some_key'] = 'some value';
|
|
* return $_post;
|
|
* }
|
|
* add_filter( 'um_submit_post_form', 'my_submit_post_form' );
|
|
*/
|
|
$this->post_form = apply_filters( 'um_submit_post_form', $formdata );
|
|
|
|
// Validate form submission by honeypot.
|
|
if ( isset( $this->post_form[ UM()->honeypot ] ) && '' !== $this->post_form[ UM()->honeypot ] ) {
|
|
// High level escape if hacking.
|
|
wp_die( esc_html__( 'Hello, spam bot!', 'ultimate-member' ) );
|
|
}
|
|
|
|
$this->post_form = $this->beautify( $this->post_form );
|
|
|
|
// Validate and filter 'role' submitted data to avoid handling roles with admin privileges.
|
|
// Remove role from post_form at first if role ! empty and there aren't custom fields with role name
|
|
$maybe_set_default_role = true;
|
|
if ( array_key_exists( 'role', $this->post_form ) ) {
|
|
if ( 'login' === $this->form_data['mode'] ) {
|
|
unset( $this->post_form['role'] );
|
|
} else {
|
|
$form_has_role_field = count( array_intersect( $all_cf_metakeys, array( 'role_select', 'role_radio' ) ) ) > 0;
|
|
if ( ! $form_has_role_field ) {
|
|
unset( $this->post_form['role'] );
|
|
} else {
|
|
$custom_field_roles = $this->custom_field_roles( $this->form_data['custom_fields'] );
|
|
if ( ! empty( $custom_field_roles ) && ! empty( $this->post_form['role'] ) ) {
|
|
if ( is_array( $this->post_form['role'] ) ) {
|
|
$role = current( $this->post_form['role'] );
|
|
$role = sanitize_key( $role );
|
|
} else {
|
|
$role = sanitize_key( $this->post_form['role'] );
|
|
}
|
|
|
|
global $wp_roles;
|
|
$exclude_roles = array_diff( array_keys( $wp_roles->roles ), UM()->roles()->get_editable_user_roles() );
|
|
|
|
if ( ! empty( $role ) &&
|
|
( ! in_array( $role, $custom_field_roles, true ) || in_array( $role, $exclude_roles, true ) ) ) {
|
|
// High level escape if hacking.
|
|
wp_die( esc_html__( 'This is not possible for security reasons.', 'ultimate-member' ) );
|
|
}
|
|
|
|
$this->post_form['role'] = $role;
|
|
$maybe_set_default_role = false;
|
|
|
|
// Force adding `role` metakey if there is a role-type field on the form. It's required to User Profile.
|
|
$this->usermeta_whitelist[] = 'role';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->post_form = $this->sanitize( $this->post_form );
|
|
$this->post_form['submitted'] = $this->post_form;
|
|
|
|
// Set default role from settings on registration form. It has been made after defined 'submitted' because predefined role isn't a submitted field.
|
|
if ( $maybe_set_default_role && 'register' === $this->form_data['mode'] ) {
|
|
$role = $this->assigned_role( $this->form_id );
|
|
$this->post_form['role'] = $role;
|
|
}
|
|
|
|
/**
|
|
* Filters $_POST submitted data by the UM login, registration or profile form.
|
|
* It's un-slashed by `wp_unslash()`, beautified and sanitized. `role` attribute is filtered by possible role.
|
|
* `submitted` key is added by code and contains summary of submission.
|
|
*
|
|
* Internal Ultimate Member callbacks (Priority -> Callback name -> Excerpt):
|
|
* 9 - `um_submit_form_data_trim_fields()` maybe over-functionality and can be removed.
|
|
* 10 - `um_submit_form_data_role_fields()` important for conditional logic based on role fields in form.
|
|
*
|
|
* @param {array} $_post Submitted data.
|
|
* @param {string} $mode Form mode. login||register||profile
|
|
* @param {array} $all_cf_metakeys Form's metakeys. Since 2.6.7.
|
|
*
|
|
* @return {array} Submitted data.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_submit_form_data
|
|
*
|
|
* @example <caption>Extends UM form submitted data.</caption>
|
|
* function my_submit_form_data( $_post, $mode, $all_cf_metakeys ) {
|
|
* $_post['some_key'] = 'some value';
|
|
* return $_post;
|
|
* }
|
|
* add_filter( 'um_submit_form_data', 'my_submit_form_data', 10, 3 );
|
|
*/
|
|
$this->post_form = apply_filters( 'um_submit_form_data', $this->post_form, $this->form_data['mode'], $all_cf_metakeys );
|
|
/* Continue based on form mode - pre-validation */
|
|
/**
|
|
* Fires for validation UM login, registration or profile form submission.
|
|
*
|
|
* Internal Ultimate Member callbacks (Priority -> Callback name -> Excerpt):
|
|
* 10 - `um_submit_form_errors_hook()` All form validation handlers.
|
|
* 20 - `um_recaptcha_validate()` reCAPTCHA form validation handlers. um-recaptcha extension.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_submit_form_errors_hook
|
|
*
|
|
* @param {array} $post $_POST Submission array.
|
|
* @param {array} $form_data UM form data. Since 2.6.7
|
|
*
|
|
* @example <caption>Make any common validation action here.</caption>
|
|
* function my_custom_before_submit_form_post( $post, $form_data ) {
|
|
* // your code here
|
|
* }
|
|
* add_action( 'um_submit_form_errors_hook', 'my_custom_submit_form_errors_hook', 10, 2 );
|
|
*/
|
|
do_action( 'um_submit_form_errors_hook', $this->post_form, $this->form_data );
|
|
/* Continue based on form mode - store data. */
|
|
/**
|
|
* Fires for make main actions on UM login, registration or profile form submission.
|
|
* Where $mode equals login, registration or profile
|
|
*
|
|
* Internal Ultimate Member callbacks (Priority -> Callback name -> Excerpt):
|
|
* ### um_submit_form_login:
|
|
* * 1 - `UM()->login()->verify_nonce()` Verify nonce.
|
|
* * 10 - `um_submit_form_login()` Login form main handler.
|
|
* ### um_submit_form_register:
|
|
* * 1 - `UM()->register()->verify_nonce()` Verify nonce.
|
|
* * 9 - `UM()->agreement_validation()` GDPR Agreement.
|
|
* * 9 - `UM()->terms_conditions()->agreement_validation()` Terms & Conditions Agreement.
|
|
* * 10 - `um_submit_form_register()` Register form main handler.
|
|
* ### um_submit_form_profile:
|
|
* * 10 - `um_submit_form_profile()` Profile form main handler.
|
|
*
|
|
* @since 1.3.x
|
|
* @hook um_submit_form_{$mode}
|
|
*
|
|
* @param {array} $post $_POST Submission array.
|
|
* @param {array} $form_data UM form data. Since 2.6.7
|
|
*
|
|
* @example <caption>Make any custom action on profile submission.</caption>
|
|
* function my_custom_submit_form_profile( $post, $form_data ) {
|
|
* // your code here
|
|
* }
|
|
* add_action( 'um_submit_form_profile', 'my_custom_submit_form_profile', 10, 2 );
|
|
*/
|
|
do_action( "um_submit_form_{$this->form_data['mode']}", $this->post_form, $this->form_data );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Beautify form data
|
|
*
|
|
* @param array $form
|
|
*
|
|
* @return array $form
|
|
*/
|
|
public function beautify( $form ) {
|
|
if ( isset( $form['form_id'] ) ) {
|
|
$this->form_suffix = '-' . $form['form_id'];
|
|
$this->processing = absint( $form['form_id'] );
|
|
|
|
foreach ( $form as $key => $value ) {
|
|
if ( strstr( $key, $this->form_suffix ) ) {
|
|
$a_key = str_replace( $this->form_suffix, '', $key );
|
|
$form[ $a_key ] = $value;
|
|
unset( $form[ $key ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Use PHP tidy extension if it's active for getting clean HTML without unclosed tags.
|
|
*
|
|
* @param string $html_fragment Textarea with active HTML option field value.
|
|
* @param array $field_data Ultimate Member form field data.
|
|
*
|
|
* @return string|\tidy
|
|
*/
|
|
private static function maybe_apply_tidy( $html_fragment, $field_data ) {
|
|
// Break if extension isn't active in php.ini
|
|
if ( ! function_exists( 'tidy_parse_string' ) ) {
|
|
return $html_fragment;
|
|
}
|
|
|
|
$tidy_config = array(
|
|
'clean' => true,
|
|
'output-xhtml' => true,
|
|
'show-body-only' => true,
|
|
'wrap' => 0,
|
|
);
|
|
/**
|
|
* Filters PHP tidy extension config.
|
|
* Get more info here https://www.php.net/manual/en/tidy.parsestring.php
|
|
*
|
|
* @param {array} $tidy_config Config.
|
|
* @param {array} $field_data UM Form Field Data.
|
|
*
|
|
* @return {array} Config.
|
|
*
|
|
* @since 2.8.9
|
|
* @hook um_tidy_config
|
|
*
|
|
* @example <caption>Customize tidy config based on field data.</caption>
|
|
* function my_um_tidy_config( $tidy_config, $field_data ) {
|
|
* // your code here
|
|
* if ( 'custom_metakey' === $field_data['metakey'] ) {
|
|
* $tidy_config['clean'] = false;
|
|
* }
|
|
* return $tidy_config;
|
|
* }
|
|
* add_filter( 'um_tidy_config', 'my_um_tidy_config', 10, 2 );
|
|
*/
|
|
$tidy_config = apply_filters( 'um_tidy_config', $tidy_config, $field_data );
|
|
|
|
// since PHP8.0 $tidy_config, 'UTF8' variables are nullable https://www.php.net/manual/en/tidy.parsestring.php
|
|
$tidy = tidy_parse_string( $html_fragment, $tidy_config, 'UTF8' );
|
|
$result = $tidy->cleanRepair();
|
|
if ( $result ) {
|
|
return $tidy;
|
|
}
|
|
|
|
return $html_fragment;
|
|
}
|
|
|
|
/**
|
|
* Beautify form data
|
|
*
|
|
* @param array $form
|
|
*
|
|
* @return array $form
|
|
*/
|
|
public function sanitize( $form ) {
|
|
if ( isset( $form['form_id'] ) ) {
|
|
if ( isset( $this->form_data['custom_fields'] ) ) {
|
|
$custom_fields = maybe_unserialize( $this->form_data['custom_fields'] );
|
|
|
|
if ( is_array( $custom_fields ) ) {
|
|
foreach ( $custom_fields as $k => $field ) {
|
|
|
|
if ( isset( $field['type'] ) ) {
|
|
if ( isset( $form[ $k ] ) ) {
|
|
|
|
switch ( $field['type'] ) {
|
|
default:
|
|
$form[ $k ] = apply_filters( 'um_sanitize_form_field', $form[ $k ], $field );
|
|
break;
|
|
case 'number':
|
|
$form[ $k ] = '' !== $form[ $k ] ? (int) $form[ $k ] : '';
|
|
break;
|
|
case 'textarea':
|
|
if ( ! empty( $field['html'] ) || ( UM()->profile()->get_show_bio_key( $form ) === $k && UM()->options()->get( 'profile_show_html_bio' ) ) ) {
|
|
$form[ $k ] = html_entity_decode( $form[ $k ] ); // required because WP_Editor send sometimes encoded content.
|
|
$form[ $k ] = self::maybe_apply_tidy( $form[ $k ], $field );
|
|
|
|
$allowed_html = UM()->get_allowed_html( 'templates' );
|
|
if ( empty( $allowed_html['iframe'] ) ) {
|
|
$allowed_html['iframe'] = array(
|
|
'allow' => true,
|
|
'frameborder' => true,
|
|
'loading' => true,
|
|
'name' => true,
|
|
'referrerpolicy' => true,
|
|
'sandbox' => true,
|
|
'src' => true,
|
|
'srcdoc' => true,
|
|
'title' => true,
|
|
'width' => true,
|
|
'height' => true,
|
|
'allowfullscreen' => true,
|
|
);
|
|
}
|
|
$form[ $k ] = wp_kses( strip_shortcodes( $form[ $k ] ), $allowed_html );
|
|
add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
|
|
} else {
|
|
$form[ $k ] = sanitize_textarea_field( strip_shortcodes( $form[ $k ] ) );
|
|
}
|
|
break;
|
|
case 'oembed':
|
|
case 'url':
|
|
$f = UM()->builtin()->get_a_field( $k );
|
|
|
|
if ( is_array( $f ) && array_key_exists( 'match', $f ) && array_key_exists( 'advanced', $f ) && 'social' === $f['advanced'] ) {
|
|
$v = $form[ $k ];
|
|
|
|
// Make a proper social link
|
|
if ( ! empty( $v ) ) {
|
|
$replace_match = is_array( $f['match'] ) ? $f['match'][0] : $f['match'];
|
|
|
|
$need_replace = false;
|
|
if ( is_array( $f['match'] ) ) {
|
|
$need_replace = true;
|
|
foreach ( $f['match'] as $arr_match ) {
|
|
if ( strstr( $v, $arr_match ) ) {
|
|
$need_replace = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! is_array( $f['match'] ) || $need_replace ) {
|
|
if ( ! strstr( $v, $replace_match ) ) {
|
|
$domain = trim(
|
|
strtr(
|
|
$replace_match,
|
|
array(
|
|
'https://' => '',
|
|
'http://' => '',
|
|
)
|
|
),
|
|
' /'
|
|
);
|
|
|
|
if ( ! strstr( $v, $domain ) ) {
|
|
$v = $replace_match . $v;
|
|
} else {
|
|
$v = 'https://' . trim(
|
|
strtr(
|
|
$v,
|
|
array(
|
|
'https://' => '',
|
|
'http://' => '',
|
|
)
|
|
),
|
|
' /'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$form[ $k ] = esc_url_raw( strip_shortcodes( $v ) );
|
|
} else {
|
|
$form[ $k ] = esc_url_raw( strip_shortcodes( $form[ $k ] ) );
|
|
}
|
|
break;
|
|
case 'password':
|
|
$form[ $k ] = trim( $form[ $k ] );
|
|
if ( array_key_exists( 'confirm_' . $k, $form ) ) {
|
|
$form[ 'confirm_' . $k ] = trim( $form[ 'confirm_' . $k ] );
|
|
}
|
|
break;
|
|
case 'text':
|
|
case 'select':
|
|
case 'image':
|
|
case 'file':
|
|
case 'date':
|
|
case 'time':
|
|
case 'rating':
|
|
case 'googlemap':
|
|
case 'youtube_video':
|
|
case 'vimeo_video':
|
|
case 'soundcloud_track':
|
|
case 'spotify':
|
|
case 'tel':
|
|
$form[ $k ] = sanitize_text_field( strip_shortcodes( $form[ $k ] ) );
|
|
break;
|
|
case 'multiselect':
|
|
case 'radio':
|
|
case 'checkbox':
|
|
$form[ $k ] = is_array( $form[ $k ] ) ? array_map( 'sanitize_text_field', array_map( 'strip_shortcodes', $form[ $k ] ) ) : sanitize_text_field( strip_shortcodes( $form[ $k ] ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$show_bio = false;
|
|
$bio_html = false;
|
|
$global_setting = UM()->options()->get( 'profile_show_html_bio' );
|
|
if ( ! empty( $form_data['use_custom_settings'] ) ) {
|
|
if ( ! empty( $form_data['show_bio'] ) ) {
|
|
$show_bio = true;
|
|
$bio_html = ! empty( $global_setting );
|
|
}
|
|
} else {
|
|
$global_show_bio = UM()->options()->get( 'profile_show_bio' );
|
|
if ( ! empty( $global_show_bio ) ) {
|
|
$show_bio = true;
|
|
$bio_html = ! empty( $global_setting );
|
|
}
|
|
}
|
|
|
|
$description_key = UM()->profile()->get_show_bio_key( $this->form_data );
|
|
if ( $show_bio && ! empty( $form[ $description_key ] ) ) {
|
|
$field_exists = false;
|
|
if ( ! empty( $this->form_data['custom_fields'] ) ) {
|
|
$custom_fields = maybe_unserialize( $this->form_data['custom_fields'] );
|
|
if ( array_key_exists( $description_key, $custom_fields ) ) {
|
|
$field_exists = true;
|
|
if ( ! empty( $custom_fields[ $description_key ]['html'] ) && $bio_html ) {
|
|
$form[ $description_key ] = html_entity_decode( $form[ $description_key ] ); // required because WP_Editor send sometimes encoded content.
|
|
$form[ $description_key ] = self::maybe_apply_tidy( $form[ $description_key ], $custom_fields[ $description_key ] );
|
|
|
|
$allowed_html = UM()->get_allowed_html( 'templates' );
|
|
if ( empty( $allowed_html['iframe'] ) ) {
|
|
$allowed_html['iframe'] = array(
|
|
'allow' => true,
|
|
'frameborder' => true,
|
|
'loading' => true,
|
|
'name' => true,
|
|
'referrerpolicy' => true,
|
|
'sandbox' => true,
|
|
'src' => true,
|
|
'srcdoc' => true,
|
|
'title' => true,
|
|
'width' => true,
|
|
'height' => true,
|
|
'allowfullscreen' => true,
|
|
);
|
|
}
|
|
$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), $allowed_html );
|
|
|
|
add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
|
|
} else {
|
|
$form[ $description_key ] = sanitize_textarea_field( strip_shortcodes( $form[ $description_key ] ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ! $field_exists ) {
|
|
if ( $bio_html ) {
|
|
$allowed_html = UM()->get_allowed_html( 'templates' );
|
|
if ( empty( $allowed_html['iframe'] ) ) {
|
|
$allowed_html['iframe'] = array(
|
|
'allow' => true,
|
|
'frameborder' => true,
|
|
'loading' => true,
|
|
'name' => true,
|
|
'referrerpolicy' => true,
|
|
'sandbox' => true,
|
|
'src' => true,
|
|
'srcdoc' => true,
|
|
'title' => true,
|
|
'width' => true,
|
|
'height' => true,
|
|
'allowfullscreen' => true,
|
|
);
|
|
}
|
|
$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), $allowed_html );
|
|
|
|
add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
|
|
} else {
|
|
$form[ $description_key ] = sanitize_textarea_field( strip_shortcodes( $form[ $description_key ] ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $form;
|
|
}
|
|
|
|
public function wp_kses_user_desc( $tags, $context ) {
|
|
if ( 'user_description' === $context || 'pre_user_description' === $context ) {
|
|
$allowed_html = UM()->get_allowed_html( 'templates' );
|
|
if ( empty( $allowed_html['iframe'] ) ) {
|
|
$allowed_html['iframe'] = array(
|
|
'allow' => true,
|
|
'frameborder' => true,
|
|
'loading' => true,
|
|
'name' => true,
|
|
'referrerpolicy' => true,
|
|
'sandbox' => true,
|
|
'src' => true,
|
|
'srcdoc' => true,
|
|
'title' => true,
|
|
'width' => true,
|
|
'height' => true,
|
|
'allowfullscreen' => true,
|
|
);
|
|
}
|
|
$tags = $allowed_html;
|
|
}
|
|
return $tags;
|
|
}
|
|
|
|
/**
|
|
* Display form type as Title
|
|
* @param string $mode
|
|
* @param integer $post_id
|
|
* @return string $output
|
|
*/
|
|
public function display_form_type( $mode, $post_id ) {
|
|
$output = null;
|
|
switch ( $mode ) {
|
|
case 'login':
|
|
$output = __( 'Login', 'ultimate-member' );
|
|
break;
|
|
case 'profile':
|
|
$output = __( 'Profile', 'ultimate-member' );
|
|
break;
|
|
case 'register':
|
|
$output = __( 'Register', 'ultimate-member' );
|
|
break;
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
|
|
/**
|
|
* Assigned roles to a form
|
|
* @param integer $post_id
|
|
* @return string $role
|
|
*/
|
|
public function assigned_role( $post_id ) {
|
|
|
|
$global_role = get_option( 'default_role' ); // WP Global settings
|
|
|
|
$um_global_role = UM()->options()->get( 'register_role' ); // UM Settings Global settings
|
|
|
|
$existing_roles = array_keys( wp_roles()->roles );
|
|
if ( ! empty( $um_global_role ) && in_array( $um_global_role, $existing_roles, true ) ) {
|
|
$global_role = $um_global_role; // Form Global settings
|
|
}
|
|
|
|
$mode = $this->form_type( $post_id );
|
|
|
|
/**
|
|
* @todo WPML integration to get role from original if it's empty
|
|
*/
|
|
$use_custom = get_post_meta( $post_id, "_um_{$mode}_use_custom_settings", true );
|
|
if ( $use_custom ) { // Custom Form settings
|
|
$role = get_post_meta( $post_id, "_um_{$mode}_role", true );
|
|
}
|
|
|
|
if ( empty( $role ) || ! in_array( $role, $existing_roles, true ) ) { // custom role is default, return default role's slug
|
|
$role = $global_role;
|
|
}
|
|
|
|
return $role;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get form type
|
|
* @param integer $post_id
|
|
* @return string
|
|
*/
|
|
public function form_type( $post_id ) {
|
|
$mode = get_post_meta( $post_id, '_um_mode', true );
|
|
return $mode;
|
|
}
|
|
|
|
/**
|
|
* Get custom field roles
|
|
*
|
|
* @param string $custom_fields serialized
|
|
* @return bool|array roles
|
|
*/
|
|
public function custom_field_roles( $custom_fields ) {
|
|
|
|
$fields = maybe_unserialize( $custom_fields );
|
|
if ( ! is_array( $fields ) ) {
|
|
return false;
|
|
}
|
|
|
|
// role field
|
|
global $wp_roles;
|
|
$exclude_roles = array_diff( array_keys( $wp_roles->roles ), UM()->roles()->get_editable_user_roles() );
|
|
|
|
$roles = UM()->roles()->get_roles( false, $exclude_roles );
|
|
$roles = array_map(
|
|
function( $item ) {
|
|
return html_entity_decode( $item, ENT_QUOTES );
|
|
},
|
|
$roles
|
|
);
|
|
|
|
foreach ( $fields as $field_key => $field_settings ) {
|
|
|
|
if ( strstr( $field_key, 'role_' ) && array_key_exists( 'options', $field_settings ) && is_array( $field_settings['options'] ) ) {
|
|
|
|
if ( isset( $this->post_form['mode'] ) && 'profile' === $this->post_form['mode'] ) {
|
|
// It's for a legacy case `array_key_exists( 'editable', $field_settings )`.
|
|
if ( ( array_key_exists( 'editable', $field_settings ) && empty( $field_settings['editable'] ) ) || ! um_can_edit_field( $field_settings ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( ! um_can_view_field( $field_settings ) ) {
|
|
continue;
|
|
}
|
|
|
|
$intersected_options = array();
|
|
foreach ( $field_settings['options'] as $key => $title ) {
|
|
if ( false !== $search_key = array_search( $title, $roles ) ) {
|
|
$intersected_options[ $search_key ] = $title;
|
|
} elseif ( isset( $roles[ $key ] ) ) {
|
|
$intersected_options[ $key ] = $title;
|
|
}
|
|
}
|
|
|
|
// getting roles only from the first role fields
|
|
return array_keys( $intersected_options );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Ignore of `wp_unslash()` for form data
|
|
*
|
|
* @param array $formdata The form data to process
|
|
* @param array $fields_map The fields map array
|
|
*
|
|
* @return array The updated form data
|
|
*/
|
|
public static function ignore_formdata_unslash( $formdata, $fields_map ) {
|
|
foreach ( $fields_map as $field ) {
|
|
if ( ! isset( $_POST[ $field ] ) ) {
|
|
continue;
|
|
}
|
|
$formdata[ $field ] = trim( $_POST[ $field ] );
|
|
}
|
|
|
|
return $formdata;
|
|
}
|
|
}
|
|
}
|