<?php
/*
Forms.php, provides functions for building forms
Copyright (C) 2004 Arend van Beelen, Auton Rijnsburg
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This program 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 General Public License for
more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For any questions, comments or whatever, you may mail me at: hide@address.com
*/
require_once('Config.php');
require_once('JavaScript.php');
require_once('Locale.php');
require_once('String.php');
require_once('Tables.php');
require_once('Widgets.php');
/**
* @brief Provides functions for building and displaying forms.
*
* Forms allow the user to enter data through Input widgets which is then
* send to the server when the form is submitted. A form is submitted when the
* user clicks on a Button which has the confirmation property set.
*/
class Form extends Container
{
/**
* Constructor.
*
* Creates an empty form.
*
* @param parent Container to add this form to.
* @param actionURL The URL to request when submitting the form.
*/
public function __construct(Container $parent, $actionURL)
{
parent::__construct($parent);
$this->actionURL = $actionURL;
$this->multiPart = false;
$this->required = array();
$this->expressions = array();
$this->errorMessage = i18n('Please fill in all required fields.');
$this->useGridLayout = false;
$this->childrenUseGridLayout = array();
$this->singleShot = false;
self::$highlightMethod = Config::globals('formLabelHighlight', 'bold');
}
/**
* Shows the form.
*/
public function show()
{
$enctypeProperty = ($this->multiPart ? " enctype=\"multipart/form-data\"" : '');
if($this->hasChecks())
{
$expressions = $this->expressions;
if(sizeof($this->required) > 0)
{
$expressions[JavaScript::checkVariables($this->required)] = $this->errorMessage;
}
$javaScript = new JavaScript($this, JavaScript::checkFormFunction($expressions, $this->id));
}
Output::write("<form$enctypeProperty action=\"{$this->actionURL}\"".$this->parentProperties().'>');
if($this->useGridLayout == true)
{
$createNewGridLayout = true;
$gridLayout = NULL;
foreach($this->children as $i => $child)
{
if($this->childrenUseGridLayout[$i] == true)
{
if($createNewGridLayout == true)
{
$gridLayout = new GridLayout($this, 2);
$createNewGridLayout = false;
}
$child->show($gridLayout);
}
else
{
if($createNewGridLayout == false)
{
$gridLayout->show();
}
$createNewGridLayout = true;
$child->show();
}
}
if($createNewGridLayout == false)
{
$gridLayout->show();
}
}
else
{
parent::show();
}
Output::write('</form>');
}
/**
* Returns the current form.
*/
public function form()
{
return $this;
}
/**
* Sets an input as being required to be filled in.
*
* Usually you don't need to call this function yourself, as most
* constructors of input widgets already contain a @p required
* argument, and they will call this function for you if it's @p true.
*
* @param id ID of the widget.
* @param required @p true if the widget is required, @p false
* otherwise.
*/
public function setRequired($id, $required)
{
if($required == true)
{
$this->required[$id] = $id;
}
else
{
unset($this->required[$id]);
}
}
/**
* Returns whether the form has any JavaScript checks, because of
* required widgets or custom checks.
*/
public function hasChecks()
{
return (sizeof($this->required) > 0 ||
sizeof($this->expressions) > 0);
}
/**
* Adds an (extra) JavaScript check.
*
* @param expression The expression to be checked when the form is
* submitted.
* @param error The error message to show if the expression
* failes. If empty, the form's error message is
* used.
*/
public function addJavaScriptCheck($expression, $error = '')
{
$this->expressions[$expression] = $error;
}
/**
* Sets whether the form is multi-part encoded.
*
* Usually you don't need to worry about this. If you add a FileInput to
* the form, this will be set automatically.
*
* @param multiPart The new multi-part value, @p true enables multi-part
* encoding, @p false disables it.
*/
public function setMultiPart($multiPart)
{
$this->multiPart = $multiPart;
}
/**
* Sets an error message which is shown when required input fields
* haven't been filled in.
*
* @param errorMessage The new error message.
*/
public function setErrorMessage($errorMessage)
{
$this->errorMessage = $errorMessage;
}
/**
* Returns the error message set for this form.
*
* @return The error message to show when not all required input fields have
* been filled in.
*/
public function errorMessage()
{
return $this->errorMessage;
}
/**
* Sets whether this form should use a GridLayout to align the form
* elements in it. By default this is off.
*
* @param useGridLayout @p true if the form should use a GridLayout for
* layout, @p false otherwise.
*/
public function useGridLayout($useGridLayout)
{
$this->useGridLayout = $useGridLayout;
}
/**
* Returns whether this form uses a GridLayout for aligning its elements.
*
* @return @p true if the form uses a grid layout, @p false otherwise.
*/
public function gridLayout()
{
return $this->useGridLayout;
}
/**
* Sets whether this form is a single shot form. A single shot form is
* a form which may be submitted only once, and which will protect the
* user from submitting it more than once.
*
* @note Even though the form will protect the user to not submit the
* form more than once, no guarantee is offered that the form is
* really only submitted once. This is purely a convenience
* function!
*
* @param singleShot Boolean determining whether this form should be
* a single shot form.
*
* @sa singleShot()
*/
public function setSingleShot($singleShot)
{
$this->singleShot = $singleShot;
}
/**
* Returns whether this form is a single shot form.
*
* @sa setSingleShot()
*/
public function singleShot()
{
return $this->singleShot;
}
public function addWidget(Widget $widget)
{
parent::addWidget($widget);
$this->childrenUseGridLayout[] = (is_a($widget, 'Input') &&
$widget->supportsGridLayout());
}
/**
* Highlights a required @p label. This can be done by either making it bold
* or by putting a star behind it.
*/
public static function highlightLabel($label, $attribute = false)
{
switch(self::$highlightMethod)
{
case 'bold':
if($attribute == false)
{
return "<label style=\"font-weight: bold\">$label</label>";
}
else
{
return "$label\" labelstyle=\"font-weight: bold";
}
case 'star':
if(String::endsWith($label, ':'))
{
return String::substringBefore($label, ':').'*:';
}
return $label.'*';
}
}
protected $children;
protected $id;
private $actionURL;
private $multiPart;
private $required;
private $expressions;
private $errorMessage;
private $useGridLayout;
private $childrenUseGridLayout;
private $singleShot;
static private $highlightMethod;
}
/**
* @brief Base class for input widgets.
*
* Input widgets are widgets which can be added to forms to enable the user to
* give input. Input widgets can provide optional input or can be required to
* provide input when the form is submitted.
*/
abstract class Input extends Container
{
/**
* Constructor.
*
* @param parent Container to add this input to.
* @param id The input's ID.
*/
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->name = $id;
$this->required = false;
$this->text = '';
$this->value = false;
$this->enabled = true;
}
/**
* Sets the text on this input widget.
*
* @param text The new text to be shown on the widget.
*/
public function setText($text)
{
$this->text = $text;
}
/**
* Sets the value of this input widget.
*
* @param value The new value of the widget.
*
* @note Some input widgets may require a boolean value, while others
* may use a string value.
*/
public function setValue($value)
{
$this->value = $value;
}
/**
* Sets this input widget to be required to be filled in.
*
* @param required @p true if the widget is required, @p false
* otherwise.
*/
public function setRequired($required)
{
$this->required = $required;
if(!isset($this->form))
{
die("Input widget without form parent. Did you add an input widget outside of a form?");
}
$this->form->setRequired($this->id, $required);
}
/**
* Sets whether the input widget is enabled. Input widgets are enabled by
* default.
*
* @param enabled @p true if this widget is enabled, @p false
* otherwise.
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
}
/**
* Returns whether this input widget can line out its contents in a
* GridLayout.
*/
public function supportsGridLayout()
{
return false;
}
protected function parentProperties()
{
$parentProperties = parent::parentProperties();
if($this->enabled == false)
{
$parentProperties .= ' enabled="false"';
}
return $parentProperties;
}
protected $form;
protected $id;
/**
* Name of the widget. By default this equals the id of the widget, but
* it can be overwritten by subclasses.
*/
protected $name;
/**
* Boolean determining whether the input widget is required to be filled
* in.
*/
protected $required;
/**
* Boolean to set the enabled state. When an input widget is not enabled, it
* is still visible, but the user can't interact with it.
*/
protected $enabled;
/**
* Text shown on the widget.
*/
protected $text;
/**
* The type of the widget, only used internally by subclasses.
*/
protected $type;
/**
* The value of the widget. Subclasses can decide to interpret the value
* as a boolean or as a string.
*/
protected $value;
}
/**
* @brief A simple text input widget.
*
* A text input widget can have a default size, or you can explicitly set the
* size of the widget in characters by using setSize().
*/
class TextInput extends Input
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->size = false;
$this->type = 'text';
}
/**
* Sets the width of the text input.
*
* @param width Width of the text input in characters, @p false means the
* default width is used.
*/
public function setWidth($width)
{
$this->width = $width;
}
public function supportsGridLayout()
{
return true;
}
public function show($gridLayout = NULL)
{
$valueProperty = ($this->value == '' ? '' : " value=\"{$this->value}\"");
$widthProperty = ($this->width == false ? '' : " size=\"{$this->width}\"");
$labelAttribute = $gridLayout == NULL;
$label = ($this->required && $this->text != '' ? Form::highlightLabel($this->text, $labelAttribute) : $this->text);
$labelProperties = ($labelAttribute && $label != '' ? " label=\"$label\" labelposition=\"front\"" : '');
$inputOpen = "<input type=\"{$this->type}\" name=\"{$this->name}\"$valueProperty$widthProperty$labelProperties".$this->parentProperties().'>';
$inputClose = '</input>';
if($gridLayout == NULL)
{
Output::write($inputOpen);
parent::show();
Output::write($inputClose);
}
else
{
new Label($gridLayout, "$label ");
$textContainer = new RawContainer($gridLayout, $inputOpen.$inputClose);
$this->reparentChildrenTo($textContainer);
}
}
protected $name;
protected $required;
protected $text;
protected $value;
/**
* The size in characters of the widget.
*/
protected $width;
}
/**
* @brief A password input widget.
*
* Basically the same as a text input widget, but the value is masked, usually
* by asterisks. For security reasons, any preset value is ignored.
*/
class PasswordInput extends TextInput
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->type = 'password';
}
public function show($gridLayout = NULL)
{
$this->value = '';
parent::show($gridLayout);
}
protected $type;
protected $value;
}
/**
* @brief A file input widget.
*
* This widget is used for selecting files on the client machine. The user can
* select the file using a browse function and the file will be uploaded when
* the form is submitted. The file is then available through the upload://
* URI namespace, where the name of the file will equal the ID of this widget.
*/
class FileInput extends TextInput
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->form->setMultiPart(true);
$this->type = 'file';
}
protected $form;
protected $type;
}
/**
* @brief A checkbox input widget.
*
* Checkboxes are simple widgets that can be checked and unchecked. You can set
* the checked state of a checkbox by calling setChecked() with a boolean value,
* where @p true means checked and @p false means unchecked (which is the
* default).
*/
class CheckboxInput extends Input
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->setValue('true');
$this->checked = false;
$this->name = $id;
$this->type = 'checkbox';
}
/**
* Sets whether the input widget is checked.
*
* @param checked @p true if the widget should be checked, @p false
* otherwise.
*/
public function setChecked($checked)
{
$this->checked = $checked;
}
public function show()
{
$checkedProperty = ($this->checked ? ' checked="true"' : '');
Output::write("<input type=\"{$this->type}\" name=\"{$this->name}\" value=\"{$this->value}\" label=\"{$this->text}\" labelposition=\"end\"$checkedProperty".$this->parentProperties().'>');
parent::show();
Output::write('</input>');
}
protected $name;
protected $text;
protected $type;
protected $value;
/**
* Boolean determining whether the input widget is checked.
*/
protected $checked;
}
/**
* @brief A container for radio buttons.
*
* This is just a container dedicated to grouping radio buttons.
*
* @sa RadioButton
*/
class RadioGroup extends Container
{
public function show()
{
Output::write('<radiogroup>');
parent::show();
Output::write('</radiogroup>');
}
}
/**
* @brief A radio button widget.
*
* Radio buttons are a special case of checkboxes where only one button can be
* checked at a time. They are grouped together using radio groups. Just create
* a RadioGroup widget and add your buttons to this widget to make a new group
* of radio buttons.
*
* @sa RadioGroup
*/
class RadioButton extends CheckboxInput
{
/**
* Constructor.
*
* @param parent Radio group this widget should be added to.
* @param id The ID of this widget.
*/
public function __construct(RadioGroup $parent, $id)
{
parent::__construct($parent, $id);
$this->setValue($id);
$this->name = $parent->id();
$this->type = 'radio';
}
protected $name;
protected $value;
}
/**
* @brief A select input widget.
*
* Select inputs provide a rollout list of options from which the user can
* choose one.
*
* Using the function setMultiple(), you can allow the user to select more than
* one option.
*/
class SelectInput extends Input
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->multiple = false;
$this->options = array();
$this->size = 1;
}
/**
* Sets all options through an array.
*
* @param options The array of options. The array should consist of
* @p key => @p text pairs. For documentation of the
* @p key and @p text attributes, see addOption().
*/
public function setOptions($options)
{
$this->options = $options;
}
/**
* Adds an option to the options list.
*
* @param key The key of this option. This is the key you will see
* when the form has been submitted.
* @param text The visible text of the option. This is the option as
* it's seen by the user.
* @param selected @p true if this option is currently option,
* @p false otherwise.
*/
public function addOption($key, $text, $selected = false)
{
$this->options[$key] = $text;
if($selected == true)
{
if($this->value == false)
{
$this->value = $key;
}
else
{
$this->value .= ",$key";
}
}
}
/**
* With this function you can allow the user to select more than one option
* from the list. When multiple selection is enabled, the input is no longer
* a rollout widget, but rather provides a plain scrollable list of options.
*
* @param multiple Boolean determining whether this select input should allow
* multiple selections.
* @param size The amount of options visible at once. This is @b not the
* number of options that may be selected.
*/
public function setMultiple($multiple, $size = 1)
{
$this->multiple = $multiple;
$this->size = $size;
}
public function supportsGridLayout()
{
return true;
}
public function show($gridLayout = NULL)
{
$inputTag = ($this->multiple == true ? 'multiselectinput' : 'selectinput');
$optionTag = ($this->multiple == true ? 'multiselectoption' : 'selectoption');
$labelAttribute = $gridLayout == NULL;
$label = ($this->required && $this->text != '' ? Form::highlightLabel($this->text, $labelAttribute) : $this->text);
$labelProperties = ($labelAttribute && $label != '' ? " label=\"$label\" labelposition=\"front\"" : '');
$sizeProperty = ($this->size != 1 ? " size=\"{$this->size}\"" : '');
$inputOpen = "<$inputTag name=\"{$this->name}\" value=\"{$this->value}\"$labelProperties$sizeProperty".$this->parentProperties().'>';
$values = explode(',', $this->value);
foreach($this->options as $key => $optionText)
{
$selectedProperty = (in_array($key, $values) ? ' selected="true"' : '');
$inputOpen .= "<$optionTag value=\"$key\"$selectedProperty>$optionText</$optionTag>";
}
$inputClose = "</$inputTag>";
if($gridLayout == NULL)
{
Output::write($inputOpen);
parent::show();
Output::write($inputClose);
}
else
{
new Label($gridLayout, "$label ");
$textContainer = new RawContainer($gridLayout, $inputOpen.$inputClose);
$this->reparentChildrenTo($textContainer);
}
}
protected $name;
protected $required;
protected $text;
protected $value;
private $multiple;
private $options;
private $size;
}
/**
* @brief An editable text area.
*
* A multi-line text area where the user can type in plain text.
*/
class TextArea extends Input
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
parent::setCssStyle('padding: 5px');
}
/**
* Sets the number of rows, or lines, the text area should be high.
*
* @param rows The new number of rows the text area will be high.
*/
public function setRows($rows)
{
$this->rows = $rows;
}
/**
* Sets the number of columns, or characters. the text area should be
* wide.
*
* @param columns The new number of columns the text area will be wide.
*/
public function setColumns($columns)
{
$this->columns = $columns;
}
public function show()
{
$rowsProperty = (!isset($this->rows) ? '' : " rows=\"{$this->rows}\"");
$colsProperty = (!isset($this->columns) ? '' : " columns=\"{$this->columns}\"");
Output::write("<textarea name=\"{$this->id}\"$rowsProperty$colsProperty".$this->parentProperties().">{$this->value}</textarea>");
parent::show();
}
protected $value;
private $columns;
private $rows;
}
/**
* @brief A hidden input widget.
*
* This widget is hidden for the user and can only be used to pass some
* information with forms. Logically, any text you set on this widget is
* ignored.
*/
class HiddenInput extends TextInput
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->type = 'hidden';
}
public function show()
{
$this->text = '';
parent::show();
}
protected $text;
protected $type;
}
/**
* @brief A form button.
*
* A button used to submit a Form.
*
* Buttons can be confirmation buttons (meaning the user wants to confirm the
* data filled in in the form) by calling the setConfirm() function. A
* confirmation generally triggers the processing of the form on the server.
* When the user clicks a confirmation button, the form is validated to make
* sure the user has filled in all required fields in the form.
*
* When the Form is a single-shot form, confirmation buttons can only be
* clicked once.
*
* @note There are two ways to set the contents of a button. You can add
* contents by adding child widgets or you can set no explicit contents
* in which case the value set with setValue() is used. Setting the text
* of a button has no effect.
*
* @sa setConfirm(), Input::setRequired(), Form::setSingleShot()
*/
class Button extends Input
{
public function __construct(Container $parent, $id)
{
parent::__construct($parent, $id);
$this->type = 'submit';
}
/**
* Sets whether the button is used to confirm the form. This means when
* this button is pressed all required fields should be checked before
* they're submitted.
*
* @param confirm @p true if this button is used to confirm the form,
* @p false otherwise.
*/
public function setConfirm($confirm)
{
$this->confirm = $confirm;
}
public function show()
{
if($this->confirm == true && $this->form->hasChecks())
{
$this->addJavaScript('onclick', "if(checkForm".$this->form->id()."() == false) return false");
}
if($this->confirm == true && $this->form->singleShot() == true)
{
$this->addJavaScript('onclick', "document.getElementById('{$this->id}').disabled = true");
}
Output::write("<button type=\"{$this->type}\" name=\"{$this->name}\" value=\"{$this->value}\"".$this->parentProperties().'>');
if(sizeof($this->children) == 0)
{
Output::write($this->value);
}
else
{
Container::show();
}
Output::write('</button>');
}
protected $children;
protected $form;
protected $id;
protected $name;
protected $type;
protected $value;
private $confirm;
}
/**
* @brief A form which shows login and password fields.
*
* When this form is submitted, the Login class will automatically pick this up
* and log in the user appropriately (or not if invalid credentials are
* supplied).
*/
class LoginForm extends Container
{
/**
* Constructor.
*
* @param parent The parent widget to add this form to.
* @param viewContainer ID of the view container this form is part of.
* @param cancelView The view to return to if login is cancelled.
*/
public function __construct(Container $parent, $viewContainer = 'view', $cancelView = 'default')
{
parent::__construct($parent);
$this->viewContainer = $viewContainer;
$this->cancelView = $cancelView;
}
public function show()
{
if(class_exists('PageIcon'))
{
new PageIcon($this, 'base/icons/password');
}
$form = new Form($this, Config::globals('secureBaseURL')."?action=Login&{$this->viewContainer}_cancel={$this->cancelView}");
$form->setErrorMessage(i18n('Please fill in both the username and password fields.'));
$form->useGridLayout(true);
$usernameInput = new TextInput($form, 'username');
$usernameInput->setText(i18n('Username:'));
$usernameInput->setRequired(true);
$passwordInput = new PasswordInput($form, 'password');
$passwordInput->setText(i18n('Password:'));
$passwordInput->setRequired(true);
$box = new Box($form);
$loginButton = new Button($box, 'button');
$loginButton->setValue(i18n('Login'));
$loginButton->setConfirm(true);
$cancelButton = new Button($box, 'button');
$cancelButton->setValue(i18n('Cancel'));
parent::show();
}
private $viewContainer;
private $cancelView;
}
?>