SelectBoxWidget.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Widget;
  16. use Cake\View\Form\ContextInterface;
  17. use Cake\View\Widget\WidgetInterface;
  18. use Traversable;
  19. /**
  20. * Input widget class for generating a selectbox.
  21. *
  22. * This class is intended as an internal implementation detail
  23. * of Cake\View\Helper\FormHelper and is not intended for direct use.
  24. */
  25. class SelectBoxWidget implements WidgetInterface
  26. {
  27. /**
  28. * Template instance.
  29. *
  30. * @var \Cake\View\StringTemplate
  31. */
  32. protected $_templates;
  33. /**
  34. * Constructor
  35. *
  36. * @param \Cake\View\StringTemplate $templates Templates list.
  37. */
  38. public function __construct($templates)
  39. {
  40. $this->_templates = $templates;
  41. }
  42. /**
  43. * Render a select box form input.
  44. *
  45. * Render a select box input given a set of data. Supported keys
  46. * are:
  47. *
  48. * - `name` - Set the input name.
  49. * - `options` - An array of options.
  50. * - `disabled` - Either true or an array of options to disable.
  51. * When true, the select element will be disabled.
  52. * - `val` - Either a string or an array of options to mark as selected.
  53. * - `empty` - Set to true to add an empty option at the top of the
  54. * option elements. Set to a string to define the display value of the
  55. * empty option.
  56. * - `escape` - Set to false to disable HTML escaping.
  57. *
  58. * ### Options format
  59. *
  60. * The options option can take a variety of data format depending on
  61. * the complexity of HTML you want generated.
  62. *
  63. * You can generate simple options using a basic associative array:
  64. *
  65. * ```
  66. * 'options' => ['elk' => 'Elk', 'beaver' => 'Beaver']
  67. * ```
  68. *
  69. * If you need to define additional attributes on your option elements
  70. * you can use the complex form for options:
  71. *
  72. * ```
  73. * 'options' => [
  74. * ['value' => 'elk', 'text' => 'Elk', 'data-foo' => 'bar'],
  75. * ]
  76. * ```
  77. *
  78. * This form **requires** that both the `value` and `text` keys be defined.
  79. * If either is not set options will not be generated correctly.
  80. *
  81. * If you need to define option groups you can do those using nested arrays:
  82. *
  83. * ```
  84. * 'options' => [
  85. * 'Mammals' => [
  86. * 'elk' => 'Elk',
  87. * 'beaver' => 'Beaver'
  88. * ]
  89. * ]
  90. * ```
  91. *
  92. * And finally, if you need to put attributes on your optgroup elements you
  93. * can do that with a more complex nested array form:
  94. *
  95. * ```
  96. * 'options' => [
  97. * [
  98. * 'text' => 'Mammals',
  99. * 'data-id' => 1,
  100. * 'options' => [
  101. * 'elk' => 'Elk',
  102. * 'beaver' => 'Beaver'
  103. * ]
  104. * ],
  105. * ]
  106. * ```
  107. *
  108. * You are free to mix each of the forms in the same option set, and
  109. * nest complex types as required.
  110. *
  111. * @param array $data Data to render with.
  112. * @param \Cake\View\Form\ContextInterface $context The current form context.
  113. * @return string A generated select box.
  114. * @throws \RuntimeException when the name attribute is empty.
  115. */
  116. public function render(array $data, ContextInterface $context)
  117. {
  118. $data += [
  119. 'name' => '',
  120. 'empty' => false,
  121. 'escape' => true,
  122. 'options' => [],
  123. 'disabled' => null,
  124. 'val' => null,
  125. ];
  126. if (empty($data['name'])) {
  127. throw new \RuntimeException('Cannot make inputs with empty name attributes.');
  128. }
  129. $options = $this->_renderContent($data);
  130. $name = $data['name'];
  131. unset($data['name'], $data['options'], $data['empty'], $data['val'], $data['escape']);
  132. if (isset($data['disabled']) && is_array($data['disabled'])) {
  133. unset($data['disabled']);
  134. }
  135. $template = 'select';
  136. if (!empty($data['multiple'])) {
  137. $template = 'selectMultiple';
  138. unset($data['multiple']);
  139. }
  140. $attrs = $this->_templates->formatAttributes($data);
  141. return $this->_templates->format($template, [
  142. 'name' => $name,
  143. 'attrs' => $attrs,
  144. 'content' => implode('', $options),
  145. ]);
  146. }
  147. /**
  148. * Render the contents of the select element.
  149. *
  150. * @param array $data The context for rendering a select.
  151. * @return array
  152. */
  153. protected function _renderContent($data)
  154. {
  155. $options = $data['options'];
  156. if ($options instanceof Traversable) {
  157. $options = iterator_to_array($options);
  158. }
  159. if (!empty($data['empty'])) {
  160. $value = $data['empty'] === true ? '' : $data['empty'];
  161. $options = ['' => $value] + (array)$options;
  162. }
  163. if (empty($options)) {
  164. return [];
  165. }
  166. $selected = isset($data['val']) ? $data['val'] : null;
  167. $disabled = null;
  168. if (isset($data['disabled']) && is_array($data['disabled'])) {
  169. $disabled = $data['disabled'];
  170. }
  171. return $this->_renderOptions($options, $disabled, $selected, $data['escape']);
  172. }
  173. /**
  174. * Render the contents of an optgroup element.
  175. *
  176. * @param string $label The optgroup label text
  177. * @param array $optgroup The opt group data.
  178. * @param array|null $disabled The options to disable.
  179. * @param array|string|null $selected The options to select.
  180. * @param bool $escape Toggle HTML escaping
  181. * @return string Formatted template string
  182. */
  183. protected function _renderOptgroup($label, $optgroup, $disabled, $selected, $escape)
  184. {
  185. $opts = $optgroup;
  186. $attrs = [];
  187. if (isset($optgroup['options'], $optgroup['text'])) {
  188. $opts = $optgroup['options'];
  189. $label = $optgroup['text'];
  190. $attrs = $optgroup;
  191. }
  192. $groupOptions = $this->_renderOptions($opts, $disabled, $selected, $escape);
  193. return $this->_templates->format('optgroup', [
  194. 'label' => $escape ? h($label) : $label,
  195. 'content' => implode('', $groupOptions),
  196. 'attrs' => $this->_templates->formatAttributes($attrs, ['text', 'options']),
  197. ]);
  198. }
  199. /**
  200. * Render a set of options.
  201. *
  202. * Will recursively call itself when option groups are in use.
  203. *
  204. * @param array $options The options to render.
  205. * @param array|null $disabled The options to disable.
  206. * @param array|string|null $selected The options to select.
  207. * @param bool $escape Toggle HTML escaping.
  208. * @return array Option elements.
  209. */
  210. protected function _renderOptions($options, $disabled, $selected, $escape)
  211. {
  212. $out = [];
  213. foreach ($options as $key => $val) {
  214. // Option groups
  215. $arrayVal = (is_array($val) || $val instanceof Traversable);
  216. if ((!is_int($key) && $arrayVal) ||
  217. (is_int($key) && $arrayVal && isset($val['options']))
  218. ) {
  219. $out[] = $this->_renderOptgroup($key, $val, $disabled, $selected, $escape);
  220. continue;
  221. }
  222. // Basic options
  223. $optAttrs = [
  224. 'value' => $key,
  225. 'text' => $val,
  226. ];
  227. if (is_array($val) && isset($optAttrs['text'], $optAttrs['value'])) {
  228. $optAttrs = $val;
  229. $key = $optAttrs['value'];
  230. }
  231. if ($this->_isSelected($key, $selected)) {
  232. $optAttrs['selected'] = true;
  233. }
  234. if ($this->_isDisabled($key, $disabled)) {
  235. $optAttrs['disabled'] = true;
  236. }
  237. $optAttrs['escape'] = $escape;
  238. $out[] = $this->_templates->format('option', [
  239. 'value' => $escape ? h($optAttrs['value']) : $optAttrs['value'],
  240. 'text' => $escape ? h($optAttrs['text']) : $optAttrs['text'],
  241. 'attrs' => $this->_templates->formatAttributes($optAttrs, ['text', 'value']),
  242. ]);
  243. }
  244. return $out;
  245. }
  246. /**
  247. * Helper method for deciding what options are selected.
  248. *
  249. * @param string $key The key to test.
  250. * @param array|string|null $selected The selected values.
  251. * @return bool
  252. */
  253. protected function _isSelected($key, $selected)
  254. {
  255. if ($selected === null) {
  256. return false;
  257. }
  258. $isArray = is_array($selected);
  259. if (!$isArray) {
  260. return (string)$key === (string)$selected;
  261. }
  262. $strict = !is_numeric($key);
  263. return in_array((string)$key, $selected, $strict);
  264. }
  265. /**
  266. * Helper method for deciding what options are disabled.
  267. *
  268. * @param string $key The key to test.
  269. * @param array|null $disabled The disabled values.
  270. * @return bool
  271. */
  272. protected function _isDisabled($key, $disabled)
  273. {
  274. if ($disabled === null) {
  275. return false;
  276. }
  277. $strict = !is_numeric($key);
  278. return in_array((string)$key, $disabled, $strict);
  279. }
  280. /**
  281. * {@inheritDoc}
  282. */
  283. public function secureFields(array $data)
  284. {
  285. return [$data['name']];
  286. }
  287. }