<?php
// TOOLSET TO CREATE AND MANAGE FIELDS/FORMS

class pc_form {
	public $fields = array(); // registered fields array 
	public $form_pages = 1; // contains form pages number (1 = no pages)
	public $mail_is_required = false; // flag for required mail 
	
	public $errors = ''; // form validation errors (HTML code)
	public $form_data = array(); // array containing form's data (associative array(field_name => value))
	
	
	// field indexes array already used and must not be overrided with add-ons
	// useful to perform add-on validations - extended on construct with registered fields
	public $forbidden_indexes = array(
		'id', 
		'name', 
		'surname', 
		'username', 
		'psw', 
		'pc_cat', 
		'categories',
		'email', 
		'tel',
		'check_psw', 
		'insert_date', 
		'categories', 
		'page_id', 
		'disable_pvt_page', 
		'status', 	
		'wp_user_id', 
		'last_access',
		'pc_disclaimer', 
		'pc_hnpt_1',
		'pc_hnpt_2',
		'pc_hnpt_3',
		'pc_reg_btn'
	);
	
	
	// fields ID array to discard when using wizards (eg. to hide username from reg form wizad's dropdown)
	public $no_wizard_indexes = array();
	
	
	/* INIT - setup plugin fields and whether mail is required 
	 * @param (array) $args = utility array, used to setup differently fields. Possible indexes
	 *	- use_custom_cat_name = whether to use custom category name
	 *	- strip_no_reg_cats = whether to remove categories not allowed on registration
	 *
	 */
	public function __construct($args = array()) {
		include_once(PC_DIR .'/functions.php');
		
		// check if WP user sync is required - otherwise // PC-FILTER - allows add-ons to require e-mail (by default is false) - acts only if PC doesn't require it
		$this->mail_is_required = (get_option('pg_wp_user_sync') && get_option('pg_require_wps_registration')) ? true : apply_filters('pc_set_mail_required', false);
		
		///////////////////////
		$fist_last_name = get_option('pg_use_first_last_name');
		$custom_cat_name = (isset($args['use_custom_cat_name']) || isset($GLOBALS['pc_custom_cat_name'])) ? trim(get_option('pg_reg_cat_label', '')) : '';
		
		$fields = array(
			'name' => array(
				'label' 	=> ($fist_last_name) ? __('First name', 'pc_ml') : __('Name', 'pc_ml'),
				'type' 		=> 'text',
				'subtype' 	=> '',
				'maxlen' 	=> 150,
				'opt'		=> '',
				'placeh'	=> get_option('pg_name_placeh'),
				'icon'		=> get_option('pg_name_icon'), // FontAwesome icon class
				'note' 		=> ($fist_last_name) ? __('User first name', 'pc_ml') : __('User name', 'pc_ml')
			),
			'surname' => array(
				'label' 	=> ($fist_last_name) ? __('Last name', 'pc_ml') : __('Surname', 'pc_ml'),
				'type' 		=> 'text',
				'subtype' 	=> '',
				'maxlen' 	=> 150,
				'opt'		=> '',
				'placeh'	=> get_option('pg_surname_placeh'),
				'icon'		=> get_option('pg_surname_icon'),
				'note' 		=> ($fist_last_name) ? __('User last name', 'pc_ml') : __('User name', 'pc_ml')
			),
			'username' => array(
				'label' 	=> __('Username', 'pc_ml'),
				'type' 		=> 'text',
				'subtype' 	=> '',
				'maxlen' 	=> 150,
				'opt'		=> '',
				'placeh'	=> get_option('pg_username_placeh'),
				'icon'		=> get_option('pg_username_icon'),
				'note' 		=> __('Username used for the login', 'pc_ml'),
				'sys_req' 	=> true,
			),
			'psw' => array(
				'label' 	=> __('Password', 'pc_ml'),
				'type' 		=> 'password',
				'subtype' 	=> '',
				'minlen' 	=> get_option('pg_psw_min_length', 4),
				'maxlen' 	=> 50,
				'opt'		=> '',
				'placeh'	=> get_option('pg_psw_placeh'),
				'icon'		=> get_option('pg_psw_icon'),
				'note' 		=> __('Password used for the login', 'pc_ml'),
				'sys_req' 	=> true
			),
			'categories' => array(
				'label' 	=> (empty($custom_cat_name)) ? __('Category', 'pc_ml') : $custom_cat_name,
				'type' 		=> 'assoc_select',
				'subtype' 	=> '',
				'maxlen' 	=> 20,
				'opt'		=> (isset($args['strip_no_reg_cats'])) ? pc_user_cats(true) : pc_user_cats(),
				'placeh'	=> get_option('pg_categories_placeh'),	
				'icon'		=> get_option('pg_categories_icon'),
				'note' 		=> 'PrivateContent '. __('Categories', 'pc_ml'),
				'multiple'	=> (get_option('pg_reg_multiple_cats')) ? true : false,		
				'sys_req' 	=> true
			),
			'email' => array(
				'label' 	=> __('E-Mail', 'pc_ml'),
				'type' 		=> 'text',
				'subtype' 	=> 'email',
				'maxlen' 	=> 255,
				'opt'		=> '',
				'placeh'	=> get_option('pg_email_placeh'),
				'icon'		=> get_option('pg_email_icon'),
				'note' 		=> __('User E-mail', 'pc_ml'),
				'sys_req' 	=> $this->mail_is_required 
			),  
			'tel' => array(
				'label' 	=> __('Telephone', 'pc_ml'),
				'type' 		=> 'text',
				'subtype' 	=> '',
				'maxlen' 	=> 20,
				'opt'		=> '',
				'placeh'	=> get_option('pg_tel_placeh'),
				'icon'		=> get_option('pg_tel_icon'),
				'note' 		=> __('User Telephone', 'pc_ml')
			),
			'pc_disclaimer' => array(
				'label' 	=> __("Disclaimer", 'pc_ml'),
				'type' 		=> 'single_checkbox',
				'subtype' 	=> '',
				'maxlen' 	=> 1,
				'opt'		=> '1',
				'check_txt'	=> strip_tags((string)get_option('pg_disclaimer_txt'), '<br><a><strong><em>'),
				'disclaimer'=> true,
				'note' 		=> __('Registration disclaimer', 'pc_ml'),
				'sys_req' 	=> true
			)
		);	
	
		// PC-FILTER - add fields to the usable ones - passes default fields structure
		$this->fields = apply_filters('pc_form_fields_filter', $fields);
		
		// PC-FILTER - allow forbidden fields extension - useful to report custom field indexes usage that must not be overrided - passes default forbidden + pc-registered fields array
		$this->forbidden_indexes = apply_filters('pc_forbidden_field_indexes', array_unique(array_merge($this->forbidden_indexes, array_keys($this->fields))));
		
		// PC-FILTER - fields ID array to discard when using wizards (eg. to hide username from reg form wizad's dropdown)
		$this->no_wizard_indexes = array_unique( (array)apply_filters('pc_no_wizard_indexes', array('username', 'psw', 'pc_disclaimer')) );
		
		return true;
	}
	

	/* 
	 * Retrieves a field from plugin ones 
	 * @since 5.0
	 */
	public function get_field($field_name) {
		return (isset($this->fields[$field_name])) ? $this->fields[$field_name] : false;
	}
	
	/* 
	 * Retrieves field name from plugin ones 
	 * @since 5.0
	 */
	public function get_field_name($field) {
		return (isset($this->fields[$field])) ? $this->fields[$field]['label'] : false;
	}
	
	
	/* 
	 * Adds HTML5 form validation attributes
	 * @since 6.0
	 *
	 * @param (array) $field = field data
	 * @param (bool) $is_required = true if field is required
	 *
	 * @return (string) attributes to be added
	 */
	protected function html5_validation_attr($field, $is_required) {
		$atts = array();
		
		// required
		if($is_required) {$atts[] = 'required="required"';}
		
		// text types
		if($field['type'] == 'text' && isset($field['subtype'])) {
			switch($field['subtype']) {
				
				case 'int' 		: 	
				case 'float' 	: 
					$atts[] = 'min="'. (float)$field['range_from'] .'" max="'. (float)$field['range_to'] .'" step="any"';
					break;
					
				case 'eu_date' :
					$atts[] = 'pattern="(0[1-9]|1[0-9]|2[0-9]|3[01])/(0[1-9]|1[012])/[0-9]{4}"';
					break;
					
				case 'us_date' :
					$atts[] = 'pattern="(0[1-9]|1[012])/(0[1-9]|1[0-9]|2[0-9]|3[01])/[0-9]{4}"';
					break;			
				
				case 'zipcode' : // 00000
					$atts[] = 'pattern="[0-9]{5}"';
					break;
					
				case 'us_tel' : // 000-000-0000
					$atts[] = 'pattern="\d{3}[\-]\d{3}[\-]\d{4}"';
					break;			
			}
		}
		
		// regex - TODO 
		
				
		return implode(' ', $atts);	
	}
	
	
	/* FORM CODE GENERATOR 
	 * @since 5.0
	 *
	 * @param (array) $fields = multidimensional array containing included and required fields array('include'=>array, 'require'=>array)
	 * @param (string) $custom_fields = custom HTML code to add custom fields to the form - should be LI elements
	 * @param (int) $user_id = pvtContent user ID, to populate fields with its data 
	 *
	 * @return (string) form fields UL list
	 */
	public function form_code($fields, $custom_fields = false, $user_id = false) {
		$included = $fields['include'];
		$required = $fields['require'];
		
		$disclaimers = '';
		
		// check form pages count
		foreach($included as $field) {
			if($field == 'custom|||page') {
				$this->form_pages++;	
			}
		}
		$paginated_class = ($this->form_pages > 1) ? 'pc_paginated_form' : '';
		
		// check texts
		$txt_count = 0;
		$texts = (isset($fields['texts']) && is_array($fields['texts'])) ? $fields['texts'] : array(); 
		
		if(!is_array($included)) {return false;}
		
		// if is specified the user id get data to fill the field
		if($user_id) {
			include_once('users_manag.php');
			$user = new pc_users;
			$query = $user->get_users(array(
				'user_id' => $user_id,
				'to_get' => $included
			)); 
			$ud = $query[0];
		}
		else {$ud = false;}


		// build
		$form = '';
		$form_pag = 1;
		$printed_pag = 0; 
		
		foreach($included as $field) {
			
			if($field == 'custom|||page') {
				$form_pag++;
				continue;
			}
			
			// fieldset with page
			if($printed_pag < $form_pag) {
				if($printed_pag) {
					$form .= '</fieldset>';
					$hidden_fs_class = 'pc_hidden_fieldset';
				}
				else {
					$hidden_fs_class = '';	
				}
				
				$form .= '<fieldset class="pc_form_flist pc_f_pag_'. $form_pag .' '.$paginated_class.' '. $hidden_fs_class .'">';
				$printed_pag++;
			}
			
			
			
			// if is a text block
			if($field == 'custom|||text') {
				if(isset($texts[$txt_count])) {
					$form .= '
					<section class="pc_form_txt_block pc_ftb_'.$txt_count.'">
						'. do_shortcode($texts[$txt_count]) .'
					</section>';
					
					$txt_count++;
				}
			}
			
			// normal field
			else {
				$fdata = $this->get_field($field);		
				if($fdata) {
					
					// required message
					$req = (in_array($field, $required) || (isset($fdata['sys_req']) && $fdata['sys_req'])) ? '<span class="pc_req_field">*</span>' : '';
					
					// html5 validation attributes
					$html5_is_req = (empty($req)) ? false : true;
					$html5_valid = $this->html5_validation_attr($fdata, $html5_is_req);
					
					// field classes
					$field_class = sanitize_title(urldecode($field));
					if($fdata['type'] == 'text' && ($fdata['subtype'] == 'eu_date' || $fdata['subtype'] == 'us_date')) {$field_class .= ' pcud_datepicker pcud_dp_'.$fdata['subtype'];}
					$type_class = 'class="'. $field_class .'"';
					
					// options for specific types
					if($fdata['type'] != 'assoc_select' && isset($fdata['opt'])) {$opts = $this->get_field_options($fdata['opt']);}
					
					// field class - for field wrapper
					$multiselect_class = (($fdata['type'] == 'select' || $fdata['type'] == 'assoc_select') && isset($fdata['multiple']) && $fdata['multiple']) ? 'pc_multiselect' : '';
					$singleselect_class = (($fdata['type'] == 'select' || $fdata['type'] == 'assoc_select') && (!isset($fdata['multiple']) || !$fdata['multiple'])) ? 'pc_singleselect' : '';
					
					$singlecheck_class = ($fdata['type'] == 'single_checkbox' && (!isset($fdata['disclaimer']) || empty($fdata['disclaimer']))) ? 'pc_single_check' : '';
					
					// has icon class
					$has_icon_class = (isset($fdata['icon']) && !empty($fdata['icon'])) ? 'pc_field_w_icon' : '';
					
					$f_class = 'class="pc_rf_field pc_rf_'. sanitize_title(urldecode($field)) .' pc_'. $fdata['type'] .'_ftype '.$has_icon_class.' '.$multiselect_class.' '.$singleselect_class.' '.$singlecheck_class.'"';
					
					// placeholder
					$placeh = (isset($fdata['placeh']) && !empty($fdata['placeh'])) ? 'placeholder="'.$fdata['placeh'].'"' : '';

					// icon
					$icon = ($has_icon_class) ? '<span class="pc_field_icon" title="'. esc_attr($fdata['label']) .'"><i class="fa '.$fdata['icon'].'"></i></span>' : '';

					
					// text types
					if($fdata['type'] == 'text') {
						$val = ($ud) ? $ud[$field] : false;
						
						// specific text type
						switch($fdata['subtype']) {
							case 'email' 	: $subtype = 'email'; break;
							case 'url'		: $subtype = 'url'; break;
							case 'int' 		: case 'float' : 	case 'zipcode'	: $subtype = 'number'; break;
							default 		: $subtype = 'text'; break;
						}
						
						$form .= '
						<section '.$f_class.'>
							<label>'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>
							
							<div class="pc_field_container">
								'. $icon .'
								<input type="'.$subtype.'" name="'.$field.'" value="'.pc_sanitize_input($val).'" maxlength="'.$fdata['maxlen'].'" '.$html5_valid.' '.$placeh.' autocomplete="off" '.$type_class.'  />
							</div>
						</section>';		
					}
					
					// password type
					elseif($fdata['type'] == 'password') {					
						$atts =  'value="" minlength="'. $fdata['minlen'] .'" maxlength="'. $fdata['maxlen'] .'" '.$type_class.' autocomplete="off" required="required"';
						
						// repeat psw specific data
						// icon
						$rp_icon = (get_option('pg_repeat_psw_icon')) ? '<span class="pc_field_icon" title="'. esc_attr(__('Repeat password', 'pc_ml')) .'"><i class="fa '.get_option('pg_repeat_psw_icon').'"></i></span>' : '';
						
						$rp_has_icon_class = ($rp_icon) ? 'pc_field_w_icon' : '';
						////
												
						$form .= '
						<section '.$f_class.'>
							<label>'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>
							<div class="pc_field_container">
								'. $icon .'
								<input type="'.$fdata['type'].'" name="'.$field.'" '. $placeh .' '. $atts .' />
							</div>
						</section>
						<section class="pc_rf_field pc_rf_psw_confirm pc_password_ftype '. $rp_has_icon_class .'">	
							<label>'. __('Repeat password', 'pc_ml').' '.$req.'</label>
							<div class="pc_field_container">
								'. $rp_icon .'
								<input type="'.$fdata['type'].'" name="check_'.$field.'" '. $atts .' placeholder="'.get_option('pg_repeat_psw_placeh').'" />
							</div>
						</section>';			
					}
					
					// textarea
					elseif($fdata['type'] == 'textarea') {
						$val = ($ud) ? $ud[$field] : false;
						$form .= '
						<section '.$f_class.'>
							<label class="pc_textarea_label">'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>
							<textarea name="'.$field.'" class="pc_textarea '.$field_class.'" '.$html5_valid.' '.$placeh.' autocomplete="off">'. str_replace('<br />', '
', $val) .'</textarea>
						</section>';		
					}
					
					// select
					elseif($fdata['type'] == 'select') {	
						$multiple = (isset($fdata['multiple']) && $fdata['multiple']) ? 'multiple="multiple"' : '';
						$multi_name = ($multiple) ? '[]' : '';
						$placeh = (isset($fdata['placeh'])) ? $fdata['placeh'] : ''; 
						
						$form .= '
						<section '.$f_class.'>
							<label>'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>
							<div class="pc_field_container">
								'. $icon .'
								<select name="'.$field.$multi_name.'" '.$type_class.' '.$multiple.' '.$html5_valid.' data-placeh="'.$placeh.'" autocomplete="off">';
							
								foreach($opts as $opt) { 
									$sel = ($ud && in_array($opt, (array)$ud[$field])) ? 'selected="selected"' : false;
									$form .= '<option value="'.$opt.'" '.$sel.'>'.$opt.'</option>'; 
								}
						
						$form .= '
								</select>
							</div>
						</section>';			
					}
					
					// associative select (for pg categories)
					elseif($fdata['type'] == 'assoc_select') {	
						$multiple = (isset($fdata['multiple']) && $fdata['multiple']) ? 'multiple="multiple"' : '';
						$multi_name = ($multiple) ? '[]' : '';
						
						$form .= '
						<section '.$f_class.'>
							<label>'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>				
							<div class="pc_field_container">
								'. $icon .'
								<select name="'.$field.$multi_name.'" '.$multiple.' data-placeh="'.$fdata['placeh'].'" required="required" autocomplete="off">';
						
									foreach($fdata['opt'] as $key => $val) { 
										$sel = ($ud && $ud[$field] == $key) ? 'selected="selected"' : false;
										$form .= '<option value="'.$key.'" '.$sel.'>'.$val.'</option>'; 
									}
						
						$form .= '
								</select>
							</div>
						</section>';			
					}
					
					// checkbox
					elseif($fdata['type'] == 'checkbox') {	
						$form .= '
						<section '.$f_class.'>
							<label class="pc_cb_block_label">'. __($fdata['label'], 'pc_ml') .' '.$req.'</label>
							<div class="pc_check_wrap">';
							
							foreach($opts as $opt) { 
								$sel = ($ud && in_array($opt, (array)$ud[$field])) ? 'checked="checked"' : false;
								$form .= '<input type="checkbox" name="'.$field.'[]" value="'.$opt.'" '.$sel.' autocomplete="off" /> <label class="pc_check_label">'.$opt.'</label>'; 
							}
						$form .= '
							</div>
						</section>';
					}
					
					// single-option checkbox
					elseif($fdata['type'] == 'single_checkbox') {	
						$sel = ($ud && !empty($ud[$field])) ? 'checked="checked"' : '';
						
						if(!isset($fdata['disclaimer']) || empty($fdata['disclaimer'])) {
							$form .= '
							<section '.$f_class.'>
								<input type="checkbox" name="'.$field.'" value="1" '.$sel.' '.$html5_valid.' autocomplete="off" />
								<label>'. $fdata['check_txt'] .' '.$req.'</label>
							</section>';
						} 
						else {
							$disclaimers .= '
							<section class="pc_rf_disclaimer">
								<div class="pc_disclaimer_check"><input type="checkbox" name="'.$field.'" value="1" '.$sel.' '.$html5_valid.' autocomplete="off" /></div>
								<div class="pc_disclaimer_txt">'. $fdata['check_txt'] .'</div>
							</section>';
						}
					}
					
					
					else {
						// PC-FILTER - allow custom field types and custom codes. Passes form structure, field type, field structure and user ID
						// @since v5.1
						$form = apply_filters('pc_custom_field_type', $form, $fdata['type'], $fdata, $user_id);  
					}
				}
			}
		}
		
		if($custom_fields) {$form .= $custom_fields;}
		
		if(!empty($disclaimers)) {
			$form .= '<section class="pc_rf_disclaimer_sep"></section>' . $disclaimers;	
		}
		
		return $form . '</fieldset>';
	}
	
	
	/* GET OPTIONS ARRAY for select, checkbox and radio fields 
	 * @since 5.0
	 *
	 * @param (string) $opts = string of options, comma spilt (if receives an array - returns it)
	 * @return (array) array of options
	 */
	public function get_field_options($opts) {
		if(is_array($opts)) {return $opts;}
		if(trim($opts) == '') {return false;}
		
		$opts_arr = explode(',', $opts);
		foreach($opts_arr as $opt) {
			$right_opts[] = trim($opt);	
		}
		return $right_opts;
	}

	
	/* FIELDS DATA AGGREGATOR - given an indexes array, scan $_GET and $_POST to store form data - if not found use false 
	 * @since 5.0
	 *
	 * @param (bool) $stripslashes = whether to use stripslashes to get true values after WP filters
	 * @return (array) associative array (index => val)
	 */
	public function get_fields_data($fields, $stripslashes = true) {
		if(!is_array($fields)) {return false;}	
		
		$return = array();
		foreach($fields as $f) {
			if(isset($_POST[$f])) {$return[$f] = $_POST[$f];}
			elseif(isset($_GET[$f])) {$return[$f] = $_GET[$f];}
			else {$return[$f] = false;}
			
			$return[$f] = (is_string($return[$f]) && $stripslashes) ? stripslashes($return[$f]) : $return[$f];
			
			// if is fetching password field - get also check psw
			if($f == 'psw' && !in_array('check_psw', $fields) && isset($_POST['check_psw'])) {
				$return['check_psw'] = (is_string($_POST['check_psw']) && $stripslashes) ? stripslashes($_POST['check_psw']) : $_POST['check_psw'];
			}
		}
		
		return $return;
	}


	
	/* SIMPLE-FORM-VALIDATOR - create array indexes
	 * @since 5.0
	 *
	 * @param (array) $form_structure = multidimensional array containing included and required fields array('include'=>array, 'require'=>array)
	 * @param (array) $custom_valid = additional validation indexes in case of extra fields
	 * @return (array) validator indexes
	 */
	public function generate_validator($form_structure, $custom_valid = array()) {
		$included = (array)$form_structure['include'];
		$required = (array)$form_structure['require'];
		
		// merge the two arrays to not have missing elements in included
		$included = array_merge($included, $required);
		if(empty($included)) {return array();}
		
		$indexes = array();
		$a = 0;
		foreach($included as $index) {
			$fval = $this->get_field($index);
			
			$indexes[$a]['index'] = str_replace('.', '_', $index); // fix for dots in indexes
			$indexes[$a]['label'] = urldecode($fval['label']);
			
			// required
			if(in_array($index, $required) || (isset($fval['sys_req']) && $fval['sys_req'])) {
				$indexes[$a]['required'] = true;
			}
			
			// min-length
			if($fval['type'] == 'password' || ($fval['type'] == 'text' && empty($fval['subtype']))) {
				if(isset($fval['minlen'])) {$indexes[$a]['min_len'] = $fval['minlen'];}
			}
			
			// maxlenght
			if($fval['type'] == 'text' && (empty($fval['subtype']) || $fval['subtype'] == 'int')) {
				$indexes[$a]['max_len'] = $fval['maxlen'];
			}
			
			// specific types
			if($fval['type'] == 'text' && !empty($fval['subtype'])) {
				$indexes[$a]['type'] = $fval['subtype'];
			}
	
			// allowed values
			if(($fval['type'] == 'select' || $fval['type'] == 'checkbox') && !empty($fval['opt'])) {
				// remove spaces between elements
				$indexes[$a]['allowed'] = $this->get_field_options($fval['opt']);
			}
			
			// numeric value range
			if($fval['type'] == 'text' && in_array($fval['subtype'], array('int', 'float')) && isset($fval['range_from']) && $fval['range_from'] !== '') {
				$indexes[$a]['min_val'] = (float)$fval['range_from'];
				$indexes[$a]['max_val'] = (float)$fval['range_to'];
			}
			
			// regex validation
			if(in_array($fval['type'], array('text', 'textarea')) && isset($fval['regex']) && !empty($fval['regex'])) {
				$indexes[$a]['preg_match'] = $fval['regex'];			
			}
	
			////////////////////////////
			// password check validation
			if($index == 'psw') {
				// add fields check
				$indexes[$a]['equal'] = 'check_psw';
				
				// check psw validation
				$a++;
				$indexes[$a]['index'] = 'check_psw';
				$indexes[$a]['label'] = __('Repeat password', 'pc_ml');
				$indexes[$a]['maxlen'] = $fval['maxlen'];
			}
	
			$a++;	
		}
		
		if(is_array($custom_valid)) {
			$indexes = array_merge($indexes, $custom_valid);	
		}
		return $indexes;
	}
	
	
	
	/* VALIDATE FORM DATA - using simple_form_validator
	 * @since 5.0
	 *
	 * @param (array) $indexes = validation structure built previously
	 * @param (array) $custom_errors = array containing html strings with custom errors
	 * @param (int) $user_id = utility value to perform database checks - contains a PC user ID
	 * @param (bool) $specific_checks = whether to perform categories and username unicity checks. Useful to avoid double checks on frontend insert/update
	 *
	 * @return (bool) true if form is valid, false otherwise (errors and data can be retrieved in related obj properties)
	 */
	public function validate_form($indexes, $custom_errors = array(), $user_id = false, $specific_checks = true) {
		include_once('simple_form_validator.php');
		global $wpdb;
		
		$validator = new simple_fv;	
		$validator->formHandle((array)$indexes);
		$fdata = $validator->form_val;
		
		// clean data and save options
		foreach($fdata as $key=>$val) {
			if(is_string($val)) {
				$fdata[$key] = stripslashes($val);
			} 
			elseif(is_array($val)) {
				$fdata[$key] = array();
				foreach($val as $arr_val) {$fdata[$key][] = stripslashes($arr_val);}
			}
		}
		
		/*** special validation cases ***/
		foreach($indexes as $field) {
		
			// password strength
			if($field['index'] == 'psw') {
				$psw_strength = $this->check_psw_strength($fdata['psw']);
				if($psw_strength !== true) {
					$validator->custom_error[__("Password strength", 'pc_ml')] = $psw_strength;
				}
			}
			
			// username unicity 
			if($specific_checks && $field['index'] == 'username') {
				$already_exists = ($user_id) ? ' AND id != '. (int)$user_id : '';
				$wpdb->query(
					$wpdb->prepare("SELECT id FROM ".PC_USERS_TABLE." WHERE username = %s AND status != 0 ".$already_exists." LIMIT 1", trim((string)$fdata['username']))
				);
				if($wpdb->num_rows) {
					$validator->custom_error[__("Username", 'pc_ml')] =  __("Another user already has this username", 'pc_ml');	
				}
			}
			
			// categories
			if($specific_checks && $field['index'] == 'categories' && !empty($fdata['categories'])) {
				$cats = (!isset($GLOBALS['pc_escape_no_reg_cats'])) ? pc_user_cats(false) : pc_user_cats(true);	
				
				foreach((array)$fdata['categories'] as $f_cat) {
					if(!isset($cats[$f_cat])) {
						$name = $this->fields['categories']['label']; 
						$validator->custom_error[$name] =  __("One or more chosen categories are wrong", 'pc_ml');	
						break;	
					}
				}
			}
		}
		
		// wrap up
		$this->form_data = $fdata;
		$errors = $validator->getErrors('array');
		if(!is_array($errors)) {$errors = array();}
		
		if(!empty($custom_errors)) {
			$errors = array_merge($errors, $custom_errors);
		}
		
		// PC-FILTER - add custom errors on form validation - passes errors array and form data
		$errors = apply_filters('pc_form_valid_errors', $errors, $fdata);
		
		
		// manage errors to be a string or a list for multiple errors
		if(empty($errors) || !is_array($errors)) {
			$this->errors = '';	
		}
		else {
			$errors = (count($errors) == 1) ? '<span>'. implode('', $errors) .'</span>' : '<ul><li>'. implode('</li><li>', $errors) .'</li></ul>';
		}
		
		$this->errors = $errors;
		return (empty($this->errors)) ? true : false;		
	}


	
	/* PASSWORD STRENGTH VALIDATOR - made to work with simple-form-validator errors 
	 * @since 5.0
	 *
	 * @return (bool/string) true if password is ok - otherwise string containing errors
	 */
	public function check_psw_strength($psw) {
		$options = get_option('pg_psw_strength', array());
		if(!is_array($options) || count($options) == 0) {return true;}
		
		// regex validation
		$new_error = array();
		foreach($options as $opt) {
			if($opt == 'chars_digits') {
				if(!preg_match("((?=.*\d)(?=.*[a-zA-Z]))", $psw)) {$new_error[] = __('characters and digits', 'pc_ml');}	
			}
			elseif($opt == 'use_uppercase') {
				if(!preg_match("(.*[A-Z])", $psw)) {$new_error[] = __('an uppercase character', 'pc_ml');}	
			}
			elseif($opt == 'use_symbols') {
				if(!preg_match("(.*[^A-Za-z0-9])", $psw)) {$new_error[] = __('a symbol', 'pc_ml');}	
			}
		}
		if(count($new_error) > 0) {
			$regex_err = __('must contain at least', 'pc_ml') .' '. implode(', ', $new_error);	
		}
		
		return (!isset($regex_err)) ? true : $regex_err;
	}	
	
	
	
	/* 
	 * HONEYPOT antispam code generator
	 * @since 5.0
	 */
	public function honeypot_generator() {
		$calculation = mt_rand(0, 100) + mt_rand(0, 100);
		$hash = md5(sha1($calculation));
		
		return '
		<div class="pc_hnpt_code" style="display: none; visibility: hidden; position: fixed; left: -9999px;">
			<label for="pc_hnpt_1">Antispam 1</label>
			<input type="text" name="pc_hnpt_1" value="" autocomplete="off" />
			
			<label for="pc_hnpt_2">Antispam 2</label>
			<input type="text" name="pc_hnpt_2" value="'.$calculation.'" autocomplete="off" />
			
			<label for="pc_hnpt_3">Antispam 3</label>
			<input type="text" name="pc_hnpt_3" value="'.$hash.'" autocomplete="off" />
		</div>'; 
	}
	
	
	/* 
	 * HONEYPOT antispam validator
	 * @since 5.0
	 */
	public function honeypot_validaton() {
		// three fields must be valid
		if(!isset($_POST['pc_hnpt_1']) || !isset($_POST['pc_hnpt_2']) || !isset($_POST['pc_hnpt_3'])) {return false;}
		
		// first field must be empty
		if(!empty($_POST['pc_hnpt_1'])) {return false;}
		
		// hash of second must be equal to third
		if(md5(sha1($_POST['pc_hnpt_2'])) != $_POST['pc_hnpt_3']) {return false;}
		
		return true;
	}
}

