PageRenderTime 253ms CodeModel.GetById 12ms RepoModel.GetById 2ms app.codeStats 0ms

/libs/HTML/QuickForm2/Renderer/Default.php

https://github.com/quarkness/piwik
PHP | 598 lines | 285 code | 40 blank | 273 comment | 38 complexity | 7dd6af9bcecf2a883a951605db183fbd MD5 | raw file
  1. <?php
  2. /**
  3. * Default renderer for HTML_QuickForm2
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE:
  8. *
  9. * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>,
  10. * Bertrand Mansion <golgote@mamasam.com>
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. *
  17. * * Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. * * Redistributions in binary form must reproduce the above copyright
  20. * notice, this list of conditions and the following disclaimer in the
  21. * documentation and/or other materials provided with the distribution.
  22. * * The names of the authors may not be used to endorse or promote products
  23. * derived from this software without specific prior written permission.
  24. *
  25. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  26. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  27. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  28. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  29. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  30. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  31. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  32. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  33. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  35. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @category HTML
  38. * @package HTML_QuickForm2
  39. * @author Alexey Borzov <avb@php.net>
  40. * @author Bertrand Mansion <golgote@mamasam.com>
  41. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  42. * @version SVN: $Id: Default.php 299706 2010-05-24 18:32:37Z avb $
  43. * @link http://pear.php.net/package/HTML_QuickForm2
  44. */
  45. /**
  46. * Abstract base class for QuickForm2 renderers
  47. */
  48. // require_once 'HTML/QuickForm2/Renderer.php';
  49. /**
  50. * Default renderer for QuickForm2
  51. *
  52. * Mostly a direct port of Default renderer from QuickForm 3.x package.
  53. *
  54. * While almost everything in this class is defined as public, its properties
  55. * and those methods that are not published (i.e. not in array returned by
  56. * exportMethods()) will be available to renderer plugins only.
  57. *
  58. * The following methods are published:
  59. * - {@link reset()}
  60. * - {@link setTemplateForClass()}
  61. * - {@link setTemplateForId()}
  62. * - {@link setErrorTemplate()}
  63. * - {@link setElementTemplateForGroupClass()}
  64. * - {@link setElementTemplateForGroupId()}
  65. *
  66. * @category HTML
  67. * @package HTML_QuickForm2
  68. * @author Alexey Borzov <avb@php.net>
  69. * @author Bertrand Mansion <golgote@mamasam.com>
  70. * @version Release: @package_version@
  71. */
  72. class HTML_QuickForm2_Renderer_Default extends HTML_QuickForm2_Renderer
  73. {
  74. /**
  75. * Whether the form contains required elements
  76. * @var bool
  77. */
  78. public $hasRequired = false;
  79. /**
  80. * HTML generated for the form
  81. * @var array
  82. */
  83. public $html = array(array());
  84. /**
  85. * HTML for hidden elements if 'group_hiddens' option is on
  86. * @var string
  87. */
  88. public $hiddenHtml = '';
  89. /**
  90. * Array of validation errors if 'group_errors' option is on
  91. * @var array
  92. */
  93. public $errors = array();
  94. /**
  95. * Default templates for elements of the given class
  96. * @var array
  97. */
  98. public $templatesForClass = array(
  99. 'html_quickform2_element_inputhidden' => '<div style="display: none;">{element}</div>',
  100. 'html_quickform2' => '<div class="quickform">{errors}<form{attributes}>{hidden}{content}</form><qf:reqnote><div class="reqnote">{reqnote}</div></qf:reqnote></div>',
  101. 'html_quickform2_container_fieldset' => '<fieldset{attributes}><qf:label><legend id="{id}-legend">{label}</legend></qf:label>{content}</fieldset>',
  102. 'special:error' => array(
  103. 'prefix' => '<div class="errors"><qf:message><p>{message}</p></qf:message><ul><li>',
  104. 'separator' => '</li><li>',
  105. 'suffix' => '</li></ul><qf:message><p>{message}</p></qf:message></div>'
  106. ),
  107. '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>',
  108. '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>'
  109. );
  110. /**
  111. * Custom templates for elements with the given IDs
  112. * @var array
  113. */
  114. public $templatesForId = array();
  115. /**
  116. * Default templates for elements in groups of the given classes
  117. *
  118. * Array has the form ('group class' => ('element class' => 'template', ...), ...)
  119. *
  120. * @var array
  121. */
  122. public $elementTemplatesForGroupClass = array(
  123. 'html_quickform2_container' => array(
  124. 'html_quickform2_element' => '{element}',
  125. 'html_quickform2_container_fieldset' => '<fieldset{attributes}><qf:label><legend id="{id}-legend">{label}</legend></qf:label>{content}</fieldset>'
  126. )
  127. );
  128. /**
  129. * Custom templates for grouped elements in the given group IDs
  130. *
  131. * Array has the form ('group id' => ('element class' => 'template', ...), ...)
  132. *
  133. * @var array
  134. */
  135. public $elementTemplatesForGroupId = array();
  136. /**
  137. * Array containing IDs of the groups being rendered
  138. * @var array
  139. */
  140. public $groupId = array();
  141. public function exportMethods()
  142. {
  143. return array(
  144. 'reset',
  145. 'setTemplateForClass',
  146. 'setTemplateForId',
  147. 'setErrorTemplate',
  148. 'setGroupedTemplateForClass',
  149. 'setElementTemplateForGroupClass',
  150. 'setElementTemplateForGroupId'
  151. );
  152. }
  153. /**
  154. * Sets template for form elements that are instances of the given class
  155. *
  156. * When searching for a template to use, renderer will check for templates
  157. * set for element's class and its parent classes, until found. Thus a more
  158. * specific template will override a more generic one.
  159. *
  160. * @param string Class name
  161. * @param mixed Template to use for elements of that class
  162. * @return HTML_QuickForm2_Renderer_Default
  163. */
  164. public function setTemplateForClass($className, $template)
  165. {
  166. $this->templatesForClass[strtolower($className)] = $template;
  167. return $this;
  168. }
  169. /**
  170. * Sets template for form element with the given id
  171. *
  172. * If a template is set for an element via this method, it will be used.
  173. * In the other case a generic template set by {@link setTemplateForClass()}
  174. * or {@link setGroupedTemplateForClass()} will be used.
  175. *
  176. * @param string Element's id
  177. * @param mixed Template to use for rendering of that element
  178. * @return HTML_QuickForm2_Renderer_Default
  179. */
  180. public function setTemplateForId($id, $template)
  181. {
  182. $this->templatesForId[$id] = $template;
  183. return $this;
  184. }
  185. /**
  186. * Sets template for rendering validation errors
  187. *
  188. * This template will be used if 'group_errors' option is set to true.
  189. * The template array should contain 'prefix', 'suffix' and 'separator'
  190. * keys.
  191. *
  192. * @param array Template for validation errors
  193. * @return HTML_QuickForm2_Renderer_Default
  194. */
  195. public function setErrorTemplate(array $template)
  196. {
  197. return $this->setTemplateForClass('special:error', $template);
  198. }
  199. /**
  200. * Sets grouped elements templates using group class
  201. *
  202. * Templates set via {@link setTemplateForClass()} will not be used for
  203. * grouped form elements. When searching for a template to use, the renderer
  204. * will first consider template set for a specific group id, then the
  205. * group templates set by group class.
  206. *
  207. * @param string Group class name
  208. * @param string Element class name
  209. * @param mixed Template
  210. * @return HTML_QuickForm2_Renderer_Default
  211. */
  212. public function setElementTemplateForGroupClass($groupClass, $elementClass, $template)
  213. {
  214. $this->elementTemplatesForGroupClass[strtolower($groupClass)][strtolower($elementClass)] = $template;
  215. return $this;
  216. }
  217. /**
  218. * Sets grouped elements templates using group id
  219. *
  220. * Templates set via {@link setTemplateForClass()} will not be used for
  221. * grouped form elements. When searching for a template to use, the renderer
  222. * will first consider template set for a specific group id, then the
  223. * group templates set by group class.
  224. *
  225. * @param string Group id
  226. * @param string Element class name
  227. * @param mixed Template
  228. * @return HTML_QuickForm2_Renderer_Default
  229. */
  230. public function setElementTemplateForGroupId($groupId, $elementClass, $template)
  231. {
  232. $this->elementTemplatesForGroupId[$groupId][strtolower($elementClass)] = $template;
  233. return $this;
  234. }
  235. /**
  236. * Resets the accumulated data
  237. *
  238. * This method is called automatically by startForm() method, but should
  239. * be called manually before calling other rendering methods separately.
  240. *
  241. * @return HTML_QuickForm2_Renderer_Default
  242. */
  243. public function reset()
  244. {
  245. $this->html = array(array());
  246. $this->hiddenHtml = '';
  247. $this->errors = array();
  248. $this->hasRequired = false;
  249. $this->groupId = array();
  250. return $this;
  251. }
  252. /**
  253. * Returns generated HTML
  254. *
  255. * @return string
  256. */
  257. public function __toString()
  258. {
  259. return (isset($this->html[0][0])? $this->html[0][0]: '') .
  260. $this->hiddenHtml;
  261. }
  262. /**
  263. * Renders a generic element
  264. *
  265. * @param HTML_QuickForm2_Node Element being rendered
  266. */
  267. public function renderElement(HTML_QuickForm2_Node $element)
  268. {
  269. $elTpl = $this->prepareTemplate($this->findTemplate($element), $element);
  270. $this->html[count($this->html) - 1][] = str_replace(array('{element}', '{id}'),
  271. array($element, $element->getId()), $elTpl);
  272. }
  273. /**
  274. * Renders a hidden element
  275. *
  276. * @param HTML_QuickForm2_Node Hidden element being rendered
  277. */
  278. public function renderHidden(HTML_QuickForm2_Node $element)
  279. {
  280. if ($this->options['group_hiddens']) {
  281. $this->hiddenHtml .= $element->__toString();
  282. } else {
  283. $this->html[count($this->html) - 1][] = str_replace('{element}', $element,
  284. $this->findTemplate($element));
  285. }
  286. }
  287. /**
  288. * Starts rendering a generic container, called before processing contained elements
  289. *
  290. * @param HTML_QuickForm2_Node Container being rendered
  291. */
  292. public function startContainer(HTML_QuickForm2_Node $container)
  293. {
  294. $this->html[] = array();
  295. $this->groupId[] = false;
  296. }
  297. /**
  298. * Finishes rendering a generic container, called after processing contained elements
  299. *
  300. * @param HTML_QuickForm2_Node Container being rendered
  301. */
  302. public function finishContainer(HTML_QuickForm2_Node $container)
  303. {
  304. array_pop($this->groupId);
  305. $cTpl = str_replace(
  306. array('{attributes}', '{id}'),
  307. array($container->getAttributes(true), $container->getId()),
  308. $this->prepareTemplate($this->findTemplate($container, '{content}'), $container)
  309. );
  310. $cHtml = array_pop($this->html);
  311. $break = HTML_Common2::getOption('linebreak');
  312. $indent = str_repeat(HTML_Common2::getOption('indent'), count($this->html));
  313. $this->html[count($this->html) - 1][] = str_replace(
  314. '{content}', $break . $indent . implode($break . $indent, $cHtml), $cTpl
  315. );
  316. }
  317. /**
  318. * Starts rendering a group, called before processing grouped elements
  319. *
  320. * @param HTML_QuickForm2_Node Group being rendered
  321. */
  322. public function startGroup(HTML_QuickForm2_Node $group)
  323. {
  324. $this->html[] = array();
  325. $this->groupId[] = $group->getId();
  326. }
  327. /**
  328. * Finishes rendering a group, called after processing grouped elements
  329. *
  330. * @param HTML_QuickForm2_Node Group being rendered
  331. */
  332. public function finishGroup(HTML_QuickForm2_Node $group)
  333. {
  334. $gTpl = str_replace(
  335. array('{attributes}', '{id}'),
  336. array($group->getAttributes(true), array_pop($this->groupId)),
  337. $this->prepareTemplate($this->findTemplate($group, '{content}'), $group)
  338. );
  339. $separator = $group->getSeparator();
  340. $elements = array_pop($this->html);
  341. if (!is_array($separator)) {
  342. $content = implode((string)$separator, $elements);
  343. } else {
  344. $content = '';
  345. $cSeparator = count($separator);
  346. for ($i = 0, $count = count($elements); $i < $count; $i++) {
  347. $content .= (0 == $i? '': $separator[($i - 1) % $cSeparator]) .
  348. $elements[$i];
  349. }
  350. }
  351. $this->html[count($this->html) - 1][] = str_replace('{content}', $content, $gTpl);
  352. }
  353. /**
  354. * Starts rendering a form, called before processing contained elements
  355. *
  356. * @param HTML_QuickForm2_Node Form being rendered
  357. */
  358. public function startForm(HTML_QuickForm2_Node $form)
  359. {
  360. $this->reset();
  361. }
  362. /**
  363. * Finishes rendering a form, called after processing contained elements
  364. *
  365. * @param HTML_QuickForm2_Node Form being rendered
  366. */
  367. public function finishForm(HTML_QuickForm2_Node $form)
  368. {
  369. $formTpl = str_replace(
  370. array('{attributes}', '{hidden}', '{errors}'),
  371. array($form->getAttributes(true), $this->hiddenHtml,
  372. $this->outputGroupedErrors()),
  373. $this->findTemplate($form, '{content}')
  374. );
  375. $this->hiddenHtml = '';
  376. // required note
  377. if (!$this->hasRequired || $form->toggleFrozen() ||
  378. empty($this->options['required_note']))
  379. {
  380. $formTpl = preg_replace('!<qf:reqnote>.*</qf:reqnote>!isU', '', $formTpl);
  381. } else {
  382. $formTpl = str_replace(
  383. array('<qf:reqnote>', '</qf:reqnote>', '{reqnote}'),
  384. array('', '', $this->options['required_note']),
  385. $formTpl
  386. );
  387. }
  388. $break = HTML_Common2::getOption('linebreak');
  389. $script = $this->getJavascriptBuilder()->__toString();
  390. $this->html[0] = array((empty($script)? '': $script . $break) . str_replace(
  391. '{content}', $break . implode($break, $this->html[0]), $formTpl
  392. ));
  393. }
  394. /**
  395. * Creates a error list if 'group_errors' option is true
  396. *
  397. * @return string HTML with a list of all validation errors
  398. */
  399. public function outputGroupedErrors()
  400. {
  401. if (empty($this->errors)) {
  402. return '';
  403. }
  404. if (!empty($this->options['errors_prefix'])) {
  405. $errorHtml = str_replace(array('<qf:message>', '</qf:message>', '{message}'),
  406. array('', '', $this->options['errors_prefix']),
  407. $this->templatesForClass['special:error']['prefix']);
  408. } else {
  409. $errorHtml = preg_replace('!<qf:message>.*</qf:message>!isU', '',
  410. $this->templatesForClass['special:error']['prefix']);
  411. }
  412. $errorHtml .= implode($this->templatesForClass['special:error']['separator'], $this->errors);
  413. if (!empty($this->options['errors_suffix'])) {
  414. $errorHtml .= str_replace(array('<qf:message>', '</qf:message>', '{message}'),
  415. array('', '', $this->options['errors_suffix']),
  416. $this->templatesForClass['special:error']['suffix']);
  417. } else {
  418. $errorHtml .= preg_replace('!<qf:message>.*</qf:message>!isU', '',
  419. $this->templatesForClass['special:error']['suffix']);
  420. }
  421. return $errorHtml;
  422. }
  423. /**
  424. * Finds a proper template for the element
  425. *
  426. * Templates are scanned in a predefined order. First, if a template was
  427. * set for a specific element by id, it is returned, no matter if the
  428. * element belongs to a group. If the element does not belong to a group,
  429. * we try to match a template using the element class.
  430. * But, if the element belongs to a group, templates are first looked up
  431. * using the containing group id, then using the containing group class.
  432. * When no template is found, the provided default template is returned.
  433. *
  434. * @param HTML_QuickForm2_Node Element being rendered
  435. * @param string Default template to use if not found
  436. * @return string Template
  437. */
  438. public function findTemplate(HTML_QuickForm2_Node $element, $default = '{element}')
  439. {
  440. if (!empty($this->templatesForId[$element->getId()])) {
  441. return $this->templatesForId[$element->getId()];
  442. }
  443. $class = strtolower(get_class($element));
  444. $groupId = end($this->groupId);
  445. $elementClasses = array();
  446. do {
  447. if (empty($groupId) && !empty($this->templatesForClass[$class])) {
  448. return $this->templatesForClass[$class];
  449. }
  450. $elementClasses[$class] = true;
  451. } while ($class = strtolower(get_parent_class($class)));
  452. if (!empty($groupId)) {
  453. if (!empty($this->elementTemplatesForGroupId[$groupId])) {
  454. while (list($elClass) = each($elementClasses)) {
  455. if (!empty($this->elementTemplatesForGroupId[$groupId][$elClass])) {
  456. return $this->elementTemplatesForGroupId[$groupId][$elClass];
  457. }
  458. }
  459. }
  460. $group = $element->getContainer();
  461. $grClass = strtolower(get_class($group));
  462. do {
  463. if (!empty($this->elementTemplatesForGroupClass[$grClass])) {
  464. reset($elementClasses);
  465. while (list($elClass) = each($elementClasses)) {
  466. if (!empty($this->elementTemplatesForGroupClass[$grClass][$elClass])) {
  467. return $this->elementTemplatesForGroupClass[$grClass][$elClass];
  468. }
  469. }
  470. }
  471. } while ($grClass = strtolower(get_parent_class($grClass)));
  472. }
  473. return $default;
  474. }
  475. /**
  476. * Processes the element's template, adding label(s), required note and error message
  477. *
  478. * @param string Element template
  479. * @param HTML_QuickForm2_Node Element being rendered
  480. * @return string Template with some substitutions done
  481. */
  482. public function prepareTemplate($elTpl, HTML_QuickForm2_Node $element)
  483. {
  484. // if element is required
  485. $elTpl = $this->markRequired($elTpl, $element->isRequired());
  486. $elTpl = $this->outputError($elTpl, $element->getError());
  487. return $this->outputLabel($elTpl, $element->getLabel());
  488. }
  489. /**
  490. * Marks element required or removes "required" block
  491. *
  492. * @param string Element template
  493. * @param bool Whether element is required
  494. * @return string Template with processed "required" block
  495. */
  496. public function markRequired($elTpl, $required)
  497. {
  498. if ($required) {
  499. $this->hasRequired = true;
  500. $elTpl = str_replace(array('<qf:required>', '</qf:required>'),
  501. array('', ''), $elTpl);
  502. } else {
  503. $elTpl = preg_replace('!<qf:required>.*</qf:required>!isU', '', $elTpl);
  504. }
  505. return $elTpl;
  506. }
  507. /**
  508. * Outputs element error, removes empty error blocks
  509. *
  510. * @param string Element template
  511. * @param string Validation error for the element
  512. * @return string Template with error substitutions done
  513. */
  514. public function outputError($elTpl, $error)
  515. {
  516. if ($error && !$this->options['group_errors']) {
  517. $elTpl = str_replace(array('<qf:error>', '</qf:error>', '{error}'),
  518. array('', '', $error), $elTpl);
  519. } else {
  520. if ($error && $this->options['group_errors']) {
  521. $this->errors[] = $error;
  522. }
  523. $elTpl = preg_replace('!<qf:error>.*</qf:error>!isU', '', $elTpl);
  524. }
  525. return $elTpl;
  526. }
  527. /**
  528. * Outputs element's label(s), removes empty label blocks
  529. *
  530. * @param string Element template
  531. * @param mixed Element label(s)
  532. * @return string Template with label substitutions done
  533. */
  534. public function outputLabel($elTpl, $label)
  535. {
  536. $mainLabel = is_array($label)? array_shift($label): $label;
  537. $elTpl = str_replace('{label}', $mainLabel, $elTpl);
  538. if (false !== strpos($elTpl, '<qf:label>')) {
  539. if ($mainLabel) {
  540. $elTpl = str_replace(array('<qf:label>', '</qf:label>'), array('', ''), $elTpl);
  541. } else {
  542. $elTpl = preg_replace('!<qf:label>.*</qf:label>!isU', '', $elTpl);
  543. }
  544. }
  545. if (is_array($label)) {
  546. foreach($label as $key => $text) {
  547. $key = is_int($key)? $key + 2: $key;
  548. $elTpl = str_replace(array('<qf:label_' . $key . '>', '</qf:label_' . $key . '>', '{label_' . $key . '}'),
  549. array('', '', $text), $elTpl);
  550. }
  551. }
  552. if (strpos($elTpl, '{label_')) {
  553. $elTpl = preg_replace('!<qf:label_([^>]+)>.*</qf:label_\1>!isU', '', $elTpl);
  554. }
  555. return $elTpl;
  556. }
  557. }
  558. ?>