<?php
/**
* Form Helper class file
*
* Builds a form, checks and validates $_POST and $_GET variables and generates HTML for
* you
*
* @package Tina-MVC
* @subpackage Core
*/
namespace TINA_MVC {
/**
* Renderer for INPUT type 'text'
*
* You can extend the form helper by using these classes as an example. You must
* define a html() function which returns the escaped HTML required to display the element.
* Look at field_googlemap or field_recaptcha for an example of a more complex field type.
*
* You can also define a function setup() which accepts the field object as a parameter. See
* field_recaptcha to see how this works.
*
* @package Tina-MVC
* @subpackage Core
*/
class field_text {
/**
* Set FALSE to prevent a LABEL element being generated before this element. If not
* defined $render_label is assumed TRUE
*/
var $render_label = TRUE; // not needed - default behaviour
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<input type="text" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($f->get_value()).'" '.$f->get_extra_attribs().' '.$f->get_xhtml_slash().'>';
}
}
/**
* Renderer for INPUT type 'submit'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_submit {
/**
* Prevents a label from being generated for this element
* @see class field_text->render_label
*/
var $render_label = FALSE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
// we use caption as button text (unless someone has the cop on to remember that the value is the button text)
if( $f->get_value() ) {
$text = $f->get_value();
}
else {
$text = $f->get_caption();
}
return '<input type="submit" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($text).'" '.$f->get_extra_attribs().' '.$f->get_xhtml_slash().'>';
}
}
/**
* Renderer for INPUT type 'reset'
*
* Note that this does not reset reCaptcha fields.
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_reset {
/**
* Prevents a label from being generated for this element
* @see class field_text->render_label
*/
var $render_label = FALSE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
// we use caption as button text (unless someone has the cop on to remember that the value is the button text)
if( $f->get_value() ) {
$text = $f->get_value();
}
else {
$text = $f->get_caption();
}
return '<input type="reset" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($text).'" '.$f->get_extra_attribs().' '.$f->get_xhtml_slash().'>';
}
}
/**
* Renderer for INPUT type 'textblock'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_textblock {
/**
* Set as TRUE to prevent the render function from trying to render a label/input pair. The output
* will be within the form pair html block. See $form_base->html_form_pair
*/
var $plain_html = TRUE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<div id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" '.$f->get_extra_attribs().'>'.esc_html($f->get_value()).'</div>';
}
}
/**
* Renderer for INPUT type 'fieldset_open'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_fieldset_open {
/**
* Set as TRUE to prevent the render function from wrapping the output within a form pair html
* block. See $form_base->html_form_pair
*/
var $no_html_wrappers = TRUE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<fieldset id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" '.$f->get_extra_attribs().'><legend>'.esc_html($f->get_value()).'</legend>';
}
}
/**
* Renderer for INPUT type 'fieldset_close'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_fieldset_close {
/**
* Set as TRUE to prevent the render function from wrapping the output within a form pair html
* block. See $form_base->html_form_pair
*/
var $no_html_wrappers = TRUE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '</fieldset>';
}
}
/**
* Renderer for INPUT type 'password'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_password {
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<input type="password" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($f->get_value()).'" '.$f->get_extra_attribs().' autocomplete="off" '.$f->get_xhtml_slash().'>';
}
}
/**
* Renderer for INPUT type 'hidden'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_hidden {
/**
* Prevents the label from being generated
* @var boolean
*/
var $render_label = FALSE;
/**
* Set as TRUE to prevent the render function from wrapping the output within a form pair html
* block. See $form_base->html_form_pair
*/
var $no_html_wrappers = TRUE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<input type="hidden" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($f->get_value()).'" '.$f->get_extra_attribs().' '.$f->get_xhtml_slash().'>';
}
}
/**
* Renderer for INPUT type 'textarea'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_textarea {
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
return '<textarea id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" '.$f->get_extra_attribs().'>'.esc_html($f->get_value()).'</textarea>';
}
}
/**
* Renderer for INPUT type 'checkbox'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_checkbox {
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
$_h = '<input type="checkbox" id="'.$f->get_id()."\" name=\"".$f->get_post_var_name()."\" value=\"1\"";
if( $f->get_value() ) {
$_h .= " CHECKED";
}
$_h .= ' '.$f->get_extra_attribs().' '.$f->get_xhtml_slash() . '>';
return $_h;
}
}
/**
* Renderer for INPUT type 'googlemap'
*
* The 'value' for a Googlemap is "latitude,longitude,zoom"
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_googlemap {
/**
* In case the default is not set in the app_settings.php file
*/
public $failsafe_location_default = '53.3406,-6.2752,6';
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
if( ! ( $google_api = get_tina_mvc_setting('google_api_key_v3') ) ) {
error('GOOGLEMAP field type requires \'google_api_key_v3\' to be set in app_settings.php');
}
$html = '';
if( ! $lat_lng_zoom = get_tina_mvc_setting('google_maps_default_location') ) {
$lat_lng_zoom = $this->failsafe_location_default;
}
$lat_lng_zoom = $f->get_value();
if( empty($lat_lng_zoom) ) {
if( ! $lat_lng_zoom = get_tina_mvc_setting('google_maps_default_location') ) {
$lat_lng_zoom = $this->failsafe_location_default;
}
}
$lat_lng_zoom = explode( ',', $lat_lng_zoom);
if( ! is_array($lat_lng_zoom) AND count($lat_lng_zoom) != 3 ) {
error('GOOGLEMAP field requires value in the form "decimal_latitude, decimal_longitude, zoom_level"');
}
// js added?
if ( ! isset( $f->form->googlemap_files_added ) ) {
$js_src = 'https://maps.googleapis.com/maps/api/js?key='.$google_api.'&sensor=false';
if( headers_sent() ) {
$html .= '<script type="text/javascript" src=".$js_src."></script>';
}
else {
wp_enqueue_script('googlemaps', $js_src, array(), NULL, true);
wp_enqueue_style( 'tina_mvc_form_helper_googlemap_css' , get_tina_mvc_folder_url().'/tina_mvc/helpers/tina_mvc_form_helper_googlemap_css.css' );
$f->form->googlemap_files_added = TRUE;
}
}
$w = ( $f->get_map_width() ? $f->get_map_width() : '400px' );
$h = ( $f->get_map_height() ? $f->get_map_height() : '300px' );
$html .= '<div style="width: '.$w.'; height: '.$h.';"><div id="map_canvas_'.$f->get_id().'" class="googlemap_canvas" style="width: 100%; height: 100%"></div></div>';
$html .= '<input type="hidden" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" value="'.esc_html($f->get_value()).'" '.$f->get_xhtml_slash().'>';
$html .= '<script type="text/javascript">
var centre_'.$f->get_id().';
var marker_'.$f->get_id().';
var map_'.$f->get_id().';
function gmaps_initialise_'.$f->get_id().'() {
centre_'.$f->get_id().' = new google.maps.LatLng('.$lat_lng_zoom[0].', '.$lat_lng_zoom[1].');
var mapOptions = {
zoom: '.$lat_lng_zoom[2].',
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: centre_'.$f->get_id().'
};
map_'.$f->get_id().' = new google.maps.Map(document.getElementById(\'map_canvas_'.$f->get_id().'\'), mapOptions);
marker_'.$f->get_id().' = new google.maps.Marker({
map:map_'.$f->get_id().',
draggable:true,
animation: google.maps.Animation.DROP,
position: centre_'.$f->get_id().'
});
google.maps.event.addListener(marker_'.$f->get_id().', \'click\', toggleBounce_'.$f->get_id().' );
google.maps.event.addListener(marker_'.$f->get_id().', \'dragend\', updateDiv_'.$f->get_id().' );
google.maps.event.addListener(map_'.$f->get_id().', \'zoom_changed\', updateDiv_'.$f->get_id().' );
function toggleBounce_'.$f->get_id().'() {
if (marker_'.$f->get_id().'.getAnimation() != null) {
marker_'.$f->get_id().'.setAnimation(null);
}
else {
marker_'.$f->get_id().'.setAnimation(google.maps.Animation.BOUNCE);
}
}
function updateDiv_'.$f->get_id().'() {
lat_lng = marker_'.$f->get_id().'.getPosition();
document.getElementById(\''.$f->get_id().'\').value = lat_lng.lat() + "," + lat_lng.lng() + "," + map_'.$f->get_id().'.getZoom();
}
}
window.onload = gmaps_initialise_'.$f->get_id().';
</script>';
return $html;
}
}
/**
* Renderer for INPUT type 'recaptcha'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
* @see http://recaptcha.net
*/
class field_recaptcha {
/**
* The setup function can be used to perform setup actions on a field.
*
* In this case we are using it to include library files and add a validation rule
*
* @param object $f field
* @return void
*/
function setup( $f ) {
// include the recaptcha libs
include_once( tina_mvc_folder().'/3rd_party/recaptcha/recaptchalib.php');
$f->add_validation( array('recaptcha'=>NULL) );
}
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
if( ! ( $recaptcha_pub_key = get_tina_mvc_setting('recaptcha_pub_key') ) OR ! ( $recaptcha_pri_key = get_tina_mvc_setting('recaptcha_pri_key') ) ) {
error('RECAPTCHA field type requires \'recaptcha_pub_key\' and \'recaptcha_pri_key\' to be set in app_settings.php');
}
// include the recaptcha libs - already done in field definition
// require_once( tina_mvc_folder().'/3rd_party/recaptcha/recaptchalib.php');
$f->add_validation( array( 'RECAPTCHA' => NULL ) );
$html = recaptcha_get_html( get_tina_mvc_setting('recaptcha_pub_key') );
return $html;
}
}
/**
* Renderer for INPUT type 'select'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_select {
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
$_h = '<select id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" '.$f->get_extra_attribs().">\r\n";
if( $options = $f->get_options() ) {
$_h .= '<option value=""></option>'."\r\n";
foreach( $f->get_options() AS $option ) {
// option is array( 'post_val'=>'blah' , 'display_val'=>meh )
if( $f->get_value() == $option['post_value'] ) {
$selected = " selected ";
}
else {
$selected = "";
}
$_h .= '<option id="'.$f->get_id().'_'.esc_html($option['post_value']).'" '.$selected.'value="'.esc_html($option['post_value']).'">'.esc_html($option['display_value']).'</option>'."\r\n";
}
}
$_h .= '</select>';
return $_h;
}
}
/**
* Renderer for INPUT type 'radio'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_radio {
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
$html = '';
if( $f->get_options() ) {
foreach( $f->get_options() AS $option ) {
// option is array( 'post_val'=>'blah' , 'display_val'=>meh )
if( $f->get_value() == $option['post_value'] ) {
$selected = " checked ";
}
else {
$selected = "";
}
$h = '';
$h .= '<label for="'.$f->get_id().'_'.esc_html($option['post_value']).'">'.esc_html($option['display_value']).'</label>: ';
$h .= '<input id="'.$f->get_id().'_'.esc_html($option['post_value']).'" type="radio" name="'.$f->get_post_var_name().'" value="'.esc_html($option['post_value']).'" '.$selected.'/>';
$h = sprintf( $f->html_form_radio_set, $h );
$html .= $h;
}
}
return $html;
}
}
/**
* Renderer for INPUT type 'file'
*
* @package Tina-MVC
* @subpackage Core
* @see field_text object
*/
class field_file {
/**
* Prevents a label from being generated for this element
* @see class field_text->render_label
*/
// var $render_label = FALSE;
/**
* Generates the HTML to display this element
*
* @param object $f the field object
* @return string
*/
function html($f) {
if( ! ini_get('file_uploads') ) {
error('File uploads are disabled on this webserver.');
}
$max_filesize = ini_get('upload_max_filesize');
$last = strtolower($max_filesize[strlen($max_filesize)-1]);
switch($last) {
case 'g':
$max_filesize *= 1024;
case 'm':
$max_filesize *= 1024;
case 'k':
$max_filesize *= 1024;
}
$h = '';
if( ! $f->form->get_file_input_added() ) {
$f->form->set_file_input_added( TRUE );
$h .= '<input type="hidden" name="MAX_FILE_SIZE" value="'.$max_filesize.'" '.$f->get_xhtml_slash().'>';
}
return $h . '<input type="file" id="'.$f->get_id().'" name="'.$f->get_post_var_name().'" '.$f->get_extra_attribs().' '.$f->get_xhtml_slash().'>';
}
}
/**
* All validate_* classes extend this class
*
* Add your own validation classes by calling them 'validate_myrule' and
* declaring them in the TINA_MVC namespace. See the 'validate_required' for
* an example.
*
* @package Tina-MVC
* @subpackage Core
* @see validate_required
*/
class validate {
/**
* The error message (if any).
*
* Your class should set this variable if your validation function fails
*
* @see validate_required class
*/
public $validation_message = '';
/**
* Checks for validation errors
*
* @return boolean
*/
function validation_errors() {
return ( ! empty( $this->validation_message ) );
}
}
/**
* Validate a field as 'required'
* @package Tina-MVC
* @subpackage Core
*/
class validate_required extends validate {
/**
* Validation code
*
* Sets $this->validation_message if validation fails
*
* @param object $field_obj
* @param mixed $params not used
* @return void
*/
function __construct( $field_obj, $params ) {
if( ! strval( $field_obj->get_value() ) ) {
$this->validation_message = '\'' . $field_obj->get_caption() . '\'' . ' is a required field';
}
}
}
/**
* Validate as empty string
* @package Tina-MVC
* @subpackage Core
*/
class validate_empty extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param string not used
* @return void
*/
function __construct( $field, $params ) {
if( strval( $field->get_value() ) != '' ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be left blank';
}
}
}
/**
* Validate as maximum string length
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_max_length extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param string $params maximum string length allowed
* @return void
*/
function __construct( $field, $params ) {
if( strlen( $field->get_value() ) > $params ) {
if( $params == 1 ) {
$characters = 'character';
}
else {
$characters = 'characters';
}
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be shorter than '.$params." $characters.";
}
}
}
/**
* Validate as minimum string length
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_min_length extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param string $params minimum length
* @return void
*/
function __construct( $field, $params ) {
if( strlen( $field->get_value() ) < $params ) {
if( $params == 1 ) {
$characters = 'character';
}
else {
$characters = 'characters';
}
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be at least '.$params." $characters.";
}
}
}
/**
* Validate as maximum value
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_max_value extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param mixed $params max value allowed
* @return void
*/
function __construct( $field, $params ) {
if( $field->get_value() > $params ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be less than '.$params;
}
}
}
/**
* Validate as minimum value
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_min_value extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param string $params min value allowed
* @return void
*/
function __construct( $field, $params ) {
if( $field->get_value() < $params ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be greater than '.$params;
}
}
}
/**
* Validate as email
* @package Tina-MVC
* @subpackage Core
*/
class validate_email extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param mixed $params not used
* @return void
*/
function __construct( $field, $params ) {
if( ! is_email( $field->get_value() ) ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' is not a valid email address';
}
}
}
/**
* Validate as SQL Datetime (YYY-MM-DD hh:mm:ss)
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_sql_datetime extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param mixed $params not used
* @return void
*/
function __construct( $field, $params ) {
if (preg_match("/^(\d{4})-(\d{2})-(\d{2}) ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/", $field->get_value(), $matches)) {
if (checkdate($matches[2], $matches[3], $matches[1])) {
return;
}
// allow mysql zero
if ( $val == '0000-00-00 00:00:00' ) {
return;
}
}
else {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be in "YYYY-MM-DD hh:mm:ss" datetime format';
}
}
}
/**
* Validate as SQL Date (YYY-MM-DD)
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_sql_date extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param mixed $params not used
* @return void
*/
function __construct( $field, $params ) {
if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $field->get_value(), $matches)) {
if (checkdate($matches[2], $matches[3], $matches[1])) {
return FALSE;
}
// allow mysql zero
if ( $val == '0000-00-00' ) {
return FALSE;
}
}
else {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be in "YYYY-MM-DD" date format';
}
}
}
/**
* Validate as SQL Date (hh:mm:ss)
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_sql_time extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param mixed $params not used
* @return void
*/
function __construct( $field, $params ) {
$err = '\'' . $field->get_caption() . '\'' . ' must be in "HH:mm:ss" time format';
if( strlen($field->get_value()) != 8 OR count(($t=explode(':',$field->get_value()))) != 3 ) {
$this->validation_message = $err;
}
else {
// allow mysql zero
if ( $field->get_value() == '00:00:00' ) {
return;
}
if( $t[0] >= 0 AND $t[0] <= 23 AND $t[1] >= 0 AND $t[1] <= 59 AND $t[2] >= 0 AND $t[2] <= 59 ) {
return;
}
}
$this->validation_message = $err;
}
}
/**
* Validate as equal to another field
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_equal_to_field extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param object $params the other field object
* @return void
*/
function __construct( $field, $params ) {
if( $field->get_value() != $params->get_value() ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be equal to \'' . $params->get_caption() . '\'';
}
}
}
/**
* Validate as less than another field
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_less_than_field extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param object $params the other field object
* @return void
*/
function __construct( $field, $params ) {
if( $field->get_value() >= $params->get_value() ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be less than \'' . $params->get_caption() . '\'';
}
}
}
/**
* Validate as greater than another field
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_greater_than_field extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param object $params the other field
* @return void
*/
function __construct( $field, $params ) {
if( $field->get_value() <= $params->get_value() ) {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' must be greater than \'' . $params->get_caption() . '\'';
}
}
}
/**
* Validate using a regular expression
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_regexp extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field
* @param array $params array of array( 'regexp' => 'validation message' )
* @return void
*/
function __construct( $field, $params ) {
foreach( $params AS $regexp => $message ) {
if( ! preg_match('/'.$regexp.'/', $field->get_value() ) ) {
// check for a custom validation message
if( ! empty( $message ) ) {
$this->validation_message = $message;
}
else {
$this->validation_message = '\'' . $field->get_caption() . '\'' . ' is not valid';
}
}
break; // only ever expect one element in the array
}
}
}
/**
* Validate a field as 'recaptcha'
*
* You should not use this validation rule directly - it is added automatically by the form class
*
* @package Tina-MVC
* @subpackage Core
*/
class validate_recaptcha extends validate {
/**
* Validation code
* @see $this->validate_required()
* @param object $field_obj
* @param mixed $params not used
* @return void
*/
function __construct( $field_obj, $params ) {
$resp = recaptcha_check_answer ( get_tina_mvc_setting('recaptcha_pri_key') ,
$_SERVER['REMOTE_ADDR'],
get_Post( 'recaptcha_challenge_field' ),
get_Post( 'recaptcha_response_field' ) );
if (!$resp->is_valid) {
$this->validation_message = 'The reCaptcha was incorrect. Please try again.';
}
}
}
/**
* The base object for forms and fields
*
* @package Tina-MVC
* @subpackage Docs
*/
class form_base {
/**
* The name for the form of the field. Userd to construct POST variable names and CSS id's
* @var string
*/
protected $name = '';
/**
* Validation rules
* @var array
*/
protected $validation_rules = array();
/**
* Whether or not to use XML short tags
* @var bool
*/
public $do_xml = TRUE;
/**
* HTML strings used for various elements generated by this helper. Use sprintf() format.
* @var string
*/
public $html_form = '<div class="tina_mvc_form">%s</div>',
$html_form_message = '<div class="tina_mvc_form_message">%s</div>',
$html_form_messages = '<div class="tina_mvc_form_messages">%s</div>',
$html_form_error = '<div class="tina_mvc_form_error_message">%s</div>',
$html_form_pair = '<div class="tina_mvc_form_pair">%s</div>',
$html_form_required_after_label = '',
$html_form_required_after_input = ' (required) ',
$html_form_label = '<span class="tina_mvc_form_label">%s </span>',
$html_form_input = '<span class="tina_mvc_form_input">%s</span>',
$html_form_radio_set = '<span class="tina_mvc_form_radio_set">%s</span>',
$html_form_field_error = '<span class="tina_mvc_error">%s </span>',
$html_form_field_message = '<div class="tina_mvc_form_field_message">%s</div>', // unused
$html_form_button = '<div class="tina_mvc_form_button">%s</div>';
/**
* Checks the name for the form or field element
*
* @param String $name The name
* @return object
*/
public function set_name( $name='' ) {
if( ! $name ) {
error("'$name' parameter is required.");
}
if( ! preg_match( "/^[a-zA-Z0-9_]*$/", $name ) ) {
error("\$name '$name' can only contain alphanumeric characters and underscores");
}
$this->name = $name;
return $this;
}
/**
* Gets the field or form name
*
* @return String
*/
public function get_name() {
return $this->name;
}
/**
* Returns a XML slash for self closing tags
*
* @return String
*/
public function get_xhtml_slash() {
return ( $this->do_xml ? ' /' : '' );
}
/**
* Sets any extra attributes you wish to pass to the form or field
*
* @param mixed $attribs An array of attribs or a string to be added directly into the element e.g. 'attrib="value"'
* @return object
*/
public function set_extra_attribs( $attribs=FALSE ) {
if( $attribs === FALSE ) error("\$attribs is required");
if( ! isset( $this->extra_attribs ) ) {
$this->extra_attribs = '';
}
else {
$this->extra_attribs .= ' ';
}
if( is_array($attribs) ) {
foreach( $attribs AS $attrib ) {
$this->extra_attribs .= $attrib . ' ';
}
$this->extra_attribs = rtrim( $this->extra_attribs );
}
else {
$this->extra_attribs .= $attribs;
}
return $this;
}
/**
* Getter
* @return string
*/
public function get_extra_attribs() {
return ( empty( $this->extra_attribs ) ? '' : $this->extra_attribs );
}
}
/**
* The field class
* @package Tina-MVC
* @subpackage Docs
*/
class field extends form_base {
/**
* The form object this field belongs to
* @var object form
*/
public
$form;
/**
* All variables should be accessed using setters and getters. Direct access is not supported.
* @var mixed
*/
protected
$name,
$type,
$caption,
$db_table,
$db_field,
$default_value,
$set_value,
$value,
$posted_value,
$extra_attribs,
$protected,
$validation_errors, // array of messages
$options, // for select or radio
$map_width, // only for use with GOOGLEMAP field
$map_height; // only for use with GOOGLEMAP field
/**
* Sets up the field
*
* @param object $form The form object the field is part of
* @param String $name A name (alphanumeric plus underscore only)
* @param String $type See the field_* classes for valid field types. You can also check the test_form_controller.php file in the samples folder
* @param String $caption Field caption used in a label (default caption is based on the name of the field)
* @param String $db_table Used to group fields
* @param String $db_field If the database is different from the field name for some reason you can set it here.
* @param String $default_value
* @param mixed $extra_attribs a string or array of strings
* @param boolean $protected Prevent load_data() from overwriting values in this field
* @return object field
*/
function __construct( $form, $name='', $type='', $caption='', $db_table='', $db_field='', $default_value='', $extra_attribs='', $protected=FALSE ) {
$this->set_form( $form );
$this->set_name( $name );
$this->set_type( $type );
$this->set_caption( $caption );
if( $db_table ) {
$this->set_db_table( $db_table );
}
if( $db_field ) {
$this->set_db_field( $db_field );
}
else {
$this->set_db_field( $this->name );
}
$this->set_default_value( $default_value );
$this->set_value( $default_value ); // current value
$this->set_extra_attribs( $extra_attribs );
$this->protected = $protected;
return $this;
}
/**
* Sets the form the field belongs to
* @param object $form_object
* @return object field
*/
private function set_form( $form_object ) {
$this->form = $form_object;
return $this;
}
/**
* Sets the field type
*
* @param string $t See field_* classes for valid field types
* @return object field
*/
public function set_type( $t ) {
if( ! class_exists( ($_c='\TINA_MVC\field_'.$t) ) ) error("\$type '$t' not implemented.");
// some fields might need code run on setup.. e.g. reCaptcha field needs a recaptcha
// validation rule added
if( method_exists( $_c, 'setup' ) ) {
$_c::setup( $this );
}
$this->type = $t;
return $this;
}
/**
* Getter for the type of field
* @return string
*/
public function get_type() {
return $this->type;
}
/**
* Set the caption for an input field
*
* @param string $caption
* @return object field
*/
public function set_caption( $caption='' ) {
if( ! $caption ) {
$this->caption = ucwords( str_replace( '_', ' ', $this->name ) );
}
else {
$this->caption = $caption;
}
return $this;
}
/**
* Sets the db_table value for a field
*
* Allows fields with the same db_table value to be retrieved together
*
* @param string $table_name
* @return object field
*/
public function set_db_table( $table_name='' ) {
$this->db_table = $table_name;
return $this;
}
/**
* Sets the database field name (if different from the field name)
*
* @param string $field_name
* @return object field
*/
public function set_db_field( $field_name='' ) {
if( ! $field_name ) {
$this->db_field = $this->name;
}
else {
$this->db_field = $field_name;
}
return $this;
}
/**
* Sets the default value for a field
*
* @param mixed $value
* @return object field
*/
public function set_default_value( $value=FALSE ) {
$this->default_value = $value;
return $this;
}
/**
* Sets a value for the field. Overrides the default value
*
* @param mixed $value
* @return object field
*/
public function set_set_value( $value=FALSE ) {
$this->set_value = $value;
return $this;
}
/**
* Sets a posted value for the field. Overrides the set value
* @param mixed $value
* @return object field
*/
public function set_posted_value( $value=FALSE ) {
$this->posted_value = $value;
return $this;
}
/**
* Sets a value for the field
*
* @param mixed $value
* @return object field
*/
public function set_value( $value=FALSE ) {
if( strtolower($this->type) == 'hidden' ) {
$this->set_default_value( $value );
}
$this->value = $value;
return $this;
}
/**
* Getter
*
* Gets the posted value, set value or default value (in that order)
*
* @return mixed
*/
public function get_value() {
if( $this->form->form_posted() ) {
$this->set_value( $this->posted_value );
return $this->posted_value;
}
elseif( isset( $this->set_value ) ) {
return $this->set_value;
}
else {
return $this->default_value;
}
}
/**
* Gets the $_POST value for the field
* @return string
*/
public function get_posted_value() {
return get_Post( $this->get_post_var_name() );
}
/**
* Gets the options for a radio or select field
*
* @return mixed
*/
public function get_options() {
return $this->options;
}
/**
* Getter
*
* @return string
*/
public function get_db_field() {
return $this->db_field;
}
/**
* Getter
*
* @return string
*/
public function get_db_table() {
return $this->db_table;
}
/**
* Gets the POST/GET variable name used in HTML
*
* @return string
*/
public function get_post_var_name() {
return $this->form->get_name() . '_' . $this->get_name();
}
/**
* Getter
*
* @return string
*/
public function get_id() {
return $this->get_post_var_name();
}
/**
* Getter
*
* @return string
*/
public function get_caption() {
return $this->caption;
}
/**
* Sets the map width for field type GOOGLEMAP
*
* @param string $w a valid css width
*/
public function set_map_width( $w ) {
$this->map_width = $w;
return $this;
}
/**
* Sets the map height for field type GOOGLEMAP
*
* @param string $h a valid css height
*/
public function set_map_height( $h ) {
$this->map_height = $h;
return $this;
}
/**
* Getter
*
* @return string
*/
public function get_map_width() {
return $this->map_width;
}
/**
* Getter
*
* @return string
*/
public function get_map_height() {
return $this->map_height;
}
/**
* Sets options for select and radio field types
*
* Options are array( array( 'post_value' , 'display_value' ) ). This allows feeding an array of
* array( 'id'=>'name' ) from a database query for example.
*
* @param array
* @return object field
*/
public function set_options( $options=array() ) {
if( ! $options OR ! is_array( $options ) ) {
error('$options must be of form array( array( "post_value", "display_value" ) )');
}
$this->options = array();
$options = array_values( $options );
foreach( $options AS $option ) {
// might be an object
if( is_object($option) ) {
// convert to array
$option = get_object_vars($option);
}
$this->options[] = array( 'post_value'=>array_shift($option), 'display_value'=>array_shift($option) );
}
return $this;
}
/**
* Checks if a fields value is protected and shouldn't be changed by a user
*
* @return boolean
*/
public function is_protected() {
return $this->protected;
}
/**
* Adds a validation error message to a field
*
* @param string $message
* @return object field
*/
public function add_validation_error( $message ) {
if( ! $message ) error( '$message parameter is required.' );
$this->validation_errors[] = $message;
$this->form->add_error_count();
return $this;
}
/**
* Adds validation rule(s) to a field
*
* @param mixed $rules A single rule ( array( 'rule_name' => 'rule_parameters'.... ). See validate_* classes for valid rules.
* @return object field
*/
public function add_validation( $rules=FALSE ) {
if( $rules === FALSE ) error( "'\$rules' is required" );
if( is_array( $rules ) ) {
foreach( $rules AS $rule => $params ) {
$this->check_validation_rule( $rule );
$this->validation_rules[$rule] = $params;
}
}
else error( "'\$rules' must be an array" );
return $this;
}
/**
* Checks a validation rule exists
*
* @param string
* @return mixed
*/
private function check_validation_rule($v) {
if( ! class_exists( 'TINA_MVC\\validate_'.$v ) ) {
error( "Validation rule '$v' is not implemented" );
}
return TRUE;
}
/**
* Gets the number of validation errors on a field
*
* @return integer
*/
public function get_error_count() {
return count( $this->validation_errors );
}
/**
* Runs validation rules on a field
*
* @return boolean
*/
public function validate() {
$this->validation_errors = array();
foreach( $this->validation_rules AS $rule => $params ) {
$fn = '\TINA_MVC\validate_'.$rule;
$validator = new $fn( $this, $params );
if( $validator->validation_errors() ) {
$this->add_validation_error( $validator->validation_message );
}
}
return ( $this->validation_errors == array() ); // false if errors
}
/**
* Renders a label for a field
*
* @return string escaped HTML
*/
public function render_label() {
$h = '';
$h .= "<label for=\"".$this->get_id()."\">".esc_html($this->get_caption())."</label>";
$h = sprintf( $this->html_form_label, $h );
if( array_key_exists( 'REQUIRED', $this->validation_rules ) ) {
$h .= $this->html_form_required_after_label;
}
return $h;
}
/**
* Renders the field
*
* @return string escaped HTML
*/
public function render() {
$fld_class = 'TINA_MVC\field_'.$this->type;
$f = new $fld_class($this);
$h = '';
if( ! empty( $f->no_html_wrappers ) ) {
return $f->html( $this );
}
if( isset($f->plain_html) AND $f->plain_html ) {
// render as a plain piece of html within the $this->html_form_pair block
$h .= $f->html($this);
}
else {
// we are rendering a normal form pair
if( ! isset( $f->render_label ) OR $f->render_label ) {
$h .= $this->render_label();
}
$h .= sprintf( $this->html_form_input, $f->html($this) );
if( array_key_exists( 'REQUIRED', $this->validation_rules ) ) {
$h .= $this->html_form_required_after_input;
}
// validation messages?
if( $this->validation_errors ) {
foreach( $this->validation_errors AS $e ) {
$h .= sprintf( $this->html_form_field_error, $e );
}
}
}
return sprintf( $this->html_form_pair, $h );
}
}
/**
* The form class
* @package Tina-MVC
* @subpackage Docs
*/
class form extends form_base {
/**
* Use setters and getters to access. Tina MVC does not support direct access or class variables.
* @var mixed
*/
protected
$fields,
$protected_fields,
$action,
$method,
$wp_nonce_field_name,
$form_posted,
$validation_errors,
$messages,
$error_messages,
$fieldset_open,
$file_input_added;
/**
* Sets up the form
*
* @param String $name
* @param String $action
* @param Object $extra_attribs
*/
function __construct( $name='', $action='', $extra_attribs=array() ) {
$this->set_name($name);
$this->action = ( $action ? $action : $_SERVER['REQUEST_URI'] );
$this->method = 'POST';
$this->set_file_input_added( FALSE ); // default
$this->do_xml = TRUE; // default
// used to check if the form has been posted - will be set in $this->add_base_fields()
$this->wp_nonce_field_name = '';
$this->set_extra_attribs( $extra_attribs );
$this->form_posted = FALSE;
$this->fieldset_open = FALSE; // name of any open fieldset
$fields = new \StdClass;
$this->add_base_fields();
if( ! headers_sent() ) {
wp_enqueue_style( 'tina_mvc_form_helper_css' , get_tina_mvc_folder_url().'/tina_mvc/helpers/tina_mvc_form_helper_css.css' );
}
}
/**
* Adds a field to the form
*
* @param String $name
* @param String $type
* @param String $caption
* @param String $db_table
* @param String $db_field
* @param String $default_val
* @param String $extra_attribs
*
* @return object field
*/
public function add_field( $name='', $type='text', $caption='', $db_table='', $db_field='', $default_val='', $extra_attribs='' ) {
if( ! $name OR ! $type ) {
error('$name parameter is required for $form->add_field().');
}
if( isset( $this->fields->$name ) ) {
error('Field name is already in use.');
}
$this->fields->$name = new field( $this, $name, $type, $caption, $db_table, $db_field, $default_val, $extra_attribs);
return $this->fields->$name;
}
/**
* Adds a block of text to the form
*
* @param String $name
* @param String $text
* @param Mixed $extra_attribs array of name => value pairs or a string
*
* @return object field
*/
public function add_text( $name='', $text='', $extra_attribs='' ) {
if( ! $name OR ! $text ) {
error('$name and $text parameters are required for $form->add_text().');
}
if( isset( $this->fields->$name ) ) {
error('Field name is already in use.');
}
$this->fields->$name = new field( $this, $name, 'textblock', NULL, NULL, NULL, $text, $extra_attribs);
return $this->fields->$name;
}
/**
* Opens a fieldset
*
* @param String $name
* @param String $legend the caption to display
* @param Mixed $extra_attribs array of name => value pairs or a string
*
* @return object field
*/
public function add_fieldset( $name='', $legend='', $extra_attribs='' ) {
if( isset( $this->fields->$name ) ) {
error('Field name is already in use.');
}
if( $this->fieldset_open ) {
$this->fieldset_close();
}
$this->fields->$name = new field( $this, $name, 'fieldset_open', NULL, 'tina_mvc_fields', NULL, $legend, $extra_attribs, $protected=TRUE);
$this->fieldset_open = $this->fields->$name->get_name();
return $this->fields->$name;
}
/**
* Alias to add_fieldset()
*
* @param String $name
* @param String $legend the caption to display
* @param Mixed $extra_attribs array of name => value pairs or a string
*
* @return object field
*/
public function fieldset_open( $name='', $legend='', $extra_attribs='' ) {
return $this->add_fieldset( $name, $legend, $extra_attribs );
}
/**
* Closes an open fieldset
*
* @param mixed $extra_attribs array of name => value pairs or a string
*
* @return object field
* @todo Mmmm, is $extra_attribs valid for a close fieldset?
*/
public function fieldset_close( $extra_attribs='' ) {
if( empty( $this->fieldset_open ) ) {
error('There is no open fieldset element.');
}
$fname = $this->fieldset_open . '_close';
$this->fields->$fname = new field( $this, $fname, 'fieldset_close', NULL, 'tina_mvc_fields', NULL, '', $extra_attribs, TRUE);
return $this->fields->$fname;
}
/**
* Adds the system field
*
* It is a WP_NONCE value and is used to check if the form was posted within the validity of the WP_NONCE.
*/
private function add_base_fields() {
$this->wp_nonce_field_name = 'tina_mvc_wp_nonce';
$this->fields->{$this->wp_nonce_field_name} = new field( $this, $this->wp_nonce_field_name, 'hidden', 'hidden needs no caption', 'tina_mvc_fields', 'wp_nonce', wp_create_nonce( $this->get_name() ), NULL, $protected=TRUE );
}
/**
* Loads an array or object of data into the form
*
* Usually a recordset from a database in the form array( array( 'field' => 'value' ) )
*
* @param mixed $data array or object
*/
public function load_data( $data=FALSE ) {
if( $data === FALSE ) {
error( '$data parameter is required' );
}
// loop through fields
foreach( $this->fields AS $field_obj ) {
foreach( $data AS $key => $value ) {
if( ! $field_obj->is_protected() ) {
// if db field name or name match, we set value
if( $field_obj->get_db_field() == $key ) {
$field_obj->set_set_value( $value );
}
elseif( $field_obj->get_name() == $key ) {
$field_obj->set_set_value( $value );
}
}
}
}
}
/**
* Loads any data from $_POST
*/
private function load_posted_data() {
foreach( $this->fields AS $f ) {
if( $val = get_Post( $f->get_post_var_name() ) ) {
$f->set_posted_value( $val );
$f->set_value( $val );
}
}
}
/**
* Gets an array of submitted data.
*
* The field names are based on the db_field values in the field
*
* @param string $db_table the db_table value for the fields you want returned
* @return object Description
*/
public function get_posted_db_data( $db_table='' ) {
return $this->get_posted_data( $db_table, TRUE );
}
/**
* Flags that a file input has been added to the form
* @param boolean $b
* @return object file
*/
public function set_file_input_added( $b=FALSE ) {
$this->file_input_added = (boolean) $b;
return $this;
}
/**
* Checks a file input has been added to the form
* @return boolean
*/
public function get_file_input_added() {
return $this->file_input_added;
}
/**
* Checks if the form has been posted by verifying the wp_nonce field
*
* @return boolean
*/
public function form_posted() {
$v = \wp_verify_nonce( get_post( $this->fields->{$this->wp_nonce_field_name}->get_post_var_name() ), $this->get_name() );
return ( $v );
}
/**
* Gets an array of data if the form was posted. Returns boolean FALSE otherwise
*
* Fields can be grouped by assigning a value to the db_table field property. The data returned is an array of
* field_name => field_value. If you want to get an array with the keys based on the db_field property, set
* $get_db_fields TRUE.
*
* @param string $db_table
* @param boolean $get_db_fields
*
* @return array
* @see $this->get_posted_db_value()
*/
public function get_posted_data( $db_table='', $get_db_fields=FALSE ) {
$data = FALSE;
if( $this->form_posted() ) {
$this->load_posted_data();
if( $this->check_validation() ) {
foreach( $this->fields AS $field ) {
if( $field->get_db_table() == $db_table ) {
if( $get_db_fields ) {
if( $field->get_type() == 'file' ) {
$data[ $field->get_db_field() ] = $_FILES[ $field->get_id() ];
}
else {
$data[ $field->get_db_field() ] = $field->get_value();
}
}
else {
if( $field->get_type() == 'file' ) {
$data[ $field->get_name() ] = $_FILES[ $field->get_id() ];
}
else {
$data[ $field->get_name() ] = $field->get_value();
}
}
}
}
}
}
return $data;
}
/**
* Processes validation rules attached to each field
*
* @return boolean TRUE if the rules passed
*/
private function check_validation() {
$this->validation_errors = 0;
foreach( $this->fields AS $field ) {
if( ! $field->validate() ) {
$this->validation_errors += $field->get_error_count();
}
}
if( $this->validation_errors ) {
return FALSE;
}
return TRUE;
}
/**
* Renders the opening form tag and any messages
*
* @return string
*/
private function render_open_form() {
$h = '';
$h .= '<form id="'.$this->get_name().'" method="'.$this->method.'" action="'.$this->action.'" enctype="multipart/form-data" '. $this->get_extra_attribs() .'>'."\r\n";
if( $this->messages ) {
$messages = '';
foreach( $this->messages AS $m ) {
$messages .= sprintf( $this->html_form_message, esc_html($m) );
}
$h .= sprintf( $this->html_form_messages, $messages );
}
return $h;
}
/**
* Renders the closing form tag
*
* @return string
*/
private function render_close_form() {
return '</form>';
}
/**
* Renders the forms HTML
*
* @return String the HTML
*/
public function render() {
$html = '';
$html .= $this->render_open_form();
if( $this->error_messages ) {
foreach( $this->error_messages AS $e ) {
$html .= sprintf( $this->html_form_error, $e );
}
}
foreach( $this->fields AS $f ) {
$html .= $f->render();
}
// have we an open fieldset?
if( ! empty( $this->fieldset_open ) ) {
$fset_close = $this->fieldset_close();
$html .= $fset_close->render();
}
$html .= $this->render_close_form();
$html = sprintf( $this->html_form, $html );
return $html;
}
/**
* Adds an error message or messages to the form.
*
* @param $m string the error message
*/
public function add_error( $m = FALSE ) {
if( $m ) {
$this->error_messages[] = $m;
$this->add_error_count();
}
}
/**
* Increments the error counter
*/
public function add_error_count() {
$this->validation_errors++;
}
/**
* Checks for errors
*/
public function get_error_count() {
return $this->validation_errors;
}
/**
* Checks for errors
*/
public function get_errors() {
return $this->validation_errors;
}
}
}