<?
/* -------------------------------------------------------------
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 <br/>
*
* @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 - <br/>
*/
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 " 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 " 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 " <b>ROW ".$i." BEGIN</b><br/>";
echo " 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 " <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);
}
}
?>