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