phpDocumentor SForm
[ class tree: SForm ] [ index: SForm ] [ all elements ]

Source for file SForm.php

Documentation is available at SForm.php

  1. <?php
  2. /**
  3.  * A fast & powerful PEAR_QuickForm replacement
  4.  *
  5.  * SForm is short for Seth's Form. Why? Because it's easy to remember, and even
  6.  * easier to type. SForm was started out of frusteration with PEAR's QuickForm.
  7.  * It was too slow, it wouldn't allow me to create complex rules, the code was
  8.  * antiquidated, it didn't nest groups, the API was convoluted, the list went
  9.  * on. After spending a few days wrestling with QF, I came to the realization
  10.  * that I could code my own package in the time it took me to figure out how to
  11.  * bend QuickForm to my will. So that's what I did.
  12.  * 
  13.  * SForm automatically does the standard QF things, like {@link }
  14.  * SForm_Rule::toJavaScript() JavaScript generation}, rendering plugins, both
  15.  * server-side and {@link SForm_Element::validate() client side validation},
  16.  * and is very customizable. Some things not included in QF include non-
  17.  * enforced <b>XHTML 1.1 compliance</b>, <b>{@link SForm_Elm_Select::lock()}
  18.  * intrinsic rules}</b>, <b>nested groups</b>, and <b>{@link }
  19.  * http://www.webstandards.org/learn/tutorials/accessible-forms/beginner/
  20.  * automatic accessibility features}</b> like
  21.  * <label> tags, optgroups, JavaScript highlighting of errors, and access keys.
  22.  * I've tried to keep the API very similar to QuickForm, but I've removed things
  23.  * that I found unnecessary or redundant.
  24.  * 
  25.  * SForm is also <i>fast</i>.
  26.  * 
  27.  * The small form that I originally used to develop SForm had a render time of
  28.  * 0.10-0.28 seconds when no form was created. Using QuickForm to build a form
  29.  * on the page increased the render time to 0.33-0.68 secs. Replacing QuickForm
  30.  * with SForm dropped the render time to 0.18-0.42 secs. SForm is almost
  31.  * <b>three times faster</b> than QuickForm in this informal test. (All trials
  32.  * used eAccelerator with caching and optimization enabled.)
  33.  * 
  34.  * <b>Example</b>:
  35.  * 
  36.  * The SForm API is intentionally very similar to QuickForm. This example was
  37.  * converted directly from the {@link http://pear.php.net/manual/en/package.}
  38.  * html.html-quickform.tutorial.php QuickForm documentation}. All of the
  39.  * examples on {@link http://www.midnighthax.com/quickform.php Keith Edmunds's}
  40.  * QuickForm tutorial} also work with similar modifications. It should be noted
  41.  * that to make this example XHTML 1.1 compliant, the elements would need to be
  42.  * grouped in a fieldset.
  43.  * 
  44.  * <code>
  45.  * <?php
  46.  * // Load the main classes
  47.  * require_once 'SForm.php';
  48.  * 
  49.  * // Instantiate the SForm object
  50.  * $form = new SForm('firstForm');
  51.  * 
  52.  * // Set defaults for the form elements
  53.  * $form->setDefaults(array( 'name' => 'Joe User' ));
  54.  * 
  55.  * // Add some elements to the form
  56.  * $form->addElement('header', null, 'SForm tutorial example');
  57.  * $form->addElement('text', 'name', 'Enter your name:',
  58.  *                     array('size' => 50, 'maxlength' => 255));
  59.  * $form->addElement('submit', null, 'Send');
  60.  * 
  61.  * // Define filters and validation rules
  62.  * $form->applyFilter('name', 'trim');
  63.  * $form->addRuleDep('name', 'Please enter your name', 'required', null,'client');
  64.  * 
  65.  * // Try to validate a form
  66.  * if ($form->validate()) {
  67.  *     echo       '<h1>Hello, ' . htmlspecialchars($form->exportValue('name')) . '!</h1>';
  68.  *     exit;
  69.  * }
  70.  * 
  71.  * // Output the form
  72.  * $form->display();
  73.  * ?>
  74.  * </code>
  75.  *
  76.  *
  77.  * Note that in the example, addRule() has been changed to {@link }
  78.  * SForm::addRuleDep() addRuleDep()}, where 'Dep' stands for
  79.  * depriciated. For more information, see the {@link SForm_Rule}
  80.  * SForm_Rule documentation}.
  81.  * 
  82.  * <b>Putting it all together</b>:
  83.  * 
  84.  * {@example  SForm_usage.php}
  85.  * Look in SForm_usage.php for this source
  86.  * 
  87.  * <b>Important differences with the QuickForm API include</b>:
  88.  * 
  89.  * - The construtor. The $target attribute {@link SForm::__construct() has been removed} because it breaks compliance  with XHTML 1.1.
  90.  * - Rule creation and usage. SForm uses a much more object oriented {@link SForm_Rule Rule model}.
  91.  * 
  92.  * <b>Requirements</b>:
  93.  * 
  94.  * SForm requires at least PHP version 5, but version 5.1 is recommended.
  95.  *
  96.  * <b>License</b>:
  97.  * 
  98.  * Copyright (c) 2006, Seth Price <{@link mailto:seth@pricepages.org seth@pricepages.org}> All rights reserved.
  99.  *
  100.  * Redistribution and use in source and binary forms, with or without
  101.  * modification, are permitted provided that the following conditions
  102.  * are met:
  103.  *
  104.  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  105.  * - 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.
  106.  * - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  107.  *
  108.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  109.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  110.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  111.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  112.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  113.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  114.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  115.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  116.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  117.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  118.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  119.  *
  120.  * @copyright    Copyright (c) 2006, Seth Price
  121.  * @author        Seth Price <seth@pricepages.org>
  122.  * @license        http://opensource.org/licenses/bsd-license.php New BSD License
  123.  * @access        public
  124.  * @package        SForm
  125.  * @see            SForm::__construct()
  126.  * @version        0.1.2
  127.  */
  128.  
  129. /**
  130.  * Document with:
  131.  * ./phpdoc -t ../docs -f ../scripts/SForm.php -d ../scripts/SForm -ti "SForm" -o HTML:frames:phpdoc.de
  132.  * 
  133.  * @ignore
  134.  */
  135.  
  136. /**
  137.  * FIXME - clean up exception messages and values.
  138.  * @deprecated
  139.  * @ignore
  140.  */
  141. define('FORM_ERR_PARENT_EXISTS'1);
  142.  
  143. /**
  144.  * Create the count interface if needed
  145.  * 
  146.  * The Countable interface is not enabled by default in PHP 5.0, so we'll
  147.  * enable it.
  148.  * 
  149.  * @ignore
  150.  */
  151. if(!interface_exists('Countable')){
  152.     interface Countable{
  153.         public function count();
  154.     }
  155. }
  156.  
  157. /**
  158.  * A element is a "leaf" that is actually rendered
  159.  * 
  160.  * All elements and containers are decendents of this class. That said, this
  161.  * class is intended to cover as much common functionality as possible.
  162.  * 
  163.  * @access        public
  164.  * @package        SForm
  165.  */
  166. abstract class SForm_Element {
  167.     
  168.     /**
  169.      * This is the element's tag ('input', 'select', 'form', etc) and should be
  170.      * set by the children as needed.
  171.      * 
  172.      * @var        string 
  173.      */
  174.     protected $tag = 'input';
  175.     
  176.     /**
  177.      * Is this element's tag empty?
  178.      * 
  179.      * A tag is empty if it can't contain elements. Examples of empty tags
  180.      * include '<input />' and '<img />'. Non-empty tags include '<form>' and
  181.      * '<fieldset>'.
  182.      * 
  183.      * @var        boolean 
  184.      */
  185.     protected $emptyTag = true;
  186.     
  187.     /**
  188.      * Should we add the 'name' attribute?
  189.      * 
  190.      * @var        boolean 
  191.      */
  192.     protected $addName = true;
  193.     
  194.     /**
  195.      * What is the parent to this element?
  196.      * 
  197.      * This points to some subclass of SForm_Container. It is set in
  198.      * SForm_Container::addElement().
  199.      * 
  200.      * @var        object 
  201.      */
  202.     protected $parent = null;
  203.     
  204.     /**
  205.      * Has the value for this element been set?
  206.      * 
  207.      * @var        boolean 
  208.      */
  209.     protected $valueSet = false;
  210.     
  211.     /**
  212.      * The local ID for this element
  213.      * 
  214.      * @var        string 
  215.      */
  216.     private $localId;
  217.     
  218.     /**
  219.      * The cached Global ID for this element
  220.      * 
  221.      * @var        string 
  222.      */
  223.     private $globalId;
  224.     
  225.     /**
  226.      * Is this element frozen?
  227.      * 
  228.      * @var        boolean 
  229.      */
  230.     private $frozen false;
  231.     
  232.     /**
  233.      * The label for this element
  234.      * 
  235.      * @var        string 
  236.      */
  237.     private $label;
  238.     
  239.     /**
  240.      * Rules to apply to this element
  241.      * 
  242.      * @var        array 
  243.      */
  244.     protected $rules = array();
  245.     
  246.     /**
  247.      * Are the rules & filters prevented from being modified?
  248.      * 
  249.      * When the rules are rendered (or otherwise used), they are locked to
  250.      * prevent someone from adding an additonal rule. This would invalidate any
  251.      * calculations done using the previous ruleset.
  252.      * 
  253.      * @var        boolean 
  254.      */
  255.     protected $locked = false;
  256.     
  257.     /**
  258.      * Is this a required element?
  259.      * 
  260.      * If an element is marked as required, the 'required' rule is added at
  261.      * render time and modifications can be made to the label HTML which mark
  262.      * this element as required.
  263.      * 
  264.      * @var        boolean 
  265.      */
  266.     protected $required = false;
  267.     
  268.     /**
  269.      * Has this element been validated?
  270.      * 
  271.      * Validated elements have a finished $errors array and will not be
  272.      * validated again.
  273.      * 
  274.      * @var        boolean 
  275.      */
  276.     protected $validated = false;
  277.     
  278.     /**
  279.      * A list of callback
  280.      */
  281.     
  282.     /**
  283.      * What were the results of the validation?
  284.      * 
  285.      * All error strings returned by validation are saved here. If there is no
  286.      * error string, then there was no error.
  287.      * 
  288.      * @var        array 
  289.      */
  290.     protected $errors = array();
  291.     
  292.     /**
  293.      * A list of filter callbacks
  294.      * 
  295.      * @var        array 
  296.      * @see        filter()
  297.      */
  298.     protected $filters = array();
  299.     
  300.     /**
  301.      * What access keys have been used already?
  302.      * 
  303.      * The default ones are in use by various systems.
  304.      * 
  305.      * @var        array 
  306.      * @todo        Comment in which default keys come from which sources.
  307.      */
  308.     private static $accesskeys array(
  309.                         'f' => true,
  310.                         'e' => true,
  311.                         'v' => true,
  312.                         'g' => true,
  313.                         'a' => true,
  314.                         't' => true,
  315.                         'h' => true,
  316.                         'd' => true,
  317.                         's' => true,
  318.                         'b' => true,
  319.                         'w' => true );
  320.  
  321.     /**
  322.      * Number the elements if not given an ID
  323.      * 
  324.      * @var        integer 
  325.      */
  326.     static protected $defId = 0;
  327.     
  328.     /**
  329.      * Attributes for this tag
  330.      * 
  331.      * @var        array 
  332.      */
  333.     protected $attributes = array();
  334.     
  335.     /**
  336.      * Global settings
  337.      * 
  338.      * Charset ('charset') is the the type of chars used to encode the HTML.
  339.      * 
  340.      * Auto access key ('autoAccessKey') controls whether access keys are
  341.      * automatically assigned to labels.
  342.      * 
  343.      * The element error color ('elmErrorColor') is the background color used on
  344.      * elements which threw some sort of error.
  345.      * 
  346.      * Xml close ('xmlClose') alters how the HTML tags are drawn. Mainly, do
  347.      * empty tags end in ' />' (XML, XHTML) or '>' (HTML).
  348.      * 
  349.      * Track id ('trackId') is the Local ID of the hidden element used to track
  350.      * this form.
  351.      * 
  352.      * The separator ('separator') is used in the Global ID to create sub-groups
  353.      * of elements.
  354.      * 
  355.      * The new submit value ('newSubmitValue') is what the JavaScript writes
  356.      * into the values of submit buttons when the form is successfully
  357.      * submitted.
  358.      * 
  359.      * The maximum upload file size ('maxFileSize') is the maximum upload file
  360.      * size. It defaults to 2 MB, which is the PHP 'upload_max_filesize' default.
  361.      * 
  362.      * @var        array 
  363.      * @see        self::getLabelHtml()
  364.      * @see        SForm_Renderer::rendJavaScript()
  365.      * @access    public
  366.      */
  367.     private static $options array(
  368.         'charset' => 'UTF-8',
  369.         'autoAccessKey' => 'ak',
  370.         'elmErrorColor' => 'yellow',
  371.         'xmlClose' => true,
  372.         'trackId' => '_trk',
  373.         'separator' => '-'// Can be used in CSS selectors
  374.                 'newSubmitValue' => ' Loading... ',
  375.         'maxFileSize' => '2097152',
  376.         'requiredSym' => '<span style="color:red;">*</span>');
  377.     
  378.     /**
  379.      * The rules
  380.      * 
  381.      * @var        array 
  382.      */
  383.     private static $regRules array(
  384.         'boolean' => array('SForm/Rule/Boolean.php''SForm_Rule_Boolean'),
  385.         'maxlength' => array(false'SForm_Rule_Maxlenth'),
  386.         'subset' => array(false'SForm_Rule_Subset'),
  387.         'required' => array(false'SForm_Rule_Required') );
  388.     
  389.     
  390.     /**
  391.      * Construct with an identity. Optionally add a label and more attributes.
  392.      * 
  393.      * $id is the Local ID of this element, and is similar to its name. If none
  394.      * is supplied, one will be created. The Local ID is different than the 'id'
  395.      * attribute that appears in the rendered form. The id attribute is referred
  396.      * to as the Global ID, and is the concatenation of the parent's Global ID,
  397.      * a colon, and this Local ID. The ID must contain only chars acceptable in
  398.      * the ID SGML token for the resulting page to be valid.
  399.      * 
  400.      * The $label is the the short string that describes this element. If it is
  401.      * rendered as HTML, it will automatically be enclosed in '<label>' tags
  402.      * which referr to the Global ID of this element. If enabled, an appropriate
  403.      * access key will also be found for this element. The access key will be
  404.      * rendered with '<span class="ak">' and '</span>' tags surrounding it. If
  405.      * you wish to communicate with your users that this access key may be used
  406.      * to access this field, I suggest that you use CSS like: '.ak{text-
  407.      * decoration:underline;}'.
  408.      * 
  409.      * Any additonal $attributes may be added and will be displayed when the
  410.      * element is rendered as HTML.
  411.      * 
  412.      * @param    string    Local ID of this element
  413.      * @param    string    Textual label for this element
  414.      * @param    array    Array of extra attributes
  415.      * @link        http://www.w3.org/TR/html4/types.html#h-6.2
  416.      * @see        getLocalId()
  417.      * @see        getGlobalId()
  418.      * @see        getLabel()
  419.      * @see        getLabelHtml()
  420.      */
  421.     public function __construct($id null$label null$attributes null){
  422.         
  423.         /*
  424.          * We should try to dynamically make this as needed from what the parent
  425.          * recommends (if avl). Also check the $attributes array for 'name' and
  426.          * 'id'.
  427.          */
  428.         if($id === null){
  429.             $id self::$defId++;
  430.         }
  431.         
  432.         $this->localId = (string) $id;
  433.         $this->label = (string) $label;
  434.         
  435.         //Apply each attribute
  436.         if(!empty($attributes)){
  437.             if(!is_array($attributes)){
  438.                 throw new Exception('Please form attributes in array("attribute" ' .
  439.                         '=> "value", ...) form. It\'s ' .
  440.                         'much faster than parsing a string.');
  441.             }
  442.             
  443.             foreach($attributes as $name => $value){
  444.                 $this->setAttribute($name$value);
  445.             }
  446.         }
  447.     }
  448.     
  449.     /**
  450.      * This is the page-wide unique ID
  451.      * 
  452.      * The Global ID is a page-unique id which is a concatination of the
  453.      * parent's Global ID, a colon, and the Local ID. This is done so that
  454.      * element groups can be nested arbitrarily deeply with non-uniqe IDs (ex:
  455.      * an array: '0','1','2'...).
  456.      * 
  457.      * The Global ID is used as the 'id' HTML attribute and is typically also
  458.      * used as the 'name' attribute.
  459.      * 
  460.      * The Global ID cannot be determined until the element is "rooted" to a
  461.      * SForm object.
  462.      * 
  463.      * @return    string    A globally usable ID
  464.      */
  465.     public function getGlobalId(){
  466.         if($this->globalId){
  467.             return $this->globalId;
  468.         }
  469.         
  470.         if(empty($this->parent)){
  471.             throw new Exception('An element must have a parent, please assign me to one. I\'m lonely.');
  472.         }
  473.         
  474.         return $this->globalId $this->parent->getGlobalId().
  475.                                 self::getOption('separator').
  476.                                 $this->getLocalId();
  477.     }
  478.     
  479.     /**
  480.      * A Locally usable ID
  481.      * 
  482.      * The local ID is the name that a parent uses to identify its child
  483.      * element.
  484.      * 
  485.      * @return    string    A locally usable ID
  486.      */
  487.     public function getLocalId(){
  488.         return $this->localId;
  489.     }
  490.     
  491.     /**
  492.      * The value of this element
  493.      * 
  494.      * The value returned is the same as if the form was rendered, displayed,
  495.      * then submitted by the user, in its current state.
  496.      * 
  497.      * @return    mixed    Element's current value
  498.      * @see        setValue()
  499.      * @see        setDefault()
  500.      * @uses        $valueSet    Is the current value overridden by setValue?
  501.      */
  502.     public function getValue(){
  503.         
  504.         //Return the set value
  505.         if($this->valueSet){
  506.             return $this->filterValue($this->getAttribute('value'));
  507.         }
  508.  
  509.         //Return the value of whatever was submited
  510.         $val $this->getRequest($this->getGlobalId());
  511.         if($val !== null){
  512.             $this->setValue($val);
  513.             return $this->filterValue($val);
  514.         }
  515.         
  516.         //Return what we have
  517.         return $this->filterValue($this->getAttribute('value'));
  518.     }
  519.     
  520.     /**
  521.      * Filters the value
  522.      * 
  523.      * Filters a value that is about to be returned from {@link getValue()}.
  524.      * 
  525.      * @var        string    Raw value
  526.      * @return    string    Filtered value
  527.      * @see        getValue()
  528.      */
  529.     protected function filterValue($val){
  530.         foreach($this->filters as $fil){
  531.             $func $fil[0];
  532.             $fil[0$val;
  533.             $val call_user_func_array($func$fil);
  534.         }
  535.         return $val;
  536.     }
  537.     
  538.     /**
  539.      * Set the value of this element
  540.      * 
  541.      * This sets the current value of the element, overriding any current value.
  542.      * 
  543.      * @param    string    Element's value.
  544.      * @see        setDefault()
  545.      * @uses        $valueSet    Overrides any current value
  546.      */
  547.     public function setValue($value){
  548.         $this->valueSet = true;
  549.         $this->setAttribute('value'$value);
  550.     }
  551.     
  552.     /**
  553.      * Set the default value of this element
  554.      * 
  555.      * This sets the default value of this element. If there is a user-submitted
  556.      * value or a {@link setValue()}, it overrides this one.
  557.      * 
  558.      * @param    string    Element's value.
  559.      * @see        setValue()
  560.      */
  561.     public function setDefault($value){
  562.         if(!$this->valueSet){
  563.             $this->setAttribute('value'$value);
  564.         }
  565.     }
  566.  
  567.     /**
  568.      * Markup a label with an accesskey
  569.      * 
  570.      * Marks up the first char in a label with CSS that can show the user which
  571.      * key is the access key for this form. If none of the chars match the
  572.      * requested one, the char is appended to the end of the label in
  573.      * parenthesis.
  574.      * 
  575.      * The class of the access key is set with the autoAccessKey option.
  576.      * 
  577.      * @param    string    Label to be marked up
  578.      * @param    string    Selected char
  579.      * @return    string    Marked up string
  580.      */
  581.     private function markupLabelAK($lbl$ak){
  582.         
  583.         $cls self::getOption('autoAccessKey');
  584.         
  585.         if(($i stripos($lbl$ak)) === false){
  586.             return self::escHtml($lbl).' (<span class="'.$cls.'">'.$ak.'</span>)';
  587.         else {
  588.             
  589.             /*
  590.              * We need to markup the access key, but escape HTML at the same
  591.              * time. It's more tricky than it sounds because we don't wan't to
  592.              * escape any chars in the HTML tags.
  593.              */
  594.             $offset strlen(self::escHtml(substr($lbl0$i)));
  595.             $replen strlen(self::escHtml(substr($lbl$i1)));
  596.             
  597.             $esc self::escHtml($lbl);
  598.             
  599.             return substr_replace($esc'<span class="'.$cls.'">'.$lbl[$i].'</span>'$offset$replen);
  600.         }
  601.     }
  602.     
  603.     /**
  604.      * Generates an access key and updated label
  605.      * 
  606.      * Uses an access key to generate a marked up label.
  607.      * 
  608.      * If access key is simply true, it generates an access key by searching
  609.      * through the chars of the label for the first one that is a regular char
  610.      * ([a-z0- 9]) and is not already in use. If none are found, numbers
  611.      * starting from the left side of the std keyboard are used.
  612.      * 
  613.      * @param    string    Label
  614.      * @param    string    Requested access key
  615.      * @return    mixed    Array of updated label if possible, null otherwise
  616.      * @uses        markupLabelAK()    Markup the label with the found key
  617.      * @uses        $accesskeys    Determine which keys are used
  618.      */
  619.     protected function autoAK($lbl$ak){
  620.         //Use requested access key
  621.         if($ak !== true){
  622.             return array($ak$this->markupLabelAK($lbl$ak));
  623.         }
  624.         
  625.         $lblLower strtolower($lbl);
  626.         $len strlen($lbl);
  627.  
  628.         //Look for an access key in the label
  629.         for($i 0$i $len$i++){
  630.             $chr $lblLower[$i];
  631.             //Only regular ASCII chars
  632.             if(ctype_alnum($chr&& !isset(self::$accesskeys[$chr])){
  633.                 
  634.                 self::$accesskeys[$chrtrue;
  635.                 return array($chr$this->markupLabelAK($lbl$chr));
  636.             }
  637.         }
  638.         
  639.         //Look for an access key in numbers
  640.         for($i 1$i 11$i++){
  641.             $ak = (string) $i%10;
  642.             if(empty(self::$accesskeys[$ak])){
  643.                 self::$accesskeys[$aktrue;
  644.                 return array($ak$this->markupLabelAK($lbl$ak));
  645.             }
  646.         }
  647.         
  648.         //Give up
  649.         return null;
  650.     }
  651.     
  652.     /**
  653.      * Return the label with markup added
  654.      * 
  655.      * Generates the marked up 'label' tag. This includes the 'for' attribute,
  656.      * which links this label to the appropriate form element. The label allows
  657.      * software (and the people who use it) to understand the layout of your
  658.      * form without being able to view it. If the element is required, the value
  659.      * of option 'requiredSym' is appended to the beginning of the label.
  660.      * 
  661.      * If requested, an access key is generated, and added to the tag. The
  662.      * access key links this label to a specific element. Not only does an
  663.      * access key make the page more friendly for people with motor
  664.      * disabilities, it will now be easier to quickly navigate the fields in
  665.      * your form for data entry.
  666.      * 
  667.      * Access keys can be enabled, set, or disabled via the passed parameters.
  668.      * By default, $ak is true and an access key is generated. If a char is
  669.      * passed, that char is used as the access key and the label is marked up
  670.      * approriately. If false is passed, no access key is used.
  671.      * 
  672.      * @param    mixed    Do we automatically generate an access key?
  673.      * @uses        autoAK()
  674.      * @link        http://www.webstandards.org/learn/tutorials/accessible-forms/beginner/
  675.      * @link        http://alistapart.com/articles/accesskeys/
  676.      */
  677.     public function getLabelHtml($accesskey true){
  678.         $lbl $this->getLabel();
  679.         
  680.         //Nothing special when frozen (or non-existant)
  681.         if($lbl === '' || $this->isFrozen()){
  682.             return $lbl;
  683.         }
  684.         
  685.         //If it has these attributes, we should be able to do some work transparently
  686.         if(self::getOption('autoAccessKey'&& $accesskey && !$this->getAttribute('disabled')){
  687.             $ak $this->autoAK($lbl$accesskey);
  688.         else {
  689.             $ak null;
  690.         }
  691.         
  692.         //Add the required symbol
  693.         if($this->required){
  694.             $req self::getOption('requiredSym');
  695.         else {
  696.             $req '';
  697.         }
  698.         
  699.         $id $this->getGlobalId();
  700.  
  701.         //Create and save the label
  702.         if($ak === null){
  703.             return $req.'<label for="'.$id.'">'.self::escHtml($lbl).'</label>';
  704.         else {
  705.             return $req.'<label for="'.$id.'" accesskey="'.$ak[0].'">'.$ak[1].'</label>';
  706.         }
  707.     }
  708.     
  709.     /**
  710.      * Return the label string (no markup)
  711.      * 
  712.      * @return    string    The raw label
  713.      * @uses        $label
  714.      */
  715.     public function getLabel(){
  716.         return $this->label;
  717.     }
  718.     
  719.     /**
  720.      * Locks & adds any last minute stuff
  721.      * 
  722.      * This is here to be overridden as needed by subclasses to add any rules,
  723.      * filters, and values immidiately before they are locked. Remember to check
  724.      * if it's already locked first, because an element may be locked more than
  725.      * once.
  726.      * 
  727.      * @uses        $locked
  728.      */
  729.     protected function lock(){
  730.         
  731.         $this->locked = true;
  732.     }
  733.     
  734.     /**
  735.      * Retrieve any JavaScript that should be run onsubmit
  736.      * 
  737.      * Locks the rules and iterates through them to produce a JavaScript string
  738.      * appropriate to be run on submission of the form.
  739.      * 
  740.      * @return    string    Rendered JavaScript
  741.      * @uses        lock()
  742.      * @uses        SForm_Rule::toJavaScript()
  743.      */
  744.     public function toJavaScript(){
  745.         $this->lock();
  746.         if($this->isFrozen(|| empty($this->rules)){
  747.             return '';
  748.         }
  749.         
  750.         $js '';
  751.         foreach($this->rules as $rule){
  752.             $js .= $rule->toJavaScript();
  753.         }
  754.         return $js;
  755.     }
  756.     
  757.     /**
  758.      * Create a HTML representation of this element
  759.      * 
  760.      * Creates a HTML tag appropriate to input a value for this element. At
  761.      * minimum, 'id' and 'name' attributes are included. Additonal attributes
  762.      * can be added via the methods appropriate for the element. Custom
  763.      * attributes can be added via setAttribute().
  764.      * 
  765.      * If this element is frozen, the string representation is returned instead
  766.      * of an input field.
  767.      * 
  768.      * @return    string    HTML
  769.      * @see        setAttribute()
  770.      * @uses        $attributes
  771.      */
  772.     public function toHtml(){
  773.         
  774.         if($this->isFrozen()){
  775.             return $this->toString();
  776.         }
  777.         
  778.         $id $this->getGlobalId();
  779.         $this->setAttribute('id'$id);
  780.         
  781.         //Some elements can't have a name attribute
  782.         if($this->addName){
  783.             $this->setAttribute('name'$id);
  784.         }
  785.         
  786.         if($this->emptyTag){
  787.             if(self::getOption('xmlClose')){
  788.                 $slash ' /';
  789.             else {
  790.                 $slash '';
  791.             }
  792.             
  793.             return '<'.$this->tag.self::getAttributesString($this->attributes).$slash.'>';
  794.         else {
  795.             return '<'.$this->tag.self::getAttributesString($this->attributes).'>';
  796.         }
  797.     }
  798.  
  799.     /**
  800.      * Close an open HTML tags
  801.      * 
  802.      * @return    string    The HTML needed to close this element
  803.      * @see        toHtml()
  804.      */
  805.     public function toHtmlFin(){
  806.         if($this->emptyTag){
  807.             return '';
  808.         else {
  809.             return '</'.$this->tag.'>';
  810.         }
  811.     }
  812.     
  813.     /**
  814.      * Do we have any hidden tags to append?
  815.      * 
  816.      * Append hidden tags seperately from regular HTML tags so they can be
  817.      * collected at the end of the form. That way the visible part of the form
  818.      * is parsed and displayed to the user first.
  819.      * 
  820.      * By default, hidden elements are only generated when this element is
  821.      * frozen.
  822.      * 
  823.      * @return    string    HTML of the hidden element
  824.      * @uses        $attributes
  825.      */
  826.     public function toHidden(){
  827.         if(!$this->isFrozen()){
  828.             return '';
  829.         }
  830.         
  831.         $id $this->getGlobalId();
  832.         $this->setAttribute('id'$id);
  833.         $this->setAttribute('name'$id);
  834.         
  835.         if(self::getOption('xmlClose')){
  836.             $slash ' /';
  837.         else {
  838.             $slash '';
  839.         }
  840.         
  841.         return '<input type="hidden"'.self::getAttributesString($this->attributes).$slash.'>';
  842.     }
  843.     
  844.     /**
  845.      * How does this value look as a HTML string?
  846.      * 
  847.      * @return    string    HTML
  848.      * @uses        getValue()
  849.      */
  850.     function toString(){
  851.         return self::escHtml($this->getValue());
  852.     }
  853.     
  854.     /**
  855.      * Set a global option
  856.      * 
  857.      * All available options are pre-set in the {@link $options} array, so we
  858.      * will be avoiding some frusteration if we throw an error on a bad option
  859.      * selection. Otherwise, we could think that we are retrieving a null value,
  860.      * but we have just misspelled the index.
  861.      * 
  862.      * @param    string    Option name
  863.      * @param    string    Option value
  864.      * @uses        $options
  865.      */
  866.     final public static function setOption($name$value){
  867.         if(isset(self::$options[$name])){
  868.             self::$options[$name$value;
  869.         else {
  870.             throw new Exception('The requested option "'.$name.'" doesn\'t exist.');
  871.         }
  872.     }
  873.     
  874.     /**
  875.      * Get a global option
  876.      * 
  877.      * All available options are pre-set in the {@link $options} array, so we
  878.      * will be avoiding some frusteration if we throw an error on a bad option
  879.      * selection. Otherwise, we could think that we are retrieving a null value,
  880.      * but we have just misspelled the index.
  881.      * 
  882.      * @param    string    Option name
  883.      * @return    string    Option value
  884.      * @uses        $options
  885.      */
  886.     final public static function getOption($name){
  887.         if(isset(self::$options[$name])){
  888.             return self::$options[$name];
  889.         else {
  890.             throw new Exception('The requested option "'.$name.'" doesn\'t exist.');
  891.         }
  892.     }
  893.     
  894.     /**
  895.      * Set an attribute in $this
  896.      * 
  897.      * @param    string    Attribute to set
  898.      * @param    string    Value to set (sets value = attribute if null)
  899.      * @uses        $attributes
  900.      */
  901.     public function setAttribute($attr$value null){
  902.         $attr strtolower($attr);
  903.         if($value === null){
  904.             $this->attributes[$attr$attr;
  905.         else {
  906.             $this->attributes[$attr= (string) $value;
  907.         }
  908.     }
  909.     
  910.     /**
  911.      * Unset an attribute
  912.      * 
  913.      * @param    string    Attribute to unset
  914.      * @uses        $attributes
  915.      */
  916.     public function unsetAttribute($attr){
  917.         unset($this->attributes[strtolower($attr)]);
  918.     }
  919.     
  920.     /**
  921.      * Get an attribute in $this
  922.      * 
  923.      * @param    string    Attribute to get
  924.      * @return    string    Attribute's value
  925.      * @uses        $attributes
  926.      */
  927.     public function getAttribute($attr){
  928.         $attr strtolower($attr);
  929.         if(isset($this->attributes[$attr])){
  930.             return $this->attributes[$attr];
  931.         else {
  932.             return null;
  933.         }
  934.     }
  935.     
  936.     /**
  937.      * Creates an HTML attribute string from an array
  938.      * 
  939.      * @param    array    Attributes as: attribute => value
  940.      * @return    string    HTML string
  941.      */
  942.     protected static function getAttributesString($attributes){
  943.         $str '';
  944.         foreach($attributes as $attr => $value){
  945.             $str .= ' '.$attr.'="'.self::escHtml($value).'"';
  946.         }
  947.         return $str;
  948.     }
  949.     
  950.     /**
  951.      * Escape HTML in a consistant manner
  952.      * 
  953.      * @param    string    Text to escape
  954.      * @return    string    Text HTML escaped
  955.      */
  956.     public static function escHtml($str){
  957.         return htmlspecialchars(    $str,
  958.                                 ENT_QUOTES,
  959.                                 self::getOption('charset'));
  960.     }
  961.     
  962.     /**
  963.      * Append this leaf element to the render queue
  964.      * 
  965.      * The renderer is passed to this element so it can call whichever method is
  966.      * wanted. (ie: toHtml(), toJavaScript(), and/or toString()).
  967.      * 
  968.      * @param    object    The renderer to use
  969.      * @uses        SForm_Renderer::append()
  970.      */
  971.     protected function render(SForm_Renderer $rend){
  972.         $rend->append($this);
  973.     }
  974.     
  975.     /**
  976.      * Add a rule to this element
  977.      * 
  978.      * A rule assigned to this element will be controled by this element. All
  979.      * validation will come through this element, and any errors will be
  980.      * returned via this element. All JavaScript will also be transmitted via
  981.      * this element.
  982.      * 
  983.      * If the first argument is a rule object, it will simply be added to this
  984.      * element. If it is a string, it is assumed that you want to {@link }
  985.      * createRule() create a rule} which is linked to this element.
  986.      * 
  987.      * Each rule can only be assigned to one element, but that element does not
  988.      * need to be used by the rule. For example: an element might validate two
  989.      * elements in a group, but if the element is added to the group, then any
  990.      * errors will be displayed at the group level.
  991.      * 
  992.      * @param    mixed    The rule to add
  993.      * @return    object    The added rule
  994.      * @uses        $locked
  995.      * @uses        SForm_Rule::assignParent()
  996.      */
  997.     public function addRule($rule,    $message null$arg2 null$arg3 null,
  998.                                     $arg4 null$arg5 null$arg6 null){
  999.         
  1000.         //Create the rule if needed
  1001.         if(is_string($rule)){
  1002.             $rule $this->createRule($rule$message$arg2$arg3$arg4$arg5$arg6);
  1003.         elseif(!($rule instanceof SForm_Rule)){
  1004.             throw new Exception('The passed parameter was neither a rule ' .
  1005.                     'object or the string of a type.');
  1006.         }
  1007.         
  1008.         //Can't add rules to locked elements
  1009.         if($this->locked){
  1010.             /*
  1011.              * The results from the rule set have been calculated. If we add any
  1012.              * more rules, it will make the previous result invalid (and maybe
  1013.              * incorrect).
  1014.              */
  1015.             throw new Exception('Attempting to add a rule when they\'ve already ' .
  1016.                     'been used. You\'re doing this in the wrong order.');
  1017.         }
  1018.         
  1019.         //Special rules
  1020.         if($rule instanceof SForm_Rule_Required){
  1021.             $this->required = true;
  1022.         }
  1023.         
  1024.         $class get_class($rule);
  1025.         
  1026.         /*
  1027.          * We should be able to avoid adding duplicate rules to an element,
  1028.          * I'll add exceptions as I find them.
  1029.          */
  1030.         if(isset($this->rules[$class])){
  1031.             trigger_error('A rule of type "'.$class.'" has already been added to this element "'.$this->getLocalId().'".'E_USER_NOTICE);
  1032.         }
  1033.         
  1034.         $this->rules[$class$rule;
  1035.         $rule->assignParent($this);
  1036.         
  1037.         return $rule;
  1038.     }
  1039.     
  1040.     /**
  1041.      * Creates & returns a rule
  1042.      * 
  1043.      * @param    string    Name of the rule
  1044.      * @param    string    Error message for the rule
  1045.      * @return    object    Created rule
  1046.      */
  1047.     protected function createRule($rule,    $message null$arg2 null$arg3 null,
  1048.                                         $arg4 null$arg5 null$arg6 null){
  1049.         
  1050.         $rule strtolower($rule);
  1051.         
  1052.         if(empty(self::$regRules[$rule])){
  1053.             throw new Exception('The given rule type "'.$rule.'" is not built in. ' .
  1054.                     'If you are adding a custom rule, please simply add ' .
  1055.                     'the object via addRule(). You might be trying to use addRule() ' .
  1056.                     'with the QuickForm API. Please refer to the docs.');
  1057.         }
  1058.         
  1059.         $meta self::$regRules[$rule];
  1060.         
  1061.         if($meta[0&& !class_exists($meta[1])){
  1062.             require $meta[0];
  1063.         }
  1064.         
  1065.         /*
  1066.          * This is an ugly way of creating an object with an "arbitrary"
  1067.          * number of arguments. If you know of a better way, please contact
  1068.          * me. :)
  1069.          */
  1070.         $rule new $meta[1]($message$this$arg2$arg3$arg4$arg5$arg6);
  1071.         
  1072.         if(!($rule instanceof SForm_Rule)){
  1073.             throw new Exception('Rule not a subclass of SForm_Rule.');
  1074.         }
  1075.         
  1076.         return $rule;
  1077.     }
  1078.     
  1079.     /**
  1080.      * Apply a filter to the value
  1081.      * 
  1082.      * This is passed a callback function which applies some sort of filter to
  1083.      * any value returned from {@link getValue()}. Any additonal arguments to
  1084.      * this function will be appended to the arguments of the callback function.
  1085.      * 
  1086.      * This cannot be called after {@link validate()} or {@link render()},
  1087.      * because the element is locked.
  1088.      * 
  1089.      * @param    mixed    Callback function
  1090.      */
  1091.     public function filter($callback){
  1092.         if($this->locked){
  1093.             throw new Exception('Attempted to add a filter to a locked element.');
  1094.         }
  1095.         
  1096.         $this->filters[func_get_args();
  1097.     }
  1098.     
  1099.     /**
  1100.      * Do we have any errors?
  1101.      * 
  1102.      * Did this element's validation generate any errors? If it did, return
  1103.      * those errors as an HTML string. Each error is seperated by a '<br />'
  1104.      * tag.
  1105.      * 
  1106.      * @return    string    Any errors that were produced
  1107.      */
  1108.     public function getErrorHtml(){
  1109.         if($this->validate()){
  1110.             return '';
  1111.         else {
  1112.             if(self::getOption('xmlClose')){
  1113.                 return implode('<br />'$this->errors);
  1114.             else {
  1115.                 return implode('<br>'$this->errors);
  1116.             }
  1117.         }
  1118.     }
  1119.     
  1120.     /**
  1121.      * Is this a valid element?
  1122.      * 
  1123.      * Performs server side validation using any of the added rules. Calls
  1124.      * {@link lock()} to provide a chance to load any intrinsic rules.
  1125.      * 
  1126.      * @return    boolean    Returns true if no errors are found.
  1127.      * @uses        SForm_Rule::toHtml()
  1128.      * @uses        lock()
  1129.      */
  1130.     protected function validate(){
  1131.         if(!$this->wasSubmitted()){
  1132.             return false;
  1133.         elseif($this->validated){
  1134.             return empty($this->errors);
  1135.         }
  1136.         
  1137.         $this->lock();
  1138.         $this->validated = true;
  1139.         $id $this->getGlobalId();
  1140.         
  1141.         foreach($this->rules as $rule){
  1142.             if($rule->isError()){
  1143.                 $this->errors[$rule->toString();
  1144.             }
  1145.         }
  1146.         
  1147.         return empty($this->errors);
  1148.     }
  1149.     
  1150.     /**
  1151.      * Set if this element is frozen
  1152.      * 
  1153.      * @param    boolean    Is it frozen?
  1154.      * @uses        $frozen
  1155.      */
  1156.     public function freeze($bool true){
  1157.         $this->frozen $bool;
  1158.     }
  1159.     
  1160.     /**
  1161.      * Is this element marked as frozen?
  1162.      * 
  1163.      * @return    boolean    Is this element frozen?
  1164.      * @uses        $frozen
  1165.      */
  1166.     public function isFrozen(){
  1167.         return $this->frozen;
  1168.     }
  1169.     
  1170.     /**
  1171.      * Is this element required?
  1172.      * 
  1173.      * @return    boolean    True if this element is required
  1174.      * @uses        $required
  1175.      */
  1176.     public function isRequired(){
  1177.         return $this->required;
  1178.     }
  1179.     
  1180.     /**
  1181.      * Assign this element's parent
  1182.      * 
  1183.      * @param    object    Parent to assign
  1184.      * @see        SForm_Container::addElement()
  1185.      * @uses        $parent
  1186.      */
  1187.     protected function assignParent(SForm_Container $parent){
  1188.         if($this->parent){
  1189.             throw new Exception('Parent already exists'FORM_ERR_PARENT_EXISTS);
  1190.         }
  1191.         
  1192.         $this->parent = $parent;
  1193.     }
  1194.     
  1195.     /**
  1196.      * Returns a value from the request
  1197.      * 
  1198.      * @param    string    Value to retrieve
  1199.      * @return    mixed    Value or null
  1200.      */
  1201.     protected function getRequest($val){
  1202.         if($this->parent){
  1203.             return $this->parent->getRequest($val);
  1204.         else {
  1205.             throw new Exception('Unable to get a request value until I have a ' .
  1206.                     'parent.');
  1207.         }
  1208.     }
  1209.     
  1210.     /**
  1211.      * Was this form submitted?
  1212.      * 
  1213.      * @return    boolean    Has it been submitted?
  1214.      */
  1215.     public function wasSubmitted(){
  1216.         if($this->parent){
  1217.             return $this->parent->wasSubmitted();
  1218.         else {
  1219.             throw new Exception('I need to be grounded in a parent.');
  1220.         }
  1221.     }
  1222. }
  1223.  
  1224. /**
  1225.  * A container holds one or more elements
  1226.  * 
  1227.  * A container serves as a parent for as many elements (and other containers) as
  1228.  * needed. It also inherits all of the properties of SForm_Element, so you can
  1229.  * assign rules to containers, they can produce JavaScript, they have labels,
  1230.  * and have opening and closing HTML tags.
  1231.  * 
  1232.  * @access        public
  1233.  * @package        SForm
  1234.  */
  1235. abstract class SForm_Container extends SForm_Element implements RecursiveIteratorCountable {
  1236.     /**
  1237.      * Containers are never empty tags
  1238.      * 
  1239.      * @var        boolean 
  1240.      */
  1241.     protected $emptyTag = false;
  1242.     
  1243.     /**
  1244.      * Children of this container
  1245.      * 
  1246.      * @var        array 
  1247.      */
  1248.     private $children array();
  1249.     
  1250.     /**
  1251.      * Filters which will be applied to the children of this container
  1252.      * 
  1253.      * @var        array 
  1254.      */
  1255.     protected    $chFilters = array();
  1256.     
  1257.     /**
  1258.      * Rules which will be applied to the children of this container
  1259.      * 
  1260.      * @var        array 
  1261.      */
  1262.     protected    $chRules = array();
  1263.     
  1264.     /**
  1265.      * List of registered elements
  1266.      * 
  1267.      * 'name' => array('include str', 'class name')
  1268.      * 
  1269.      * @var        array 
  1270.      */
  1271.     private static $regElms array(
  1272.             'fieldset' => array(false'SForm_Fieldset'),
  1273.             'header' => array('SForm/Elm/Header.php''SForm_Elm_Header'),
  1274.             'hidden' => array(false'SForm_Elm_Hidden'),
  1275.             'checkbox' => array(false'SForm_Elm_Checkbox'),
  1276.             'text' => array(false'SForm_Elm_Text'),
  1277.             'submit' => array(false'SForm_Elm_Submit'),
  1278.             'reset' => array(false'SForm_Elm_Reset'),
  1279.             'textarea' => array(false'SForm_Elm_Textarea'),
  1280.             'select' => array(false'SForm_Elm_Select'),
  1281.             'group' => array(false'SForm_Group'));
  1282.     
  1283.     /**
  1284.      * Does the current element have children?
  1285.      * 
  1286.      * Implemented as part of the {@link http://www.php.net/~helly/php/ext/spl/interfaceRecursiveIterator.html RecursiveIterator}
  1287.      * SPL interface}
  1288.      * 
  1289.      * @return    boolean    Is the current element a container?
  1290.      */
  1291.     final public function hasChildren(){
  1292.         return current($this->childreninstanceof SForm_Container;
  1293.     }
  1294.     
  1295.     /**
  1296.      * Return the current element
  1297.      * 
  1298.      * Implemented as part of the {@link http://www.php.net/~helly/php/ext/spl/interfaceRecursiveIterator.html RecursiveIterator}
  1299.      * SPL interface}
  1300.      * 
  1301.      * @return    object    The current child
  1302.      */
  1303.     final public function getChildren(){
  1304.         if($this->hasChildren()){
  1305.             return current($this->children);
  1306.         else {
  1307.             return null;
  1308.         }
  1309.     }
  1310.     
  1311.     /**
  1312.      * Move the current element to the first one
  1313.      * 
  1314.      * Implemented as part of the {@link http://www.php.net/manual/en/language.oop5.iterations.php}
  1315.      * Iterator SPL interface}
  1316.      */
  1317.     final public function rewind(){
  1318.         reset($this->children);
  1319.     }
  1320.     
  1321.     /**
  1322.      * Return the current element
  1323.      * 
  1324.      * Implemented as part of the {@link http://www.php.net/manual/en/language.oop5.iterations.php}
  1325.      * Iterator SPL interface}
  1326.      * 
  1327.      * @return    object 
  1328.      */
  1329.     final public function current(){
  1330.         return current($this->children);
  1331.     }
  1332.     
  1333.     /**
  1334.      * Return the key of the current element
  1335.      * 
  1336.      * Implemented as part of the {@link http://www.php.net/manual/en/language.oop5.iterations.php}
  1337.      * Iterator SPL interface}
  1338.      * 
  1339.      * @return    string    Returns the current Local ID
  1340.      */
  1341.     final public function key(){
  1342.         return key($this->children);
  1343.     }
  1344.     
  1345.     /**
  1346.      * Advance pointer and return the new current
  1347.      * 
  1348.      * Implemented as part of the {@link http://www.php.net/manual/en/language.oop5.iterations.php}
  1349.      * Iterator SPL interface}
  1350.      * 
  1351.      * @return    object    New current object
  1352.      */
  1353.     final public function next(){
  1354.         return next($this->children);
  1355.     }
  1356.     
  1357.     /**
  1358.      * Is the current ponter valid?
  1359.      * 
  1360.      * Implemented as part of the {@link http://www.php.net/manual/en/language.oop5.iterations.php}
  1361.      * Iterator SPL interface}
  1362.      * 
  1363.      * @return    boolean    True if the current pointer is valid
  1364.      */
  1365.     final public function valid(){
  1366.         return (boolean) current($this->children);
  1367.     }
  1368.     
  1369.     /**
  1370.      * Return number of children
  1371.      * 
  1372.      * Implemented as part of the {@link http://www.php.net/~helly/php/ext/spl/interfaceCountable.html Countable SPL interface}
  1373.      * 
  1374.      * @return    integer    How many children are there
  1375.      */
  1376.     final public function count(){
  1377.         return count($this->children);
  1378.     }
  1379.     
  1380.     /**
  1381.      * Adds an element to this container
  1382.      * 
  1383.      * This function has behavior that changes depending on the first argument
  1384.      * passed to it.
  1385.      * 
  1386.      * If the first argument is an element object, it is added to this container
  1387.      * as a child.
  1388.      * 
  1389.      * If the first argument is a string, the arguments are first passed through
  1390.      * {@link createElement()} and an element is created. That element is then
  1391.      * added to this container as a child.
  1392.      * 
  1393.      * The element is then returned.
  1394.      * 
  1395.      * @param    mixed    Element or type of element to add
  1396.      * @return    object    Added element
  1397.      * @uses        $children
  1398.      * @uses        createElement()
  1399.      */
  1400.     public function addElement($elm,    $arg1 null$arg2 null$arg3 null,
  1401.                                     $arg4 null$arg5 null$arg6 null){
  1402.         
  1403.         if(is_string($elm)){
  1404.             $elm $this->createElement($elm$arg1$arg2$arg3$arg4$arg5$arg6);
  1405.         elseif(!($elm instanceof SForm_Element)){
  1406.             throw new Exception('The passed parameter was neither an element ' .
  1407.                     'object or the string of a type.');
  1408.         }
  1409.         
  1410.         $elmId $elm->getLocalId();
  1411.         if(isset($this->children[$elmId])){
  1412.             throw new Exception('Child by this ID already exists');
  1413.         }
  1414.         
  1415.         $elm->assignParent($this);
  1416.         
  1417.         $this->children[$elmId$elm;
  1418.         
  1419.         return $elm;
  1420.     }
  1421.     
  1422.     /**
  1423.      * Creates an element
  1424.      * 
  1425.      * Creates whichever argument is specified by the first argument.
  1426.      * Valid strings are generally the same as the the element that you are
  1427.      * trying to create. A '<select>' element is created with a 'select' string.
  1428.      * 
  1429.      * Parameters past the first will be passed to the constructor of the
  1430.      * created object.
  1431.      * 
  1432.      * @param    string    Type of element to add.
  1433.      * @param    mixed    First argument for object constructor...
  1434.      * @return    object    Created object.
  1435.      * @uses        $regElms
  1436.      */
  1437.     public static function createElement($elm,    $arg1 null$arg2 null$arg3 null,
  1438.                                                 $arg4 null$arg5 null$arg6 null){
  1439.         $elm strtolower($elm);
  1440.         
  1441.         if(empty(self::$regElms[$elm])){
  1442.             throw new Exception('The given element type "'.$elm.'" is not built in. ' .
  1443.                     'If you have created a custom element, please simply ' .
  1444.                     'pass the object to addElement().');
  1445.         }
  1446.         
  1447.         $meta self::$regElms[$elm];
  1448.         
  1449.         if($meta[0&& !class_exists($meta[1])){
  1450.             require $meta[0];
  1451.         }
  1452.         
  1453.         /*
  1454.          * This is an ugly way of creating an object with an "arbitrary"
  1455.          * number of arguments. If you know of a better way, please contact
  1456.          * me. :)
  1457.          */
  1458.         $elm new $meta[1]($arg1$arg2$arg3$arg4$arg5$arg6);
  1459.         
  1460.         if(!($elm instanceof SForm_Element)){
  1461.             throw new Exception('Element not a subclass of SForm_Element.');
  1462.         }
  1463.         
  1464.         return $elm;
  1465.     }
  1466.     
  1467.     /**
  1468.      * Returns an element using the Global ID
  1469.      * 
  1470.      * The Global ID is the ID used in the HTML document or by getElementById()
  1471.      * in JavaScript.
  1472.      * 
  1473.      * @param    string    Global ID to fetch
  1474.      * @return    object    Requested object (or null)
  1475.      */
  1476.     public function getGlobalElement($id){
  1477.         if(!$this->parent){
  1478.             throw new Exception('Unable to retrieve an element by Global ID when ' .
  1479.                     'this container isn\'t rooted to a form. Please add me to ' .
  1480.                     'a form.');
  1481.         }
  1482.         
  1483.         $this->parent->getGlobalElement($id);
  1484.     }
  1485.     
  1486.     /**
  1487.      * Returns an element using the Local ID
  1488.      * 
  1489.      * The Local ID is the ID that passed to an element's constructor. It is
  1490.      * useful for reequesting a specific child of an element. For iterating
  1491.      * through elements, see the Iterator interface. Specifically {@link }
  1492.      * next()}.
  1493.      * 
  1494.      * @param    string    Local ID to fetch
  1495.      * @return    object    Requested obect (or null)
  1496.      */
  1497.     public function getElement($id){
  1498.         $id = (string) $id;
  1499.         if(empty($this->children[$id])){
  1500.             return null;
  1501.         else {
  1502.             return $this->children[$id];
  1503.         }
  1504.     }
  1505.     
  1506.     /**
  1507.      * Removes an element from this group
  1508.      * 
  1509.      * @param    string    The id of the object to remove
  1510.      * @return    object    The removed object
  1511.      */
  1512.     public function removeElement($id){
  1513.         $ret $this->getElement($id);
  1514.         unset($this->children[$id]);
  1515.         return $ret;
  1516.     }
  1517.     
  1518.     /**
  1519.      * Apply a filter to the elements
  1520.      * 
  1521.      * This is the same as using {@link SForm_Element::filter()}, but the first
  1522.      * argument is the child to apply this filter to.
  1523.      * 
  1524.      * The filter is not applied to child elements until this container is
  1525.      * {@link render() rendered} or {@link validate() validated}. If the child
  1526.      * doesn't exist at that time, an exception is thrown.
  1527.      * 
  1528.      * If you wish to apply the filter to <i>this</i> container's value, see
  1529.      * {@link filter()}.
  1530.      * 
  1531.      * @param    string    Element to apply the filter too
  1532.      * @param    mixed    The filter callback
  1533.      * @see        SForm_Element::filter()
  1534.      * @see        render()
  1535.      * @see        validate()
  1536.      */
  1537.     public function applyFilter($element$filter){
  1538.         $this->chFilters[func_get_args();
  1539.     }
  1540.     
  1541.     /**
  1542.      * Create a HTML representation of this container
  1543.      * 
  1544.      * Constructs the HTML tag that opens this container. Normally this is
  1545.      * either a <form> tag or a <fieldset> tag. The 'id' attribute is
  1546.      * automatically filled with this container's Global ID. Other attributes
  1547.      * may be added via {@link setAttribute()}.
  1548.      * 
  1549.      * @return    string    Opening tag for this container
  1550.      * @see        setAttribute()
  1551.      */
  1552.     public function toHtml(){
  1553.         $id $this->getGlobalId();
  1554.         $this->setAttribute('id'$id);
  1555.         return '<'.$this->tag.self::getAttributesString($this->attributes).'>';
  1556.     }
  1557.     
  1558.     /**
  1559.      * Add any requested filters and rules, then lock this container
  1560.      * 
  1561.      * We override the parent in order to add filters and rules.
  1562.      * 
  1563.      * @see        applyFilter()
  1564.      */
  1565.     protected function lock(){
  1566.         
  1567.         if($this->locked){
  1568.             return;
  1569.         }
  1570.         
  1571.         //Apply all filters
  1572.         foreach($this->chFilters as $filter){
  1573.             $elmId array_shift($filter);
  1574.             
  1575.             if($elmId == '__ALL__'){
  1576.                 foreach($this->children as $elm){
  1577.                     call_user_func_array(array($elm,'filter')$filter);
  1578.                 }
  1579.             else {
  1580.                 if($elm $this->getElement($elmId)){
  1581.                     call_user_func_array(array($elm,'filter')$filter);
  1582.                 else {
  1583.                     throw new Exception('The element "'.$elmId.'" wasn\'t ' .
  1584.                             'found when we were trying to apply a filter.');
  1585.                 }
  1586.             }
  1587.         }
  1588.         
  1589.         parent::lock();
  1590.     }
  1591.     
  1592.     /**
  1593.      * Is this valid?
  1594.      * 
  1595.      * Validates this container and all of its children. Keep in mind that no
  1596.      * rules can be added after an element is validated because that would
  1597.      * invalidate the validation. If there was no request submitted, it is
  1598.      * assumed that validation is not passed.
  1599.      * 
  1600.      * Rules can be added via {@link addRule()}.
  1601.      * 
  1602.      * @return    boolean    Is this container & its children valid?
  1603.      * @uses        parent::validate()
  1604.      */
  1605.     protected function validate(){
  1606.         
  1607.         //No request? No validate.
  1608.         if(!$this->wasSubmitted()){
  1609.             return false;
  1610.         }
  1611.         
  1612.         //Validate all children
  1613.         $valid parent::validate();
  1614.         foreach($this->children as $child){
  1615.             if(!$child->validate()){
  1616.                 $valid false;
  1617.             }
  1618.         }
  1619.         return $valid;
  1620.     }
  1621.     
  1622.     /**
  1623.      * Set the frozen status of this container and its children
  1624.      * 
  1625.      * Setting a container as frozen. Being frozen doesn't have an effect on
  1626.      * containers. {@link parent::freeze() Frozen elements} will prevent their
  1627.      * values from being altered, though.
  1628.      * 
  1629.      * @param    boolean    Are we freezing this or thawing it?
  1630.      * @uses        parent::freeze()
  1631.      */
  1632.     public function freeze($bool true){
  1633.         $this->frozen $bool;
  1634.         foreach($this->children as $child){
  1635.             $child->freeze($bool);
  1636.         }
  1637.     }
  1638.     
  1639.     /**
  1640.      * Render this container and all elements in it
  1641.      * 
  1642.      * @param    object    The renderer to use
  1643.      * @uses        parent::render()
  1644.      */
  1645.     protected function render(SForm_Renderer $rend){
  1646.         $this->lock();
  1647.         $rend->push($this);
  1648.         foreach($this->children as $child){
  1649.             $child->render($rend);
  1650.         }
  1651.         $rend->pop($this);
  1652.     }
  1653. }
  1654.  
  1655. /**
  1656.  * I am the alpha and the omega, I am the form.
  1657.  * 
  1658.  * The SForm is the root for all elements, and they will not fully funcion
  1659.  * without being rooted to a SForm. All rendering operations should begin
  1660.  * with {@link SForm::render()}, validation begins with {@link SForm::validate()},
  1661.  * and processing can be accomplished with {@link SForm::toArray()}. For more
  1662.  * information on SForm usage, see the {@link SForm.php package documentation}.
  1663.  * 
  1664.  * @package    SForm
  1665.  * @see        __construct()
  1666.  * @see        SForm.php
  1667.  */
  1668. class SForm extends SForm_Container {
  1669.     /**
  1670.      * Why yes, this is a form
  1671.      * 
  1672.      * @var        string 
  1673.      */
  1674.     protected $tag = 'form';
  1675.     
  1676.     /**
  1677.      * Defaults are stored here
  1678.      * 
  1679.      * @var        array 
  1680.      * @deprecated
  1681.      */
  1682.     protected $defaults = array();
  1683.     
  1684.     /**
  1685.      * Are we tracking the submit?
  1686.      * 
  1687.      * @var        boolean 
  1688.      */
  1689.     protected $trackSubmit;
  1690.     
  1691.     /**
  1692.      * Create a form
  1693.      * 
  1694.      * The ID parameter sets both the Global ID and the Local ID to be the same,
  1695.      * since we are at the top level. All children of this form will use this ID
  1696.      * as a prefix to their Global IDs.
  1697.      * 
  1698.      * The method determines how we will send the data that is input. A 'get'
  1699.      * method sends the data in the URL after a '?' and 'post' sends the data in
  1700.      * the body of the HTTP request. An advantage of using 'get' is the
  1701.      * results of the submission can be book marked. A disadvantage of using
  1702.      * 'get' is that a form can be maliciously submitted without the user's
  1703.      * knowledge by making them access the URL (ex: an embedded imag). 'get'
  1704.      * requests are also awkward for large amounts of data.
  1705.      * 
  1706.      * The action is what URL to send the data to. Generally you want to send
  1707.      * the data back to the original URL so you can continue processing it and
  1708.      * present errors if needed.
  1709.      * 
  1710.      * Include extra attributes if you feel lucky.
  1711.      * 
  1712.      * $trackSubmit inserts an extra hidden element. If this element isn't
  1713.      * included in the submission, it is ignored. It defaults to true. Be
  1714.      * careful when setting this to false, because the submission may then
  1715.      * validate without a button ever being clicked. You might want to add a
  1716.      * required rule to prevent this from happening.
  1717.      * 
  1718.      * <b>Changes from HTML_QuickForm</b>:
  1719.      * $target has been removed because it is not in XHTML 1.1. Use $attributes
  1720.      * if you wish to add it back.
  1721.      * 
  1722.      * $action is required in XHTML 1.1, so if one isn't provided, $_SERVER
  1723.      * ['SCRIPT_URL'] is used.
  1724.      * 
  1725.      * $trackSubmit has been set to true by default, because otherwise a form
  1726.      * without required elements may validate without being submitted.
  1727.      * 
  1728.      * $id does not apply a 'name' attribute to the form because this is illegal
  1729.      * in XHTML 1.1.
  1730.      * 
  1731.      * @param    string    This form's ID
  1732.      * @param    string    Method attribute of the form ('post' or 'get')
  1733.      * @param    string    Action attribute of the form
  1734.      * @param    array    Custom attributes for this form
  1735.      * @param    boolean    Track whether this form was submitted.
  1736.      * @uses        $request
  1737.      */
  1738.     function __construct($id$method 'post'$action ''$attributes null$trackSubmit true){
  1739.         
  1740.         parent::__construct($id''$attributes);
  1741.         
  1742.         //Set the method
  1743.         switch($method){
  1744.             case 'post':
  1745.                 $this->request =$_POST;
  1746.                 break;
  1747.             case 'get':
  1748.                 $this->request =$_GET;
  1749.                 break;
  1750.             case 'request':
  1751.                 $this->request =$_REQUEST;
  1752.                 $method 'post';
  1753.                 break;
  1754.             default:
  1755.                 throw new Exception('Method type not recognized: '.$method);
  1756.         }
  1757.         
  1758.         $this->setAttribute('method'$method);
  1759.         
  1760.         //Set the action (required in XHTML 1.1)
  1761.         if($action === ''){
  1762.             //Send form to self
  1763.             if(isset($_SERVER['SCRIPT_URL'])){
  1764.                 $action $_SERVER['SCRIPT_URL'];
  1765.             }
  1766.             // SCRIPT_URL is not set in PHP 5.1
  1767.             elseif(isset($_SERVER['SCRIPT_NAME'])){
  1768.                 $action $_SERVER['SCRIPT_NAME'];
  1769.             }
  1770.             //Hopefully the browser will take care of it
  1771.             else {
  1772.                 $action '';
  1773.             }
  1774.         }
  1775.         
  1776.         $this->setAttribute('action'$action);
  1777.         
  1778.         //If we are tracking submit, then we need to have a hidden field
  1779.         if($trackSubmit){
  1780.             //Setup a required hidden element
  1781.             $tid SForm_Element::getOption('trackId');
  1782.             
  1783.             $this->addElement(new SForm_Elm_Hidden($tid''array('value' => true)));
  1784.             
  1785.             $this->trackSubmit = true;
  1786.         }
  1787.     }
  1788.     
  1789.     /**
  1790.      * Get an element by its Global ID
  1791.      * 
  1792.      * Retrieves an element based on it's Global ID. When at all possible, I'd
  1793.      * suggest that you retrieve elements via {@link getElement()}.
  1794.      * 
  1795.      * @param    string    Global ID
  1796.      * @return    object    Requested element (or null)
  1797.      */
  1798.     public function getGlobalElement($id){
  1799.         return $this->recursiveGetElement(strval($id)$this);
  1800.     }
  1801.     
  1802.     /**
  1803.      * Find an element by its Global ID
  1804.      * 
  1805.      * @param    string    Part of Global ID
  1806.      * @param    object    Container we are currently searching in
  1807.      * @return    object    The requested element
  1808.      */
  1809.     private function recursiveGetElement($idSForm_Container $cont){
  1810.         $locId $id;
  1811.         $sep self::getOption('separator');
  1812.         
  1813.         while(!($elm $cont->getElement($locId))){
  1814.             if(($i strrpos($locId$sep)) === false){
  1815.                 return null;
  1816.             }
  1817.             
  1818.             $locId substr($locId0$i);
  1819.         }
  1820.         
  1821.         if($id == $locId){
  1822.             return $elm;
  1823.         }
  1824.         
  1825.         if(($i strpos($id$sep)) === false){
  1826.             return null;
  1827.         }
  1828.         
  1829.         return $this->recursiveGetElement(substr($id$i+1)$elm);
  1830.     }
  1831.     
  1832.     /**
  1833.      * All children of this form inheret their IDs from it.
  1834.      * 
  1835.      * When {@link parent::getGlobalId()} is called on a child, it calls its
  1836.      * parent's getGlobalId() method and appends the results to its own
  1837.      * Local ID. When the calling reaches the SForm level, it's at the root and
  1838.      * there are no more parents, so the Local ID is returned.
  1839.      * 
  1840.      * @return    string    The Local and Global ID
  1841.      * @uses        getLocalId()
  1842.      */
  1843.     public function getGlobalId(){
  1844.         return $this->getLocalId();
  1845.     }
  1846.     
  1847.     /**
  1848.      * Returns a nested array of all returned values
  1849.      * 
  1850.      * Uses the default renderer to collect the values of all elements into an
  1851.      * array. If objects are nested, then the array is nested also.
  1852.      * 
  1853.      * @return    array    Values of this form in an array.
  1854.      * @uses        SForm_Renderer
  1855.      */
  1856.     public function toArray(){
  1857.         $rend new SForm_Renderer();
  1858.         $this->render($rend);
  1859.         return $rend->fetch($this);
  1860.     }
  1861.     
  1862.     /**
  1863.      * Was this form submitted?
  1864.      * 
  1865.      * @return    boolean    Has it been submitted?
  1866.      */
  1867.     public function wasSubmitted(){
  1868.         //Use the tracking element
  1869.         if($this->trackSubmit){
  1870.             $id SForm_Element::getOption('trackId');
  1871.             return (bool) $this->getRequest($this->getElement($id)->getGlobalId());
  1872.         }
  1873.         //This is how we track the form without tracking it
  1874.         else {
  1875.             return !empty($this->request);
  1876.         }
  1877.     }
  1878.     
  1879.     /**
  1880.      * Set the default values of local elements
  1881.      * 
  1882.      * This is here for HTML_QuickForm compatability. Please use
  1883.      * {@link SForm_Element::setDefault()} instead. The reasoning is that all
  1884.      * operations that are associated with perticular elements should operate on
  1885.      * those elements, so this usage makes more sense.
  1886.      * 
  1887.      * Also, when using QF, I found myself simply collecting values into an
  1888.      * array at the same layer as element creation. After the the array was
  1889.      * completed, I had to pass it through various layers of my program to
  1890.      * finally set the defaults. Using {@link SForm_Element::setDefault()} makes
  1891.      * things easier, also.
  1892.      * 
  1893.      * Internally, element defaults are stored and set in {@link render()} using
  1894.      * the {@link SForm_Element::setDefault()} method at render time. Any calls
  1895.      * to {@link SForm_Element::setDefault()} will be overridden by the
  1896.      * values in this form. The elements are retrieved using {@link }
  1897.      * getElement()}. This is due to the internal differences between QF and
  1898.      * SForm. It doesn't mesh exactly with QuickForm, and you have been warned.
  1899.      * Maybe you should use {@link SForm_Element::setDefault()} instead.
  1900.      *
  1901.      * @param    array    List of defaults for the elements.
  1902.      * @see        SForm_Element::setDefault()
  1903.      * @see        render()
  1904.      * @see        getElement()
  1905.      * @deprecated
  1906.      */
  1907.     public function setDefaults($defaults){
  1908.         foreach($defaults as $id => $def){
  1909.             $this->defaults[$id$def;
  1910.         }
  1911.     }
  1912.     
  1913.     /**
  1914.      * Returns an element's value
  1915.      * 
  1916.      * @param    string    Element to retrieve value for
  1917.      * @return    string    Element's value
  1918.      * @deprecated
  1919.      */
  1920.     public function exportValue($name){
  1921.         if($elm $this->getElement($name)){
  1922.             return $elm->getValue();
  1923.         else {
  1924.             throw new Exception('The element "'.$name.'" wasn\'t found.');
  1925.         }
  1926.     }
  1927.     
  1928.     /**
  1929.      * Add a rule to given field(s)
  1930.      * 
  1931.      * Please use {@link addRule()} instead. This is only here for compatability
  1932.      * reasons.
  1933.      * 
  1934.      * @param    mixed    Element name(s)
  1935.      * @param    string    Error message
  1936.      * @param    string    Type of rule
  1937.      * @param    string    Extra data
  1938.      * @param    string    Where to preform validation
  1939.      * @deprecated
  1940.      */
  1941.     public function addRuleDep($element$message$type$format ''$validation 'server'){
  1942.         $elms array();
  1943.         
  1944.         //Sort out the elements
  1945.         if(is_array($element)){
  1946.             foreach($element as $elmId){
  1947.                 if($elm $this->getElement($elmId)){
  1948.                     $elms[$elm;
  1949.                 else {
  1950.                     throw new Exception('Bad element name "'.$elmId.'"');
  1951.                 }
  1952.             }
  1953.             $baseElm $elms[0];
  1954.         else {
  1955.             if($elms $this->getElement($element)){
  1956.                 $baseElm $elms;
  1957.             else {
  1958.                 throw new Exception('Bad element name "'.$element.'"');
  1959.             }
  1960.         }
  1961.         
  1962.         //Create the rule
  1963.         if($validation == 'server'){
  1964.             $rule $baseElm->createRule($type$message$elms$format);
  1965.             $rule->validateOnClient(false);
  1966.             $baseElm->addRule($rule);
  1967.         else {
  1968.             $baseElm->addRule($type$message$elms$format);
  1969.         }
  1970.     }
  1971.     
  1972.     /**
  1973.      * Renders a form with the provided renderer
  1974.      * 
  1975.      * Render the form using the given {@link SForm_Renderer renderer}. If any
  1976.      * defaults have been applied using {@link setDefaults()}, they are applied
  1977.      * to the local elements here.
  1978.      * 
  1979.      * @param    object    The renderer to use
  1980.      * @uses        $defaults
  1981.      * @uses        parent::render()
  1982.      */
  1983.     public function render(SForm_Renderer $rend){
  1984.         //Apply all defaults
  1985.         foreach($this->defaults as $id => $def){
  1986.             if($elm $this->getElement($id)){
  1987.                 $elm->setDefault($def);
  1988.             else {
  1989.                 throw new Exception('The element "'.$id.'" wasn\'t found when ' .
  1990.                         'we were trying to apply it\'s default "'.$def.'".');
  1991.             }
  1992.         }
  1993.         
  1994.         $rend->startForm($this);
  1995.         parent::render($rend);
  1996.     }
  1997.     
  1998.     /**
  1999.      * Is this valid?
  2000.      * 
  2001.      * Validates this form. Keep in mind that no rules can be added after an
  2002.      * element is validated because that would invalidate the validation. If
  2003.      * there was no request submitted, it is assumed that validation is not
  2004.      * passed.
  2005.      * 
  2006.      * Rules can be added via {@link addRule()}.
  2007.      * 
  2008.      * @return    boolean    Is this form valid?
  2009.      * @uses        parent::validate()
  2010.      */
  2011.     public function validate(){
  2012.         return parent::validate();
  2013.     }
  2014.     
  2015.     /**
  2016.      * Prints this form to std out
  2017.      * 
  2018.      * @param    object    Renderer to use
  2019.      */
  2020.     public function display($rend null){
  2021.         
  2022.         if($rend == null){
  2023.             $rend new SForm_Renderer_HTML();
  2024.             
  2025.         elseif(!($rend instanceof SForm_Renderer)){
  2026.             throw new Exception('Passed renderer is not a subclass of ' .
  2027.                     'SForm_Renderer.');
  2028.         }
  2029.         
  2030.         $this->render($rend);
  2031.         echo $rend->fetch($this);
  2032.     }
  2033.     
  2034.     /**
  2035.      * Enables file uploading
  2036.      * 
  2037.      * This is not currently working. Please {@link mailto:}
  2038.      * sprice@pricepages.org request} if needed.
  2039.      */
  2040.     public function enableFileUpload(){
  2041.         throw new Exception('FIXME');
  2042.     }
  2043.     
  2044.     /**
  2045.      * Process the results of the form
  2046.      * 
  2047.      * This method is depreciated because it takes less code and you gain more
  2048.      * functionality if you just call the callback function yourself. Use {@link }
  2049.      * toArray()} to get the array, then call the function. That simple.
  2050.      * 
  2051.      * @param    mixed    Callback function
  2052.      * @deprecated
  2053.      */
  2054.     public function process($callback){
  2055.         return call_user_func($callback$this->toArray());
  2056.     }
  2057.     
  2058.     /**
  2059.      * Create a group and add it to this form
  2060.      * 
  2061.      * This is depreciated because you can just do this yourself by
  2062.      * {@link SForm_Group creating a group} (or {@link SForm_Fieldset fieldset})
  2063.      * and adding to the form via {@link SForm:: addElement()}. It's less
  2064.      * confusing, also.
  2065.      * 
  2066.      * @param    array    Elements in group
  2067.      * @param    string    Group name
  2068.      * @param    string    Group label
  2069.      * @return    object    Added group
  2070.      * @deprecated
  2071.      */
  2072.     public function addGroup($elements$id null$label null){
  2073.         $grp new SForm_Group($id$label);
  2074.         foreach($elements as $elm){
  2075.             $grp->addElement($elm);
  2076.         }
  2077.         return $this->addElement($grp);
  2078.     }
  2079.     
  2080.     /**
  2081.      * Returns a value from the request
  2082.      * 
  2083.      * @param    string    Value to retrieve
  2084.      * @return    mixed    Value or null
  2085.      */
  2086.     protected function getRequest($val){
  2087.         if(isset($this->request[$val])){
  2088.             return $this->request[$val];
  2089.         else {
  2090.             return null;
  2091.         }
  2092.     }
  2093.     
  2094.     /**
  2095.      * Temp method for testing
  2096.      * 
  2097.      * @deprecated
  2098.      * @ignore
  2099.      */
  2100.     function smartyFetch($file$addlVars null){
  2101.         if(!class_exists('ECSmarty'))
  2102.             require 'ECSmarty.php';
  2103.         $tpl new ECSmarty(false);//Disable Smarty cache
  2104.         
  2105.         $rndr new SForm_Renderer_Array();
  2106.         $this->render($rndr);
  2107.         
  2108.         $arr $rndr->fetch($this);
  2109. //        var_dump($arr);exit;
  2110.     
  2111.         $tpl->assign($arr);
  2112.         if($addlVars){
  2113.             $tpl->assign($addlVars);
  2114.         }
  2115.         return $tpl->fetch($file);
  2116.     }
  2117. }
  2118.  
  2119. /**
  2120.  * Group elements
  2121.  * 
  2122.  * @package        SForm
  2123.  */
  2124. class SForm_Group extends SForm_Container {
  2125.     /**
  2126.      * Return the label
  2127.      * 
  2128.      * @param    boolean    This isn't used here
  2129.      * @return    string    The label
  2130.      */
  2131.     public function getLabelHtml($accesskey true){
  2132.         if($lbl $this->getLabel()){
  2133.             return self::escHtml($lbl);
  2134.         else {
  2135.             return '';
  2136.         }
  2137.     }
  2138.     
  2139.     /**
  2140.      * Open the group
  2141.      * 
  2142.      * @return    string    Nothing. It's a boring group.
  2143.      */
  2144.     public function toHtml(){
  2145.         return '';
  2146.     }
  2147.     
  2148.     /**
  2149.      * Close this element group
  2150.      * 
  2151.      * @return    string    Nothing. It's a boring group.
  2152.      */
  2153.     public function toHtmlFin(){
  2154.         return '';
  2155.     }
  2156. }
  2157.  
  2158. /**
  2159.  * Group elements in a '<fieldset>'
  2160.  * 
  2161.  * Groups a set of elements in '<fieldset>' tags. All XHTML 1.1 compliant
  2162.  * scripts must have elements seperated from their surrounding form by tags such
  2163.  * as the fieldset. Also, it is pleasing to the eye and {@link http://www.webstandards.org/learn/tutorials/accessible-forms/intermediate/ more}
  2164.  * accessable} to use fieldsets to group associated elements.
  2165.  * 
  2166.  * Use the {@link __construct() label in the constructor} to create a legend for
  2167.  * this fieldset. The legend will be rendered in the html label, using {@link }
  2168.  * getLabelHtml()}.
  2169.  * 
  2170.  * You can group fieldsets within each other, so feel free to use them with
  2171.  * reckless abandon.
  2172.  * 
  2173.  * @package        SForm
  2174.  */
  2175. class SForm_Fieldset extends SForm_Container {
  2176.     /**
  2177.      * By default, render this as a fieldset
  2178.      * 
  2179.      * @var    string 
  2180.      */
  2181.     protected $tag = 'fieldset';
  2182.     
  2183.     /**
  2184.      * Return the label as a legend tag
  2185.      * 
  2186.      * Place a legend on this fieldset. The legend should be contained inside
  2187.      * the fieldset tags.
  2188.      * 
  2189.      * @param    boolean    This isn't used here
  2190.      * @return    string    The label as a legend tag
  2191.      */
  2192.     public function getLabelHtml($accesskey true){
  2193.         if($lbl $this->getLabel()){
  2194.             return '<legend>'.self::escHtml($lbl).'</legend>';
  2195.         else {
  2196.             return '';
  2197.         }
  2198.     }
  2199.     
  2200.     /**
  2201.      * Close this element group
  2202.      * 
  2203.      * @return    string    HTML that closes the group
  2204.      */
  2205.     public function toHtmlFin(){
  2206.         return '</'.$this->tag.'>';
  2207.     }
  2208. }
  2209.  
  2210. /**
  2211.  * Create rules for elements
  2212.  * 
  2213.  * The SForm rules are very different than the QuickForm rules. Their advantages
  2214.  * over QuickForm rules are twofold: They are designed to easily validate any
  2215.  * number of elements simultaneously. They are also designed to throw an error
  2216.  * (if needed) at any location in the form.
  2217.  * 
  2218.  * This means that you can easily create complex rules, such as validating a
  2219.  * credit card number depnding on which type of credit card is selected. You can
  2220.  * require a user to fill in identifying information if they don't have a user
  2221.  * name. You can better control filling out billing vs. shipping information.
  2222.  * You can also do any of this in JavaScript.
  2223.  * 
  2224.  * Much of this can even be done without defining a new class by using the
  2225.  * {@link SForm_Rule_Boolean} to create an on-the-fly server and client side
  2226.  * boolean statement.
  2227.  *
  2228.  * <b>Usage</b>:
  2229.  *
  2230.  * Because of the changes, the API is different. For example, this code from
  2231.  * the original quick start guide:
  2232.  *
  2233.  * <code>
  2234.  * $form->addElement('text', 'name', 'Enter your name:',
  2235.  *                     array('size' => 50, 'maxlength' => 255));
  2236.  *
  2237.  * $form->addRule('name', 'Please enter your name', 'required', null,'client');
  2238.  * </code>
  2239.  *
  2240.  *
  2241.  * Can be changed into this, for the same functionality:
  2242.  *
  2243.  * <code>
  2244.  * $form->addElement('text', 'name', 'Enter your name:',
  2245.  *                     array('size' => 50, 'maxlength' => 255));
  2246.  *
  2247.  * $form->addRuleDep('name', 'Please enter your name', 'required', null,'client');
  2248.  * </code>
  2249.  *
  2250.  *
  2251.  * Note that in the example, addRule() has been changed to {@link }
  2252.  * SForm::addRuleDep() addRuleDep()}, where 'Dep' stands for
  2253.  * depriciated.
  2254.  *
  2255.  *
  2256.  * The correct SForm usage, though, is (shorthand):
  2257.  *
  2258.  * <code>
  2259.  * $nameElm = $form->addElement('text', 'name', 'Enter your name:',
  2260.  *                     array('size' => 50, 'maxlength' => 255));
  2261.  * 
  2262.  * $nameElm->addRule('required', 'Please enter your name');
  2263.  * </code>
  2264.  * 
  2265.  *
  2266.  * Or even (fully object oriented):
  2267.  *
  2268.  * <code>
  2269.  * $nameElm = new SForm_Elm_Text('name', 'Enter your name:',
  2270.  *                     array('size' => 50, 'maxlength' => 255));
  2271.  * 
  2272.  * $reqRule = new SForm_Rule_Required('Please enter your name', $nameElm);
  2273.  * 
  2274.  * $nameElm->addRule($reqRule);
  2275.  *
  2276.  * $form->addElement($nameElm);
  2277.  * </code>
  2278.  * 
  2279.  * @package        SForm
  2280.  * @subpackage    Rules
  2281.  */
  2282. abstract class SForm_Rule {
  2283.     /**
  2284.      * Has this rule been asigned to an element?
  2285.      * 
  2286.      * @var        boolean 
  2287.      */
  2288.     protected $hasParent = false;
  2289.     
  2290.     /**
  2291.      * Should we use this rule with JavaScript?
  2292.      * 
  2293.      * @var        boolean 
  2294.      */
  2295.     protected $validateOnClient = true;
  2296.     
  2297.     /**
  2298.      * NO YUO!
  2299.      * 
  2300.      * The message that is returned when an error is thrown. {@link toHtml()}
  2301.      * can be overridden and the message made more dynamic, if desired.
  2302.      * 
  2303.      * @var        string 
  2304.      */
  2305.     protected $message;
  2306.     
  2307.     /**
  2308.      * Which elements fall under this jurisdiction?
  2309.      * 
  2310.      * @var        array 
  2311.      */
  2312.     protected $elements = array();
  2313.     
  2314.     /**
  2315.      * The names of local JavaScript variables
  2316.      * 
  2317.      * To save cycles and bytes, values are stored in local JavaScript variables
  2318.      * before they are used in client side validation. This array is indexed by
  2319.      * the Global ID, and the values are the names of the local variables.
  2320.      * 
  2321.      * @var        array 
  2322.      */
  2323.     static protected $jsElmVar = array();
  2324.     
  2325.     /**
  2326.      * The counter for temp local variables
  2327.      * 
  2328.      * @var        integer 
  2329.      */
  2330.     static protected $jsElmNum = 0;
  2331.     
  2332.     /**
  2333.      * Construct a rule with one element and one error message
  2334.      * 
  2335.      * The first argument is always the error message, and the second argument
  2336.      * is always an element or array of elements (as appropriate).
  2337.      * 
  2338.      * The elements passed here are not necessarily the element that validates
  2339.      * this rule and throws an error message. This way, the rule can
  2340.      * validate two or more fields in a group, but the error appears in the
  2341.      * group. This rule will later be added to said element or container with
  2342.      * {@link SForm_Element::addRule()}.
  2343.      * 
  2344.      * By default, the message is static, but you are encouraged to create
  2345.      * dynamic messages that address the specific error and element via {@link }
  2346.      * SForm_Element::getValue()} and {@link SForm_Element::getLabel()}
  2347.      * respectively. Make sure, though, that messages still work on both the
  2348.      * server side and client side.