drupal-civicrm/sites/all/modules/civicrm/packages/HTML/QuickForm.php

2103 lines
81 KiB
PHP
Raw Normal View History

2018-01-14 15:10:16 +02:00
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Create, validate and process HTML forms
*
* PHP versions 4 and 5
*
* LICENSE: This source file is subject to version 3.01 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_01.txt If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
*
* @category HTML
* @package HTML_QuickForm
* @author Adam Daniel <adaniel1@eesus.jnj.com>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Alexey Borzov <avb@php.net>
* @copyright 2001-2009 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: QuickForm.php,v 1.166 2009/04/04 21:34:02 avb Exp $
* @link http://pear.php.net/package/HTML_QuickForm
*/
/**
* PEAR and PEAR_Error classes, for error handling
*/
require_once 'PEAR.php';
/**
* Base class for all HTML classes
*/
require_once 'HTML/Common.php';
/**
* Element types known to HTML_QuickForm
* @see HTML_QuickForm::registerElementType(), HTML_QuickForm::getRegisteredTypes(),
* HTML_QuickForm::isTypeRegistered()
* @global array $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']
*/
$GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] =
array(
'group' =>array('HTML/QuickForm/group.php','HTML_QuickForm_group'),
'hidden' =>array('HTML/QuickForm/hidden.php','HTML_QuickForm_hidden'),
'reset' =>array('HTML/QuickForm/reset.php','HTML_QuickForm_reset'),
'checkbox' =>array('HTML/QuickForm/checkbox.php','HTML_QuickForm_checkbox'),
'file' =>array('HTML/QuickForm/file.php','HTML_QuickForm_file'),
'image' =>array('HTML/QuickForm/image.php','HTML_QuickForm_image'),
'password' =>array('HTML/QuickForm/password.php','HTML_QuickForm_password'),
'radio' =>array('HTML/QuickForm/radio.php','HTML_QuickForm_radio'),
'button' =>array('HTML/QuickForm/button.php','HTML_QuickForm_button'),
'submit' =>array('HTML/QuickForm/submit.php','HTML_QuickForm_submit'),
'select' =>array('HTML/QuickForm/select.php','HTML_QuickForm_select'),
'hiddenselect' =>array('HTML/QuickForm/hiddenselect.php','HTML_QuickForm_hiddenselect'),
'text' =>array('HTML/QuickForm/text.php','HTML_QuickForm_text'),
'textarea' =>array('HTML/QuickForm/textarea.php','HTML_QuickForm_textarea'),
'link' =>array('HTML/QuickForm/link.php','HTML_QuickForm_link'),
'advcheckbox' =>array('HTML/QuickForm/advcheckbox.php','HTML_QuickForm_advcheckbox'),
'date' =>array('HTML/QuickForm/date.php','HTML_QuickForm_date'),
'static' =>array('HTML/QuickForm/static.php','HTML_QuickForm_static'),
'header' =>array('HTML/QuickForm/header.php', 'HTML_QuickForm_header'),
'html' =>array('HTML/QuickForm/html.php', 'HTML_QuickForm_html'),
'hierselect' =>array('HTML/QuickForm/hierselect.php', 'HTML_QuickForm_hierselect'),
'autocomplete' =>array('HTML/QuickForm/autocomplete.php', 'HTML_QuickForm_autocomplete'),
'xbutton' =>array('HTML/QuickForm/xbutton.php','HTML_QuickForm_xbutton'),
'advmultiselect'=>array('HTML/QuickForm/advmultiselect.php','HTML_QuickForm_advmultiselect'),
);
/**
* Validation rules known to HTML_QuickForm
* @see HTML_QuickForm::registerRule(), HTML_QuickForm::getRegisteredRules(),
* HTML_QuickForm::isRuleRegistered()
* @global array $GLOBALS['_HTML_QuickForm_registered_rules']
*/
$GLOBALS['_HTML_QuickForm_registered_rules'] = array(
'required' => array('html_quickform_rule_required', 'HTML/QuickForm/Rule/Required.php'),
'maxlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'),
'minlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'),
'rangelength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'),
'email' => array('html_quickform_rule_email', 'HTML/QuickForm/Rule/Email.php'),
'regex' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'lettersonly' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'alphanumeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'numeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'nopunctuation' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'nonzero' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'),
'callback' => array('html_quickform_rule_callback', 'HTML/QuickForm/Rule/Callback.php'),
'compare' => array('html_quickform_rule_compare', 'HTML/QuickForm/Rule/Compare.php')
);
// {{{ error codes
/**#@+
* Error codes for HTML_QuickForm
*
* Codes are mapped to textual messages by errorMessage() method, if you add a
* new code be sure to add a new message for it to errorMessage()
*
* @see HTML_QuickForm::errorMessage()
*/
define('QUICKFORM_OK', 1);
define('QUICKFORM_ERROR', -1);
define('QUICKFORM_INVALID_RULE', -2);
define('QUICKFORM_NONEXIST_ELEMENT', -3);
define('QUICKFORM_INVALID_FILTER', -4);
define('QUICKFORM_UNREGISTERED_ELEMENT', -5);
define('QUICKFORM_INVALID_ELEMENT_NAME', -6);
define('QUICKFORM_INVALID_PROCESS', -7);
define('QUICKFORM_DEPRECATED', -8);
define('QUICKFORM_INVALID_DATASOURCE', -9);
/**#@-*/
// }}}
/**
* Create, validate and process HTML forms
*
* @category HTML
* @package HTML_QuickForm
* @author Adam Daniel <adaniel1@eesus.jnj.com>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Alexey Borzov <avb@php.net>
* @version Release: 3.2.11
*/
class HTML_QuickForm extends HTML_Common
{
// {{{ properties
/**
* Array containing the form fields
* @since 1.0
* @var array
* @access private
*/
var $_elements = array();
/**
* Array containing element name to index map
* @since 1.1
* @var array
* @access private
*/
var $_elementIndex = array();
/**
* Array containing indexes of duplicate elements
* @since 2.10
* @var array
* @access private
*/
var $_duplicateIndex = array();
/**
* Array containing required field IDs
* @since 1.0
* @var array
* @access private
*/
var $_required = array();
/**
* Prefix message in javascript alert if error
* @since 1.0
* @var string
* @access public
*/
var $_jsPrefix = 'Invalid information entered.';
/**
* Postfix message in javascript alert if error
* @since 1.0
* @var string
* @access public
*/
var $_jsPostfix = 'Please correct these fields.';
/**
* Datasource object implementing the informal
* datasource protocol
* @since 3.3
* @var object
* @access private
*/
var $_datasource;
/**
* Array of default form values
* @since 2.0
* @var array
* @access private
*/
var $_defaultValues = array();
/**
* Array of constant form values
* @since 2.0
* @var array
* @access private
*/
var $_constantValues = array();
/**
* Array of submitted form values
* @since 1.0
* @var array
* @access private
*/
var $_submitValues = array();
/**
* Array of submitted form files
* @since 1.0
* @var integer
* @access public
*/
var $_submitFiles = array();
/**
* Value for maxfilesize hidden element if form contains file input
* @since 1.0
* @var integer
* @access public
*/
var $_maxFileSize = 1048576; // 1 Mb = 1048576
/**
* Flag to know if all fields are frozen
* @since 1.0
* @var boolean
* @access private
*/
var $_freezeAll = false;
/**
* Array containing the form rules
* @since 1.0
* @var array
* @access private
*/
var $_rules = array();
/**
* Form rules, global variety
* @var array
* @access private
*/
var $_formRules = array();
/**
* Array containing the validation errors
* @since 1.0
* @var array
* @access private
*/
var $_errors = array();
/**
* Note for required fields in the form
* @var string
* @since 1.0
* @access private
*/
var $_requiredNote = '<span style="font-size:80%; color:#ff0000;">*</span><span style="font-size:80%;"> denotes required field</span>';
/**
* Whether the form was submitted
* @var boolean
* @access private
*/
var $_flagSubmitted = false;
// }}}
// {{{ constructor
/**
* Class constructor
* @param string $formName Form's name.
* @param string $method (optional)Form's method defaults to 'POST'
* @param string $action (optional)Form's action
* @param string $target (optional)Form's target defaults to '_self'
* @param mixed $attributes (optional)Extra attributes for <form> tag
* @param bool $trackSubmit (optional)Whether to track if the form was submitted by adding a special hidden field
* @access public
*/
function __construct($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit = false)
{
parent::__construct($attributes);
$method = (strtoupper($method) == 'GET') ? 'get' : 'post';
$action = CRM_Utils_System::postURL( $action );
// $action = ($action == '') ? $_SERVER['PHP_SELF'] : $action;
$target = empty($target) ? array() : array('target' => $target);
$attributes = array('action'=>$action, 'method'=>$method, 'name'=>$formName, 'id'=>$formName) + $target;
$this->updateAttributes($attributes);
if (!$trackSubmit || isset($_REQUEST['_qf__' . $formName])) {
if (1 == get_magic_quotes_gpc()) {
$this->_submitValues = $this->_recursiveFilter('stripslashes', 'get' == $method? $_GET: $_POST);
foreach ($_FILES as $keyFirst => $valFirst) {
foreach ($valFirst as $keySecond => $valSecond) {
if ('name' == $keySecond) {
$this->_submitFiles[$keyFirst][$keySecond] = $this->_recursiveFilter('stripslashes', $valSecond);
} else {
$this->_submitFiles[$keyFirst][$keySecond] = $valSecond;
}
}
}
} else {
$this->_submitValues = 'get' == $method? $_GET: $_POST;
$this->_submitFiles = $_FILES;
}
$this->_flagSubmitted = count($this->_submitValues) > 0 || count($this->_submitFiles) > 0;
}
if ($trackSubmit) {
unset($this->_submitValues['_qf__' . $formName]);
$this->addElement('hidden', '_qf__' . $formName, null);
}
if (preg_match('/^([0-9]+)([a-zA-Z]*)$/', ini_get('upload_max_filesize'), $matches)) {
// see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
switch (strtoupper($matches['2'])) {
case 'G':
$this->_maxFileSize = $matches['1'] * 1073741824;
break;
case 'M':
$this->_maxFileSize = $matches['1'] * 1048576;
break;
case 'K':
$this->_maxFileSize = $matches['1'] * 1024;
break;
default:
$this->_maxFileSize = $matches['1'];
}
}
} // end constructor
// }}}
// {{{ apiVersion()
/**
* Returns the current API version
*
* @since 1.0
* @access public
* @return float
*/
function apiVersion()
{
return 3.2;
} // end func apiVersion
// }}}
// {{{ registerElementType()
/**
* Registers a new element type
*
* @param string $typeName Name of element type
* @param string $include Include path for element type
* @param string $className Element class name
* @since 1.0
* @access public
* @return void
*/
function registerElementType($typeName, $include, $className)
{
$GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($typeName)] = array($include, $className);
} // end func registerElementType
// }}}
// {{{ registerRule()
/**
* Registers a new validation rule
*
* @param string $ruleName Name of validation rule
* @param string $type Either: 'regex', 'function' or 'rule' for an HTML_QuickForm_Rule object
* @param string $data1 Name of function, regular expression or HTML_QuickForm_Rule classname
* @param string $data2 Object parent of above function or HTML_QuickForm_Rule file path
* @since 1.0
* @access public
* @return void
*/
function registerRule($ruleName, $type, $data1, $data2 = null)
{
include_once('HTML/QuickForm/RuleRegistry.php');
$registry = HTML_QuickForm_RuleRegistry::singleton();
$registry->registerRule($ruleName, $type, $data1, $data2);
} // end func registerRule
// }}}
// {{{ elementExists()
/**
* Returns true if element is in the form
*
* @param string $element form name of element to check
* @since 1.0
* @access public
* @return boolean
*/
function elementExists($element=null)
{
return isset($this->_elementIndex[$element]);
} // end func elementExists
// }}}
// {{{ setDatasource()
/**
* Sets a datasource object for this form object
*
* Datasource default and constant values will feed the QuickForm object if
* the datasource implements defaultValues() and constantValues() methods.
*
* @param object $datasource datasource object implementing the informal datasource protocol
* @param mixed $defaultsFilter string or array of filter(s) to apply to default values
* @param mixed $constantsFilter string or array of filter(s) to apply to constants values
* @since 3.3
* @access public
* @return void
* @throws HTML_QuickForm_Error
*/
function setDatasource(&$datasource, $defaultsFilter = null, $constantsFilter = null)
{
if (is_object($datasource)) {
$this->_datasource =& $datasource;
if (is_callable(array($datasource, 'defaultValues'))) {
$this->setDefaults($datasource->defaultValues($this), $defaultsFilter);
}
if (is_callable(array($datasource, 'constantValues'))) {
$this->setConstants($datasource->constantValues($this), $constantsFilter);
}
} else {
return PEAR::raiseError(null, QUICKFORM_INVALID_DATASOURCE, null, E_USER_WARNING, "Datasource is not an object in QuickForm::setDatasource()", 'HTML_QuickForm_Error', true);
}
} // end func setDatasource
// }}}
// {{{ setDefaults()
/**
* Initializes default form values
*
* @param array $defaultValues values used to fill the form
* @param mixed $filter (optional) filter(s) to apply to all default values
* @since 1.0
* @access public
* @return void
* @throws HTML_QuickForm_Error
*/
function setDefaults($defaultValues = null, $filter = null)
{
if (is_array($defaultValues)) {
if (isset($filter)) {
if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) {
foreach ($filter as $val) {
if (!is_callable($val)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true);
} else {
$defaultValues = $this->_recursiveFilter($val, $defaultValues);
}
}
} elseif (!is_callable($filter)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true);
} else {
$defaultValues = $this->_recursiveFilter($filter, $defaultValues);
}
}
$this->_defaultValues = HTML_QuickForm::arrayMerge($this->_defaultValues, $defaultValues);
foreach (array_keys($this->_elements) as $key) {
$this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
}
}
} // end func setDefaults
// }}}
// {{{ setConstants()
/**
* Initializes constant form values.
* These values won't get overridden by POST or GET vars
*
* @param array $constantValues values used to fill the form
* @param mixed $filter (optional) filter(s) to apply to all default values
*
* @since 2.0
* @access public
* @return void
* @throws HTML_QuickForm_Error
*/
function setConstants($constantValues = null, $filter = null)
{
if (is_array($constantValues)) {
if (isset($filter)) {
if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) {
foreach ($filter as $val) {
if (!is_callable($val)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true);
} else {
$constantValues = $this->_recursiveFilter($val, $constantValues);
}
}
} elseif (!is_callable($filter)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true);
} else {
$constantValues = $this->_recursiveFilter($filter, $constantValues);
}
}
$this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, $constantValues);
foreach (array_keys($this->_elements) as $key) {
$this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
}
}
} // end func setConstants
// }}}
// {{{ setMaxFileSize()
/**
* Sets the value of MAX_FILE_SIZE hidden element
*
* @param int $bytes Size in bytes
* @since 3.0
* @access public
* @return void
*/
function setMaxFileSize($bytes = 0)
{
if ($bytes > 0) {
$this->_maxFileSize = $bytes;
}
if (!$this->elementExists('MAX_FILE_SIZE')) {
$this->addElement('hidden', 'MAX_FILE_SIZE', $this->_maxFileSize);
} else {
$el =& $this->getElement('MAX_FILE_SIZE');
$el->updateAttributes(array('value' => $this->_maxFileSize));
}
} // end func setMaxFileSize
// }}}
// {{{ getMaxFileSize()
/**
* Returns the value of MAX_FILE_SIZE hidden element
*
* @since 3.0
* @access public
* @return int max file size in bytes
*/
function getMaxFileSize()
{
return $this->_maxFileSize;
} // end func getMaxFileSize
// }}}
// {{{ &createElement()
/**
* Creates a new form element of the given type.
*
* This method accepts variable number of parameters, their
* meaning and count depending on $elementType
*
* @param string $elementType type of element to add (text, textarea, file...)
* @since 1.0
* @access public
* @return HTML_QuickForm_Element
* @throws HTML_QuickForm_Error
*/
function &createElement($elementType)
{
$args = func_get_args();
$element = HTML_QuickForm::_loadElement('createElement', $elementType, array_slice($args, 1));
return $element;
} // end func createElement
// }}}
// {{{ _loadElement()
/**
* Returns a form element of the given type
*
* @param string $event event to send to newly created element ('createElement' or 'addElement')
* @param string $type element type
* @param array $args arguments for event
* @since 2.0
* @access private
* @return HTML_QuickForm_Element
* @throws HTML_QuickForm_Error
*/
function &_loadElement($event, $type, $args)
{
$type = strtolower($type);
if (!HTML_QuickForm::isTypeRegistered($type)) {
$error = PEAR::raiseError(null, QUICKFORM_UNREGISTERED_ELEMENT, null, E_USER_WARNING, "Element '$type' does not exist in HTML_QuickForm::_loadElement()", 'HTML_QuickForm_Error', true);
return $error;
}
$className = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][1];
$includeFile = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][0];
include_once($includeFile);
$elementObject = new $className();
for ($i = 0; $i < 5; $i++) {
if (!isset($args[$i])) {
$args[$i] = null;
}
}
$err = $elementObject->onQuickFormEvent($event, $args, $this);
if ($err !== true) {
return $err;
}
return $elementObject;
} // end func _loadElement
// }}}
// {{{ addElement()
/**
* Adds an element into the form
*
* If $element is a string representing element type, then this
* method accepts variable number of parameters, their meaning
* and count depending on $element
*
* @param mixed $element element object or type of element to add (text, textarea, file...)
* @since 1.0
* @return HTML_QuickForm_Element a reference to newly added element
* @access public
* @throws HTML_QuickForm_Error
*/
function &addElement($element)
{
if (is_object($element) && is_subclass_of($element, 'html_quickform_element')) {
$elementObject = &$element;
$elementObject->onQuickFormEvent('updateValue', null, $this);
} else {
$args = func_get_args();
$elementObject =& $this->_loadElement('addElement', $element, array_slice($args, 1));
if (PEAR::isError($elementObject)) {
return $elementObject;
}
}
$elementName = $elementObject->getName();
// Add the element if it is not an incompatible duplicate
if (!empty($elementName) && isset($this->_elementIndex[$elementName])) {
if ($this->_elements[$this->_elementIndex[$elementName]]->getType() ==
$elementObject->getType()) {
$this->_elements[] =& $elementObject;
$elKeys = array_keys($this->_elements);
$this->_duplicateIndex[$elementName][] = end($elKeys);
} else {
$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::addElement()", 'HTML_QuickForm_Error', true);
return $error;
}
} else {
$this->_elements[] =& $elementObject;
$elKeys = array_keys($this->_elements);
$this->_elementIndex[$elementName] = end($elKeys);
}
if ($this->_freezeAll) {
$elementObject->freeze();
}
if ( $elementObject->getType( ) == 'text' ||
$elementObject->getType( ) == 'textarea' ) {
$this->addRule( $elementName,
ts( 'Illegal characters in input (potential scripting attack)' ),
'xssString' );
}
return $elementObject;
} // end func addElement
// }}}
// {{{ insertElementBefore()
/**
* Inserts a new element right before the other element
*
* Warning: it is not possible to check whether the $element is already
* added to the form, therefore if you want to move the existing form
* element to a new position, you'll have to use removeElement():
* $form->insertElementBefore($form->removeElement('foo', false), 'bar');
*
* @access public
* @since 3.2.4
* @param HTML_QuickForm_element Element to insert
* @param string Name of the element before which the new
* one is inserted
* @return HTML_QuickForm_element reference to inserted element
* @throws HTML_QuickForm_Error
*/
function &insertElementBefore(&$element, $nameAfter)
{
if (!empty($this->_duplicateIndex[$nameAfter])) {
$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, 'Several elements named "' . $nameAfter . '" exist in HTML_QuickForm::insertElementBefore().', 'HTML_QuickForm_Error', true);
return $error;
} elseif (!$this->elementExists($nameAfter)) {
$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$nameAfter' does not exist in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true);
return $error;
}
$elementName = $element->getName();
$targetIdx = $this->_elementIndex[$nameAfter];
$duplicate = false;
// Like in addElement(), check that it's not an incompatible duplicate
if (!empty($elementName) && isset($this->_elementIndex[$elementName])) {
if ($this->_elements[$this->_elementIndex[$elementName]]->getType() != $element->getType()) {
$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true);
return $error;
}
$duplicate = true;
}
// Move all the elements after added back one place, reindex _elementIndex and/or _duplicateIndex
$elKeys = array_keys($this->_elements);
for ($i = end($elKeys); $i >= $targetIdx; $i--) {
if (isset($this->_elements[$i])) {
$currentName = $this->_elements[$i]->getName();
$this->_elements[$i + 1] =& $this->_elements[$i];
if ($this->_elementIndex[$currentName] == $i) {
$this->_elementIndex[$currentName] = $i + 1;
} else {
$dupIdx = array_search($i, $this->_duplicateIndex[$currentName]);
$this->_duplicateIndex[$currentName][$dupIdx] = $i + 1;
}
unset($this->_elements[$i]);
}
}
// Put the element in place finally
$this->_elements[$targetIdx] =& $element;
if (!$duplicate) {
$this->_elementIndex[$elementName] = $targetIdx;
} else {
$this->_duplicateIndex[$elementName][] = $targetIdx;
}
$element->onQuickFormEvent('updateValue', null, $this);
if ($this->_freezeAll) {
$element->freeze();
}
// If not done, the elements will appear in reverse order
ksort($this->_elements);
return $element;
}
// }}}
// {{{ addGroup()
/**
* Adds an element group
* @param array $elements array of elements composing the group
* @param string $name (optional)group name
* @param string $groupLabel (optional)group label
* @param string $separator (optional)string to separate elements
* @param string $appendName (optional)specify whether the group name should be
* used in the form element name ex: group[element]
* @return HTML_QuickForm_group reference to a newly added group
* @since 2.8
* @access public
* @throws HTML_QuickForm_Error
*/
function &addGroup($elements, $name=null, $groupLabel='', $separator=null, $appendName = true)
{
static $anonGroups = 1;
if (0 == strlen($name)) {
$name = 'qf_group_' . $anonGroups++;
$appendName = false;
}
$group =& $this->addElement('group', $name, $groupLabel, $elements, $separator, $appendName);
return $group;
} // end func addGroup
// }}}
// {{{ &getElement()
/**
* Returns a reference to the element
*
* @param string $element Element name
* @since 2.0
* @access public
* @return HTML_QuickForm_element reference to element
* @throws HTML_QuickForm_Error
*/
function &getElement($element)
{
if (isset($this->_elementIndex[$element])) {
return $this->_elements[$this->_elementIndex[$element]];
} else {
$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElement()", 'HTML_QuickForm_Error', true);
return $error;
}
} // end func getElement
// }}}
// {{{ &getElementValue()
/**
* Returns the element's raw value
*
* This returns the value as submitted by the form (not filtered)
* or set via setDefaults() or setConstants()
*
* @param string $element Element name
* @since 2.0
* @access public
* @return mixed element value
* @throws HTML_QuickForm_Error
*/
function &getElementValue($element)
{
if (!isset($this->_elementIndex[$element])) {
$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
return $error;
}
$value = $this->_elements[$this->_elementIndex[$element]]->getValue();
if (isset($this->_duplicateIndex[$element])) {
foreach ($this->_duplicateIndex[$element] as $index) {
if (null !== ($v = $this->_elements[$index]->getValue())) {
if (is_array($value)) {
$value[] = $v;
} else {
$value = (null === $value)? $v: array($value, $v);
}
}
}
}
return $value;
} // end func getElementValue
// }}}
// {{{ getSubmitValue()
/**
* Returns the elements value after submit and filter
*
* @param string Element name
* @since 2.0
* @access public
* @return mixed submitted element value or null if not set
*/
function getSubmitValue($elementName)
{
$value = null;
if (isset($this->_submitValues[$elementName]) || isset($this->_submitFiles[$elementName])) {
$value = isset($this->_submitValues[$elementName])? $this->_submitValues[$elementName]: array();
if (is_array($value) && isset($this->_submitFiles[$elementName])) {
foreach ($this->_submitFiles[$elementName] as $k => $v) {
$value = HTML_QuickForm::arrayMerge($value, $this->_reindexFiles($this->_submitFiles[$elementName][$k], $k));
}
}
} elseif ('file' == $this->getElementType($elementName)) {
return $this->getElementValue($elementName);
} elseif (false !== ($pos = strpos($elementName, '['))) {
$base = str_replace(
array('\\', '\''), array('\\\\', '\\\''),
substr($elementName, 0, $pos)
);
$idx = "['" . str_replace(
array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
substr($elementName, $pos + 1, -1)
) . "']";
if (isset($this->_submitValues[$base])) {
$value = eval("return (isset(\$this->_submitValues['{$base}']{$idx})) ? \$this->_submitValues['{$base}']{$idx} : null;");
}
if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) {
$props = array('name', 'type', 'size', 'tmp_name', 'error');
$code = "if (!isset(\$this->_submitFiles['{$base}']['name']{$idx})) {\n" .
" return null;\n" .
"} else {\n" .
" \$v = array();\n";
foreach ($props as $prop) {
$code .= " \$v = HTML_QuickForm::arrayMerge(\$v, \$this->_reindexFiles(\$this->_submitFiles['{$base}']['{$prop}']{$idx}, '{$prop}'));\n";
}
$fileValue = eval($code . " return \$v;\n}\n");
if (null !== $fileValue) {
$value = null === $value? $fileValue: HTML_QuickForm::arrayMerge($value, $fileValue);
}
}
}
// This is only supposed to work for groups with appendName = false
if (null === $value && 'group' == $this->getElementType($elementName)) {
$group =& $this->getElement($elementName);
$elements =& $group->getElements();
foreach (array_keys($elements) as $key) {
$name = $group->getElementName($key);
// prevent endless recursion in case of radios and such
if ($name != $elementName) {
if (null !== ($v = $this->getSubmitValue($name))) {
$value[$name] = $v;
}
}
}
}
return $value;
} // end func getSubmitValue
// }}}
// {{{ _reindexFiles()
/**
* A helper function to change the indexes in $_FILES array
*
* @param mixed Some value from the $_FILES array
* @param string The key from the $_FILES array that should be appended
* @return array
*/
function _reindexFiles($value, $key)
{
if (!is_array($value)) {
return array($key => $value);
} else {
$ret = array();
foreach ($value as $k => $v) {
$ret[$k] = $this->_reindexFiles($v, $key);
}
return $ret;
}
}
// }}}
// {{{ getElementError()
/**
* Returns error corresponding to validated element
*
* @param string $element Name of form element to check
* @since 1.0
* @access public
* @return string error message corresponding to checked element
*/
function getElementError($element)
{
if (isset($this->_errors[$element])) {
return $this->_errors[$element];
}
} // end func getElementError
// }}}
// {{{ setElementError()
/**
* Set error message for a form element
*
* @param string $element Name of form element to set error for
* @param string $message Error message, if empty then removes the current error message
* @since 1.0
* @access public
* @return void
*/
function setElementError($element, $message = null)
{
if (!empty($message)) {
$this->_errors[$element] = $message;
} else {
unset($this->_errors[$element]);
}
} // end func setElementError
// }}}
// {{{ getElementType()
/**
* Returns the type of the given element
*
* @param string $element Name of form element
* @since 1.1
* @access public
* @return string Type of the element, false if the element is not found
*/
function getElementType($element)
{
if (isset($this->_elementIndex[$element])) {
return $this->_elements[$this->_elementIndex[$element]]->getType();
}
return false;
} // end func getElementType
// }}}
// {{{ updateElementAttr()
/**
* Updates Attributes for one or more elements
*
* @param mixed $elements Array of element names/objects or string of elements to be updated
* @param mixed $attrs Array or sting of html attributes
* @since 2.10
* @access public
* @return void
*/
function updateElementAttr($elements, $attrs)
{
if (is_string($elements)) {
$elements = preg_split('/[ ]?,[ ]?/', $elements);
}
foreach (array_keys($elements) as $key) {
if (is_object($elements[$key]) && is_a($elements[$key], 'HTML_QuickForm_element')) {
$elements[$key]->updateAttributes($attrs);
} elseif (isset($this->_elementIndex[$elements[$key]])) {
$this->_elements[$this->_elementIndex[$elements[$key]]]->updateAttributes($attrs);
if (isset($this->_duplicateIndex[$elements[$key]])) {
foreach ($this->_duplicateIndex[$elements[$key]] as $index) {
$this->_elements[$index]->updateAttributes($attrs);
}
}
}
}
} // end func updateElementAttr
// }}}
// {{{ removeElement()
/**
* Removes an element
*
* The method "unlinks" an element from the form, returning the reference
* to the element object. If several elements named $elementName exist,
* it removes the first one, leaving the others intact.
*
* @param string $elementName The element name
* @param boolean $removeRules True if rules for this element are to be removed too
* @access public
* @since 2.0
* @return HTML_QuickForm_element a reference to the removed element
* @throws HTML_QuickForm_Error
*/
function &removeElement($elementName, $removeRules = true)
{
if (!isset($this->_elementIndex[$elementName])) {
$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$elementName' does not exist in HTML_QuickForm::removeElement()", 'HTML_QuickForm_Error', true);
return $error;
}
$el =& $this->_elements[$this->_elementIndex[$elementName]];
unset($this->_elements[$this->_elementIndex[$elementName]]);
if (empty($this->_duplicateIndex[$elementName])) {
unset($this->_elementIndex[$elementName]);
} else {
$this->_elementIndex[$elementName] = array_shift($this->_duplicateIndex[$elementName]);
}
if ($removeRules) {
$this->_required = array_diff($this->_required, array($elementName));
unset($this->_rules[$elementName], $this->_errors[$elementName]);
if ('group' == $el->getType()) {
foreach (array_keys($el->getElements()) as $key) {
unset($this->_rules[$el->getElementName($key)]);
}
}
}
return $el;
} // end func removeElement
// }}}
// {{{ addRule()
/**
* Adds a validation rule for the given field
*
* If the element is in fact a group, it will be considered as a whole.
* To validate grouped elements as separated entities,
* use addGroupRule instead of addRule.
*
* @param string $element Form element name
* @param string $message Message to display for invalid data
* @param string $type Rule type, use getRegisteredRules() to get types
* @param string $format (optional)Required for extra rule data
* @param string $validation (optional)Where to perform validation: "server", "client"
* @param boolean $reset Client-side validation: reset the form element to its original value if there is an error?
* @param boolean $force Force the rule to be applied, even if the target form element does not exist
* @since 1.0
* @access public
* @throws HTML_QuickForm_Error
*/
function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
{
if (!$force) {
if (!is_array($element) && !$this->elementExists($element)) {
return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
} elseif (is_array($element)) {
foreach ($element as $el) {
if (!$this->elementExists($el)) {
return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$el' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
}
}
}
}
if (false === ($newName = $this->isRuleRegistered($type, true))) {
return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
} elseif (is_string($newName)) {
$type = $newName;
}
if (is_array($element)) {
$dependent = $element;
$element = array_shift($dependent);
} else {
$dependent = null;
}
if ($type == 'required' || $type == 'uploadedfile') {
$this->_required[] = $element;
}
if (!isset($this->_rules[$element])) {
$this->_rules[$element] = array();
}
if ($validation == 'client') {
$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
}
$this->_rules[$element][] = array(
'type' => $type,
'format' => $format,
'message' => $message,
'validation' => $validation,
'reset' => $reset,
'dependent' => $dependent
);
} // end func addRule
// }}}
// {{{ addGroupRule()
/**
* Adds a validation rule for the given group of elements
*
* Only groups with a name can be assigned a validation rule
* Use addGroupRule when you need to validate elements inside the group.
* Use addRule if you need to validate the group as a whole. In this case,
* the same rule will be applied to all elements in the group.
* Use addRule if you need to validate the group against a function.
*
* @param string $group Form group name
* @param mixed $arg1 Array for multiple elements or error message string for one element
* @param string $type (optional)Rule type use getRegisteredRules() to get types
* @param string $format (optional)Required for extra rule data
* @param int $howmany (optional)How many valid elements should be in the group
* @param string $validation (optional)Where to perform validation: "server", "client"
* @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed.
* @since 2.5
* @access public
* @throws HTML_QuickForm_Error
*/
function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
{
if (!$this->elementExists($group)) {
return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Group '$group' does not exist in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
}
$groupObj =& $this->getElement($group);
if (is_array($arg1)) {
$required = 0;
foreach ($arg1 as $elementIndex => $rules) {
$elementName = $groupObj->getElementName($elementIndex);
foreach ($rules as $rule) {
$format = (isset($rule[2])) ? $rule[2] : null;
$validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
$reset = isset($rule[4]) && $rule[4];
$type = $rule[1];
if (false === ($newName = $this->isRuleRegistered($type, true))) {
return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
} elseif (is_string($newName)) {
$type = $newName;
}
$this->_rules[$elementName][] = array(
'type' => $type,
'format' => $format,
'message' => $rule[0],
'validation' => $validation,
'reset' => $reset,
'group' => $group);
if ('required' == $type || 'uploadedfile' == $type) {
$groupObj->_required[] = $elementName;
$this->_required[] = $elementName;
$required++;
}
if ('client' == $validation) {
$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
}
}
}
if ($required > 0 && count($groupObj->getElements()) == $required) {
$this->_required[] = $group;
}
} elseif (is_string($arg1)) {
if (false === ($newName = $this->isRuleRegistered($type, true))) {
return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
} elseif (is_string($newName)) {
$type = $newName;
}
// addGroupRule() should also handle <select multiple>
if (is_a($groupObj, 'html_quickform_group')) {
// Radios need to be handled differently when required
if ($type == 'required' && $groupObj->getGroupType() == 'radio') {
$howmany = ($howmany == 0) ? 1 : $howmany;
} else {
$howmany = ($howmany == 0) ? count($groupObj->getElements()) : $howmany;
}
}
$this->_rules[$group][] = array('type' => $type,
'format' => $format,
'message' => $arg1,
'validation' => $validation,
'howmany' => $howmany,
'reset' => $reset);
if ($type == 'required') {
$this->_required[] = $group;
}
if ($validation == 'client') {
$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
}
}
} // end func addGroupRule
// }}}
// {{{ addFormRule()
/**
* Adds a global validation rule
*
* This should be used when for a rule involving several fields or if
* you want to use some completely custom validation for your form.
* The rule function/method should return true in case of successful
* validation and array('element name' => 'error') when there were errors.
*
* @access public
* @param mixed Callback, either function name or array(&$object, 'method')
* @param string $format (optional)Required for extra rule data
* @throws HTML_QuickForm_Error
*/
function addFormRule($rule, $format=null)
{
if (!is_callable($rule)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, 'Callback function does not exist in HTML_QuickForm::addFormRule()', 'HTML_QuickForm_Error', true);
}
$this->_formRules[] = array($rule,$format);
}
// }}}
// {{{ applyFilter()
/**
* Applies a data filter for the given field(s)
*
* @param mixed $element Form element name or array of such names
* @param mixed $filter Callback, either function name or array(&$object, 'method')
* @since 2.0
* @access public
* @throws HTML_QuickForm_Error
*/
function applyFilter($element, $filter)
{
if (!is_callable($filter)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::applyFilter()", 'HTML_QuickForm_Error', true);
}
if ($element == '__ALL__') {
$this->_submitValues = $this->_recursiveFilter($filter, $this->_submitValues);
} else {
if (!is_array($element)) {
$element = array($element);
}
foreach ($element as $elName) {
$value = $this->getSubmitValue($elName);
if (null !== $value) {
if (false === strpos($elName, '[')) {
$this->_submitValues[$elName] = $this->_recursiveFilter($filter, $value);
} else {
$idx = "['" . str_replace(
array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
$elName
) . "']";
eval("\$this->_submitValues{$idx} = \$this->_recursiveFilter(\$filter, \$value);");
}
}
}
}
} // end func applyFilter
// }}}
// {{{ _recursiveFilter()
/**
* Recursively apply a filter function
*
* @param string $filter filter to apply
* @param mixed $value submitted values
* @since 2.0
* @access private
* @return cleaned values
*/
function _recursiveFilter($filter, $value)
{
if (is_array($value)) {
$cleanValues = array();
foreach ($value as $k => $v) {
$cleanValues[$k] = $this->_recursiveFilter($filter, $v);
}
return $cleanValues;
} else {
return call_user_func($filter, $value);
}
} // end func _recursiveFilter
// }}}
// {{{ arrayMerge()
/**
* Merges two arrays
*
* Merges two array like the PHP function array_merge but recursively.
* The main difference is that existing keys will not be renumbered
* if they are integers.
*
* @access public
* @param array $a original array
* @param array $b array which will be merged into first one
* @return array merged array
*/
static function arrayMerge($a, $b)
{
foreach ($b as $k => $v) {
if (is_array($v)) {
if (isset($a[$k]) && !is_array($a[$k])) {
$a[$k] = $v;
} else {
if (!isset($a[$k])) {
$a[$k] = array();
}
$a[$k] = HTML_QuickForm::arrayMerge($a[$k], $v);
}
} else {
$a[$k] = $v;
}
}
return $a;
} // end func arrayMerge
// }}}
// {{{ isTypeRegistered()
/**
* Returns whether or not the form element type is supported
*
* @param string $type Form element type
* @since 1.0
* @access public
* @return boolean
*/
function isTypeRegistered($type)
{
return isset($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($type)]);
} // end func isTypeRegistered
// }}}
// {{{ getRegisteredTypes()
/**
* Returns an array of registered element types
*
* @since 1.0
* @access public
* @return array
*/
function getRegisteredTypes()
{
return array_keys($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']);
} // end func getRegisteredTypes
// }}}
// {{{ isRuleRegistered()
/**
* Returns whether or not the given rule is supported
*
* @param string $name Validation rule name
* @param bool Whether to automatically register subclasses of HTML_QuickForm_Rule
* @since 1.0
* @access public
* @return mixed true if previously registered, false if not, new rule name if auto-registering worked
*/
function isRuleRegistered($name, $autoRegister = false)
{
if (is_scalar($name) && isset($GLOBALS['_HTML_QuickForm_registered_rules'][$name])) {
return true;
} elseif (!$autoRegister) {
return false;
}
// automatically register the rule if requested
include_once 'HTML/QuickForm/RuleRegistry.php';
$ruleName = false;
if (is_object($name) && is_a($name, 'html_quickform_rule')) {
$ruleName = !empty($name->name)? $name->name: strtolower(get_class($name));
} elseif (is_string($name) && class_exists($name)) {
$parent = strtolower($name);
do {
if ('html_quickform_rule' == strtolower($parent)) {
$ruleName = strtolower($name);
break;
}
} while ($parent = get_parent_class($parent));
}
if ($ruleName) {
$registry = HTML_QuickForm_RuleRegistry::singleton();
$registry->registerRule($ruleName, null, $name);
}
return $ruleName;
} // end func isRuleRegistered
// }}}
// {{{ getRegisteredRules()
/**
* Returns an array of registered validation rules
*
* @since 1.0
* @access public
* @return array
*/
function getRegisteredRules()
{
return array_keys($GLOBALS['_HTML_QuickForm_registered_rules']);
} // end func getRegisteredRules
// }}}
// {{{ isElementRequired()
/**
* Returns whether or not the form element is required
*
* @param string $element Form element name
* @since 1.0
* @access public
* @return boolean
*/
function isElementRequired($element)
{
return in_array($element, $this->_required, true);
} // end func isElementRequired
// }}}
// {{{ isElementFrozen()
/**
* Returns whether or not the form element is frozen
*
* @param string $element Form element name
* @since 1.0
* @access public
* @return boolean
*/
function isElementFrozen($element)
{
if (isset($this->_elementIndex[$element])) {
return $this->_elements[$this->_elementIndex[$element]]->isFrozen();
}
return false;
} // end func isElementFrozen
// }}}
// {{{ setJsWarnings()
/**
* Sets JavaScript warning messages
*
* @param string $pref Prefix warning
* @param string $post Postfix warning
* @since 1.1
* @access public
* @return void
*/
function setJsWarnings($pref, $post)
{
$this->_jsPrefix = $pref;
$this->_jsPostfix = $post;
} // end func setJsWarnings
// }}}
// {{{ setRequiredNote()
/**
* Sets required-note
*
* @param string $note Message indicating some elements are required
* @since 1.1
* @access public
* @return void
*/
function setRequiredNote($note)
{
$this->_requiredNote = $note;
} // end func setRequiredNote
// }}}
// {{{ getRequiredNote()
/**
* Returns the required note
*
* @since 2.0
* @access public
* @return string
*/
function getRequiredNote()
{
return $this->_requiredNote;
} // end func getRequiredNote
// }}}
// {{{ validate()
/**
* Performs the server side validation
* @access public
* @since 1.0
* @return boolean true if no error found
* @throws HTML_QuickForm_Error
*/
function validate()
{
if (count($this->_rules) == 0 && count($this->_formRules) == 0 &&
$this->isSubmitted()) {
return (0 == count($this->_errors));
} elseif (!$this->isSubmitted()) {
return false;
}
include_once('HTML/QuickForm/RuleRegistry.php');
$registry = HTML_QuickForm_RuleRegistry::singleton();
foreach ($this->_rules as $target => $rules) {
$submitValue = $this->getSubmitValue($target);
foreach ($rules as $rule) {
if ((isset($rule['group']) && isset($this->_errors[$rule['group']])) ||
isset($this->_errors[$target])) {
continue 2;
}
// If element is not required and is empty, we shouldn't validate it
if (!$this->isElementRequired($target)) {
if (!isset($submitValue) || '' == $submitValue) {
continue 2;
// Fix for bug #3501: we shouldn't validate not uploaded files, either.
// Unfortunately, we can't just use $element->isUploadedFile() since
// the element in question can be buried in group. Thus this hack.
// See also bug #12014, we should only consider a file that has
// status UPLOAD_ERR_NO_FILE as not uploaded, in all other cases
// validation should be performed, so that e.g. 'maxfilesize' rule
// will display an error if status is UPLOAD_ERR_INI_SIZE
// or UPLOAD_ERR_FORM_SIZE
} elseif (is_array($submitValue)) {
if (false === ($pos = strpos($target, '['))) {
$isUpload = !empty($this->_submitFiles[$target]);
} else {
$base = str_replace(
array('\\', '\''), array('\\\\', '\\\''),
substr($target, 0, $pos)
);
$idx = "['" . str_replace(
array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
substr($target, $pos + 1, -1)
) . "']";
eval("\$isUpload = isset(\$this->_submitFiles['{$base}']['name']{$idx});");
}
if ($isUpload && (!isset($submitValue['error']) || UPLOAD_ERR_NO_FILE == $submitValue['error'])) {
continue 2;
}
}
}
if (isset($rule['dependent']) && is_array($rule['dependent'])) {
$values = array($submitValue);
foreach ($rule['dependent'] as $elName) {
$values[] = $this->getSubmitValue($elName);
}
$result = $registry->validate($rule['type'], $values, $rule['format'], true);
} elseif (is_array($submitValue) && !isset($rule['howmany'])) {
$result = $registry->validate($rule['type'], $submitValue, $rule['format'], true);
} else {
$result = $registry->validate($rule['type'], $submitValue, $rule['format'], false);
}
if (!$result || (!empty($rule['howmany']) && $rule['howmany'] > (int)$result)) {
if (isset($rule['group'])) {
$this->_errors[$rule['group']] = $rule['message'];
} else {
$this->_errors[$target] = $rule['message'];
}
}
}
}
// process the global rules now
foreach ($this->_formRules as $value) {
list($rule, $options) = $value;
if (true !== ($res = call_user_func($rule, $this->_submitValues, $this->_submitFiles, $options))) {
if (is_array($res)) {
$this->_errors += $res;
} else {
return PEAR::raiseError(null, QUICKFORM_ERROR, null, E_USER_WARNING, 'Form rule callback returned invalid value in HTML_QuickForm::validate()', 'HTML_QuickForm_Error', true);
}
}
}
return (0 == count($this->_errors));
} // end func validate
// }}}
// {{{ freeze()
/**
* Displays elements without HTML input tags
*
* @param mixed $elementList array or string of element(s) to be frozen
* @since 1.0
* @access public
* @throws HTML_QuickForm_Error
*/
function freeze($elementList=null)
{
if (!isset($elementList)) {
$this->_freezeAll = true;
$elementList = array();
} else {
if (!is_array($elementList)) {
$elementList = preg_split('/[ ]*,[ ]*/', $elementList);
}
$elementList = array_flip($elementList);
}
foreach (array_keys($this->_elements) as $key) {
$name = $this->_elements[$key]->getName();
if ($this->_freezeAll || isset($elementList[$name])) {
$this->_elements[$key]->freeze();
unset($elementList[$name]);
}
}
if (!empty($elementList)) {
return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Nonexistant element(s): '" . implode("', '", array_keys($elementList)) . "' in HTML_QuickForm::freeze()", 'HTML_QuickForm_Error', true);
}
return true;
} // end func freeze
// }}}
// {{{ isFrozen()
/**
* Returns whether or not the whole form is frozen
*
* @since 3.0
* @access public
* @return boolean
*/
function isFrozen()
{
return $this->_freezeAll;
} // end func isFrozen
// }}}
// {{{ process()
/**
* Performs the form data processing
*
* @param mixed $callback Callback, either function name or array(&$object, 'method')
* @param bool $mergeFiles Whether uploaded files should be processed too
* @since 1.0
* @access public
* @throws HTML_QuickForm_Error
* @return mixed Whatever value the $callback function returns
*/
function process($callback, $mergeFiles = true)
{
if (!is_callable($callback)) {
return PEAR::raiseError(null, QUICKFORM_INVALID_PROCESS, null, E_USER_WARNING, "Callback function does not exist in QuickForm::process()", 'HTML_QuickForm_Error', true);
}
$values = ($mergeFiles === true) ? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles) : $this->_submitValues;
return call_user_func($callback, $values);
} // end func process
// }}}
// {{{ accept()
/**
* Accepts a renderer
*
* @param object An HTML_QuickForm_Renderer object
* @since 3.0
* @access public
* @return void
*/
function accept(&$renderer)
{
$renderer->startForm($this);
foreach (array_keys($this->_elements) as $key) {
$element =& $this->_elements[$key];
$elementName = $element->getName();
$required = ($this->isElementRequired($elementName) && !$element->isFrozen());
$error = $this->getElementError($elementName);
$element->accept($renderer, $required, $error);
}
$renderer->finishForm($this);
} // end func accept
// }}}
// {{{ defaultRenderer()
/**
* Returns a reference to default renderer object
*
* @access public
* @since 3.0
* @return object a default renderer object
*/
function &defaultRenderer()
{
if (!isset($GLOBALS['_HTML_QuickForm_default_renderer'])) {
include_once('HTML/QuickForm/Renderer/Default.php');
$GLOBALS['_HTML_QuickForm_default_renderer'] = new HTML_QuickForm_Renderer_Default();
}
return $GLOBALS['_HTML_QuickForm_default_renderer'];
} // end func defaultRenderer
// }}}
// {{{ toHtml ()
/**
* Returns an HTML version of the form
*
* @param string $in_data (optional) Any extra data to insert right
* before form is rendered. Useful when using templates.
*
* @return string Html version of the form
* @since 1.0
* @access public
*/
function toHtml ($in_data = null)
{
if (!is_null($in_data)) {
$this->addElement('html', $in_data);
}
$renderer =& $this->defaultRenderer();
$this->accept($renderer);
return $renderer->toHtml();
} // end func toHtml
// }}}
// {{{ getValidationScript()
/**
* Returns the client side validation script
*
* @since 2.0
* @access public
* @return string Javascript to perform validation, empty string if no 'client' rules were added
*/
function getValidationScript()
{
if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
return '';
}
include_once('HTML/QuickForm/RuleRegistry.php');
$registry = HTML_QuickForm_RuleRegistry::singleton();
$test = array();
$js_escape = array(
"\r" => '\r',
"\n" => '\n',
"\t" => '\t',
"'" => "\\'",
'"' => '\"',
'\\' => '\\\\'
);
foreach ($this->_rules as $elementName => $rules) {
foreach ($rules as $rule) {
if ('client' == $rule['validation']) {
unset($element);
$dependent = isset($rule['dependent']) && is_array($rule['dependent']);
$rule['message'] = strtr($rule['message'], $js_escape);
if (isset($rule['group'])) {
$group =& $this->getElement($rule['group']);
// No JavaScript validation for frozen elements
if ($group->isFrozen()) {
continue 2;
}
$elements =& $group->getElements();
foreach (array_keys($elements) as $key) {
if ($elementName == $group->getElementName($key)) {
$element =& $elements[$key];
break;
}
}
} elseif ($dependent) {
$element = array();
$element[] =& $this->getElement($elementName);
foreach ($rule['dependent'] as $elName) {
$element[] =& $this->getElement($elName);
}
} else {
$element =& $this->getElement($elementName);
}
// No JavaScript validation for frozen elements
if (is_object($element) && $element->isFrozen()) {
continue 2;
} elseif (is_array($element)) {
foreach (array_keys($element) as $key) {
if ($element[$key]->isFrozen()) {
continue 3;
}
}
}
$test[] = $registry->getValidationScript($element, $elementName, $rule);
}
}
}
if (count($test) > 0) {
return
"\n<script type=\"text/javascript\">\n" .
"//<![CDATA[\n" .
"function validate_" . $this->_attributes['id'] . "(frm) {\n" .
" var value = '';\n" .
" var errFlag = new Array();\n" .
" var _qfGroups = {};\n" .
" _qfMsg = '';\n\n" .
join("\n", $test) .
"\n if (_qfMsg != '') {\n" .
" _qfMsg = '" . strtr($this->_jsPrefix, $js_escape) . "' + _qfMsg;\n" .
" _qfMsg = _qfMsg + '\\n" . strtr($this->_jsPostfix, $js_escape) . "';\n" .
" alert(_qfMsg);\n" .
" return false;\n" .
" }\n" .
" return true;\n" .
"}\n" .
"//]]>\n" .
"</script>";
}
return '';
} // end func getValidationScript
// }}}
// {{{ getSubmitValues()
/**
* Returns the values submitted by the form
*
* @since 2.0
* @access public
* @param bool Whether uploaded files should be returned too
* @return array
*/
function getSubmitValues($mergeFiles = false)
{
return $mergeFiles? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles): $this->_submitValues;
} // end func getSubmitValues
// }}}
// {{{ toArray()
/**
* Returns the form's contents in an array.
*
* The description of the array structure is in HTML_QuickForm_Renderer_Array docs
*
* @since 2.0
* @access public
* @param bool Whether to collect hidden elements (passed to the Renderer's constructor)
* @return array of form contents
*/
function toArray($collectHidden = false)
{
include_once 'HTML/QuickForm/Renderer/Array.php';
$renderer = new HTML_QuickForm_Renderer_Array($collectHidden);
$this->accept($renderer);
return $renderer->toArray();
} // end func toArray
// }}}
// {{{ exportValue()
/**
* Returns a 'safe' element's value
*
* This method first tries to find a cleaned-up submitted value,
* it will return a value set by setValue()/setDefaults()/setConstants()
* if submitted value does not exist for the given element.
*
* @param string Name of an element
* @access public
* @return mixed
* @throws HTML_QuickForm_Error
*/
function exportValue($element)
{
if (!isset($this->_elementIndex[$element])) {
return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
}
$value = $this->_elements[$this->_elementIndex[$element]]->exportValue($this->_submitValues, false);
if (isset($this->_duplicateIndex[$element])) {
foreach ($this->_duplicateIndex[$element] as $index) {
if (null !== ($v = $this->_elements[$index]->exportValue($this->_submitValues, false))) {
if (is_array($value)) {
$value[] = $v;
} else {
$value = (null === $value)? $v: array($value, $v);
}
}
}
}
return $value;
}
// }}}
// {{{ exportValues()
/**
* Returns 'safe' elements' values
*
* Unlike getSubmitValues(), this will return only the values
* corresponding to the elements present in the form.
*
* @param mixed Array/string of element names, whose values we want. If not set then return all elements.
* @access public
* @return array An assoc array of elements' values
* @throws HTML_QuickForm_Error
*/
function exportValues($elementList = null)
{
$values = array();
if (null === $elementList) {
// iterate over all elements, calling their exportValue() methods
foreach (array_keys($this->_elements) as $key) {
$value = $this->_elements[$key]->exportValue($this->_submitValues, true);
$fldName = null;
if ( isset( $this->_elements[$key]->_attributes['name'] ) ) {
//filter the value across XSS vulnerability issues.
$fldName = $this->_elements[$key]->_attributes['name'];
}
if (
// if not a text/textarea…
!in_array($this->_elements[$key]->_type, array('text', 'textarea'))
// …or should be skipped…
or CRM_Core_HTMLInputCoder::isSkippedField($fldName)
//CRM-17962 - do not encode if element is a wysiwyg editor
or CRM_Utils_Array::value('class', $this->_elements[$key]->_attributes) == 'crm-form-wysiwyg'
) {
// …dont filter, otherwise filter (else clause below)
} else {
//here value might be array or single value.
//so we should iterate and get filtered value.
CRM_Core_HTMLInputCoder::encodeInput( $value );
}
if (is_array($value)) {
// This shit throws a bogus warning in PHP 4.3.x
$values = HTML_QuickForm::arrayMerge($values, $value);
}
}
} else {
if (!is_array($elementList)) {
$elementList = array_map('trim', explode(',', $elementList));
}
foreach ($elementList as $elementName) {
$value = $this->exportValue($elementName);
//filter the value across XSS vulnerability issues.
if ( ! CRM_Core_HTMLInputCoder::isSkippedField($elementName)) {
CRM_Core_HTMLInputCoder::encodeInput( $value );
}
if (PEAR::isError($value)) {
return $value;
}
$values[$elementName] = $value;
}
}
return $values;
}
// }}}
// {{{ isSubmitted()
/**
* Tells whether the form was already submitted
*
* This is useful since the _submitFiles and _submitValues arrays
* may be completely empty after the trackSubmit value is removed.
*
* @access public
* @return bool
*/
function isSubmitted()
{
return $this->_flagSubmitted;
}
// }}}
// {{{ isError()
/**
* Tell whether a result from a QuickForm method is an error (an instance of HTML_QuickForm_Error)
*
* @access public
* @param mixed result code
* @return bool whether $value is an error
* @static
*/
public static function isError($value)
{
return (is_object($value) && is_a($value, 'html_quickform_error'));
} // end func isError
// }}}
// {{{ errorMessage()
/**
* Return a textual error message for an QuickForm error code
*
* @access public
* @param int error code
* @return string error message
* @static
*/
static function errorMessage($value)
{
// make the variable static so that it only has to do the defining on the first call
static $errorMessages;
// define the varies error messages
if (!isset($errorMessages)) {
$errorMessages = array(
QUICKFORM_OK => 'no error',
QUICKFORM_ERROR => 'unknown error',
QUICKFORM_INVALID_RULE => 'the rule does not exist as a registered rule',
QUICKFORM_NONEXIST_ELEMENT => 'nonexistent html element',
QUICKFORM_INVALID_FILTER => 'invalid filter',
QUICKFORM_UNREGISTERED_ELEMENT => 'unregistered element',
QUICKFORM_INVALID_ELEMENT_NAME => 'element already exists',
QUICKFORM_INVALID_PROCESS => 'process callback does not exist',
QUICKFORM_DEPRECATED => 'method is deprecated',
QUICKFORM_INVALID_DATASOURCE => 'datasource is not an object'
);
}
// If this is an error object, then grab the corresponding error code
if (HTML_QuickForm::isError($value)) {
$value = $value->getCode();
}
// return the textual error message corresponding to the code
return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[QUICKFORM_ERROR];
} // end func errorMessage
// }}}
} // end class HTML_QuickForm
/**
* Class for errors thrown by HTML_QuickForm package
*
* @category HTML
* @package HTML_QuickForm
* @author Adam Daniel <adaniel1@eesus.jnj.com>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @version Release: 3.2.11
*/
class HTML_QuickForm_Error extends PEAR_Error {
// {{{ properties
/**
* Prefix for all error messages
* @var string
*/
var $error_message_prefix = 'QuickForm Error: ';
// }}}
// {{{ constructor
/**
* Creates a quickform error object, extending the PEAR_Error class
*
* @param int $code the error code
* @param int $mode the reaction to the error, either return, die or trigger/callback
* @param int $level intensity of the error (PHP error code)
* @param mixed $debuginfo any information that can inform user as to nature of the error
*/
function __construct($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN,
$level = E_USER_NOTICE, $debuginfo = null)
{
if (is_int($code)) {
parent::__construct(HTML_QuickForm::errorMessage($code), $code, $mode, $level, $debuginfo);
} else {
parent::__construct("Invalid error code: $code", QUICKFORM_ERROR, $mode, $level, $debuginfo);
}
}
// }}}
} // end class HTML_QuickForm_Error
?>