attrs = $attrs; if (is_null($attrs)) $this->attrs = array(); } // API public function isHidden() { return $this->isHidden; } public function isRequired() { return $this->isRequired; } public function needsMultipartForm() { return $this->needsMultipartForm; } public function getIdForLabel($id) { return $id; } // Subclassing API function render($name, $value, $attrs=null) { throw new \Exception("Subclasses must implement"); } function valueFromDataDict($data, $files, $name) { if (isset($data[$name])) return $data[$name]; return null; } // Protected function hasChanged($initial, $data) { if (is_null($data)) $dataValue = ''; else $dataValue = $data; if (is_null($initial)) $initialValue = ''; else $initialValue = $initial; return $initialValue != $dataValue; } function buildAttrs(array $extra_attrs=null, array $more_attrs=null) { if (is_null($more_attrs)) $more_attrs = array(); $attrs = array_merge($this->attrs, $more_attrs); if (!is_null($extra_attrs)) $attrs = array_merge($attrs, $extra_attrs); return $attrs; } function setRequired($required) { $this->isRequired = $required; } } abstract class Input extends Widget { protected $inputType = null; function render($name, $value, $attrs=null) { if (is_null($value)) $value = ""; $finalAttrs = $this->buildAttrs($attrs, array( "type" => $this->inputType, "name" => $name)); if ($value !== "") $finalAttrs["value"] = $value; return html::mark_safe(sprintf('', flatatt($finalAttrs))); } } class TextInput extends Input { protected $inputType = "text"; } class PasswordInput extends Input { protected $inputType = "password", $renderValue = false; function __construct($attrs=null, $renderValue=false) { parent::__construct($attrs); $this->renderValue = $renderValue; } function render($name, $value, $attrs=null) { if (!$this->renderValue) $value = null; return parent::render($name, $value, $attrs); } } class HiddenInput extends Input { protected $isHidden = true, $inputType = "hidden"; } class MultipleHiddenInput extends HiddenInput { protected $choices; function __construct($attrs=null, array $choices=null) { parent::__construct($attrs); if (is_null($choices)) $choices = array(); $this->choices = $choices; } function render($name, $value, $attrs=null, array $choices=null) { if (is_null($value)) $value = array(); $finalAttrs = $this->buildAttrs($attrs, array('type' => $this->inputType, 'name' => "{$name}[]")); $id = isset($finalAttrs['id']) ? $finalAttrs['id'] : null; $inputs = array(); $i = 0; foreach ($value as $v) { $inputAttrs = array_merge(array('value' => $v), $finalAttrs); if (!is_null($id)) $inputAttrs['id'] = "{$id}_{$i}"; $inputs[] = sprintf('', flatatt($inputAttrs)); $i++; } return html::mark_safe(implode("\n", $inputs)); } function valueFromDataDict($data, $files, $name) { if (is_a($data, 'bjork\utils\datastructures\MultiValueDict')) return $data->getList($name); if (isset($data[$name])) return $data[$name]; return null; } } class FileInput extends Input { protected $needsMultipartForm = true, $inputType = "file"; function render($name, $value, $attrs=null) { return parent::render($name, null, $attrs); } function valueFromDataDict($data, $files, $name) { if (isset($files[$name])) return $files[$name]; return null; } } class Textarea extends Widget { function __construct($attrs=null) { $defaultAttrs = array("cols" => "40", "rows" => "10"); if (!empty($attrs)) $defaultAttrs = array_merge($defaultAttrs, $attrs); parent::__construct($defaultAttrs); } function render($name, $value, $attrs=null) { if (is_null($value)) $value = ""; $finalAttrs = $this->buildAttrs($attrs, array("name" => $name)); return html::mark_safe(sprintf('', flatatt($finalAttrs), html::conditional_escape($value))); } } class CheckboxInput extends Widget { var $checkTest = null; function __construct($attrs=null, $checkTest=null) { parent::__construct($attrs); if (is_null($checkTest)) { $checkTest = function($val) { return (bool)$val; }; } $this->checkTest = $checkTest; } function render($name, $value, $attrs=null) { $finalAttrs = $this->buildAttrs($attrs, array( "type" => "checkbox", "name" => $name)); $checkTest = $this->checkTest; $result = $checkTest($value); if ($result) $finalAttrs['checked'] = 'checked'; if (!in_array($value, array('', true, false, null), true)) $finalAttrs['value'] = strval($value); return html::mark_safe(sprintf('', flatatt($finalAttrs))); } function valueFromDataDict($data, $files, $name) { if (!isset($data[$name])) return false; $value = $data[$name]; $values = array('true' => true, 'false' => false); if (is_string($value) && in_array(strtolower($value), array_keys($values))) $value = $values[strtolower($value)]; return $value; } function hasChanged($initial, $data) { return (bool)$initial != (bool)$data; } } class Select extends Widget { var $choices; function __construct($attrs=null, $choices=null) { parent::__construct($attrs); if (empty($choices)) $choices = array(); $this->choices = $choices; } function render($name, $value, $attrs=null, array $choices=null) { if (is_null($value)) $value = ""; if (is_null($choices)) $choices = array(); $finalAttrs = $this->buildAttrs($attrs, array("name" => $name)); $output = array(sprintf("'; return html::mark_safe(implode("\n", $output)); } function renderOptions(array $choices, array $selectedChoices) { $selectedChoices = array_unique(array_map('strval', $selectedChoices), SORT_LOCALE_STRING); $output = array(); foreach (array($this->choices, $choices) as $c) { foreach ($c as $cval => $clabel) { if (is_array($clabel)) { $output[] = sprintf(''; } else { $output[] = $this->renderOption($selectedChoices, $cval, $clabel); } } } return implode("\n", $output); } function renderOption(array $selectedChoices, $optionValue, $optionLabel) { $selectedHtml = in_array(strval($optionValue), $selectedChoices, true) ? ' selected="selected"' : ''; return sprintf('', html::escape($optionValue), $selectedHtml, html::conditional_escape($optionLabel)); } } class NullBooleanSelect extends Select { function __construct($attrs=null, $choices=null) { $choices = array( '1' => translation::gettext('Unknown'), '2' => translation::gettext('Yes'), '3' => translation::gettext('No'), ); parent::__construct($attrs, $choices); } function render($name, $value, $attrs=null, array $choices=null) { if ($value === true) $value = '2'; else if ($value === false) $value = '3'; else { if (!in_array($value, array('2', '3'), true)) $value = '1'; } return parent::render($name, $value, $attrs, $choices); } function valueFromDataDict($data, $files, $name) { if (isset($data[$name])) $value = $data[$name]; else $value = null; if (in_array($value, array('2', 'true', true), true)) $value = true; else if (in_array($value, array('3', 'false', false), true)) $value = false; else $value = null; return $value; } function hasChanged($initial, $data) { if (!is_null($data)) $data = (bool)$data; if (!is_null($initial)) $initial = (bool)$initial; return $initial != $data; } } class SelectMultiple extends Select { function render($name, $value, $attrs=null, array $choices=null) { if (is_null($value)) $value = array(); if (is_null($choices)) $choices = array(); $finalAttrs = $this->buildAttrs($attrs, array("name" => "{$name}[]")); $output = array(sprintf(''; return html::mark_safe(implode("\n", $output)); } function valueFromDataDict($data, $files, $name) { if (is_a($data, 'bjork\utils\datastructures\MultiValueDict')) return $data->getList($name); if (isset($data[$name])) return $data[$name]; return null; } function hasChanged($initial, $data) { if (is_null($data)) $dataValue = array(); else $dataValue = $data; if (is_null($initial)) $initialValue = array(); else $initialValue = $initial; if (count($initialValue) != count($dataValue)) return true; $initialSet = array_unique(array_map('strval', $initialValue), SORT_LOCALE_STRING); $dataSet = array_unique(array_map('strval', $dataValue), SORT_LOCALE_STRING); return $initialSet != $dataSet; } } class RadioInput { var $name, $value, $attrs, $choiceValue, $choiceLabel, $index; function __construct($name, $value, $attrs, $choice, $index) { $this->name = $name; $this->value = $value; $this->attrs = $attrs; $choice = each($choice); $this->choiceValue = $choice['key']; $this->choiceLabel = $choice['value']; $this->index = $index; } function render() { if (isset($this->attrs['id'])) $labelFor = sprintf(' for="%s_%s"', $this->attrs['id'], $this->index); else $labelFor = ''; $choiceLabel = html::conditional_escape($this->choiceLabel); return html::mark_safe(sprintf('', $labelFor, $this->tag(), $choiceLabel)); } function isChecked() { return strval($this->value) === strval($this->choiceValue); } function tag() { if (isset($this->attrs['id'])) $this->attrs['id'] = "{$this->attrs['id']}_{$this->index}"; $finalAttrs = array_merge($this->attrs, array( 'type' => 'radio', 'name' => $this->name, 'value' => $this->choiceValue, )); if ($this->isChecked()) $finalAttrs['checked'] = 'checked'; return html::mark_safe(sprintf('', flatatt($finalAttrs))); } } class RadioFieldRenderer implements \IteratorAggregate, \ArrayAccess, \Countable { public $inputClass = 'bjork\forms\RadioInput'; var $name, $value, $attrs, $choices; function __construct($name, $value, $attrs, $choices) { $this->name = $name; $this->value = $value; $this->attrs = $attrs; $this->choices = $choices; } function __toString() { return $this->render(); } function render() { $choices = array(); foreach ($this as $c) $choices[] = "