Location: PHPKode > projects > Aukyla Platform > aukyla/base/Forms.php
<?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&amp;{$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;
}

?>
Return current item: Aukyla Platform