<?php
namespace row\form;
use row\core\Options;
use row\utils\Inflector;
use row\Output;
abstract class SimpleForm {
public $_elements = array(); // internal cache
abstract protected function elements( $defaults = null, $options = array() );
public $input = array();
public $errors = array();
public $output = array();
public $inlineErrors = true;
public $elementWrapperTag = 'div';
public function validate( $data ) {
$this->input = $data;
$elements =& $this->useElements();
$validators = $output = array();
$this->_fire('pre_validate');
function murlencode($name, $k, $arr) {
$out = array();
foreach ( $arr AS $k2 => $v ) {
$out[] = $name . '['.$k.']['.$k2.']=' . urlencode((string)$v);
}
return implode('&', $out);
}
foreach ( $elements AS $name => &$element ) {
$element['_name'] = $name;
$this->elementTitle($element);
if ( is_string($name) ) {
// form element
if ( !empty($element['required']) ) {
if ( !$this->validateRequired($name, $element) ) {
$this->errors[$name][] = $this->errorMessage('required', $element);
}
}
if ( !empty($element['regex']) && empty($this->errors[$name]) ) {
$match = preg_match('/^'.$element['regex'].'$/', (string)$this->input($name, ''));
if ( !$match ) {
$this->errors[$name][] = $this->errorMessage('regex', $element);
}
}
if ( !empty($element['validation']) && empty($this->errors[$name]) && !empty($element['required']) ) {
$fn = $element['validation'];
if ( is_string($fn) ) {
$validationFunction = 'validate'.ucfirst($fn);
$fn = array($this, $validationFunction);
}
if ( is_callable($fn) ) {
$r = call_user_func($fn, $this, $name);
if ( false === $r || is_string($r) ) {
$this->errors[$name][] = false === $r ? $this->errorMessage('custom', $element) : $r;
}
}
}
if ( isset($this->input[$name]) && !array_key_exists($name, $this->output) ) {
$input = $this->input[$name];
$this->output($name, $input);
$elName = $this->elementName($element);
foreach ( (array)$input AS $k => $v ) {
$output[] = is_array($v) ? murlencode($elName, $k, $v) : $elName . '=' . urlencode((string)$v);
}
}
}
else if ( isset($element['validation']) ) {
// validator
$validators[] = $element;
}
unset($element);
}
//print_r($output);
$output = implode('&', $output);
//var_dump($output);
parse_str($output, $this->output);
if ( 0 == count($this->errors) ) {
foreach ( $validators AS $validator ) {
if ( empty($validator['require']) || 0 == count(array_intersect((array)$validator['require'], array_keys($this->errors))) ) {
//echo "\n do custom validator on [".implode(', ', (array)$validator['fields'])."] ...\n";
$v = $validator['validation'];
$r = $v($this);
if ( false === $r || is_string($r) ) {
$error = false === $r ? $this->errorMessage('custom', $validator) : $r;
foreach ( (array)$validator['fields'] AS $name ) {
$this->errors[$name][] = $error;
}
}
}
}
}
if ( 0 == count($this->errors) ) {
$this->_fire('post_validate');
return true;
}
return false;
}
public function validateOptions( $form, $name ) {
$element =& $this->useElements();
$value = $this->input($name, '');
$options = $element['options'];
foreach ( $options AS $k => $v ) {
if ( $this->getOptionValue($k, $v) == $value ) {
return true;
}
}
return false;
}
public function validateEmail( $form, $name ) {
$value = $form->input($name);
return false !== filter_var($value, FILTER_VALIDATE_EMAIL);
}
public function validateDate( $form, $name ) {
$value = $form->input($name);
if ( 0 >= preg_match('/^(\d\d\d\d)-(\d\d?)-(\d\d?)$/', $value, $match) ) {
return false;
}
$date = $match[1] . '-' . str_pad((string)(int)$match[2], 2, '0', STR_PAD_LEFT) . '-' . str_pad((string)(int)$match[3], 2, '0', STR_PAD_LEFT);
$this->output($name, $date);
}
public function validateRequired( $name, $element ) {
if ( !isset($this->input[$name]) ) {
return false;
}
$length = is_array($this->input[$name]) ? count($this->input[$name]) : strlen(trim((string)$this->input[$name]));
$minlength = isset($element['minlength']) ? (int)$element['minlength'] : 1;
return $length >= $minlength;
}
public function errorMessage( $type, $element ) {
$this->elementTitle($element);
$title = $element['title'];
switch ( $type ) {
case 'required':
return 'Field "'.$title.'" is required';
break;
case 'regex':
return 'Field "'.$title.'" has invalid format';
break;
case 'custom':
$fields = !isset($element['fields']) ? array($element['_name']) : (array)$element['fields'];
foreach ( $fields AS &$name ) {
$name = $this->_elements[$name]['title'];
unset($name);
}
return isset($element['message']) ? $element['message'] : 'Validation failed for: "'.implode('", "', $fields).'"';
break;
}
return 'Unknown type of error for field "'.$title.'".';
}
public function errors() {
$errors = array();
foreach ( $this->errors AS $errs ) {
$errors = array_merge($errors, array_filter($errs));
}
return array_unique($errors);
}
public function error( $elementName, $error = ' error', $noError = ' no-error' ) {
return isset($this->errors[$elementName]) ? $error : $noError;
}
public function input( $name, $alt = '' ) {
return isset($this->input[$name]) ? $this->input[$name] : $alt;
}
public function output( $name, $value = null ) {
if ( 1 < func_num_args() ) {
return $this->output[$name] = $value;
}
return isset($this->output[$name]) ? $this->output[$name] : '';
}
public function splitOutput( $lists ) {
$output = array();
foreach ( $lists AS $listName => $fields ) {
$output[$listName] = array();
is_array($fields) or $fields = explode(',', $fields);
foreach ( $fields AS $fieldName ) {
if ( array_key_exists($fieldName, $this->output) ) {
$output[$listName][$fieldName] = $this->output[$fieldName];
}
}
}
return $output;
}
public function renderGridElement( $name, $element ) {
$html = "\n".'<table class="grid">'."\n";
$html .= ' <tr>'."\n";
$html .= ' <th class="corner"></th>'."\n";
foreach ( $element['horizontal'][1] AS $k => $hLabel ) {
$html .= ' '.$this->renderGridHorizontalTH($element, $hLabel)."\n";
}
$html .= ' </tr>'."\n";
foreach ( $element['vertical'][1] AS $vKey => $vLabel ) {
$vKey = $this->getOptionValue($vKey, $vLabel);
$html .= ' <tr>'."\n";
$html .= ' '.$this->renderGridVerticalTH($element, $vLabel)."\n";
foreach ( $element['horizontal'][1] AS $hKey => $hLabel ) {
$hKey = $this->getOptionValue($hKey, $hLabel);
$sub = '?';
$fn = 'renderGrid'.$element['subtype'];
if ( is_callable($method = array($this, $fn)) ) {
list($xValue, $yValue) = empty($element['reverse']) ? array($vKey, $hKey) : array($hKey, $vKey);
// $sub = $this->$fn($name.'__'.$xValue.'[]', $yValue);
$sub = $this->$fn($element, $xValue, $yValue);
}
$html .= ' <td>'.$sub.'</td>'."\n";
}
$html .= ' </tr>'."\n";
}
$html .= '</table>'."\n";
return $this->renderElementWrapperWithTitle($html, $element);
}
protected function renderGridHorizontalTH( $element, $label ) {
$html = '<th class="horizontal">'.$label.'</th>';
return $html;
}
protected function renderGridVerticalTH( $element, $label ) {
$html = '<th class="vertical">'.$label.'</th>';
return $html;
}
protected function renderGridOptions( $element, $xValue, $yValue ) {
$name = $element['_name'];
$options = $element['options'];
$dummy = isset($element['dummy']) ? $element['dummy'] : '';
$value = isset($this->input[$name][$xValue][$yValue]) ? (array)$this->input[$name][$xValue][$yValue] : array();
$elName = $name."[$xValue][$yValue]";
$html = $this->renderSelect($elName, $options, $value, $dummy);
return $html;
}
protected function renderGridCheckbox( $element, $xValue, $yValue ) {
$name = $element['_name'];
$value = isset($this->input[$name][$xValue]) ? (array)$this->input[$name][$xValue] : array();
$checked = in_array($yValue, $value) ? ' checked' : '';
$html = '<input type="checkbox" name="'.$name.'['.$xValue.'][]" value="'.$yValue.'"'.$checked.' />';
return $html;
}
/* public function renderGridCheckboxElement( $elementName, $elementValue ) {
$formValue = $this->input(trim($elementName, ']['), array());
$checked = in_array($elementValue, $formValue) ? ' checked' : '';
$html = '<input type="checkbox" name="'.$elementName.'" value="'.$elementValue.'"'.$checked.' />';
return $html;
}*/
public function renderRadioElement( $name, $element ) {
$type = $element['type'];
$elName = $name;
$checked = $this->input($name, null);
$options = array();
foreach ( (array)$element['options'] AS $k => $v ) {
$k = $this->getOptionValue($k, $v);
$options[] = '<span class="option"><label><input type="radio" name="'.$elName.'" value="'.$k.'"'.( $checked === $k ? ' checked' : '' ).' /> '.$v.'</label></span>';
}
$html = implode(' ', $options);
return $this->renderElementWrapperWithTitle($html, $element);
}
public function renderCheckboxElement( $name, $element ) {
$elName = $name;
$checked = null !== $this->input($name, null) ? ' checked' : '';
$value = isset($element['value']) ? ' value="'.Output::html($element['value']).'"' : '';
$input = '<label><input type="checkbox" name="'.$elName.'"'.$value.$checked.' /> '.$element['title'].'</label>';
$html = '<span class="input">'.$input.'</span>';
if ( !empty($element['description']) ) {
$html .= ' ';
$html .= '<span class="description">'.$element['description'].'</span>';
}
return $this->renderElementWrapper($html, $element);
}
public function renderCheckboxesElement( $name, $element ) {
$elName = $name.'[]';
$checked = (array)$this->input($name, array());
$options = array();
foreach ( $element['options'] AS $k => $v ) {
$k = $this->getOptionValue($k, $v);
$options[] = '<span class="option"><label><input type="checkbox" name="'.$elName.'" value="'.$k.'"'.( in_array($k, $checked) ? ' checked' : '' ).' /> '.$v.'</label></span>';
}
$html = implode(' ', $options);
return $this->renderElementWrapperWithTitle($html, $element);
}
public function renderDropdownElement( $name, $element, $wrapper = true ) {
return $this->renderOptionsElement($name, $element, $wrapper);
}
public function renderOptionsElement( $name, $element, $wrapper = true ) {
$value = $this->input($name, 0);
$elName = $name;
$dummy = isset($element['dummy']) ? $element['dummy'] : '';
$html = $this->renderSelect($elName, $element['options'], $value, $dummy);
return $this->renderElementWrapperWithTitle($html, $element);
}
protected function renderSelect( $elName, $options, $value = '', $dummy = '' ) {
$html = '<select name="'.$elName.'">';
if ( $dummy ) {
$html .= '<option value="'.$this->getDummyOptionValue().'">'.$dummy.'</option>';
}
foreach ( (array)$options AS $k => $v ) {
$k = $this->getOptionValue($k, $v);
$html .= '<option value="'.$k.'"'.( (string)$k === $value ? ' selected' : '' ).'>'.$v.'</option>';
}
$html .= '</select>';
return $html;
}
protected function getDummyOptionValue( $element = null ) {
return '';
}
protected function getOptionValue( $k, $v ) {
if ( is_a($v, '\row\database\Model') ) {
$k = implode(',', $v->_pkValue());
}
return $k;
}
public function renderTextElement( $name, $element ) {
$type = $element['type'];
$elName = $name;
$value = $this->input($name);
$html = '<input type="'.$type.'" name="'.$elName.'" value="'.$value.'" />';
return $this->renderElementWrapperWithTitle($html, $element);
}
public function renderTextareaElement( $name, $element ) {
$value = $this->input($name);
$elName = $name;
$options = Options::make($element);
$rows = $options->rows ? ' rows="'.$options->rows.'"' : '';
$cols = $options->cols ? ' cols="'.$options->cols.'"' : '';
$html = '<textarea'.$rows.$cols.' name="'.$elName.'">'.$value.'</textarea>';
return $this->renderElementWrapperWithTitle($html, $element);
}
public function renderMarkupElement( $name, $element ) {
$options = Options::make(Options::make($element));
$inside = $options->inside ?: $options->text;
if ( $inside ) {
return '<'.$this->elementWrapperTag.' class="form-element markup '.$name.'">'.$inside.'</'.$this->elementWrapperTag.'>';
}
else if ( $options->outside ) {
return $options->outside;
}
return '';
}
// This method is now absolutely useless as without it the exact same thing would happen
public function renderPasswordElement( $name, $element ) {
$element['type'] = 'password';
return $this->renderTextElement($name, $element);
}
public function &useElements() {
if ( !$this->_elements ) {
$this->_elements = $this->elements();
}
return $this->_elements;
}
public function render( $withForm = true, $options = array() ) {
$elements =& $this->useElements();
// Render 1 element?
if ( is_string($withForm) && isset($this->_elements[$withForm]) ) {
// First argument is element name, so render only that element
return $this->renderElement($withForm, $this->_elements[$withForm]);
}
$index = 0;
$html = '';
foreach ( $elements AS $name => $element ) {
if ( is_string($name) || ( isset($element['type']) && in_array($element['type'], array('markup')) ) ) {
$element['_name'] = $name;
$element['_index'] = $index++;
$html .= $this->renderElement($name, $element);
$html .= $this->elementSeparator();
}
}
if ( $withForm ) {
$options = Options::make($options);
$method = $options->get('action', 'post');
$action = Output::url($this->application->_uri);
$html =
'<form method="'.$method.'" action="'.$action.'">' .
$this->elementSeparator() .
$html.$this->renderButtons() .
$this->elementSeparator() .
'</form>';
}
return $html;
}
public function renderElement( $name, $element ) {
$this->elementTitle($element);
if ( empty($element['type']) ) {
return '';
}
$type = $element['type'];
$renderFunction = 'render'.ucfirst($type).'Element';
$renderMethod = array($this, $renderFunction);
if ( !empty($element['render']) ) {
$renderMethod = $element['render'];
}
if ( is_callable($renderMethod) ) {
return call_user_func($renderMethod, $name, $element);
}
return $this->renderTextElement($name, $element);
}
public function elementName( $element ) {
$name = $element['_name'];
if ( isset($element['name']) ) {
$name = $element['name'];
}
else if ( isset($element['type']) && in_array($element['type'], array('checkboxes')) ) {
$name .= '[]';
}
return $name;
}
public function renderButtons() {
return '<'.$this->elementWrapperTag.' class="form-submit"><input type=submit></'.$this->elementWrapperTag.'>';
}
public function renderElementWrapper( $html, $element ) {
$name = $element['_name'];
$description = empty($element['description']) ? '' : '<span class="description">'.$element['description'].'</span>';
return '<'.$this->elementWrapperTag.' class="form-element '.$element['type'].' '.$name.$this->error($name).'">'.$html.'</'.$this->elementWrapperTag.'>';
}
public function renderElementWrapperWithTitle( $input, $element ) {
$name = $element['_name'];
$description = empty($element['description']) ? '' : '<span class="description">'.$element['description'].'</span>';
$error = $this->inlineErrors && isset($this->errors[$name]) ? '<span class="error">'.$this->errors[$name][0].'</span>' : '';
$html = '<label>'.$element['title'].'</label><span class="input">'.$input.'</span>'.$error.$description;
return $this->renderElementWrapper($html, $element);
}
public function elementSeparator() {
return "\n\n";
}
public function elementTitle( &$element ) {
if ( empty($element['title']) ) {
$element['title'] = $this->nameToTitle($element['_name']);
}
}
public function nameToTitle( $name ) {
return Inflector::spacify($name); // 'beautify'
}
public function __tostring() {
return $this->render();
}
}
|