<?php
use iThemesSecurity\User_Groups\Matchables_Source;
final class ITSEC_Form {
private $options = array();
private $tracked_booleans = array();
private $tracked_strings = array();
private $tracked_arrays = array();
private $input_group = '';
private $input_group_stack = array();
public function __construct( $options = array() ) {
$this->options =& $options;
}
public static function get_post_data( $desired_inputs = false ) {
$remove_vars = array( 'itsec-nonce', '_wp_http_referer' );
$data = $_POST;
foreach ( $remove_vars as $var ) {
unset( $_POST[$var] );
}
$data = stripslashes_deep( $data );
if ( isset( $data['data'] ) && isset( $data['data']['--itsec-form-serialized-data'] ) ) {
parse_str( $data['data']['--itsec-form-serialized-data'], $data );
}
$defaults = array(
'booleans' => false,
'strings' => '',
'arrays' => array(),
);
foreach ( $defaults as $name => $default ) {
if ( ! isset( $data["--itsec-form-tracked-$name"] ) || ! is_array( $data["--itsec-form-tracked-$name"] ) ) {
continue;
}
foreach ( $data["--itsec-form-tracked-$name"] as $index ) {
$value = ITSEC_Form::get_array_value( $data, $index );
if ( false === $default ) {
$value = ( $value ) ? true : false;
ITSEC_Form::add_array_value( $data, $index, $value );
} else if ( '' === $default ) {
if ( is_null( $value ) ) {
ITSEC_Form::add_array_value( $data, $index, $default );
}
} else if ( ! is_array( $value ) ) {
ITSEC_Form::add_array_value( $data, $index, $default );
}
}
unset( $data["--itsec-form-tracked-$name"] );
}
if ( isset( $data['--itsec-form-convert-to-array'] ) && is_array( $data['--itsec-form-convert-to-array'] ) ) {
foreach ( $data['--itsec-form-convert-to-array'] as $index ) {
$value = ITSEC_Form::get_array_value( $data, $index );
if ( is_array( $value ) ) {
continue;
} else if ( ! is_string( $value ) ) {
ITSEC_Form::add_array_value( $data, $index, array() );
}
$lines = preg_split( "/[\r\n]+/", $value );
foreach ( $lines as $key => $val ) {
$val = trim( $val );
if ( empty( $val ) ) {
unset( $lines[$key] );
} else {
$lines[$key] = $val;
}
}
ITSEC_Form::add_array_value( $data, $index, $lines );
}
unset( $data['--itsec-form-convert-to-array'] );
}
if ( is_array( $desired_inputs ) ) {
foreach ( array_keys( $data ) as $key ) {
if ( ! in_array( $key, $desired_inputs ) ) {
unset( $data[$key] );
}
}
}
return $data;
}
public static function parse_values( $values = array(), $args = array() ) {
$new_values = array();
foreach ( (array) $values as $var => $val ) {
$new_values[$var] = $val;
}
return $new_values;
}
public function start_form( $options = array() ) {
if ( isset( $_REQUEST['page'] ) ) {
list ( $location, $query ) = explode( '?', $_SERVER['REQUEST_URI'] );
$file = basename( $location );
if ( 'admin.php' == $file ) {
$default_action = "$location?page={$_REQUEST['page']}";
} else if ( ( 'edit.php' == $file ) && isset( $_REQUEST['post_type'] ) ) {
$default_action = "$location?post_type={$_REQUEST['post_type']}&page={$_REQUEST['page']}";
}
}
if ( ! isset( $default_action ) ) {
$default_action = $_SERVER['REQUEST_URI'];
}
$defaults = array(
'enctype' => 'multipart/form-data',
'method' => 'post',
'action' => $default_action,
);
if ( is_string( $options ) ) {
$options = array(
'id' => $options,
);
} else if ( ! is_array( $options ) ) {
$options = array();
}
$options = array_merge( $defaults, $options );
echo '<form';
foreach ( (array) $options as $var => $val ) {
if ( ! is_array( $val ) ) {
$val = str_replace( '"', '"', $val );
echo " $var=\"$val\"";
}
}
echo ">\n";
}
public function end_form() {
echo "</form>\n";
$this->new_form();
}
public function set_input_group() {
$this->remove_all_input_groups();
$args = func_get_args();
call_user_func_array( array( &$this, 'add_input_group' ), $args );
}
public function add_input_group() {
$args = func_get_args();
$this->input_group = '';
if ( is_array( $args[0] ) ) {
$args = $args[0];
}
if ( ( 1 === count( $args ) ) && ! is_string( $args[0] ) && ! is_numeric( $args[0] ) ) {
$args = array();
}
$this->input_group_stack = array_merge( $this->input_group_stack, $args );
$this->generate_input_group();
}
public function remove_input_group() {
array_pop( $this->input_group_stack );
$this->generate_input_group();
}
public function remove_all_input_groups() {
$this->input_group_stack = array();
$this->generate_input_group();
}
public function push_input_groups() {
if ( ! isset( $this->input_group_stack_cache ) || ! is_array( $this->input_group_stack_cache ) ) {
$this->input_group_stack_cache = array();
}
array_push( $this->input_group_stack_cache, $this->input_group_stack );
}
public function pop_input_groups() {
if ( ! is_array( $this->input_group_stack_cache ) || empty( $this->input_group_stack_cache ) ) {
return;
}
$this->input_group_stack = array_pop( $this->input_group_stack_cache );
$this->generate_input_group();
}
private function generate_input_group() {
$args = $this->input_group_stack;
$this->input_group = '';
if ( ! empty( $args ) ) {
$this->input_group = $args[0];
for ( $x = 1; $x < count( $args ); $x++ ) {
if ( ! is_array( $args[$x] ) && ! is_object( $args[$x] ) ) {
$this->input_group .= '[' . ( (string) $args[$x] ) . ']';
}
}
}
}
public function get_option( $name ) {
if ( ! empty( $this->input_group ) ) {
if ( false === strpos( $name, '[' ) ) {
$name = "[$name]";
} else {
$name = preg_replace( '/^([^\[]+)\[/', '[$1][', $name );
}
$name = "{$this->input_group}$name";
}
return ITSEC_Form::get_array_value( $this->options, $name );
}
public function get_options() {
if ( ! empty( $this->input_group ) ) {
return ITSEC_Form::get_array_value( $this->options, $this->input_group );
}
return $this->options;
}
public function get_all_options() {
return $this->options;
}
public function set_option( $name, $value ) {
if ( ! empty( $this->input_group ) ) {
if ( false === strpos( $name, '[' ) ) {
$name = "[$name]";
} else {
$name = preg_replace( '/^([^\[]+)\[/', '[$1][', $name );
}
$name = "{$this->input_group}$name";
}
ITSEC_Form::add_array_value( $this->options, $name, $value );
}
public function set_options( $values ) {
foreach ( $values as $name => $value ) {
$this->set_option( $name, $value );
}
}
public function set_default( $name, $value ) {
if ( is_null( $this->get_option( $name ) ) ) {
$this->set_option( $name, $value );
}
}
public function set_defaults( $values ) {
foreach ( $values as $name => $value ) {
$this->set_default( $name, $value );
}
}
public function clear_options() {
$this->options = array();
}
public function push_options() {
if ( ! isset( $this->options_cache ) || ! is_array( $this->options_cache ) ) {
$this->options_cache = array();
}
array_push( $this->options_cache, $this->options );
}
public function pop_options() {
if ( ! is_array( $this->options_cache ) || empty( $this->options_cache ) ) {
return;
}
$this->options = array_pop( $this->options_cache );
}
public static function add_nonce( $name = null ) {
wp_nonce_field( $name, 'itsec-nonce' );
}
public static function check_nonce( $name = null ) {
check_admin_referer( $name, 'itsec-nonce' );
}
public function new_form() {
$this->input_group = '';
$this->input_group_stack = array();
}
public function add_submit( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'submit';
$options['class'] = ( empty( $options['class'] ) ) ? 'button-primary' : $options['class'];
$this->add_custom_input( $var, $options );
}
public function add_button( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'button';
$this->add_custom_input( $var, $options );
}
public function add_text( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'text';
$this->add_custom_input( $var, $options );
}
public function add_html5_input( $var, $type, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = $type;
$this->add_custom_input( $var, $options );
}
public function add_textarea( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'textarea';
$this->add_custom_input( $var, $options );
}
public function add_password( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'password';
$this->add_custom_input( $var, $options );
}
public function add_file( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'file';
$this->add_custom_input( $var, $options );
}
public function add_checkbox( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'checkbox';
if ( empty( $options['value'] ) ) {
$options['value'] = '1';
}
$this->add_custom_input( $var, $options );
}
public function add_multi_checkbox( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'checkbox';
$options['append_val_to_id'] = true;
$var = $var . '[]';
$this->add_custom_input( $var, $options );
}
public function add_radio( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'radio';
$options['append_val_to_id'] = true;
$this->add_custom_input( $var, $options );
}
public function add_select( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array();
} else if ( ! isset( $options['value'] ) || ! is_array( $options['value'] ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'select';
$this->add_custom_input( $var, $options );
}
public function add_multi_select( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array();
} else if ( ! isset( $options['value'] ) || ! is_array( $options['value'] ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'select';
$options['multiple'] = 'multiple';
$var = $var . '[]';
$this->add_custom_input( $var, $options );
}
public function add_number( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'number';
$this->add_custom_input( $var, $options );
}
public function add_hidden( $var, $options = array() ) {
if ( ! is_array( $options ) ) {
$options = array( 'value' => $options );
}
$options['type'] = 'hidden';
$this->add_custom_input( $var, $options );
}
public function add_canonical_roles( $var, $options = array() ) {
$roles = array(
'administrator' => translate_user_role( 'Administrator' ),
'editor' => translate_user_role( 'Editor' ),
'author' => translate_user_role( 'Author' ),
'contributor' => translate_user_role( 'Contributor' ),
'subscriber' => translate_user_role( 'Subscriber' ),
);
if ( isset( $options['value'] ) ) {
$options['value'] = wp_parse_args( $options['value'], $roles );
} else {
$options['value'] = $roles;
}
$this->add_select( $var, $options );
}
public function add_user_group( $var, $options = array() ) {
$source = ITSEC_Modules::get_container()->get( Matchables_Source::class );
$user_groups = [];
foreach ( $source->all() as $matchable ) {
$user_groups[ $matchable->get_id() ] = $matchable->get_label();
}
if ( isset( $options['value'] ) ) {
$options['value'] = wp_parse_args( $options['value'], $user_groups );
} else {
$options['value'] = $user_groups;
}
$this->add_select( $var, $options );
}
public function add_user_groups( $var, $module, $setting = '', $options = array() ) {
_deprecated_function( __METHOD__, '7.0.0' );
$source = ITSEC_Modules::get_container()->get( Matchables_Source::class );
$user_groups = [];
foreach ( $source->all() as $matchable ) {
$user_groups[ $matchable->get_id() ] = $matchable->get_label();
}
if ( isset( $options['value'] ) ) {
$options['value'] = wp_parse_args( $options['value'], $user_groups );
} else {
$options['value'] = $user_groups;
}
$options['data-module'] = $module;
$options['data-setting'] = $setting ?: $var;
$options['class'] = 'itsec-form-input--type-user-groups';
$this->add_multi_select( $var, $options );
}
public function get_dotted_var( $var ) {
$dot = $this->input_group_stack;
$dot[] = $var;
return implode( '.', $dot );
}
public function get_clean_var( $var ) {
$clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $var ), '-' );
if ( ! empty( $this->input_group ) ) {
if ( false === strpos( $var, '[' ) ) {
$var = "[{$var}]";
} else {
$var = preg_replace( '/^([^\[]+)\[/', '[$1][', $var );
}
$var = "{$this->input_group}{$var}";
$clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $var ), '-' );
}
return "itsec-$clean_var";
}
private function add_custom_input( $var, $options ) {
if ( empty( $options['type'] ) ) {
trigger_error( 'add_custom_input called without a type option set' );
}
$scrub_list['textarea']['value'] = true;
$scrub_list['file']['value'] = true;
$scrub_list['select']['value'] = true;
$scrub_list['select']['type'] = true;
$defaults = array(
'name' => $var,
);
$input_group_name = $defaults['name'];
$var = str_replace( '[]', '', $var );
$clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $var ), '-' );
if ( ! empty( $this->input_group ) ) {
if ( false === strpos( $defaults['name'], '[' ) ) {
$defaults['name'] = "[{$defaults['name']}]";
} else {
$defaults['name'] = preg_replace( '/^([^\[]+)\[/', '[$1][', $defaults['name'] );
}
$input_group_name = $defaults['name'];
$defaults['name'] = "{$this->input_group}{$defaults['name']}";
$clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $defaults['name'] ), '-' );
}
$val = $this->get_option( $var );
$defaults['id'] = "itsec-$clean_var";
if ( ! empty( $options['append_val_to_id'] ) && ( true === $options['append_val_to_id'] ) && ! empty( $options['value'] ) ) {
unset( $options['append_val_to_id'] );
$defaults['id'] .= '-' . trim( preg_replace( '/[^a-z0-9_]+/i', '-', $options['value'] ), '-' );
}
$options = ITSEC_Form::merge_defaults( $options, $defaults );
if ( ! is_null( $val ) ) {
if ( in_array( $options['type'], array( 'checkbox', 'radio' ) ) ) {
if ( ( is_array( $val ) && in_array( $options['value'], $val ) ) || ( ! is_array( $val ) && ( (string) $val === (string) $options['value'] ) ) ) {
$options['checked'] = 'checked';
}
} else if ( 'select' !== $options['type'] ) {
$options['value'] = $val;
}
}
$attributes = '';
if ( false !== $options ) {
foreach ( (array) $options as $name => $content ) {
if ( ! is_array( $content ) && ( ! isset( $scrub_list[$options['type']][$name] ) || ( true !== $scrub_list[$options['type']][$name] ) ) ) {
if ( 'value' == $name ) {
$content = ITSEC_Form::esc_value_attr( $content );
} else if ( ! in_array( $options['type'], array( 'submit', 'button' ) ) ) {
$content = esc_attr( $content );
}
$attributes .= "$name=\"$content\" ";
}
}
}
if ( 'textarea' === $options['type'] ) {
if ( ! isset( $options['value'] ) ) {
$options['value'] = '';
} else if ( is_array( $options['value'] ) ) {
$options['value'] = implode( "\n", $options['value'] );
echo '<input type="hidden" name="--itsec-form-convert-to-array[]" value="' . esc_attr( $options['name'] ) . '" />' . "\n";
}
echo "<textarea $attributes >" . ITSEC_Form::esc_value_attr( $options['value'] ) . '</textarea>';
} else if ( 'select' === $options['type'] ) {
echo "<select $attributes>\n";
if ( isset( $options['value'] ) && is_array( $options['value'] ) ) {
foreach ( (array) $options['value'] as $content => $name ) {
if ( is_array( $name ) ) {
if ( preg_match( '/^__optgroup_\d+$/', $content ) ) {
echo "<optgroup class='it-classes-optgroup-separator'>\n";
} else {
echo "<optgroup label='" . esc_attr( $content ) . "'>\n";
}
foreach ( (array) $name as $content => $sub_name ) {
if ( is_array( $val ) ) {
$selected = '';
foreach ( $val as $set_val ) {
if ( (string) $set_val === (string) $content ) {
$selected = ' selected="selected"';
}
}
} else {
$selected = ( ! is_null( $val ) && ( (string) $val === (string) $content ) ) ? ' selected="selected"' : '';
}
echo "<option value=\"" . ITSEC_Form::esc_value_attr( $content ) . "\"$selected>$sub_name</option>\n";
}
echo "</optgroup>\n";
} else {
if ( is_array( $val ) ) {
$selected = '';
foreach ( $val as $set_val ) {
if ( (string) $set_val === (string) $content ) {
$selected = ' selected="selected"';
}
}
} else {
$selected = ( ! is_null( $val ) && ( (string) $val === (string) $content ) ) ? ' selected="selected"' : '';
}
echo "<option value=\"" . ITSEC_Form::esc_value_attr( $content ) . "\"$selected>$name</option>\n";
}
}
}
echo "</select>\n";
} else {
echo '<input ' . $attributes . '/>' . "\n";
}
if ( '[]' === substr( $options['name'], -2 ) ) {
if ( ! isset( $this->tracked_arrays[$options['name']] ) ) {
echo '<input type="hidden" name="--itsec-form-tracked-arrays[]" value="' . esc_attr( substr( $options['name'], 0, -2 ) ) . '" />' . "\n";
$this->tracked_arrays[$options['name']] = true;
}
} else if ( 'checkbox' === $options['type'] ) {
if ( ! isset( $this->tracked_booleans[$options['name']] ) ) {
echo '<input type="hidden" name="--itsec-form-tracked-booleans[]" value="' . esc_attr( $options['name'] ) . '" />' . "\n";
$this->tracked_booleans[$options['name']] = true;
}
} else if ( 'radio' === $options['type'] ) {
if ( ! isset( $this->tracked_strings[$options['name']] ) ) {
echo '<input type="hidden" name="--itsec-form-tracked-strings[]" value="' . esc_attr( $options['name'] ) . '" />' . "\n";
$this->tracked_strings[$options['name']] = true;
}
}
}
private static function esc_value_attr( $text ) {
$text = wp_check_invalid_utf8( $text );
$text = htmlspecialchars( htmlspecialchars_decode( htmlspecialchars_decode( $text ) ), ENT_QUOTES );
return $text;
}
private static function get_array_value( $array, $index ) {
if ( is_string( $index ) ) {
if ( false === strpos( $index, '[' ) ) {
$index = array( $index );
} else {
$index = rtrim( $index, '[]' );
$index = preg_split( '/[\[\]]+/', $index );
}
}
while ( count( $index ) > 1 ) {
if ( isset( $array[$index[0]] ) ) {
$array = $array[$index[0]];
array_shift( $index );
} else {
return null;
}
}
if ( isset( $array[$index[0]] ) ) {
return $array[$index[0]];
}
return null;
}
private static function add_array_value( &$array, $index, $val ) {
if ( is_string( $index ) ) {
if ( false === strpos( $index, '[' ) ) {
$index = array( $index );
} else {
$index = rtrim( $index, '[]' );
$index = preg_split( '/[\[\]]+/', $index );
}
}
$cur_array =& $array;
while ( count( $index ) > 1 ) {
if ( ! isset( $cur_array[$index[0]] ) || ! is_array( $cur_array[$index[0]] ) ) {
$cur_array[$index[0]] = array();
}
$cur_array =& $cur_array[$index[0]];
array_shift( $index );
}
$cur_array[$index[0]] = $val;
}
private static function merge_defaults( $values, $defaults, $force = false ) {
if ( ! ITSEC_Form::is_associative_array( $defaults ) ) {
if ( ! isset( $values ) ) {
return $defaults;
}
if ( false === $force ) {
return $values;
}
if ( isset( $values ) || is_array( $values ) ) {
return $values;
}
return $defaults;
}
foreach ( (array) $defaults as $key => $val ) {
if ( ! isset( $values[$key] ) ) {
$values[$key] = null;
}
$values[$key] = ITSEC_Form::merge_defaults( $values[$key], $val, $force );
}
return $values;
}
private static function is_associative_array( &$array ) {
if ( ! is_array( $array ) || empty( $array ) ) {
return false;
}
$next = 0;
foreach ( $array as $k => $v ) {
if ( $k !== $next++ ) {
return true;
}
}
return false;
}
}