599 lines
22 KiB
PHP
599 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Default renderer for HTML_QuickForm2
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* LICENSE:
|
|
*
|
|
* Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>,
|
|
* Bertrand Mansion <golgote@mamasam.com>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * The names of the authors may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* @category HTML
|
|
* @package HTML_QuickForm2
|
|
* @author Alexey Borzov <avb@php.net>
|
|
* @author Bertrand Mansion <golgote@mamasam.com>
|
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
|
* @version SVN: $Id: Default.php 299706 2010-05-24 18:32:37Z avb $
|
|
* @link http://pear.php.net/package/HTML_QuickForm2
|
|
*/
|
|
|
|
/**
|
|
* Abstract base class for QuickForm2 renderers
|
|
*/
|
|
// require_once 'HTML/QuickForm2/Renderer.php';
|
|
|
|
/**
|
|
* Default renderer for QuickForm2
|
|
*
|
|
* Mostly a direct port of Default renderer from QuickForm 3.x package.
|
|
*
|
|
* While almost everything in this class is defined as public, its properties
|
|
* and those methods that are not published (i.e. not in array returned by
|
|
* exportMethods()) will be available to renderer plugins only.
|
|
*
|
|
* The following methods are published:
|
|
* - {@link reset()}
|
|
* - {@link setTemplateForClass()}
|
|
* - {@link setTemplateForId()}
|
|
* - {@link setErrorTemplate()}
|
|
* - {@link setElementTemplateForGroupClass()}
|
|
* - {@link setElementTemplateForGroupId()}
|
|
*
|
|
* @category HTML
|
|
* @package HTML_QuickForm2
|
|
* @author Alexey Borzov <avb@php.net>
|
|
* @author Bertrand Mansion <golgote@mamasam.com>
|
|
* @version Release: @package_version@
|
|
*/
|
|
class HTML_QuickForm2_Renderer_Default extends HTML_QuickForm2_Renderer
|
|
{
|
|
/**
|
|
* Whether the form contains required elements
|
|
* @var bool
|
|
*/
|
|
public $hasRequired = false;
|
|
|
|
/**
|
|
* HTML generated for the form
|
|
* @var array
|
|
*/
|
|
public $html = array(array());
|
|
|
|
/**
|
|
* HTML for hidden elements if 'group_hiddens' option is on
|
|
* @var string
|
|
*/
|
|
public $hiddenHtml = '';
|
|
|
|
/**
|
|
* Array of validation errors if 'group_errors' option is on
|
|
* @var array
|
|
*/
|
|
public $errors = array();
|
|
|
|
/**
|
|
* Default templates for elements of the given class
|
|
* @var array
|
|
*/
|
|
public $templatesForClass = array(
|
|
'html_quickform2_element_inputhidden' => '<div style="display: none;">{element}</div>',
|
|
'html_quickform2' => '<div class="quickform">{errors}<form{attributes}>{hidden}{content}</form><qf:reqnote><div class="reqnote">{reqnote}</div></qf:reqnote></div>',
|
|
'html_quickform2_container_fieldset' => '<fieldset{attributes}><qf:label><legend id="{id}-legend">{label}</legend></qf:label>{content}</fieldset>',
|
|
'special:error' => array(
|
|
'prefix' => '<div class="errors"><qf:message><p>{message}</p></qf:message><ul><li>',
|
|
'separator' => '</li><li>',
|
|
'suffix' => '</li></ul><qf:message><p>{message}</p></qf:message></div>'
|
|
),
|
|
'html_quickform2_element' => '<div class="row"><label for="{id}" class="element"><qf:required><span class="required">* </span></qf:required>{label}</label><div class="element<qf:error> error</qf:error>"><qf:error><span class="error">{error}</span><br /></qf:error>{element}</div></div>',
|
|
'html_quickform2_container_group' => '<div class="row"><label class="element"><qf:required><span class="required">* </span></qf:required>{label}</label><div class="element group<qf:error> error</qf:error>"><qf:error><span class="error">{error}</span><br /></qf:error>{content}</div></div>'
|
|
);
|
|
|
|
/**
|
|
* Custom templates for elements with the given IDs
|
|
* @var array
|
|
*/
|
|
public $templatesForId = array();
|
|
|
|
/**
|
|
* Default templates for elements in groups of the given classes
|
|
*
|
|
* Array has the form ('group class' => ('element class' => 'template', ...), ...)
|
|
*
|
|
* @var array
|
|
*/
|
|
public $elementTemplatesForGroupClass = array(
|
|
'html_quickform2_container' => array(
|
|
'html_quickform2_element' => '{element}',
|
|
'html_quickform2_container_fieldset' => '<fieldset{attributes}><qf:label><legend id="{id}-legend">{label}</legend></qf:label>{content}</fieldset>'
|
|
)
|
|
);
|
|
|
|
/**
|
|
* Custom templates for grouped elements in the given group IDs
|
|
*
|
|
* Array has the form ('group id' => ('element class' => 'template', ...), ...)
|
|
*
|
|
* @var array
|
|
*/
|
|
public $elementTemplatesForGroupId = array();
|
|
|
|
/**
|
|
* Array containing IDs of the groups being rendered
|
|
* @var array
|
|
*/
|
|
public $groupId = array();
|
|
|
|
public function exportMethods()
|
|
{
|
|
return array(
|
|
'reset',
|
|
'setTemplateForClass',
|
|
'setTemplateForId',
|
|
'setErrorTemplate',
|
|
'setGroupedTemplateForClass',
|
|
'setElementTemplateForGroupClass',
|
|
'setElementTemplateForGroupId'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets template for form elements that are instances of the given class
|
|
*
|
|
* When searching for a template to use, renderer will check for templates
|
|
* set for element's class and its parent classes, until found. Thus a more
|
|
* specific template will override a more generic one.
|
|
*
|
|
* @param string Class name
|
|
* @param mixed Template to use for elements of that class
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function setTemplateForClass($className, $template)
|
|
{
|
|
$this->templatesForClass[strtolower($className)] = $template;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets template for form element with the given id
|
|
*
|
|
* If a template is set for an element via this method, it will be used.
|
|
* In the other case a generic template set by {@link setTemplateForClass()}
|
|
* or {@link setGroupedTemplateForClass()} will be used.
|
|
*
|
|
* @param string Element's id
|
|
* @param mixed Template to use for rendering of that element
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function setTemplateForId($id, $template)
|
|
{
|
|
$this->templatesForId[$id] = $template;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets template for rendering validation errors
|
|
*
|
|
* This template will be used if 'group_errors' option is set to true.
|
|
* The template array should contain 'prefix', 'suffix' and 'separator'
|
|
* keys.
|
|
*
|
|
* @param array Template for validation errors
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function setErrorTemplate(array $template)
|
|
{
|
|
return $this->setTemplateForClass('special:error', $template);
|
|
}
|
|
|
|
/**
|
|
* Sets grouped elements templates using group class
|
|
*
|
|
* Templates set via {@link setTemplateForClass()} will not be used for
|
|
* grouped form elements. When searching for a template to use, the renderer
|
|
* will first consider template set for a specific group id, then the
|
|
* group templates set by group class.
|
|
*
|
|
* @param string Group class name
|
|
* @param string Element class name
|
|
* @param mixed Template
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function setElementTemplateForGroupClass($groupClass, $elementClass, $template)
|
|
{
|
|
$this->elementTemplatesForGroupClass[strtolower($groupClass)][strtolower($elementClass)] = $template;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets grouped elements templates using group id
|
|
*
|
|
* Templates set via {@link setTemplateForClass()} will not be used for
|
|
* grouped form elements. When searching for a template to use, the renderer
|
|
* will first consider template set for a specific group id, then the
|
|
* group templates set by group class.
|
|
*
|
|
* @param string Group id
|
|
* @param string Element class name
|
|
* @param mixed Template
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function setElementTemplateForGroupId($groupId, $elementClass, $template)
|
|
{
|
|
$this->elementTemplatesForGroupId[$groupId][strtolower($elementClass)] = $template;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Resets the accumulated data
|
|
*
|
|
* This method is called automatically by startForm() method, but should
|
|
* be called manually before calling other rendering methods separately.
|
|
*
|
|
* @return HTML_QuickForm2_Renderer_Default
|
|
*/
|
|
public function reset()
|
|
{
|
|
$this->html = array(array());
|
|
$this->hiddenHtml = '';
|
|
$this->errors = array();
|
|
$this->hasRequired = false;
|
|
$this->groupId = array();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns generated HTML
|
|
*
|
|
* @return string
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return (isset($this->html[0][0])? $this->html[0][0]: '') .
|
|
$this->hiddenHtml;
|
|
}
|
|
|
|
/**
|
|
* Renders a generic element
|
|
*
|
|
* @param HTML_QuickForm2_Node Element being rendered
|
|
*/
|
|
public function renderElement(HTML_QuickForm2_Node $element)
|
|
{
|
|
$elTpl = $this->prepareTemplate($this->findTemplate($element), $element);
|
|
$this->html[count($this->html) - 1][] = str_replace(array('{element}', '{id}'),
|
|
array($element, $element->getId()), $elTpl);
|
|
}
|
|
|
|
/**
|
|
* Renders a hidden element
|
|
*
|
|
* @param HTML_QuickForm2_Node Hidden element being rendered
|
|
*/
|
|
public function renderHidden(HTML_QuickForm2_Node $element)
|
|
{
|
|
if ($this->options['group_hiddens']) {
|
|
$this->hiddenHtml .= $element->__toString();
|
|
} else {
|
|
$this->html[count($this->html) - 1][] = str_replace('{element}', $element,
|
|
$this->findTemplate($element));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts rendering a generic container, called before processing contained elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Container being rendered
|
|
*/
|
|
public function startContainer(HTML_QuickForm2_Node $container)
|
|
{
|
|
$this->html[] = array();
|
|
$this->groupId[] = false;
|
|
}
|
|
|
|
/**
|
|
* Finishes rendering a generic container, called after processing contained elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Container being rendered
|
|
*/
|
|
public function finishContainer(HTML_QuickForm2_Node $container)
|
|
{
|
|
array_pop($this->groupId);
|
|
|
|
$cTpl = str_replace(
|
|
array('{attributes}', '{id}'),
|
|
array($container->getAttributes(true), $container->getId()),
|
|
$this->prepareTemplate($this->findTemplate($container, '{content}'), $container)
|
|
);
|
|
$cHtml = array_pop($this->html);
|
|
$break = HTML_Common2::getOption('linebreak');
|
|
$indent = str_repeat(HTML_Common2::getOption('indent'), count($this->html));
|
|
$this->html[count($this->html) - 1][] = str_replace(
|
|
'{content}', $break . $indent . implode($break . $indent, $cHtml), $cTpl
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Starts rendering a group, called before processing grouped elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Group being rendered
|
|
*/
|
|
public function startGroup(HTML_QuickForm2_Node $group)
|
|
{
|
|
$this->html[] = array();
|
|
$this->groupId[] = $group->getId();
|
|
}
|
|
|
|
/**
|
|
* Finishes rendering a group, called after processing grouped elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Group being rendered
|
|
*/
|
|
public function finishGroup(HTML_QuickForm2_Node $group)
|
|
{
|
|
$gTpl = str_replace(
|
|
array('{attributes}', '{id}'),
|
|
array($group->getAttributes(true), array_pop($this->groupId)),
|
|
$this->prepareTemplate($this->findTemplate($group, '{content}'), $group)
|
|
);
|
|
|
|
$separator = $group->getSeparator();
|
|
$elements = array_pop($this->html);
|
|
if (!is_array($separator)) {
|
|
$content = implode((string)$separator, $elements);
|
|
} else {
|
|
$content = '';
|
|
$cSeparator = count($separator);
|
|
for ($i = 0, $count = count($elements); $i < $count; $i++) {
|
|
$content .= (0 == $i? '': $separator[($i - 1) % $cSeparator]) .
|
|
$elements[$i];
|
|
}
|
|
}
|
|
|
|
$this->html[count($this->html) - 1][] = str_replace('{content}', $content, $gTpl);
|
|
}
|
|
|
|
/**
|
|
* Starts rendering a form, called before processing contained elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Form being rendered
|
|
*/
|
|
public function startForm(HTML_QuickForm2_Node $form)
|
|
{
|
|
$this->reset();
|
|
}
|
|
|
|
/**
|
|
* Finishes rendering a form, called after processing contained elements
|
|
*
|
|
* @param HTML_QuickForm2_Node Form being rendered
|
|
*/
|
|
public function finishForm(HTML_QuickForm2_Node $form)
|
|
{
|
|
$formTpl = str_replace(
|
|
array('{attributes}', '{hidden}', '{errors}'),
|
|
array($form->getAttributes(true), $this->hiddenHtml,
|
|
$this->outputGroupedErrors()),
|
|
$this->findTemplate($form, '{content}')
|
|
);
|
|
$this->hiddenHtml = '';
|
|
|
|
// required note
|
|
if (!$this->hasRequired || $form->toggleFrozen() ||
|
|
empty($this->options['required_note']))
|
|
{
|
|
$formTpl = preg_replace('!<qf:reqnote>.*</qf:reqnote>!isU', '', $formTpl);
|
|
} else {
|
|
$formTpl = str_replace(
|
|
array('<qf:reqnote>', '</qf:reqnote>', '{reqnote}'),
|
|
array('', '', $this->options['required_note']),
|
|
$formTpl
|
|
);
|
|
}
|
|
|
|
$break = HTML_Common2::getOption('linebreak');
|
|
$script = $this->getJavascriptBuilder()->__toString();
|
|
$this->html[0] = array((empty($script)? '': $script . $break) . str_replace(
|
|
'{content}', $break . implode($break, $this->html[0]), $formTpl
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Creates a error list if 'group_errors' option is true
|
|
*
|
|
* @return string HTML with a list of all validation errors
|
|
*/
|
|
public function outputGroupedErrors()
|
|
{
|
|
if (empty($this->errors)) {
|
|
return '';
|
|
}
|
|
if (!empty($this->options['errors_prefix'])) {
|
|
$errorHtml = str_replace(array('<qf:message>', '</qf:message>', '{message}'),
|
|
array('', '', $this->options['errors_prefix']),
|
|
$this->templatesForClass['special:error']['prefix']);
|
|
} else {
|
|
$errorHtml = preg_replace('!<qf:message>.*</qf:message>!isU', '',
|
|
$this->templatesForClass['special:error']['prefix']);
|
|
}
|
|
$errorHtml .= implode($this->templatesForClass['special:error']['separator'], $this->errors);
|
|
if (!empty($this->options['errors_suffix'])) {
|
|
$errorHtml .= str_replace(array('<qf:message>', '</qf:message>', '{message}'),
|
|
array('', '', $this->options['errors_suffix']),
|
|
$this->templatesForClass['special:error']['suffix']);
|
|
} else {
|
|
$errorHtml .= preg_replace('!<qf:message>.*</qf:message>!isU', '',
|
|
$this->templatesForClass['special:error']['suffix']);
|
|
}
|
|
return $errorHtml;
|
|
}
|
|
|
|
/**
|
|
* Finds a proper template for the element
|
|
*
|
|
* Templates are scanned in a predefined order. First, if a template was
|
|
* set for a specific element by id, it is returned, no matter if the
|
|
* element belongs to a group. If the element does not belong to a group,
|
|
* we try to match a template using the element class.
|
|
* But, if the element belongs to a group, templates are first looked up
|
|
* using the containing group id, then using the containing group class.
|
|
* When no template is found, the provided default template is returned.
|
|
*
|
|
* @param HTML_QuickForm2_Node Element being rendered
|
|
* @param string Default template to use if not found
|
|
* @return string Template
|
|
*/
|
|
public function findTemplate(HTML_QuickForm2_Node $element, $default = '{element}')
|
|
{
|
|
if (!empty($this->templatesForId[$element->getId()])) {
|
|
return $this->templatesForId[$element->getId()];
|
|
}
|
|
$class = strtolower(get_class($element));
|
|
$groupId = end($this->groupId);
|
|
$elementClasses = array();
|
|
do {
|
|
if (empty($groupId) && !empty($this->templatesForClass[$class])) {
|
|
return $this->templatesForClass[$class];
|
|
}
|
|
$elementClasses[$class] = true;
|
|
} while ($class = strtolower(get_parent_class($class)));
|
|
|
|
if (!empty($groupId)) {
|
|
if (!empty($this->elementTemplatesForGroupId[$groupId])) {
|
|
while (list($elClass) = each($elementClasses)) {
|
|
if (!empty($this->elementTemplatesForGroupId[$groupId][$elClass])) {
|
|
return $this->elementTemplatesForGroupId[$groupId][$elClass];
|
|
}
|
|
}
|
|
}
|
|
|
|
$group = $element->getContainer();
|
|
$grClass = strtolower(get_class($group));
|
|
do {
|
|
if (!empty($this->elementTemplatesForGroupClass[$grClass])) {
|
|
reset($elementClasses);
|
|
while (list($elClass) = each($elementClasses)) {
|
|
if (!empty($this->elementTemplatesForGroupClass[$grClass][$elClass])) {
|
|
return $this->elementTemplatesForGroupClass[$grClass][$elClass];
|
|
}
|
|
}
|
|
}
|
|
} while ($grClass = strtolower(get_parent_class($grClass)));
|
|
}
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* Processes the element's template, adding label(s), required note and error message
|
|
*
|
|
* @param string Element template
|
|
* @param HTML_QuickForm2_Node Element being rendered
|
|
* @return string Template with some substitutions done
|
|
*/
|
|
public function prepareTemplate($elTpl, HTML_QuickForm2_Node $element)
|
|
{
|
|
// if element is required
|
|
$elTpl = $this->markRequired($elTpl, $element->isRequired());
|
|
$elTpl = $this->outputError($elTpl, $element->getError());
|
|
return $this->outputLabel($elTpl, $element->getLabel());
|
|
}
|
|
|
|
/**
|
|
* Marks element required or removes "required" block
|
|
*
|
|
* @param string Element template
|
|
* @param bool Whether element is required
|
|
* @return string Template with processed "required" block
|
|
*/
|
|
public function markRequired($elTpl, $required)
|
|
{
|
|
if ($required) {
|
|
$this->hasRequired = true;
|
|
$elTpl = str_replace(array('<qf:required>', '</qf:required>'),
|
|
array('', ''), $elTpl);
|
|
} else {
|
|
$elTpl = preg_replace('!<qf:required>.*</qf:required>!isU', '', $elTpl);
|
|
}
|
|
return $elTpl;
|
|
}
|
|
|
|
/**
|
|
* Outputs element error, removes empty error blocks
|
|
*
|
|
* @param string Element template
|
|
* @param string Validation error for the element
|
|
* @return string Template with error substitutions done
|
|
*/
|
|
public function outputError($elTpl, $error)
|
|
{
|
|
if ($error && !$this->options['group_errors']) {
|
|
$elTpl = str_replace(array('<qf:error>', '</qf:error>', '{error}'),
|
|
array('', '', $error), $elTpl);
|
|
} else {
|
|
if ($error && $this->options['group_errors']) {
|
|
$this->errors[] = $error;
|
|
}
|
|
$elTpl = preg_replace('!<qf:error>.*</qf:error>!isU', '', $elTpl);
|
|
}
|
|
return $elTpl;
|
|
}
|
|
|
|
/**
|
|
* Outputs element's label(s), removes empty label blocks
|
|
*
|
|
* @param string Element template
|
|
* @param mixed Element label(s)
|
|
* @return string Template with label substitutions done
|
|
*/
|
|
public function outputLabel($elTpl, $label)
|
|
{
|
|
$mainLabel = is_array($label)? array_shift($label): $label;
|
|
$elTpl = str_replace('{label}', $mainLabel, $elTpl);
|
|
if (false !== strpos($elTpl, '<qf:label>')) {
|
|
if ($mainLabel) {
|
|
$elTpl = str_replace(array('<qf:label>', '</qf:label>'), array('', ''), $elTpl);
|
|
} else {
|
|
$elTpl = preg_replace('!<qf:label>.*</qf:label>!isU', '', $elTpl);
|
|
}
|
|
}
|
|
if (is_array($label)) {
|
|
foreach($label as $key => $text) {
|
|
$key = is_int($key)? $key + 2: $key;
|
|
$elTpl = str_replace(array('<qf:label_' . $key . '>', '</qf:label_' . $key . '>', '{label_' . $key . '}'),
|
|
array('', '', $text), $elTpl);
|
|
}
|
|
}
|
|
if (strpos($elTpl, '{label_')) {
|
|
$elTpl = preg_replace('!<qf:label_([^>]+)>.*</qf:label_\1>!isU', '', $elTpl);
|
|
}
|
|
return $elTpl;
|
|
}
|
|
}
|
|
?>
|