Location: PHPKode > scripts > B-Forms > b-forms/b-forms.inc
<?

/* -------------------------------------------------------------

    B-Forms
    An object-oriented library to manage web-forms in PHP.

    Release: $Name:  $

    Copyright (C) 2004 Alexei Peterkin, hide@address.com

    This library is free software; you can redistribute it
    and/or modify it under the terms of the GNU Lesser General
    Public License as published by the Free Software
    Foundation; either version 2.1 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be
    useful, but WITHOUT ANY WARRANTY; without even the implied
    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
    PURPOSE. See the GNU Lesser General Public License for more
    details.

    You should have received a copy of the GNU Lesser General
    Public License along with this library; if not, write to
    the Free Software Foundation, Inc., 59 Temple Place, Suite
    330, Boston, MA 02111-1307 USA

---------------------------------------------------------------- */

/**
 * Base class for all displays.
 *
 * Most displays will take an optional parameter <var>$extras</var>,
 * which is some extra text that should be inserted into the corresponding
 * <input> or similar tag. This can be used for specifying tag style,
 * or to add JavaScript event handlers.
 *
 * @abstract
 */
class Display {
   /**
     * Automatic hidden-field generation indicator.
     *
     * If true, the property with this display will automatically save
     * its value as hidden field. By default - FALSE.
     *
     * @access private
     * @var bool
     */
   var $auto_store = FALSE;

   /**
    * Indicates whether this display expects a label.
    *
    * Used by standard layouts to determine whether to make a {@link label()} call.
    * Most displays will need a label, thus default value is FALSE.
    *
    * @access private
    * @var bool
    */
   var $no_label = FALSE;

   /**
    * Indicates whether this display expects a <label> tag around the label.
    *
    * Only makes sense when {@link $no_label} is FALSE.
    * By default - TRUE, as most widgets will expect a clicable label.
    *
    * @access private
    * @bar bool
    */
   var $use_label_tag = TRUE;

   /**
    * Generates the HTML code for the property.
    *
    * As default implementation, does nothing.
    *
    * @access: private
    * @abstract
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {} // To be overridden by implementations

   /**
    * Indicates read-only properties.
    *
    * This is used by the form's auto-detect-changes and validation mechamisms
    * to avoid detection of changes and validation of properties that cannot be
    * edited by the user.
    *
    * The default implementation returns FALSE.
    *
    * @returns bool
    * @access private
    */
   function is_readonly() {
      return FALSE;
   }
}

/**
 * Hidden value widget.
 *
 * Renders a property as <input type=hidden>, including it into the electronic signature.
 */
class HiddenField extends Display {

   /**
   * Constructor
   *
   * {@internal All this class does is sets the {@link Display::$auto_store} property
   * to true - and the <input type=hidden> tag will be automatically generated and
   * included in the signature. }
   */
   function HiddenField() {
      $this->auto_store = TRUE;
   }

   /**
    * Returns TRUE.
    *
    * This method overrides the {@link Display::is_readonly()} method to
    * always return TRUE, since this is a read-only property.
    *
    * @returns bool
    * @access private
    */
   function is_readonly() {
      return TRUE;
   }
}
/**
 * Button widget.
 *
 * This display is equivalent to <input type=submit> HTML tag.
 */
class SubmitButton extends Display {
   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * @access public
    * @param string optional, for explanation see {@link Display}.
    */
   function SubmitButton($extras="") {
      $this->extras = $extras;
   }

   /**
    * Returns TRUE.
    *
    * This method overrides the {@link Display::is_readonly()} method to
    * always return TRUE, since this is a read-only property.
    *
    * @returns bool
    * @access private
    */
   function is_readonly() {
      return TRUE;
   }

   /**
    * Generates the HTML code for the property.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      echo "<input type=\"submit\" name=\"".
           $property->get_form_name($rownum).
           "\" value=\"".
           htmlspecialchars($property->label).
           "\" ".$this->extras."/>\n";
   }
}

/**
 * Check box widget.
 */
class CheckBox extends Display {

   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * @access public
    * @param string optional, for explanation see {@link Display}.
    */
   function CheckBox($extras="") {
      $this->extras = $extras;
   }

   /**
    * Generates the HTML code for the property.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      $name = $property->get_form_name($rownum);
      echo '<input type="checkbox" name="'.$name.
           '" id="'.$name.
           '" value="'.$property->get_form_value($rownum).
           '"'.
           ($property->get_value($rownum)?" checked":"").
           " ".$this->extras."/>\n";
   }
}

/**
 * Read-only text widget.
 *
 * Can be used with text, numeric, or date properties.
 * The value is presented as a simple <span> for which you can specify $extras,
 * plus a hidden field, which ensures that the value is still there after the
 * the form is submitted and restored from the <var>$HTTP_POST_VARS</var>.
 * As with all hidden fields, the value is included in the
 * electronic signature - so it is very difficult to temper with the value.
 */
class TextDisplay extends Display  {
   /**
    * Contains text that should be included within the <span> tag.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * @access public
    * @param string the text that should be included within the <span> tag.
    */
   function TextDisplay($extras="") {
     $this->extras = $extras;
     $this->auto_store = TRUE;
     // The label for this field should not be clickable, since it cannot
     // be edited anyway
     $this->use_label_tag = FALSE;
   }

   /**
    * Generates the HTML code for the property.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      if ($this->extras != "") echo "<span ".$this->extras.">";
      echo $property->get_form_value($rownum);
      if ($this->extras != "") echo "</span>";
   }

   /**
    * Returns TRUE.
    *
    * This method overrides the {@link Display::is_readonly()} method to
    * always return TRUE, since this is a read-only property.
    *
    * @returns bool
    * @access private
    */
   function is_readonly() {
      return TRUE;
   }

}

/**
 * Password entry widget.
 *
 * This is a descendant of TextBox, the only difference being that the entered text
 * is masked with asterisks.
 */
class PasswordBox extends TextBox {
   /**
    * Constructor.
    *
    * {@internal The difference is achieved by assigning the value "password" to
    *            {@link TextBox::$boxtype} member.
    *
    * @access public
    * @param int optional, equivalent of the SIZE attibute of the INPUT tag. See {@link TextBox::TextBox()}
    * @param string optional, for explanation see {@link Display}.
    *
    */
   function PasswordBox($size = null, $extras = "" ) {
      $this->size = $size;
      $this->extras = $extras;
      $this->boxtype = "password";
   }

}

/**
 * Text entry widget.
 *
 * This widget is an equivalent of <input type=text>.
 */

class TextBox extends Display {

   /**
    * The size of the textbox in characters.
    *
    * @access private
    * @var int
    */
   var $size;

   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * Determines the behaviour of widget.
    *
    * {@internal If the value is "text", it shows the enter text in the clear, while
    * if it is "password", the entered text will be masked with asterisks. }
    *
    * @access private
    * @var string
    */
   var $boxtype;

   /**
    * @access public
    * @param int the size of the textbox in characters. If omitted, the
    *       {@link TextProperty::$length} value of the displayed property will
    *       be used. For other properties that do not have length,
    *       the size attribute of the <input type=text> tag will be omitted.
    * @param string optional, for explanation see {@link Display}.
    */
   function TextBox($size = null, $extras = "" ) {
      $this->size = $size;
      $this->extras = $extras;
      $this->boxtype = "text";
   }

   /**
    * Generates the HTML code for the property.
    *
    * For properties that have <var>$length</var> attribute, its value
    * will become the maximum allowed length for the widget.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      $name = $property->get_form_name($rownum);
      echo '<input type="'.$this->boxtype.'" name="'.$name.
           '" id="'.$name.
           '" value="'.$property->get_form_value($rownum);
      if (isset($this->size) || isset($property->length))
         echo "\" size=\"".
              (isset($this->size)?$this->size:$property->length);
      if (isset($property->length))
         echo "\" maxlength=\"".$property->length;
      echo "\" ".$this->extras."/>\n";
   }

}

/**
 * Multi-line text entry widget.
 *
 * This display is equivalent to the <textarea> HTML tag.
 */
class TextArea extends Display {
   /**
    * The cols attribute of <textarea>
    *
    * @access private
    * @var int
    */
   var $width;

   /**
    * The rows attribute of <textarea>
    *
    * @access private
    * @var int
    */
   var $height;

   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * @access public
    * @param int the width of textarea in characters;
    * @param int the height of textarea in characters;
    * @param string optional, for explanation see {@link Display}.
    */
   function TextArea($width, $height, $extras = "" ) {
      $this->width = $width;
      $this->height = $height;
      $this->extras = $extras;
   }

   /**
    * Generates the HTML code for the property.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      $name = $property->get_form_name($rownum);
      echo '<textarea wrap="physical" name="'.$name.
           '" id="'.$name.
           "\" cols=\"".
           $this->width.
           "\" rows=\"".
           $this->height.
           "\" ".$this->extras.">".
           $property->get_form_value($rownum).
           "</textarea>\n";;
   }

}
/**
 * DropDown widget.
 *
 * I usually use this display with numeric properties, where the value is numeric,
 * but what the user sees is text labels for those numbers.
 */
class DropDown extends Display {
   /**
    * An associative array of value=>label pairs.
    *
    * You can re-assign this array, but only before {@link Form::start_form()} call.
    * Typically, this array is assigned either in the constructor, in the
    * ON_OPEN and AFTER_RESTORE triggers. If you do ON_OPEN you usually have
    * to do AFTER_RESTORE too.
    *
    * @access public
    * @var array
    */
   var $values;

   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * @access public
    * @param array an associative array of value=>label pairs, that will be used in the actual radio-buttons on the form.
    * @param string optional, for explanation see {@link Display}.
    */
   function DropDown($values, $extras=null) {
       $this->values = $values;
       $this->extras = $extras;
   }

   /**
    * Generates the HTML code for the property.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
       $name = $property->get_form_name($rownum);
       echo '<select name="'.$name.
            '" id="'.$name.
            '" '.$this->extras.">\n";
       foreach ($this->values as $key => $value) {
          echo "<option value=\"".$key.'"';
          if ($key == $property->get_value($rownum)) echo " selected";
          echo ">".$value."</option>\n";
       }
       echo "</select>\n";
   }
}

/**
 * Radio buttons widget.
 *
 * This class is an alternative to DropDown. Its added value is that the user
 * sees all options at once, and can use hot keys. Not very good if you have a
 * lot of options.
 *
 * Also, currently it does not work with multi-row blocks.  Or, actually it does work,
 * but it will look strange.
 */

class RadioButtons extends Display {
   /**
    * An associative array of value=>label pairs.
    *
    * You can re-assign this array, but only before {@link Form::start_form()} call.
    * Typically, this array is assigned either in the constructor, in the
    * ON_OPEN and AFTER_RESTORE triggers. If you do ON_OPEN you usually have
    * to do AFTER_RESTORE too.
    *
    * @access public
    * @var array
    */
   var $values;

   /**
    * For explanation of usage see {@link Display}.
    *
    * @access private
    * @var string
    */
   var $extras;

   /**
    * The text that will be rendered between radio options.
    *
    * By default it equals to &lt;br/&gt;
    *
    * @access private
    * @var string
    */
   var $separator;

   /**
    * An associative array of value=>key pairs.
    *
    * The key will be used in the accesskey attribute of the <input type=radio>
    * tag. You can reassign this member using the same guidelines as for the {@link $values}
    * member.
    *
    * @access public
    * @var array
    */
   var $accesskeys;

   /**
    * @access public
    * @param array an associative array of value=>label pairs, that will be used in the actual radio-buttons on the form.
    * @param array optional, an associative array of value=>key pairs, that will be used as accesskey attribute of the <input type=radio> tags.
    * @param string optional, for explanation see {@link Display}. This parameter will be inserted
    *                         within the <input> tag. Currently $extras are not supported within
    *                         <label> tags.
    * @param string optional, the text that will be inserted between radio options.
    *                       By default - &lt;br/&gt;
    */
   function RadioButtons($values, $accesskeys=null, $extras=null, $separator="<br/>") {
       $this->values = $values;
       $this->extras = $extras;
       $this->separator = $separator;
       $this->accesskeys = $accesskeys;
       // The label for this field should not be clickable,
       // instead individual radio labels will be clickable.
       $this->use_label_tag = FALSE;
   }

   /**
    * Generates the HTML code for the property.
    *
    * At present the class uses line-break to separate radio buttons.
    * In the future versions of the library the HTML-layout will become
    * configurable.
    *
    * @access: private
    * @param Property the property to be displayed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($property, $rownum = -1) {
      $first = TRUE;
       foreach ($this->values as $key => $value) {
          if (!$first)
             echo $this->separator."\n";
          else
             $first = FALSE;
          echo '<label>';
          echo "<input type=\"radio\" name=\"".$property->get_form_name($rownum);
          echo "\" value=\"".$key.'"';
          if ($key == $property->get_value($rownum)) echo " checked";
          if ($this->accesskeys != null && is_array($this->accesskeys))
              echo ' accesskey="'.$this->accesskeys[$key].'"';
          echo ' '.$this->extras.'/>'.$value.'</label>';
       }
   }
}

/**
 * Base class for all properties.
 *
 * This class implements most of the needed functionality.
 * It knows what this property should be called in the HTML form
 * (taking into account block name and record number), it is able to
 * generate itself on the form (using provided display), it is able to read
 * itself back from <var>$HTTP_POST_VARS</var> after the form has been submitted.
 *
 * This class still has some leftovers from the time when I tried to implement
 * base-table blocks – it has 3 functions: {@link get_sql_value()}, {@link sql_read()}
 * and {@link get_sql_name()} that were originally part of the plan to
 * automatically build and execute sql statements. I kept them since they are
 * operational, but right now I never use them, except maybe {@link get_sql_value()}
 * function, that formats the value in a way suitable for this property's
 * particular data type. Of course, this only works if the actual descendant
 * property type overrides these functions.
 *
 * If you are planning to develop your own property classes, please implement the
 * sql methods too: base-table blocks are likely to be in the next major version
 * of the library.
 *
 * <b>Important:</b> several functions take an optional $rownum parameter,
 * that is omitted or is set to -1 when you work with single-row blocks,
 * and is greater than or equal to 0 when you work with multi-row blocks.
 * Right now there is no check whether the owning class single or multi-row,
 * and the access is based solely on the presence and value of this $rownum parameter.
 *
 * However, when designing forms most of these functions are normally not used,
 * as the values can be accessed directly, using scalar values or arrays depending
 * on whether the block is single or multi-row.
 *
 * So, instead of writing: <code>$form->block->_properties["property"]->get_value[$i]</code>
 * one would normally write <code>$form->block->property[$i]</code>
 */
class Property {

   /**
    * The name of the property.
    *
    * The value is set in the constructor. Never reassign this variable,
    * or the form may stop working.
    *
    * @access private
    * @var string
    */
   var $name;

   /**
    * The default value of this property.
    *
    * When a new record is appended to the block, this property will receive
    * this value during initialization.
    *
    * @access private
    * @var mixed
    */
   var $default_value;

   /**
    * The on-screen label of the property.
    *
    * The value is set in the constructor. It is used by
    * - the validation mechanism in the error message when the mandatory field is missing
    * - by the {@link label()} funciton
    * - by standard layouts, which actually use the {@link label()} call.
    *
    * @access private
    * @var string
    */
   var $label;

   /**
    * Indicates whether the field is mandatory.
    *
    * @access private
    * @var bool
    */
   var $required;

   /**
    * The reference to the owning block.
    *
    * Set during {@link Block::add_property()} call. Do not change it.
    *
    * @access private
    * @var Block
    */
   var $block;

   /**
    * An instance of display class for this property.
    *
    * It is set by {@link Block::add_property()} method to whatever it receives as the
    * second argument. If this field is NULL, then the property will never
    * be displayed, even if you call the {@link field()} function for this property.
    *
    * If needed, you can re-assign the value of <var>$display</var> after the form
    * definition but before you call {@link Form::start_form()}. Typical places are
    * ON_OPEN, AFTER_RESTORE, and sometimes PRE_DISPLAY triggers.
    *
    * @access public
    * @var Display
    */
   var $display;

   /**
    * Indicates whether the property is data or action (control).
    *
    * By default it is FALSE as most property types are data-related.
    * The constructor of {@link ButtonProperty} sets it to TRUE.
    *
    * The only use for this right now is in {@link BaseLayout} which renders
    * all controls at the bottom of the form.
    *
    * @access private
    * @var bool
    */
   var $is_control;

   /**
    * Indicates whether the property should be displayed.
    *
    * Only used by the standard layouts. The {@link field{}} and {@link label()}
    * calls ignore it.
    *
    * A good place to hide properties is in the PRE_DISPLAY triggers.
    *
    * @access public
    * @var bool
    */
   var $visible;

   /**
    * Indicates whether this property requires validation.
    *
    * Readonly and button properties should not be validated.
    * All other properties should. The default implementation returns TRUE
    * if a {@link Display} is assigned to this property, and that display is
    * not readonly.
    *
    * @access private
    * @returns bool
    */
   function needs_validation() {
      return isset($this->display) && is_a($this->display, "Display") &&
             !$this->display->is_readonly();
   }

   /**
    * Indicates whether this property should go with a label.
    *
    * This method only affects stadard layouts. Since most properties do
    * require a label, the default implementation returns TRUE.
    *
    * @access private
    * @returns bool
    */
   function layout_needs_label() {
      return TRUE;
   }

   /**
    * Calculates whether this property is visible.
    *
    * This method only affects stadard layouts. Calculation is based on
    * - {@link $visible} attribute
    * - having a {@link Display} assigned
    * - this display not being {@link HiddenField}
    *
    * @access private
    * @returns bool
    */
   function is_visible() {
      return $this->visible &&
             is_a($this->display, "Display")
             && !is_a($this->display, "HiddenField");
   }

   /**
    * Prints out the property definition and data
    *
    * This is a service method, part of the {@link Block::printblock()} debug call.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function printproperty($rownum = -1) {
       echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPERTY=".$this->block->_name.".".$this->name.
            " VALUE=<b>\"".$this->get_value($rownum)."\"</b> DEFAULT_VALUE=".$this->default_value.
            "<br/>";
   }

   /**
    * Returns an instance of default display for this property type.
    *
    * This base implementation returns an instance of {@link HiddenField}.
    *
    * @access private
    * @returns Display
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_default_display() {  // Must be overriden for things like buttons
      return new HiddenField();
   }

   /**
    * Sets the current value of the property to the default value.
    *
    * The name may be confusing - it is not about assigning a new value to
    * the {@link $default_value} member. It is about assigning the value of {@link $default_value}
    * to the property.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_default_value($rownum = -1) { // To be overriden if more than one value.
      $this->set_value($this->default_value, $rownum);
   }

   /**
    * Sets the value of the property.
    *
    * No validation on the value is performed.
    *
    * @access private
    * @param mixed the new value
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_value($value, $rownum = -1) {
      $name = $this->name;
      if ($rownum >=0) {
         $ar = &$this->block->$name;
         $ar[$rownum] = $value;
      }
      else {
         $this->block->$name = $value;
      }
   }

   /**
    * Sets the orginal value of the property.
    *
    * No validation on the value is performed.
    *
    * This method is called only right after execution of the ON_OPEN triggers.
    *
    * @access private
    * @param mixed the new value
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_orig_value($value, $rownum = -1) {
      if ($rownum >=0) {
         $this->block->_orig_values[$this->name][$rownum] = $value;
      }
      else {
         $this->block->_orig_values[$this->name] = $value;
      }
   }

   /**
    * Returns the value of the property.
    *
    * @access private
    * @returns mixed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_value($rownum = -1) {
      $name = $this->name;
      if ($rownum >=0) {
         $ar = &$this->block->$name;
         return $ar[$rownum];
      }
      else {
         return $this->block->$name;
      }
   }

   /**
    * Returns the original value of the property.
    *
    * @access private
    * @returns mixed
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_orig_value($rownum = -1) {
      if ($rownum >=0) {
         // Here we will rely on the defaulting to an empty string
         // So we will supress the notices
         return @$this->block->_orig_values[$this->name][$rownum];
      }
      else {
         return $this->block->_orig_values[$this->name];
      }
   }

   /**
    * Gets a post var by a given name, stripping slashes if necessary.
    *
    * @param string
    * @access private
    * @returns string
    */
   function strip_post_var($name) {
      global $HTTP_POST_VARS;
      if (isset($HTTP_POST_VARS[$name])) {
         if (get_magic_quotes_gpc())
            return stripslashes($HTTP_POST_VARS[$name]);
         else
            return $HTTP_POST_VARS[$name];
      }
      else
         return '';
   }

   /**
    * Reads the value of the property from <var>$HTTP_POST_VARS</var>.
    *
    * Do not override this method in a descendant class unless you require
    * value conversion or have a compound field, consisting of several <input>'s.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function form_read($rownum = -1) {
      $this->set_value($this->strip_post_var($this->get_form_name($rownum)),$rownum);
   }

   /**
    * Reads the original value of the property from <var>$HTTP_POST_VARS</var>.
    *
    * This method is only called if auto-detect-changes mode is on.
    *
    * Do not override this method in a descendant class unless you require
    * value conversion or have a compound field, consisting of several <input>'s.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function form_orig_read($rownum = -1) {
      if ($this->block->_form->_auto_detect_changes) {
         $this->set_orig_value(
             $this->strip_post_var('_orig_'.$this->get_form_name($rownum)),
             $rownum);
      }
   }

   /**
    * Retrieve the property value from the SQL query result.
    *
    * The <var>$row</var> parameter contains an array of values returned by
    * <i>mysql_fetch_row()</i> function or similar. It reads the value number
    * <var>$count</var>, and increases the <var>$count</var> by one.
    *
    * Similarly to {@link form_read()} method, you should only override this
    * method if your value is compound or requires data conversion.
    *
    * @access private
    * @param array array of values returned by SQL SELECT
    * @param int first not yet used value in the array
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function sql_read($row, &$count, $rownum=-1) {
      $this->set_value($row[$count++], $rownum);
   }

   /**
    * Validates the value.
    *
    * This method implements part of the property level step of the validation
    * chain described in the trigger section of the tutorial. You can override
    * this method without a risk to screw up the trigger mechanism, as all
    * property-level triggers are fired in {@link Block::validate()} method.
    *
    * However, if you still want to do the default validation (checking required fields),
    * then make sure that you call parent::validate() in your implementation.
    *
    * @access private
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function validate($rownum = -1) {
      global $error;
      if ($this->required && !($this->get_value($rownum) != "")) {
         $error = "Missing required field: ".$this->label;
         return FALSE;
      }
      return TRUE;
   }

   /**
    * Returns the SQL name of the property.
    *
    * Also a survivor of base-table block attempts.
    * Currently it simply returns the name of the property.
    *
    * @access private
    * @returns string
    */
   function get_sql_name() {
      return $this->name;
   }

   /**
    * Returns the name of the property that will be used in the form.
    *
    * For single-row blocks it is <blockname>_<propertyname>,
    * for multi-row blocks it is <blockname>_<propertyname>_<rownum>.
    *
    * @access private
    * @returns string
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_form_name($rownum = -1) {
      if ($rownum >= 0) {
         return $this->block->_name."_".$this->name."_".$rownum;
      }
      else {
         return $this->block->_name."_".$this->name;
      }
   }

   /**
    * Returns the value of the property in format, prepared for SQL.
    *
    * This default implementation only callins mysql_escape_string() on the value.
    * In descendant classes it may do more, like replacing empty strings with NULL.
    * In {@link DateProperty} it is recommded to use this method to actually retrieve
    * the date.
    *
    * This method is one of the few that remain from the attempt to do base-table blocks.
    *
    * @access public
    * @returns string
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_sql_value($rownum = -1) {   // to be overridden to provide proper escape
      return mysql_escape_string($this->get_value($rownum));
   }

   /**
    * Returns the value of this property formatted for the HTML form.
    *
    * @access private
    * @returns string
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_form_value($rownum = -1) {   // to be overridden to provide proper escape
      return htmlspecialchars($this->get_value($rownum));
   }

   /**
    * Returns the oliginal value of this property formatted for the HTML form.
    *
    * Only called when auto-detect-changes mode is on.
    *
    * @access private
    * @returns string
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_orig_form_value($rownum = -1) {   // to be overridden to provide proper escape
      return htmlspecialchars($this->get_orig_value($rownum));
   }

   /**
    * Generates the HTML code for the property.
    *
    * This is a facade method that calls the {@link Display::show()} method
    * on the assigned Display. If not display is assigned for this property,
    * nothing will be generated.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function show($rownum = -1) {
      if (isset($this->display)) {
         $this->display->show($this, $rownum);
      }
   }

   /**
    * @access public
    * @param string The name of the property. Should be compliant with the PHP
    *               variable naming rules. Should not start with an underscore,
    *               as you will risk overwriting the owning block's internal class members.
    * @param string this text will be used by the label() function, and in the error message for missing mandatory field.
    * @param string the default value of the property
    * @param bool TRUE if this property is mandatory.
    */
   function Property($name, $label, $default_value, $required) {
      $this->name          = $name;
      $this->label         = $label;
      $this->default_value = $default_value;
      $this->required      = $required;
      $this->is_control    = FALSE;
      $this->visible       = TRUE;
   }

   /**
    * Allows an action property to set itself as default actions.
    *
    * Action properties, such as ButtonProperty, should override this method to
    * assign the property's name to the {@link Block::$_default_action} attribute
    * of the block. The base implementation is empty. This method is called by the
    * {@link Block::add_property()} method.
    *
    * @access private
    */
   function check_default() {}

   /**
    * Indicates whether this property type requires multipart encoding of the form.
    *
    * This method should be overridden by properties that require multipart
    * form encoding to return TRUE. Base implementation returns FALSE.
    *
    * @access private
    * @returns bool
    */
   function is_multipart() {
      return FALSE;
   }

   /**
    * Automatically stores all hidden information for this property.
    *
    * If the property's display has {@link Display::$auto_store} attribute set
    * to TRUE, it generates the needed hidden field for this property, and
    * adds corresponding name=>value pair to the <var>$attributes</var> array –
    * for digital signing.
    *
    * @access private
    * @param array a reference to array, that contains name=>value pairs
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function auto_store(&$attributes, $rownum = -1) {
      if (isset($this->display)) {
         if ($this->display->auto_store) {
            // Add to the attributes
            $attributes[$this->get_form_name($rownum)] = $this->get_value($rownum);
            // Create a hidden field
            echo "<input type=\"hidden\" name=\"".
              $this->get_form_name($rownum).
              "\" value=\"".
              $this->get_form_value($rownum).
              "\"/>\n";
         }

         // Preserve original value if needed
         if ($this->block->_form->_auto_detect_changes &&
             !$this->display->is_readonly()) {

            // Add to the attributes
            $attributes["_orig_".$this->get_form_name($rownum)] =
               $this->get_orig_value($rownum);
            // Create a hidden field
            echo "<input type=\"hidden\" name=\"_orig_".
               $this->get_form_name($rownum).
               "\" value=\"".
               $this->get_orig_form_value($rownum).
               "\"/>\n";
         }
      }
   }

   /**
    * Automatically restores all hidden information for this property.
    *
    * If the property's display has {@link Display::$auto_store} attribute set
    * to TRUE, it adds name=>value pair to the <var>$attributes</var> array –
    * for validation of digital signature.
    *
    * In auto-detect-changes mode it also restores the original value of the
    * property from the form.
    *
    * @access private
    * @param array a reference to array, that contains name=>value pairs
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function auto_restore(&$attributes, $rownum = -1) {
      if (isset($this->display)) {
         if ($this->display->auto_store) {
            // Add to the attributes
            $attributes[$this->get_form_name($rownum)] = $this->get_value($rownum);
         }

         if ($this->block->_form->_auto_detect_changes &&
             !$this->display->is_readonly()) {
            $this->form_orig_read($rownum);
            $attributes["_orig_".$this->get_form_name($rownum)] =
                $this->get_orig_value($rownum);

            // Since this method is called after the original record statuses
            // were added to the $attributes array, we can safely check for
            // changes
            if ($this->get_value($rownum) != $this->get_orig_value($rownum)) {
               $this->block->mark_changed($rownum);
            }
         }
      }
   }
}

/**
 * Action property.
 *
 * This property does not correspond to data. It is a control.
 */
class ButtonProperty extends Property {

   /**
    * Indicator of default action.
    *
    * TRUE if this button is default. Used only once: when the button instance is
    * added to the block and {@link check_default()} method is called.
    *
    * @access private
    * @var bool
    */
   var $default;

   /**
    * @access public
    * @param string the name of the property, as in all properties.
    * @param string unlike with other properties, this label will be ON the button.
    * @param bool optional parameter; if TRUE means that this button is default.
    *             Because of the way it is implemented, if you specify several
    *             default buttons, the last one will actually become default.
    */
   function ButtonProperty($name, $label, $default = FALSE) {
      parent::Property($name, $label, null, FALSE);
      $this->default = $default;
      $this->is_control = TRUE;
   }

   /**
    * Indicates validation requirements.
    *
    * Since buttons to not hold values, they do not require validation, thus
    * this implementation returns FALSE.
    *
    * @returns bool
    */
   function needs_validation() {
      return FALSE;
   }

   /**
    * Retrieves the value from the submitted data.
    *
    * Instead of reading a value, this method checks if the button has been
    * pressed by its presence in the <var>$HTTP_POST_VARS</var>, and if so,
    * it sets {@link Form::$_action}, {@link Form::$_action_block},
    * {@link Form::$_action_record} members on the form.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function form_read($rownum = -1) {
      global $HTTP_POST_VARS;
      if ($this->strip_post_var($this->get_form_name($rownum))==$this->label) {
         // The button has been pressed
         $this->block->_form->_action_block = &$this->block;
         $this->block->_form->_action_record = $rownum;
         $this->block->_form->_action = &$this;
      }
   }

   /**
    * Returns an instance of the default display for this class.
    *
    * This implementation returns in instance of {@link SubmitButton}.
    *
    * @returns Display
    * @access private
    */
   function get_default_display() {
      return new SubmitButton();
   }

   /**
    * If default, installs itself as default into the block.
    *
    * Overrides the default implementation to set {@link Block::$_default_action}
    * member to the name of this button, if it is default.
    *
    * @access private
    */
   function check_default() {
      if ($this->default)
         $this->block->_default_action = $this->name;
   }
}

/**
 * Boolean property
 *
 * Usually displayed as a check box, using {@link CheckBox} display.
 */
class CheckBoxProperty extends Property {

   /**
    * @access public
    * @param string the name of the property. See {@link Property::$name} for more details.
    * @param string the label of the property. See {@link Property::$label} for more details.
    * @param bool the default value of the property.
    */
   function CheckBoxProperty($name, $label, $default_value) {
      parent::Property($name, $label, $default_value, FALSE);
   }

   /**
    * Indicates validation requirements.
    *
    * Since the value of a boolean property is either TRUE or FALSE, and
    * both values are valid, validation is not needed. Thus
    * this implementation returns FALSE.
    *
    * @returns bool
    */
   function needs_validation() {
      return FALSE;
   }

   /**
    * Returns the value of the property pre-formmated for SQL statements.
    *
    * Values are returned as 'Y' or 'N'.
    *
    * @returns string
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_sql_value($rownum = -1) {
      $value = parent::get_value($rownum);
      return ($value) ? "'Y'" : "'N'";
   }

   /**
    * Returns the value formatted for HTML form output.
    *
    * Because of the way check boxes work in HTML, this implementation
    * always returns Y.
    *
    * @returns string
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_form_value($rownum = -1) {
      return "Y";
   }

   /**
    * Returns an instance of the default display for this class.
    *
    * This implementation returns in instance of {@link CheckBox}.
    *
    * @returns Display
    * @access private
    */
   function get_default_display() {
      return new CheckBox();
   }

   /**
    * Retrieves the value from the submitted data.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function form_read($rownum = -1) {
      $this->set_value($this->strip_post_var($this->get_form_name($rownum))=="Y",$rownum);
   }

   /**
    * Retrieves the value from the submitted data.
    *
    * For explanation of parameters see {@link Property::sql_read()}.
    * This method expects to find Y or N as its input.
    *
    * @access private
    * @param array
    * @param int
    * @param int
    */
   function sql_read($row, &$count, $rownum=-1) {
      $this->set_value($row[$count++]=="Y", $rownum);
   }

}

/**
 * Decimal number property.
 */
class NumericProperty extends Property {
   /**
    * The maximium number of digits in the integer part of the number.
    *
    * @var int
    * @access private
    */
   var $digits;
   /**
    * The maximium number of digits in the decimal part of the number.
    *
    * @var int
    * @access private
    */
   var $decimals;

   /**
    * The maximum length of the field.
    *
    * The value of this variable will be used by {@link TextBox::show()} method
    * to specify the maximum length of the <input>
    *
    * @var int
    * @access private
    */
   var $length;

   /**
    * @access public
    * @param string the name of the property. See {@link Property::$name} for more details.
    * @param string the label of the property. See {@link Property::$label} for more details.
    * @param number the default value of the property, always truncated to the <var>$length</var>.
    * @param bool TRUE if this property is mandatory.
    * @param int the maximum number of integer digits.
    * @param int the maximum number of decimal digits, by default 0.
    */
   function NumericProperty($name, $label, $default_value, $required, $digits, $decimals = 0) {
      parent::Property($name, $label, $default_value, $required);
      $this->digits = $digits;
      $this->decimals = $decimals;
      $this->length = $digits;
      if ($decimals>0)
        $this->length += $decimals+1;
   }

   /**
    * Performs validation of the property.
    *
    * Check if the value is present (if it mandatory) and that it actually contains
    * a number.
    *
    * @access private
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function validate($rownum = -1) {
      global $error;

      // First, call the default implementation to check for required fields
      if (!parent::validate($rownum))
         return FALSE;

      $value = $this->get_value($rownum);

      if ($value != "") {
         // Prepare the mask
         if ($this->decimals > 0)
            $mask = '/^\d{0,'.$this->digits.'}(\.\d{0,'.$this->decimals.'}){0,1}$/';
         else
            $mask = '/^\d{0,'.$this->digits.'}$/';

         // Validate
         if (!preg_match($mask, $value)) {
            $error = "Invalid number format: ".$this->label;
            return FALSE;
         }
      }
      return TRUE;
   }


   /**
    * Returns the value of the property pre-formmated for SQL statements.
    *
    * If the value is empty, a string "<b>null</b>" will be returned. Otherwise it will
    * return a string containing the number.
    *
    * @returns string
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_sql_value($rownum = -1) {
      $value = $this->get_value($rownum);
      if ($value == "")
         return "null";
      return $value;
   }

   /**
    * Returns an instance of the default display for this class.
    *
    * This implementation returns in instance of {@link TextBox} with its <var>$size</var>
    * set just enough to accomodate the number based on {@link $digits} and {@link $decimals}.
    *
    * @returns Display
    * @access private
    */
   function get_default_display() {
      return new TextBox($this->length);
   }
}

/**
 * Text property.
 */
class TextProperty extends Property {
   /**
    * The maximum allowed length of the data.
    *
    * @var int
    * @access private
    */
   var $length;

   /**
    * @access public
    * @param string the name of the property. See {@link Property::$name} for more details.
    * @param string the label of the property. See {@link Property::$label} for more details.
    * @param string the default value of the property, always truncated to the <var>$length</var>.
    * @param bool TRUE if this property is mandatory.
    * @param int the maximum length of this property in characters.
    */
   function TextProperty($name, $label, $default_value, $required, $length) {
      parent::Property($name, $label, substr($default_value, 0, $length), $required);
      $this->length=$length;
   }

   /**
    * Sets the value of the property.
    *
    * This method overrides the default implementation to truncate the data to the
    * maximum allowed length.
    *
    * @access public
    * @param string the new value
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_value($value, $rownum = -1) {
      parent::set_value(substr($value, 0, $this->length), $rownum);
   }

   /**
    * Returns the value of the property pre-formatted for SQL statements.
    *
    * If the value is empty, a string "<b>null</b>" will be returned. Otherwise,
    * the value will be escaped and enclosed in single quotes.
    *
    * @returns string
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_sql_value($rownum = -1) {
      $value = parent::get_sql_value($rownum);
      return ($value=="") ? "null" : "'$value'";
   }

   /**
    * Returns an instance of the default display for this class.
    *
    * This implementation returns in instance of {@link TextBox} with its <var>$size</var>
    * set to <var>$length</var>.
    *
    * @returns Display
    * @access private
    */
   function get_default_display() {  // Must be overriden for things like buttons
      return new TextBox($this->length);
   }
}

/**
 * Date property
 *
 * This class implements property that operates with dates and timestamps in the
 * format of MySQL - YYYY-MM-DD or YYYY-MM-DD HH:MM:SS.
 */
class DateProperty extends TextProperty {
   /**
    * Indicates whether the field should contain date and time or date only.
    *
    * @var bool
    * @access private
    */
   var $date_only;

   /**
    * Holds the mask that will be displayed to the user if the field opens empty.
    *
    * @var string
    * @access private
    */
   var $mask;

   /**
    * @access public
    * @param string the name of the property. See {@link Property::$name} for more details.
    * @param string the label of the property. See {@link Property::$label} for more details.
    * @param string the default value of the property, always truncated to the <var>$length</var>.
    * @param bool TRUE if this property is mandatory.
    * @param bool TRUE if the field should contain only date and no time
    */
   function DateProperty($name, $label, $default_value, $required, $date_only = FALSE) {
      if ($date_only) {
         $mask = "yyyy-mm-dd";
      }
      else {
         $mask = "yyyy-mm-dd hh:mm:ss";
      }
      parent::TextProperty($name,$label,
                           substr($default_value,0,strlen($mask)),
                           $required,strlen($mask));
      $this->date_only = $date_only;
      $this->mask = $mask;
   }

   /**
    * Performs validation of the property.
    *
    * Check if the value is present (if it mandatory) and that it actually contains
    * a date (or a timestamp). Validation takes into account the fact, that the value
    * in the field may be still the mask, so it will be treated as empty.
    *
    * @access private
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function validate($rownum = -1) {
      global $error;

      // Let's call the default validation
      if (!parent::validate($rownum))
         return FALSE;

      // If not required and empty - then OK
      if ($this->get_value($rownum) == "")
         return TRUE;


      if ($this->date_only) {
         $mask = '/^(\d{4})-(\d{2})-(\d{2})$/';
      }
      else {
         $mask = '/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/';
      }

      $matches = array();

      if (!preg_match(
            $mask,
            $this->get_value($rownum),
            $matches)) {
         $error = "Invalid date format: ".$this->label;
         return FALSE;
      }

      $parts = array( 1=>"year", "month", "day", "hour", "minutes", "seconds");
      $low = array ( 1=>1000, 1, 1, 0, 0, 0);
      $high = array ( 1=>9999, 12, 31, 23, 59, 59);

      for ($i=1; $i<count($matches); $i++) {
         if ($matches[$i] < $low[$i] ||
             $matches[$i] > $high[$i] ) {
            $error = "Invalid ".$parts[$i]." value: ".$this->label;
            return FALSE;
         }
      }

      if (!checkdate($matches[2], $matches[3], $matches[1])) {
         $error = "Invalid date: ".$this->label;
         return FALSE;
      }

      return TRUE;

   }

   /**
    * Returns the value of the property pre-formmated for SQL statements.
    *
    * If the value is empty, a string "<b>null</b>" will be returned. Otherwise,
    * the value will be escaped and enclosed in single quotes.
    *
    * @returns string
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_sql_value($rownum = -1) {
      $value = $this->get_value($rownum);
      return ($value=="") ? "null" : "'$value'";
   }

   /**
    * Returns the value of this property formatted for the HTML form.
    *
    * If the value is empty, this implementation will return the date mask.
    *
    * @access private
    * @returns string
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_form_value($rownum = -1) {   // to be overridden to provide proper escape
      $value = $this->get_value($rownum);
      if ($value == "")
         return htmlspecialchars($this->mask);
      else
         return htmlspecialchars($value);
   }

   /**
    * Reads the value of the property from <var>$HTTP_POST_VARS</var>.
    *
    * If the form value is equal to the mask, it is converted back to an
    * empty string.
    *
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function form_read($rownum = -1) {
      $value = $this->strip_post_var($this->get_form_name($rownum));
      if ($value == $this->mask)
         $value = "";
      $this->set_value($value,$rownum);
   }

}

/**
 * Status is chosen automatically between RS_NEW and RS_INSERT.
 *
 * @access private
 */
DEFINE('RS_AUTO', -1);

/**
 * A new record.
 *
 * In auto-detect-changes mode also means unchanged.
 *
 * @access public
 */
DEFINE('RS_NEW', 0);

/**
 * Existing record.
 *
 * In auto-detect-chanes mode also means unchanged.
 *
 * @access public
 */
DEFINE('RS_OLD', 1);

/**
 * An RS_NEW record that was marked for deletion.
 *
 * No saving action required.
 *
 * @access public
 */
DEFINE('RS_CANCEL', 2);

/**
 * An existing record that was marked for deletion.
 *
 * @access public
 */
DEFINE('RS_DELETE', 3);

/**
 * A new changed record - requires an INSERT.
 *
 * @access public
 */
DEFINE('RS_INSERT', 4);

/**
 * An existing changed record - requires an UPDATE.
 *
 * @access public
 */
DEFINE('RS_UPDATE', 5);

/**
 * Mask to determine whether the record originally existed.
 *
 * @access private
 */
DEFINE('RS_EXISTED_MASK', 1);

/**
 * Mask to determine that the record is deleted or canceled
 *
 * @access private
 */
DEFINE('RS_DELETED_MASK', 2);

/**
 * Mask to determine whether the record has changed.
 *
 * @access private
 */
DEFINE('RS_CHANGED_MASK', 4);


/**
 * The core building block of forms.
 *
 * Blocks reperesent objects or tables. They consist of properties (columns) and
 * records (rows).
 *
 * Every time a property is added to the block, a member whose name matches the
 * name of the property is added too. This member contains the value of the
 * property, and <b>not</b> the property object reference (which is accessible
 * through the {@link $_properties} array). Please note that you should avoid
 * naming your properties starting with an underscore or you may overwrite
 * some internal members of the class screwing up its functioning.
 *
 * For single row blocks this member holds the actual value of the property,
 * while for multi-row blocks this member is an array, holding values for this
 * property similarly to the {@link $id} property.
 *
 * To access the values of the properties you can use the following construct
 * <code>$form->blockname->propertyname</code> for single-row blocks, and
 * <code>$form->blockname->propertyname[$recordnumber]</code> for multi-row blocks.
 */
class Block {
   /**
    * A reference to the parent form.
    *
    * @access private
    * @var Form
    */
   var $_form;

   /**
    * The name of the block
    *
    * @access private
    * @var string
    */
   var $_name;

   /**
    * An associative array of the actual property objects.
    *
    * Most of the times you do not need to access the property objects,
    * but if you do – you do it through the <var>$_properties</var> array.
    * Keys are the names of the properties, and the values are references to
    * the properties.
    *
    * @access public
    * @var array
    */
   var $_properties = array();

   /**
    * An array that holds statuses of all records in the block.
    *
    * Unlike the {@link $id} and the property-value-holding members,
    * this member is always an array, even for single row blocks,
    * in which case the status is recorded under key 0.
    *
    * @access private
    * @var array
    */
   var $_record_statuses = array();

   /**
    * Id (or Ids) of the record in the block.
    *
    * For single row blocks, this field should hold the id of the object stored in the
    * block, however the programmer is responsible for setting this value.
    * For multi-row blocks, this is an array of ids. Keys are consecutive integers
    * starting with 0. Values are the ids of object stored in the row with
    * that number.
    *
    * When a new record (in status RS_NEW) is appended, the id for
    * that record is set to -1.
    *
    * @access public
    * @var mixed
    */
   var $id;

   /**
    * Indicates multi-row blocks.
    *
    * TRUE for multi-row blocks.
    *
    * @access private
    * @var bool
    */
   var $_multirow;

   /**
    * Indicates auto-append mode.
    *
    * When TRUE, the block will automatically create a record in status
    * RS_NEW if it does not have any records after ON_OPEN triggers have
    * finished execution.
    *
    * @access private
    * @var bool
    */
   var $_autoappend;

   /**
    * The number of records in the block.
    *
    * For single row blocks it can be 0 or 1.
    *
    * @access private
    * @var int
    */
   var $_nrows = 0;

   /**
    * The name of the ButtonProperty that is marked as default.
    *
    * If you mark more than one default property on one block,
    * then the latest one will be the actual default action.
    *
    * @access private
    * @var string
    */
   var $_default_action = "";

   /**
    * An associative array of original values of properties.
    *
    * Only used in auto-detect-changes mode. The values are saved
    * into this array after the ON_OPEN trigger sequence.
    *
    * @access private
    * @var array
    */
   var $_orig_values;

   /**
    * Executes block-level trigger.
    *
    * First it tries to find user-defined trigger - <blockname>_<trigger_name>
    *
    * If user-defined trigger not found, it checks if the current block
    * object has a method named after the trigger. The purpose of this mechanism
    * is for creating new types of blocks, such as base table blocks, which
    * need to handle most of the triggers by themselves.
    *
    * Omit the <var>$rownum</var> parameter for single row blocks.
    *
    * @access private
    * @param string trigger name
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function call_trigger($trigger, $rownum = 0) {
      // First let's try the user defined triggers.
      if ($this->_form->call_trigger($this->_name."_".$trigger, $rownum)) {
         return TRUE;
      }

      // If user-defined trigger not found, let's see if the
      // block has a trigger-method

      if (method_exists($this, $trigger)) {
          if ($this->_form->_debug_triggers) {
               echo $this->_name.": firing class-defined $trigger for row $rownum<br/>\n";
          }
          $this->$trigger($rownum);
          return TRUE;
      }
      else {
          if ($this->_form->_debug_triggers) {
               echo $this->_name.": class-defined $trigger not found<br/>\n";
          }
      }
      return FALSE;
   }

   /**
    * Marks record as deleted.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function mark_deleted($rownum = 0) {
      $this->set_record_status_flag(RS_DELETED_MASK, $rownum);
   }

   /**
    * Marks record as changed.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function mark_changed($rownum = 0) {
      $this->set_record_status_flag(RS_CHANGED_MASK, $rownum);
   }

   /**
    * Check if the record has changed.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function is_record_changed($rownum = 0) {
      return ($this->get_record_status($rownum) & RS_CHANGED_MASK) > 0;
   }

   /**
    * Check if the record has been marked for deletion.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function is_record_deleted($rownum = 0) {
      return ($this->get_record_status($rownum) & RS_DELETED_MASK) > 0;
   }

   /**
    * Check if the record is not new.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @returns bool
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function is_record_existing($rownum = 0) {
      return ($this->get_record_status($rownum) & RS_EXISTED_MASK) > 0;
   }

   /**
    * Returns TRUE if this block requires a multipart form.
    *
    * This function queries all properties on the block if they require
    * enctype=”multipart/form-data” attribute to the FORM tag.
    * Currently only FileProperty does, but it is experimental.
    *
    * @access public
    * @returns bool
    */
   function is_multipart() {
      foreach ($this->_properties as $name => $property) {
         if ($this->_properties[$name]->is_multipart())
            return TRUE;
      }
      return FALSE;
   }

   /**
    * Print out the content of the block.
    *
    * I often find the need to see what's inside by block. Unfortunately,
    * the built-in PHP function <i>print_r</i> that is supposed to print
    * objects in reader-friendly manner does not work very well in this case,
    * because it is not very well capable of handling cross-linking
    * references. However, blocks are cross-linked with the form,
    * and properties are cross-linked with their blocks. As a result <i>print_r</i>
    * produces a lot of repeating garbage.
    *
    * This method prints out the content of the block, row by row if rows
    * are present, in a much more friendly manner than the <i>print_r</i> function.
    *
    * @access public
    */
   function printblock() {
      echo "<b>Block ".$this->_name." BEGIN</b><br/>";
      echo "&nbsp;&nbsp;&nbsp; NROWS=".$this->_nrows." MULTIROW=".($this->_multirow?"TRUE":"FALSE")." AUTOAPPEND=".($this->_autoappend?"TRUE":"FALSE")."<br/>";
      if ($this->_nrows > 0) {
         for ($i=0; $i<$this->_nrows; $i++) {
            echo "&nbsp;&nbsp;&nbsp; <b>ROW ".$i." BEGIN</b><br/>";
            echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RECORD STATUS=".$this->_record_statuses[$i].
                 " ID=".$this->id[$i]."<br/>";
            foreach ($this->_properties as $name => $property) {
                if ($this->_multirow)
                   $property->printproperty($i);
                else
                   $property->printproperty(-1);
            }
            echo "&nbsp;&nbsp;&nbsp; <b>ROW ".$i." END</b><br/>";
         }
      }
      else {
         foreach ($this->_properties as $name => $property) {
             $property->printproperty($i);
         }
      }
      echo "<b>Block ".$this->_name." END</b><br/>";
   }

   /**
    * The Block contructor.
    *
    * It takes one mandatory parameter <var>$name</var> – the name of the
    * block, and two optional parameters. The first optional parameter
    * specifies whether the block is multi-row. By default it is not.
    * The second optional parameter specifies whether the block will run
    * in autoappend mode. See {@link $_autoappend} internal member for
    * details. If not specified, single-row blocks will run in autoappend mode,
    * and multi-row blocks will not run in autoappend mode.
    *
    * Remember, that you should always take the reference of the new Block
    * when you create it, for example: <code>
    * $block = & new Block("example"); </code>
    * Also, remember, that you should not name blocks starting with an
    * underscore (or you may overwrite some internal members in the {@link Form} class)
    * and you should not name blocks as <b>form</b> - otherwise you will
    * run into trigger naming conflict.
    *
    * @access public
    * @param string the name of the block
    * @param bool TRUE indicates multi-row block
    * @param bool indicates auto-append mode
    */
   function Block($name, $multirow = FALSE, $autoappend=null) {
      $this->_name = $name;
      $this->_multirow = $multirow;
      if ($multirow)
        $this->id = array();
      else
        $this->id = null;
      if ($autoappend == null) {
         // Set default values
         $this->_autoappend = !$this->_multirow;
      }
      else
         $this->_autoappend = $autoappend;
   }

   /**
    * Returns the status of the specified record.
    *
    * Omit the parameter for single row blocks.
    *
    * @access public
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function get_record_status($rownum = 0) {
      return $this->_record_statuses[$rownum==-1?0:$rownum];
   }

   /**
    * Sets the status of the specified record.
    *
    * Omit the parameter for single row blocks.
    *
    * It is recommended to use {@link mark_changed()}, {@link mark_deleted()}
    * to change statuses instead of setting the record status directly.
    *
    * @access public
    * @param int the new status of the record
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_record_status($status, $rownum = 0) {
      $this->_record_statuses[$rownum==-1?0:$rownum] = $status;
   }

   /**
    * Sets a bit in the status of the specified record according to provided mask.
    *
    * Omit the parameter for single row blocks.
    *
    * @access private
    * @param int the mask for the bit to be set
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function set_record_status_flag($mask, $rownum = 0) {
      $this->_record_statuses[$rownum==-1?0:$rownum] |= $mask;
   }

   /**
    * Clears a bit in the status of the specified record according to provided mask.
    *
    * Omit the parameter for single row blocks.
    *
    * @access private
    * @param int the mask for the bit to be cleared
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function clear_record_status_flag($mask, $rownum = 0) {
      $this->_record_statuses[$rownum==-1?0:$rownum] &= ~$mask;
   }

   /**
    * Returns the number of records in the block.
    *
    * @access public
    */
   function get_record_count() {
      return $this->_nrows;
   }

   /**
    * Adds a record to the block with the status given as the parameter.
    *
    * If you omit the parameter or specify RS_AUTO, then the new record's status will
    * be chosen among two:
    * - RS_NEW if auto-detect-changes mode is OFF, or for multi-row blocks
    * - RS_INSERT if auto-detect-changes mode is ON and the block is single row.
    *
    * In any case, the values of the properties are initialized with default
    * values. If the record is created with statuses RS_NEW or RS_INSERT, then the
    * ON_APPEND trigger is fired.
    *
    * For multi-row blocks it always adds a new record. However, for single row
    * blocks that already have a record, the existing record is replaced
    * with the new record.
    *
    * @access public
    * @param int the status of the new record, but default - RS_AUTO
    */
   function append($status = RS_AUTO) {
      if ($this->_multirow) {
         // Replace RS_AUTO with RS_NEW (for multi-row blocks)
         if ($status == RS_AUTO)
            $status = RS_NEW;

         $this->_record_statuses[$this->_nrows] = $status;
         $this->id[$this->_nrows] = -1;

         foreach ($this->_properties as $name=>$property) {
            $this->_properties[$name]->set_default_value($this->_nrows);
         }

         $this->_nrows++;

         if (($status & RS_EXISTED_MASK) == 0 )
            $this->call_trigger("on_append", $this->_nrows-1);
      }
      else {
         // Replace RS_AUTO with appropriate status
         if ($status == RS_AUTO)
            $status = $this->_form->_auto_detect_changes?RS_INSERT:RS_NEW;

         $this->_record_statuses[0] = $status;
         $this->id = -1; // Ids must be positive integers

         foreach ($this->_properties as $name=>$property) {
            $this->_properties[$name]->set_default_value();
         }

         $this->_nrows = 1;

         if (($status & RS_EXISTED_MASK) == 0 )
            $this->call_trigger("on_append");
      }
   }

   /**
    * Adds record if needed.
    *
    * Called after ON_OPEN triggers.
    *
    * @access private
    */
   function auto_append() { // not to be called by applications
      if (($this->_autoappend && $this->_multirow) ||
          (!$this->_multirow && $this->_nrows == 0)) {
         $this->append();
      }
   }

   /**
    * Adds a property to the block.
    *
    * It adds the property to the {@link $_properties} array and creating
    * a corresponding $<propertyname> member.
    * The <var>$display</var> parameter is optional. If not specified,
    * the property will be queried for its default display.
    *
    * @access public
    * @param Property reference to the property to be added.
    * @param Display optional, a display to be used to render this property.
    */
   function add_property(&$property, $display = null) {

      // Insert the property as the next in the array
      $this->_properties[$property->name] = &$property;

      // Link the property back to us
      $property->block = &$this;

      if (isset($display))
         $property->display = $display;
      else
         $property->display = $property->get_default_display();

      if ($this->_multirow) {
         $name = $property->name;
         $this->$name = array();
      }
      else {
         $property->set_default_value();
      }

      $property->check_default();
   }

   /**
    * Validates the content of the block.
    *
    * Implements the block and property level validation sequence described
    * in details in the tutorial.
    *
    * @access private
    * @returns bool
    */
   function validate() {
      global $error;
      if ($this->_multirow) {
          for ($i=0; $i<$this->_nrows; $i++) {
             // Do not validate the row if it is unchanged or deleted
             $status = $this->get_record_status($i);
             if (($status & RS_DELETED_MASK) > 0 ||
                 (($status & RS_CHANGED_MASK) == 0 &&
                  $this->_form->_auto_detect_changes))
                continue;

             // Call the block row level ON VALIDATE trigger.
             if ($this->call_trigger("on_validate", $i)) {
                if ($error != "")
                   return FALSE;
             }
             else { // Do the normal validation for the row

                foreach ($this->_properties as $name=>$property) {

                   // Skip properties that do not need validation
                   if (!$this->_properties[$name]->needs_validation())
                      continue;

                   // Call the property level ON VALIDATE trigger
                   if ($this->_form->call_trigger($this->_name."_".$name."_on_validate")) {
                      if ($error != "")
                         return FALSE;
                   }
                   else { // Do the normal validation since ON VALIDATE is not found

                      if (!$this->_properties[$name]->validate($i))
                         return FALSE;

                      if ($this->_form->call_trigger($this->_name."_".$name."_when_validate") &&
                         $error != "")
                         return FALSE;
                   }
                }

                if ($this->call_trigger("when_validate", $i) &&
                    $error != "")
                    return FALSE;
             } // End of normal validation for the row
          } // for
      } // end if multirow
      else {
         // Do not validate the row if it is unchanged or deleted
         $status = $this->get_record_status();
         if (($status & RS_DELETED_MASK) > 0 ||
             (($status & RS_CHANGED_MASK) == 0 &&
              $this->_form->_auto_detect_changes))
            return TRUE;

         // Call the ON_VALIDATE trigger for the block
         if ($this->call_trigger("on_validate")) {
            if ($error != "")
               return FALSE;
         }
         else { // Do the normal validation since ON_VALIDATE was not found.

            foreach ($this->_properties as $name=>$property) {

               // Skip properties that do not need validation
               if (!$this->_properties[$name]->needs_validation())
                  continue;

               // Call the property level ON VALIDATE trigger
               if ($this->_form->call_trigger($this->_name."_".$name."_on_validate")) {
                  if ($error != "")
                     return FALSE;
               }
               else { // Do the normal validation

                  if (!$this->_properties[$name]->validate())
                     return FALSE;

                  if ($this->_form->call_trigger($this->_name."_".$name."_when_validate") &&
                     $error != "")
                     return FALSE;
               }
            }

            if ($this->call_trigger("when_validate") &&
                $error != "")
               return FALSE;

         }
      }
      return TRUE;
   }

   /**
    * Restores the block data from the <var>$HTTP_POST_VARS</var> array.
    *
    * First, it reads the control data it stored there. Then using this data,
    * it reads all records, property by property.
    *
    * It adds own control data to the $attributes array, and calls
    * {@link Property::auto_restore()} method on each property,
    * giving it a chance to add itself to the <var>$attributes</var> array too,
    * so that the <i>should-be</i> electronic signature is computed
    * against a comparable set of name=>value pairs.
    *
    * @access private
    */
   function restore(&$attributes) {
      global $HTTP_POST_VARS;
      if ($this->_multirow) {
         $nrows=$HTTP_POST_VARS[$this->_name."_nrows"];
         $attributes[$this->_name."_nrows"]=$nrows;
         // Now load rows one by one
         for ($i = 0; $i<$nrows; $i++) {
            $this->append(RS_OLD); // Avoid firing ON_APPEND trigger
            // First, read the status
            $this->_record_statuses[$i] = $HTTP_POST_VARS[$this->_name."_rs_".$i];
            $attributes[$this->_name."_rs_".$i]=$this->_record_statuses[$i];

            // Now read id (we actually assume that all ids are called id in every table)
            $this->id[$i] = $HTTP_POST_VARS[$this->_name."_id_".$i];
            $attributes[$this->_name."_id_".$i] = $this->id[$i];

            // Now we can read the actual values of the row
            foreach ($this->_properties as $name=>$property) {
               if (isset($this->_properties[$name]->display)) {
                  $this->_properties[$name]->form_read($i);
                  $this->_properties[$name]->auto_restore($attributes, $i);
               }
            }
         }
      }
      else {
         // We need to create the record in which we will store the just read data.
         $this->append(RS_OLD); // Avoid firing ON_APPEND trgger
         // First, read the status
         $this->_record_statuses[0] = $HTTP_POST_VARS[$this->_name."_rs"];
         $attributes[$this->_name."_rs"]=$this->_record_statuses[0];

         // Now read id (we actually assume that all ids are called id in every table)
         $this->id = $HTTP_POST_VARS[$this->_name."_id"];
         $attributes[$this->_name."_id"] = $this->id;


         // Now we can read the actual values of the row
         foreach ($this->_properties as $name=>$property) {
            if (isset($this->_properties[$name]->display)) {
               $this->_properties[$name]->form_read();
               $this->_properties[$name]->auto_restore($attributes);
            }
         }
      }
   }

   /**
    * Returns the default action on the block.
    *
    * @access private
    * @returns string
    */
   function get_default_action() {
      return $this->_default_action;
   }

   /**
    * Generates hidden inputs for all block internal data, hidden fields, and original values.
    *
    * This method generates hidden fields for all the values that need
    * to be preserved and also adds corresponding name=>value pairs to
    * the <var>$attributes</var> array – for the purpose of computing
    * the electronic signature.
    *
    * Each block stores the following parameters:<ul>
    * <li>number or records (stored as <blockname>_nrows) – for multi-row blocks only.</li>
    * <li>for multi-row blocks for each record:<ul>
    *     <li>record status (stored as <blockname>_rs_<record_number>)</li>
    *     <li>id of the object in the record (stored as <blockname>_id_<record_number>)</li></ul></li>
    * <li>for single row blocks:<ul>
    *     <li>record status (stored as <blockname>_rs)</li>
    *     <li>id of the object (stored as <blockname>_id).</li></ul></li></ul>
    *
    * Also, the block queries each property if it also wants to store some
    * information by calling {@link Property::auto_store()} method on it.
    * If the property has hidden fields to be stored, it generates
    * them and adds the corresponding name=>value pair to the
    * <var>$attributes</var> array for electronic signature computation.
    *
    * @access private
    */
   function store_controls(&$attributes) {
      if ($this->_multirow) {
         echo "<input type=\"hidden\" name=\"".$this->_name."_nrows\" value=\"".$this->_nrows."\"/>\n";
         $attributes[$this->_name."_nrows"]=$this->_nrows;

         for ($i=0; $i<$this->_nrows; $i++) {
            echo "<input type=\"hidden\" name=\"".$this->_name."_rs_".$i."\" value=\"".$this->_record_statuses[$i]."\"/>\n";
            $attributes[$this->_name."_rs_".$i]=$this->_record_statuses[$i];
            echo "<input type=\"hidden\" name=\"".$this->_name."_id_".$i."\" value=\"".$this->id[$i]."\"/>\n";
            $attributes[$this->_name."_id_".$i]=$this->id[$i];

            foreach ($this->_properties as $name => $property) {
               $this->_properties[$name]->auto_store($attributes, $i);
            }
         }
      }
      else {
            echo "<input type=\"hidden\" name=\"".$this->_name."_rs\" value=\"".$this->_record_statuses[0]."\"/>\n";
            $attributes[$this->_name."_rs"]=$this->_record_statuses[0];
            echo "<input type=\"hidden\" name=\"".$this->_name."_id\" value=\"".$this->id."\"/>\n";
            $attributes[$this->_name."_id"]=$this->id;

            foreach ($this->_properties as $name => $property) {
               $this->_properties[$name]->auto_store($attributes);
            }
      }
   }

   /**
    * Preserves original values of all properties.
    *
    * The original values are stored in the {@link $_orig_values} array.
    *
    * @access private
    */
   function preserve_original_values() {
      // We do not need to check if $form->_auto_detect_changes is TRUE,
      // since this function should only be called if it is.
      foreach ($this->_properties as $name => $property) {
         if (isset($this->_properties[$name]->display) &&
             !$this->_properties[$name]->display->is_readonly())
            $this->_orig_values[$name] = $this->$name;

      }
   }

   /**
    * Performs the save trigger sequence on a record.
    *
    * For details, see the tutorial. Returns TRUE if save operation was successful.
    *
    *
    * @returns bool
    * @access private
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function save_record($rownum = -1) {
      global $error;

      $status = $this->get_record_status($rownum);

      // Check for deletion first
      if (($status & RS_DELETED_MASK) > 0) {
         // We will only delete records that existed before
         if (($status & RS_EXISTED_MASK) > 0)
            if ($this->call_trigger("on_delete", $rownum))
               return $error == "";
         return TRUE; // it was a new record, or the trigger was not defined
      }
      if (($status & RS_CHANGED_MASK) > 0 || !$this->_form->_auto_detect_changes) {
         if (($status & RS_EXISTED_MASK) > 0) {
            if ($this->call_trigger("on_update", $rownum))
               return $error == "";
         }
         else {
            if ($this->call_trigger("on_insert", $rownum))
               return $error == "";
         }
      }
      return TRUE;
   }

   /**
    * Save all changed reocrds on the block.
    *
    * Run the appropriate trigger sequence to save the block. Return FALSE if
    * save operation unsuccessful and the save operation should be aborted.
    * The abortion is limited to stopping further operations. ROLLBACK should
    * be executed in the trigger at the point of failure. The global <var>$error</var> variable
    * should contain the description of the failure, so that it can be seen by
    * the user (that of course is if you are going to display the form again).
    *
    * @access private
    * @returns bool
    */
   function save() {
      if ($this->_multirow) {
         for ($i=0; $i<$this->_nrows; $i++) {
            if (!$this->save_record($i)) return FALSE;
         }
         return TRUE;
      }
      else if ($this->_nrows > 0)
         return $this->save_record();
   }

   /**
    * Returns TRUE if the block requires saving.
    *
    * Only works in auto-detect-changes mode.
    *
    * @access private
    * @returns bool
    */
   function is_changed() {
      for ($i=0; $i<$this->_nrows; $i++) {
         if (($this->get_record_status($i) & (RS_CHANGED_MASK|RS_DELETED_MASK)) > 0)
            return TRUE;
      }
      return FALSE;
   }
}

/**
 * This is the core class of the library.
 *
 * To access blocks added to the from (as objects) you can simply use
 * <i>$form -> blockname</i>. In order for such access to work without
 * breaking the functionality of the form, you <b>must not</b> name your blocks
 * starting with an underscore.
 */
class Form {
   /**
     * This value is used in computation of the electronic signature for your form.
     *
     * Basic security rules require that each site uses a different magic cookie,
     * or it will be very easy to temper with your forms. You can either set it
     * directly on all pages where you define forms (but before calling the {@link process()}
     * method on the form), or you can set up a global variable
     * <var>$bforms_magic_cookie</var> somewhere in your auto-prepend file,
     * in which case all Forms created will take the value from this variable
     * when they are constructed.
     *
     * @access public
     * @var string
     */
   var $_magic_cookie = "Magic!";

   /**
    * TRUE if the form was restored, FALSE if it is opened the first time.
    *
    * @access private
    * @var bool
    */
   var $_restored = FALSE;

   /**
    * The location of the page, where the user should be redirected, if the form
    * detects tempering with hidden fields.
    *
    * The value is taken from the constructor parameter.
    *
    * @access public
    * @var string
    */
   var $_denied_target;

   /**
    * TRUE by default, tells the form whether it should apply the md5
    * function to the form signature.
    *
    * Only set to FALSE if you want to debug the electronic signature.
    *
    * @access private
    * @var bool
    */
   var $_use_md5 = TRUE;

   /**
    * Associative array holding all blocks of the form.
    *
    * Usually used only for looping through blocks, such as <code>
    * foreach ($form->_blocks as $name => $block) { ... }</code>.
    *
    * @access private
    * @var array
    */
   var $_blocks = array();

   /**
    * A reference to the {@link ButtonProperty} that triggered the submission of the form.
    *
    * If the form was sumbitted with Enter key, points to the first default
    * button found on the blocks.
    *
    * @access private
    * @var Property
    */
   var $_action;

   /**
    * A reference to the {@link Block} where {@link $_action} belongs.
    *
    * @access private
    * @var Block
    */
   var $_action_block;

   /**
    * The row number where {@link $_action} was pressed.
    *
    * For single row blocks always -1;
    *
    * @access private
    * @var int
    */
   var $_action_record;

   /**
    * Control over debugging of trigger firing sequence.
    *
    * Set this value to TRUE before calling the {@link process()} method on
    * the form if you want to see which triggers fire and in what sequence.
    * Beware that this surely will break the http-redirecting logic,
    * since trigger sequences will be displayed before your code gets to
    * execute a header() call.
    *
    * @access public
    * @var bool
    */
   var $_debug_triggers;

   /**
    * Indicates whether the form needs to be multipart.
    *
    * By default is FALSE. It is set to TRUE in {@link the add_block()} function
    * if the block being added is multipart (i.e. contains a {@link FileProperty}).
    * The value is used in {@link start_form()} to provide ENCTYPE attribute
    * to the FORM tag if file uploads are required.
    *
    * Currently, support for file uploads is experimental.
    *
    * @access private
    * @var bool
    */
   var $_multipart = FALSE;

   /**
    * Indicates auto-detect-changes mode.
    *
    * By default it is FALSE to preserve compatibility with previous versions
    * of the library.
    *
    * @access private
    * @var bool
    */
   var $_auto_detect_changes;

   /**
    * Indicates if the form has any changes to save.
    *
    * @access private
    * @returns bool
    */
   function is_changed() {
      if (!$this->_auto_detect_changes)
         return TRUE;

      foreach ($this->_blocks as $name=>$block) {
         if ($this->_blocks[$name]->is_changed())
            return TRUE;
      }
      return FALSE;
   }

   /**
    * Executes the saving sequence.
    *
    * For details of the saving sequence see the tutorial.
    *
    * Returns TRUE if saving has been successful. Otherwise, the global
    * variable <var>$error</var> should contain an error message.
    *
    * @access public
    * @returns bool
    */
   function save() {
      global $error;
      if (!$this->is_changed())
         return TRUE;

      if (!$this->validate())
         return FALSE;

      $this->call_trigger("form_pre_save");
      if ($error != "")
         return FALSE;

      // Save each block
      foreach ($this->_blocks as $name => $block) {
         if (!$this->_blocks[$name]->save())
            return FALSE;
      }

      $this->call_trigger("form_post_save");
      return $error == "";
   }

   /**
    * @access public
    * @param string see {@link $_denied_target}.
    * @param bool optional, by default FALSE.
    */
   function Form($denied_target, $auto_detect_changes = FALSE) {
      global $bforms_magic_cookie;
      // Do some sort of setup before we can operate
//      if (substr($denied_target, 0, 1) == '/')
         $this->_denied_target = $denied_target;
//      else
//         $this->_denied_target = '/'.$denied_target;
      if (isset($bforms_magic_cookie))
         $this->_magic_cookie = $bforms_magic_cookie;
      $this->_auto_detect_changes = $auto_detect_changes;
   }

   /**
    * Adds a block to the form.
    *
    * Takes a reference to the block object that needs to be added.
    * Adds to the {@link $_blocks} array, creates a member named after the block,
    * sets the reference from the block to the form,
    * and checks if the block requires a multipart form.
    *
    * @access public
    * @param Block a <b>reference</b>[!!!] to the block
    */
   function add_block(&$block) {
      $this->_blocks[$block->_name] = &$block;
      $name = $block->_name;
      $this->$name = &$block;
      $block->_form = &$this;
      if (!$this->_multipart && $block->is_multipart())
         $this->_multipart = TRUE;
   }

   /**
    * Does the core processing of the form.
    *
    * Attempts to restore the form, and fires the
    * right sequence of triggers depending on the result.
    *
    * @access public
    */
   function process() {
      // Do all processing except displaying the form

      // Attempt to restore the form from the environment
      $this->restore();
                        // We will not return here if the form has been meddled with.

      // Now, if the form has been restored, we must process action.
      if ($this->_restored) {
         if ($this->_action)
            $this->call_trigger($this->_action_block->_name."_".$this->_action->name."_on_action", $this->_action_record);
                        // Again, if the action handler decides that we do not need to display this
                        // page any more, it will call exit. If it returns, we may have an error (with a
                        // respective error message.
      }
      else { // A first run through this form. We need to prepare data.
             // First, we call form-wide on-open trigger.  This trigger should validate access to data
                 // and exit if not allowed
         $this->call_trigger("form_on_open");

         // Now we need to run on_open trigger on each block, so that they can prepare data to display
         foreach ($this->_blocks as $name => $block) {
            $this->_blocks[$name]->call_trigger("on_open");
         }

         // Run blocks' auto-append mechanism
         foreach ($this->_blocks as $name=>$block) {
            $this->_blocks[$name]->auto_append();
         }

         // Preserve original values
         if ($this->_auto_detect_changes) {
            foreach ($this->_blocks as $name=>$block) {
               $this->_blocks[$name]->preserve_original_values();
            }
         }

      }
   }

   /**
    * Start the generation of the form.
    *
    * Opens the FORM tag, sets <var>$_form</var> global variable to itself,
    * and fires PRE_DISPLAY triggers.
    *
    * The <var>$extras</var> parameter is inserted inside the <form> tag.
    *
    * @access public
    * @param string
    */
   function start_form($extras = "") {
      global $_form, $_SERVER;

      // Set current form to this
      $_form = $this;

      // Call PRE_DISPLAY triggers
      $this->call_trigger("form_pre_display");
      foreach ($this->_blocks as $name=>$block) {
         $this->_blocks[$name]->call_trigger("pre_display");
      }

      // Actually display the opening of the form
      print "<form action=\"".$_SERVER["REQUEST_URI"]."\" method=\"post\" name=\"mainform\"";
      if ($this->_multipart) {
         print " enctype=\"multipart/form-data\"";
      }
      print " $extras>\n";
   }

   /**
    * Closes form generation.
    *
    * Generates all hidden fields (currently except the ones that are part of the
    * FileProperty), computes the form's digital signature and stores it in a
    * hidden field, closes the FORM tag and unsets the <var>$_form</var>
    * global variable.
    *
    * @access public
    */
   function end_form() {
      global $_form;

      print "<input type=\"hidden\" name=\"restore\" value=\"true\"/>\n";

      // Now store the control info of the blocks
      $attributes = array();

      foreach ($this->_blocks as $name=>$block) {
         $this->_blocks[$name]->store_controls($attributes);
      }

      $s = $this->get_signature($attributes);
      print "<input type=\"hidden\" name=\"signature\" value=\"$s\"/>\n";


      print "</form>\n";
      unset($_form);
   }

   /**
    * Fires a trigger if it is defined.
    *
    * Check if the trigger with the name contained in the <var>$trigger</var>
    * parameter is defined. If it is, then the corresponding function is called,
    * with <var>$rownum</var> as parameter but only if it is not null.
    *
    * The function returns TRUE if the trigger was found and fired, and FALSE
    * if the trigger was not found.
    *
    * <b>Important</b>: the call_trigger method does not return TRUE or FALSE
    * based on the result that the trigger function returned.
    * That result is always ignored.
    *
    * If {@link $_debug_triggers} is set to TRUE, then this function will
    * also echo a line about its progress.
    *
    * @returns bool
    * @access private
    * @param string the name of the trigger
    * @param int the record number for which the property is to be displayed. If -1 or
    *            omitted, it is a single-row block.
    */
   function call_trigger($trigger, $rownum = null) {
      if (function_exists($trigger)) {
         if (isset($rownum)) {
            if ($this->_debug_triggers)
               echo "Firing $trigger for row $rownum<br/>\n";
            call_user_func($trigger,$rownum);
         }
         else {
            if ($this->_debug_triggers)
               echo "Firing $trigger<br/>\n";
            call_user_func($trigger);
         }
         return TRUE;
      }
      if ($this->_debug_triggers)
         echo "$trigger not found<br/>\n";
      return FALSE;
   }

   /**
    * Attempt to restore the form from <var>$HTTP_POST_VARS</var> array.
    *
    * This function attempts to restore the form if it was submitted.
    * It uses a hidden field called "restore" with the value "true"
    * as an indicator that restoration is required.
    * (This field is generated by the {@link end_form()} method).
    *
    * If such field is present in the $HTTP_POST_VARS, then this method calls
    * {@link Block::restore()) method on all blocks, calculates a should-be
    * electronic signature and compares it with the actual electronic signature
    * that arrived. If they don't match then the browser is redirected to
    * the {@link $_denied_target}. If they do, then it sets the {@link $_restored}
    * member to TRUE and fires the AFTER_RESTORE triggers.
    *
    * @access private
    */
   function restore() {
      global $HTTP_POST_VARS;

      if (isset($HTTP_POST_VARS['restore'])) {

         $attibutes = array();

         // We need to restore blocks
         foreach ($this->_blocks as $name => $block) {
             $this->_blocks[$name]->restore($attributes);
         }

         if ($this->_action == "") {
             // We need to find default action
             foreach ($this->_blocks as $name => $block) {
                 $this->_action = $this->_blocks[$name]->get_default_action();
                 if ($this->_action != "") {
                     $this->_action_block = $name;
                     $this->_action_record = -1;
                     break;
                 }
             }
         }

         if ($HTTP_POST_VARS["signature"]!=$this->get_signature($attributes)) {
             // We have a problem, somebody has meddled with the form.
             header("Location: http://".$_SERVER["SERVER_NAME"].$this->_denied_target);
             exit;
         }
         $this->_restored = TRUE;

         $this->call_trigger("form_after_restore");
      }
   }

   /**
    * Validates the form.
    *
    * For the desciption of the validation mechanism see the tutorial.
    * Returns TRUE if validation was successful. If not, it returns FALSE
    * and the global variable <var>$error</var> should contain the error text.
    *
    * If you are using {@link Form::save()} functionality, you do not need to
    * call this function manually.
    *
    * @access public
    * @returns bool
    */
   function validate() {
      global $error;

      if ($this->call_trigger("form_on_validate"))
         return $error == "";

      foreach ($this->_blocks as $name => $block) {
         if (!$this->_blocks[$name]->validate())
            return FALSE;
      }
      $this->call_trigger("form_when_validate");
      return $error == "";
   }

   /**
    * Calculates the electronic signature.
    *
    * This function merges all <var>$attributes</var> (which is essentially
    * an associative array of hidden fields that are part of the signature)
    * into a string, adds the {@link $_magic_cookie} and runs <i>md5</i> on
    * the result.
    *
    * See also {@link $_use_md5}.
    *
    * @returns string
    * @access private
    */
   function get_signature($attributes) { // Something to work on.
      $s = "restore=true";

      // Now add the attributes
      ksort($attributes);
      foreach($attributes as $key=>$value) {
         $s .= ','.$key.'='.trim($value);
      }

      $s .= ",magic=".$this->_magic_cookie;

      return ($this->_use_md5? md5($s):$s);
   }
}

/**
 * Internal function to validate parameters provided in calls to {@link field()} and {@link label()}.
 *
 * Since if parameters are invalid, <i>trigger_error()</i> is called, the function only returns
 * if parameters are valid.
 *
 * @access private
 * @returns bool
 * @param string the name of the function that called the validation: will be included in the error message.
 * @param string the name of the block
 * @param string the name of the property
 * @param int the record number for which the property is to be displayed. If -1 or
 *            omitted, it is a single-row block.
 */
function ___validate($func, $block, $field, $rownum = -1) {
   global $_form;

   if (!$_form) {
      trigger_error("Call to $func outside start_form() and end_form() calls", E_USER_ERROR);
      return FALSE;
   }

   if (!is_a($_form, "Form")) {
      trigger_error("$func: invalid form in \$_form variable", E_USER_ERROR);
      return FALSE;
   }

   if (!$_form->$block) {
      trigger_error("$func: unknown block $block", E_USER_ERROR);
      return FALSE;
   }

   if (!is_a($_form->$block, "Block")) {
      trigger_error("$func: invalid block $block", E_USER_ERROR);
      return FALSE;
   }

   if (!$_form->$block->_properties[$field]) {
      trigger_error("$func: property $field does not exist in block $block", E_USER_ERROR);
      return FALSE;
   }

   if (!is_a($_form->$block->_properties[$field], "Property")) {
      trigger_error("$func: invalid property $field in block $block", E_USER_ERROR);
      return FALSE;
   }

   if ($func != "label()" && $rownum != -1) {
      if ($_form->$block->_nrows <= $rownum) {
         trigger_error("$func: invalid record $rownum in block $block", E_USER_ERROR);
         return FALSE;
      }
   }

   return TRUE;
}

/**
 * Generate the label for the specified property.
 *
 * Can only be used between {@link Form::start_form()} and {@link Form::end_form()}
 * calls.
 *
 * @access public
 * @param string the name of the block
 * @param string the name of the property
 * @param integer the number of the row for which the record is to be displayed. It
 *        is expected to be omitted for singlerow blocks, and if omitted for multi-row
 *        blocks, then the label will not be clicable.
 */
function label($block, $field, $rownum = -1) {
   global $_form;

   if (___validate("label()", $block, $field)) {
      $do_label = ($rownum >=0 || !$_form->$block->_multirow) &&
                  $_form->$block->_properties[$field]->display->use_label_tag;
      if ($do_label)
         echo '<label for="'.
              $_form->$block->_properties[$field]->get_form_name($rownum).
              '">';
      echo $_form->$block->_properties[$field]->label;
      if ($do_label)
         echo '</label>';
   }
}

/**
 * Generate the HTML tags for the specified property.
 *
 * Can only be used between {@link Form::start_form()} and {@link Form::end_form()}
 * calls.
 *
 * @access public
 * @param string the name of the block
 * @param string the name of the property
 * @param int the record number for which the property is to be displayed. If -1 or
 *            omitted, it is a single-row block.
 */
function field($block, $field, $rownum = -1) {
   global $_form;

   if (___validate("field()", $block, $field, $rownum)) {
      $_form->call_trigger($block.'_'.$field.'_pre_render', $rownum);
      if (!$_form->call_trigger($block.'_'.$field.'_on_render', $rownum))
         $_form->$block->_properties[$field]->show($rownum);
      $_form->call_trigger($block.'_'.$field.'_post_render', $rownum);
   }
}

?>
Return current item: B-Forms