RadioWidget.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Widget;
  16. use Cake\View\Form\ContextInterface;
  17. use Cake\View\Helper\IdGeneratorTrait;
  18. use Traversable;
  19. /**
  20. * Input widget class for generating a set of radio buttons.
  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 RadioWidget implements WidgetInterface
  26. {
  27. use IdGeneratorTrait;
  28. /**
  29. * Template instance.
  30. *
  31. * @var \Cake\View\StringTemplate
  32. */
  33. protected $_templates;
  34. /**
  35. * Label instance.
  36. *
  37. * @var \Cake\View\Widget\LabelWidget
  38. */
  39. protected $_label;
  40. /**
  41. * Constructor
  42. *
  43. * This class uses a few templates:
  44. *
  45. * - `radio` Used to generate the input for a radio button.
  46. * Can use the following variables `name`, `value`, `attrs`.
  47. * - `radioWrapper` Used to generate the container element for
  48. * the radio + input element. Can use the `input` and `label`
  49. * variables.
  50. *
  51. * @param \Cake\View\StringTemplate $templates Templates list.
  52. * @param \Cake\View\Widget\LabelWidget $label Label widget instance.
  53. */
  54. public function __construct($templates, $label)
  55. {
  56. $this->_templates = $templates;
  57. $this->_label = $label;
  58. }
  59. /**
  60. * Render a set of radio buttons.
  61. *
  62. * Data supports the following keys:
  63. *
  64. * - `name` - Set the input name.
  65. * - `options` - An array of options. See below for more information.
  66. * - `disabled` - Either true or an array of inputs to disable.
  67. * When true, the select element will be disabled.
  68. * - `val` - A string of the option to mark as selected.
  69. * - `label` - Either false to disable label generation, or
  70. * an array of attributes for all labels.
  71. * - `required` - Set to true to add the required attribute
  72. * on all generated radios.
  73. * - `idPrefix` Prefix for generated ID attributes.
  74. *
  75. * @param array $data The data to build radio buttons with.
  76. * @param \Cake\View\Form\ContextInterface $context The current form context.
  77. * @return string
  78. */
  79. public function render(array $data, ContextInterface $context)
  80. {
  81. $data += [
  82. 'name' => '',
  83. 'options' => [],
  84. 'disabled' => null,
  85. 'val' => null,
  86. 'escape' => true,
  87. 'label' => true,
  88. 'empty' => false,
  89. 'idPrefix' => null,
  90. 'templateVars' => [],
  91. ];
  92. if ($data['options'] instanceof Traversable) {
  93. $options = iterator_to_array($data['options']);
  94. } else {
  95. $options = (array)$data['options'];
  96. }
  97. if (!empty($data['empty'])) {
  98. $empty = $data['empty'] === true ? 'empty' : $data['empty'];
  99. $options = ['' => $empty] + $options;
  100. }
  101. unset($data['empty']);
  102. $this->_idPrefix = $data['idPrefix'];
  103. $this->_clearIds();
  104. $opts = [];
  105. foreach ($options as $val => $text) {
  106. $opts[] = $this->_renderInput($val, $text, $data, $context);
  107. }
  108. return implode('', $opts);
  109. }
  110. /**
  111. * Disabled attribute detection.
  112. *
  113. * @param array $radio Radio info.
  114. * @param array|null|true $disabled The disabled values.
  115. * @return bool
  116. */
  117. protected function _isDisabled($radio, $disabled)
  118. {
  119. if (!$disabled) {
  120. return false;
  121. }
  122. if ($disabled === true) {
  123. return true;
  124. }
  125. $isNumeric = is_numeric($radio['value']);
  126. return (!is_array($disabled) || in_array((string)$radio['value'], $disabled, !$isNumeric));
  127. }
  128. /**
  129. * Renders a single radio input and label.
  130. *
  131. * @param string|int $val The value of the radio input.
  132. * @param string|array $text The label text, or complex radio type.
  133. * @param array $data Additional options for input generation.
  134. * @param \Cake\View\Form\ContextInterface $context The form context
  135. * @return string
  136. */
  137. protected function _renderInput($val, $text, $data, $context)
  138. {
  139. $escape = $data['escape'];
  140. if (is_int($val) && isset($text['text'], $text['value'])) {
  141. $radio = $text;
  142. } else {
  143. $radio = ['value' => $val, 'text' => $text];
  144. }
  145. $radio['name'] = $data['name'];
  146. if (!isset($radio['templateVars'])) {
  147. $radio['templateVars'] = [];
  148. }
  149. if (!empty($data['templateVars'])) {
  150. $radio['templateVars'] = array_merge($data['templateVars'], $radio['templateVars']);
  151. }
  152. if (empty($radio['id'])) {
  153. if (isset($data['id'])) {
  154. $radio['id'] = $data['id'] . '-' . trim(
  155. $this->_idSuffix($radio['value']),
  156. '-'
  157. );
  158. } else {
  159. $radio['id'] = $this->_id($radio['name'], $radio['value']);
  160. }
  161. }
  162. if (isset($data['val']) && is_bool($data['val'])) {
  163. $data['val'] = $data['val'] ? 1 : 0;
  164. }
  165. if (isset($data['val']) && (string)$data['val'] === (string)$radio['value']) {
  166. $radio['checked'] = true;
  167. $radio['templateVars']['activeClass'] = 'active';
  168. }
  169. if (!is_bool($data['label']) && isset($radio['checked']) && $radio['checked']) {
  170. $data['label'] = $this->_templates->addClass($data['label'], 'selected');
  171. }
  172. $radio['disabled'] = $this->_isDisabled($radio, $data['disabled']);
  173. if (!empty($data['required'])) {
  174. $radio['required'] = true;
  175. }
  176. if (!empty($data['form'])) {
  177. $radio['form'] = $data['form'];
  178. }
  179. $input = $this->_templates->format('radio', [
  180. 'name' => $radio['name'],
  181. 'value' => $escape ? h($radio['value']) : $radio['value'],
  182. 'templateVars' => $radio['templateVars'],
  183. 'attrs' => $this->_templates->formatAttributes($radio + $data, ['name', 'value', 'text', 'options', 'label', 'val', 'type']),
  184. ]);
  185. $label = $this->_renderLabel(
  186. $radio,
  187. $data['label'],
  188. $input,
  189. $context,
  190. $escape
  191. );
  192. if ($label === false &&
  193. strpos($this->_templates->get('radioWrapper'), '{{input}}') === false
  194. ) {
  195. $label = $input;
  196. }
  197. return $this->_templates->format('radioWrapper', [
  198. 'input' => $input,
  199. 'label' => $label,
  200. 'templateVars' => $data['templateVars'],
  201. ]);
  202. }
  203. /**
  204. * Renders a label element for a given radio button.
  205. *
  206. * In the future this might be refactored into a separate widget as other
  207. * input types (multi-checkboxes) will also need labels generated.
  208. *
  209. * @param array $radio The input properties.
  210. * @param false|string|array $label The properties for a label.
  211. * @param string $input The input widget.
  212. * @param \Cake\View\Form\ContextInterface $context The form context.
  213. * @param bool $escape Whether or not to HTML escape the label.
  214. * @return string|bool Generated label.
  215. */
  216. protected function _renderLabel($radio, $label, $input, $context, $escape)
  217. {
  218. if (isset($radio['label'])) {
  219. $label = $radio['label'];
  220. } elseif ($label === false) {
  221. return false;
  222. }
  223. $labelAttrs = is_array($label) ? $label : [];
  224. $labelAttrs += [
  225. 'for' => $radio['id'],
  226. 'escape' => $escape,
  227. 'text' => $radio['text'],
  228. 'templateVars' => $radio['templateVars'],
  229. 'input' => $input,
  230. ];
  231. return $this->_label->render($labelAttrs, $context);
  232. }
  233. /**
  234. * {@inheritDoc}
  235. */
  236. public function secureFields(array $data)
  237. {
  238. return [$data['name']];
  239. }
  240. }