,
* Bertrand Mansion
* 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
* @author Bertrand Mansion
* @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
* @author Bertrand Mansion
* @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' => '
'
);
/**
* 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' => ''
)
);
/**
* 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('!.*!isU', '', $formTpl);
} else {
$formTpl = str_replace(
array('', '', '{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('', '', '{message}'),
array('', '', $this->options['errors_prefix']),
$this->templatesForClass['special:error']['prefix']);
} else {
$errorHtml = preg_replace('!.*!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('', '', '{message}'),
array('', '', $this->options['errors_suffix']),
$this->templatesForClass['special:error']['suffix']);
} else {
$errorHtml .= preg_replace('!.*!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('', ''),
array('', ''), $elTpl);
} else {
$elTpl = preg_replace('!.*!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('', '', '{error}'),
array('', '', $error), $elTpl);
} else {
if ($error && $this->options['group_errors']) {
$this->errors[] = $error;
}
$elTpl = preg_replace('!.*!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, '')) {
if ($mainLabel) {
$elTpl = str_replace(array('', ''), array('', ''), $elTpl);
} else {
$elTpl = preg_replace('!.*!isU', '', $elTpl);
}
}
if (is_array($label)) {
foreach($label as $key => $text) {
$key = is_int($key)? $key + 2: $key;
$elTpl = str_replace(array('', '', '{label_' . $key . '}'),
array('', '', $text), $elTpl);
}
}
if (strpos($elTpl, '{label_')) {
$elTpl = preg_replace('!]+)>.*!isU', '', $elTpl);
}
return $elTpl;
}
}
?>