Source for file SForm.php
Documentation is available at SForm.php
* A fast & powerful PEAR_QuickForm replacement
* SForm is short for Seth's Form. Why? Because it's easy to remember, and even
* easier to type. SForm was started out of frusteration with PEAR's QuickForm.
* It was too slow, it wouldn't allow me to create complex rules, the code was
* antiquidated, it didn't nest groups, the API was convoluted, the list went
* on. After spending a few days wrestling with QF, I came to the realization
* that I could code my own package in the time it took me to figure out how to
* bend QuickForm to my will. So that's what I did.
* SForm automatically does the standard QF things, like {@link }
* SForm_Rule::toJavaScript() JavaScript generation}, rendering plugins, both
* server-side and {@link SForm_Element::validate() client side validation},
* and is very customizable. Some things not included in QF include non-
* enforced <b>XHTML 1.1 compliance</b>, <b>{@link SForm_Elm_Select::lock()}
* intrinsic rules}</b>, <b>nested groups</b>, and <b>{@link }
* http://www.webstandards.org/learn/tutorials/accessible-forms/beginner/
* automatic accessibility features}</b> like
* <label> tags, optgroups, JavaScript highlighting of errors, and access keys.
* I've tried to keep the API very similar to QuickForm, but I've removed things
* that I found unnecessary or redundant.
* SForm is also <i>fast</i>.
* The small form that I originally used to develop SForm had a render time of
* 0.10-0.28 seconds when no form was created. Using QuickForm to build a form
* on the page increased the render time to 0.33-0.68 secs. Replacing QuickForm
* with SForm dropped the render time to 0.18-0.42 secs. SForm is almost
* <b>three times faster</b> than QuickForm in this informal test. (All trials
* used eAccelerator with caching and optimization enabled.)
* The SForm API is intentionally very similar to QuickForm. This example was
* converted directly from the {@link http://pear.php.net/manual/en/package.}
* html.html-quickform.tutorial.php QuickForm documentation}. All of the
* examples on {@link http://www.midnighthax.com/quickform.php Keith Edmunds's}
* QuickForm tutorial} also work with similar modifications. It should be noted
* that to make this example XHTML 1.1 compliant, the elements would need to be
* // Load the main classes
* require_once 'SForm.php';
* // Instantiate the SForm object
* $form = new SForm('firstForm');
* // Set defaults for the form elements
* $form->setDefaults(array( 'name' => 'Joe User' ));
* // Add some elements to the form
* $form->addElement('header', null, 'SForm tutorial example');
* $form->addElement('text', 'name', 'Enter your name:',
* array('size' => 50, 'maxlength' => 255));
* $form->addElement('submit', null, 'Send');
* // Define filters and validation rules
* $form->applyFilter('name', 'trim');
* $form->addRuleDep('name', 'Please enter your name', 'required', null,'client');
* // Try to validate a form
* if ($form->validate()) {
* echo '<h1>Hello, ' . htmlspecialchars($form->exportValue('name')) . '!</h1>';
* Note that in the example, addRule() has been changed to {@link }
* SForm::addRuleDep() addRuleDep()}, where 'Dep' stands for
* depriciated. For more information, see the {@link SForm_Rule}
* SForm_Rule documentation}.
* <b>Putting it all together</b>:
* {@example SForm_usage.php}
* Look in SForm_usage.php for this source
* <b>Important differences with the QuickForm API include</b>:
* - The construtor. The $target attribute {@link SForm::__construct() has been removed} because it breaks compliance with XHTML 1.1.
* - Rule creation and usage. SForm uses a much more object oriented {@link SForm_Rule Rule model}.
* SForm requires at least PHP version 5, but version 5.1 is recommended.
* Copyright (c) 2006, Seth Price <{@link mailto:seth@pricepages.org seth@pricepages.org}> All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* @copyright Copyright (c) 2006, Seth Price
* @author Seth Price <seth@pricepages.org>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @see SForm::__construct()
* ./phpdoc -t ../docs -f ../scripts/SForm.php -d ../scripts/SForm -ti "SForm" -o HTML:frames:phpdoc.de
* FIXME - clean up exception messages and values.
define('FORM_ERR_PARENT_EXISTS', 1);
* Create the count interface if needed
* The Countable interface is not enabled by default in PHP 5.0, so we'll
* A element is a "leaf" that is actually rendered
* All elements and containers are decendents of this class. That said, this
* class is intended to cover as much common functionality as possible.
* This is the element's tag ('input', 'select', 'form', etc) and should be
* set by the children as needed.
protected $tag = 'input';
* Is this element's tag empty?
* A tag is empty if it can't contain elements. Examples of empty tags
* include '<input />' and '<img />'. Non-empty tags include '<form>' and
* Should we add the 'name' attribute?
* What is the parent to this element?
* This points to some subclass of SForm_Container. It is set in
* SForm_Container::addElement().
* Has the value for this element been set?
* The local ID for this element
* The cached Global ID for this element
* Is this element frozen?
* The label for this element
* Rules to apply to this element
* Are the rules & filters prevented from being modified?
* When the rules are rendered (or otherwise used), they are locked to
* prevent someone from adding an additonal rule. This would invalidate any
* calculations done using the previous ruleset.
* Is this a required element?
* If an element is marked as required, the 'required' rule is added at
* render time and modifications can be made to the label HTML which mark
* this element as required.
* Has this element been validated?
* Validated elements have a finished $errors array and will not be
* What were the results of the validation?
* All error strings returned by validation are saved here. If there is no
* error string, then there was no error.
* A list of filter callbacks
* What access keys have been used already?
* The default ones are in use by various systems.
* @todo Comment in which default keys come from which sources.
private static $accesskeys = array(
* Number the elements if not given an ID
* Attributes for this tag
* Charset ('charset') is the the type of chars used to encode the HTML.
* Auto access key ('autoAccessKey') controls whether access keys are
* automatically assigned to labels.
* The element error color ('elmErrorColor') is the background color used on
* elements which threw some sort of error.
* Xml close ('xmlClose') alters how the HTML tags are drawn. Mainly, do
* empty tags end in ' />' (XML, XHTML) or '>' (HTML).
* Track id ('trackId') is the Local ID of the hidden element used to track
* The separator ('separator') is used in the Global ID to create sub-groups
* The new submit value ('newSubmitValue') is what the JavaScript writes
* into the values of submit buttons when the form is successfully
* The maximum upload file size ('maxFileSize') is the maximum upload file
* size. It defaults to 2 MB, which is the PHP 'upload_max_filesize' default.
* @see self::getLabelHtml()
* @see SForm_Renderer::rendJavaScript()
private static $options = array(
'elmErrorColor' => 'yellow',
'separator' => '-', // Can be used in CSS selectors
'newSubmitValue' => ' Loading... ',
'maxFileSize' => '2097152',
'requiredSym' => '<span style="color:red;">*</span>');
private static $regRules = array(
'boolean' => array('SForm/Rule/Boolean.php', 'SForm_Rule_Boolean'),
'maxlength' => array(false, 'SForm_Rule_Maxlenth'),
'subset' => array(false, 'SForm_Rule_Subset'),
'required' => array(false, 'SForm_Rule_Required') );
* Construct with an identity. Optionally add a label and more attributes.
* $id is the Local ID of this element, and is similar to its name. If none
* is supplied, one will be created. The Local ID is different than the 'id'
* attribute that appears in the rendered form. The id attribute is referred
* to as the Global ID, and is the concatenation of the parent's Global ID,
* a colon, and this Local ID. The ID must contain only chars acceptable in
* the ID SGML token for the resulting page to be valid.
* The $label is the the short string that describes this element. If it is
* rendered as HTML, it will automatically be enclosed in '<label>' tags
* which referr to the Global ID of this element. If enabled, an appropriate
* access key will also be found for this element. The access key will be
* rendered with '<span class="ak">' and '</span>' tags surrounding it. If
* you wish to communicate with your users that this access key may be used
* to access this field, I suggest that you use CSS like: '.ak{text-
* decoration:underline;}'.
* Any additonal $attributes may be added and will be displayed when the
* element is rendered as HTML.
* @param string Local ID of this element
* @param string Textual label for this element
* @param array Array of extra attributes
* @link http://www.w3.org/TR/html4/types.html#h-6.2
public function __construct($id = null, $label = null, $attributes = null){
* We should try to dynamically make this as needed from what the parent
* recommends (if avl). Also check the $attributes array for 'name' and
$this->localId = (string) $id;
$this->label = (string) $label;
if(!is_array($attributes)){
throw new Exception('Please form attributes in array("attribute" ' .
'=> "value", ...) form. It\'s ' .
'much faster than parsing a string.');
foreach($attributes as $name => $value){
* This is the page-wide unique ID
* The Global ID is a page-unique id which is a concatination of the
* parent's Global ID, a colon, and the Local ID. This is done so that
* element groups can be nested arbitrarily deeply with non-uniqe IDs (ex:
* an array: '0','1','2'...).
* The Global ID is used as the 'id' HTML attribute and is typically also
* used as the 'name' attribute.
* The Global ID cannot be determined until the element is "rooted" to a
* @return string A globally usable ID
throw new Exception('An element must have a parent, please assign me to one. I\'m lonely.');
return $this->globalId = $this->parent->getGlobalId().
self::getOption('separator').
* The local ID is the name that a parent uses to identify its child
* @return string A locally usable ID
* The value of this element
* The value returned is the same as if the form was rendered, displayed,
* then submitted by the user, in its current state.
* @return mixed Element's current value
* @uses $valueSet Is the current value overridden by setValue?
//Return the value of whatever was submited
* Filters a value that is about to be returned from {@link getValue()}.
* @return string Filtered value
* Set the value of this element
* This sets the current value of the element, overriding any current value.
* @param string Element's value.
* @uses $valueSet Overrides any current value
* Set the default value of this element
* This sets the default value of this element. If there is a user-submitted
* value or a {@link setValue()}, it overrides this one.
* @param string Element's value.
* Markup a label with an accesskey
* Marks up the first char in a label with CSS that can show the user which
* key is the access key for this form. If none of the chars match the
* requested one, the char is appended to the end of the label in
* The class of the access key is set with the autoAccessKey option.
* @param string Label to be marked up
* @param string Selected char
* @return string Marked up string
private function markupLabelAK($lbl, $ak){
$cls = self::getOption('autoAccessKey');
if(($i = stripos($lbl, $ak)) === false){
return self::escHtml($lbl). ' (<span class="'. $cls. '">'. $ak. '</span>)';
* We need to markup the access key, but escape HTML at the same
* time. It's more tricky than it sounds because we don't wan't to
* escape any chars in the HTML tags.
$esc = self::escHtml($lbl);
return substr_replace($esc, '<span class="'. $cls. '">'. $lbl[$i]. '</span>', $offset, $replen);
* Generates an access key and updated label
* Uses an access key to generate a marked up label.
* If access key is simply true, it generates an access key by searching
* through the chars of the label for the first one that is a regular char
* ([a-z0- 9]) and is not already in use. If none are found, numbers
* starting from the left side of the std keyboard are used.
* @param string Requested access key
* @return mixed Array of updated label if possible, null otherwise
* @uses markupLabelAK() Markup the label with the found key
* @uses $accesskeys Determine which keys are used
protected function autoAK($lbl, $ak){
//Use requested access key
return array($ak, $this->markupLabelAK($lbl, $ak));
//Look for an access key in the label
for($i = 0; $i < $len; $i++ ){
//Only regular ASCII chars
|