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:
20: /**
21: * Input widget class for generating multiple checkboxes.
22: */
23: class MultiCheckboxWidget implements WidgetInterface
24: {
25:
26: use IdGeneratorTrait;
27:
28: /**
29: * Template instance to use.
30: *
31: * @var \Cake\View\StringTemplate
32: */
33: protected $_templates;
34:
35: /**
36: * Label widget instance.
37: *
38: * @var \Cake\View\Widget\LabelWidget
39: */
40: protected $_label;
41:
42: /**
43: * Render multi-checkbox widget.
44: *
45: * This class uses the following templates:
46: *
47: * - `checkbox` Renders checkbox input controls. Accepts
48: * the `name`, `value` and `attrs` variables.
49: * - `checkboxWrapper` Renders the containing div/element for
50: * a checkbox and its label. Accepts the `input`, and `label`
51: * variables.
52: * - `multicheckboxWrapper` Renders a wrapper around grouped inputs.
53: * - `multicheckboxTitle` Renders the title element for grouped inputs.
54: *
55: * @param \Cake\View\StringTemplate $templates Templates list.
56: * @param \Cake\View\Widget\LabelWidget $label Label widget instance.
57: */
58: public function __construct($templates, $label)
59: {
60: $this->_templates = $templates;
61: $this->_label = $label;
62: }
63:
64: /**
65: * Render multi-checkbox widget.
66: *
67: * Data supports the following options.
68: *
69: * - `name` The name attribute of the inputs to create.
70: * `[]` will be appended to the name.
71: * - `options` An array of options to create checkboxes out of.
72: * - `val` Either a string/integer or array of values that should be
73: * checked. Can also be a complex options set.
74: * - `disabled` Either a boolean or an array of checkboxes to disable.
75: * - `escape` Set to false to disable HTML escaping.
76: * - `options` An associative array of value=>labels to generate options for.
77: * - `idPrefix` Prefix for generated ID attributes.
78: *
79: * ### Options format
80: *
81: * The options option can take a variety of data format depending on
82: * the complexity of HTML you want generated.
83: *
84: * You can generate simple options using a basic associative array:
85: *
86: * ```
87: * 'options' => ['elk' => 'Elk', 'beaver' => 'Beaver']
88: * ```
89: *
90: * If you need to define additional attributes on your option elements
91: * you can use the complex form for options:
92: *
93: * ```
94: * 'options' => [
95: * ['value' => 'elk', 'text' => 'Elk', 'data-foo' => 'bar'],
96: * ]
97: * ```
98: *
99: * This form **requires** that both the `value` and `text` keys be defined.
100: * If either is not set options will not be generated correctly.
101: *
102: * @param array $data The data to generate a checkbox set with.
103: * @param \Cake\View\Form\ContextInterface $context The current form context.
104: * @return string
105: */
106: public function render(array $data, ContextInterface $context)
107: {
108: $data += [
109: 'name' => '',
110: 'escape' => true,
111: 'options' => [],
112: 'disabled' => null,
113: 'val' => null,
114: 'idPrefix' => null,
115: 'templateVars' => [],
116: 'label' => true
117: ];
118: $this->_idPrefix = $data['idPrefix'];
119: $this->_clearIds();
120:
121: return implode('', $this->_renderInputs($data, $context));
122: }
123:
124: /**
125: * Render the checkbox inputs.
126: *
127: * @param array $data The data array defining the checkboxes.
128: * @param \Cake\View\Form\ContextInterface $context The current form context.
129: * @return array An array of rendered inputs.
130: */
131: protected function _renderInputs($data, $context)
132: {
133: $out = [];
134: foreach ($data['options'] as $key => $val) {
135: // Grouped inputs in a fieldset.
136: if (is_string($key) && is_array($val) && !isset($val['text'], $val['value'])) {
137: $inputs = $this->_renderInputs(['options' => $val] + $data, $context);
138: $title = $this->_templates->format('multicheckboxTitle', ['text' => $key]);
139: $out[] = $this->_templates->format('multicheckboxWrapper', [
140: 'content' => $title . implode('', $inputs)
141: ]);
142: continue;
143: }
144:
145: // Standard inputs.
146: $checkbox = [
147: 'value' => $key,
148: 'text' => $val,
149: ];
150: if (is_array($val) && isset($val['text'], $val['value'])) {
151: $checkbox = $val;
152: }
153: if (!isset($checkbox['templateVars'])) {
154: $checkbox['templateVars'] = $data['templateVars'];
155: }
156: if (!isset($checkbox['label'])) {
157: $checkbox['label'] = $data['label'];
158: }
159: if (!empty($data['templateVars'])) {
160: $checkbox['templateVars'] = array_merge($data['templateVars'], $checkbox['templateVars']);
161: }
162: $checkbox['name'] = $data['name'];
163: $checkbox['escape'] = $data['escape'];
164: $checkbox['checked'] = $this->_isSelected($checkbox['value'], $data['val']);
165: $checkbox['disabled'] = $this->_isDisabled($checkbox['value'], $data['disabled']);
166: if (empty($checkbox['id'])) {
167: $checkbox['id'] = $this->_id($checkbox['name'], $checkbox['value']);
168: }
169: $out[] = $this->_renderInput($checkbox + $data, $context);
170: }
171:
172: return $out;
173: }
174:
175: /**
176: * Render a single checkbox & wrapper.
177: *
178: * @param array $checkbox An array containing checkbox key/value option pairs
179: * @param \Cake\View\Form\ContextInterface $context Context object.
180: * @return string
181: */
182: protected function _renderInput($checkbox, $context)
183: {
184: $input = $this->_templates->format('checkbox', [
185: 'name' => $checkbox['name'] . '[]',
186: 'value' => $checkbox['escape'] ? h($checkbox['value']) : $checkbox['value'],
187: 'templateVars' => $checkbox['templateVars'],
188: 'attrs' => $this->_templates->formatAttributes(
189: $checkbox,
190: ['name', 'value', 'text', 'options', 'label', 'val', 'type']
191: )
192: ]);
193:
194: if ($checkbox['label'] === false && strpos($this->_templates->get('checkboxWrapper'), '{{input}}') === false) {
195: $label = $input;
196: } else {
197: $labelAttrs = is_array($checkbox['label']) ? $checkbox['label'] : [];
198: $labelAttrs += [
199: 'for' => $checkbox['id'],
200: 'escape' => $checkbox['escape'],
201: 'text' => $checkbox['text'],
202: 'templateVars' => $checkbox['templateVars'],
203: 'input' => $input
204: ];
205:
206: if ($checkbox['checked']) {
207: $labelAttrs = $this->_templates->addClass($labelAttrs, 'selected');
208: }
209:
210: $label = $this->_label->render($labelAttrs, $context);
211: }
212:
213: return $this->_templates->format('checkboxWrapper', [
214: 'templateVars' => $checkbox['templateVars'],
215: 'label' => $label,
216: 'input' => $input
217: ]);
218: }
219:
220: /**
221: * Helper method for deciding what options are selected.
222: *
223: * @param string $key The key to test.
224: * @param array|string|null $selected The selected values.
225: * @return bool
226: */
227: protected function _isSelected($key, $selected)
228: {
229: if ($selected === null) {
230: return false;
231: }
232: $isArray = is_array($selected);
233: if (!$isArray) {
234: return (string)$key === (string)$selected;
235: }
236: $strict = !is_numeric($key);
237:
238: return in_array((string)$key, $selected, $strict);
239: }
240:
241: /**
242: * Helper method for deciding what options are disabled.
243: *
244: * @param string $key The key to test.
245: * @param array|bool|null $disabled The disabled values.
246: * @return bool
247: */
248: protected function _isDisabled($key, $disabled)
249: {
250: if ($disabled === null || $disabled === false) {
251: return false;
252: }
253: if ($disabled === true || is_string($disabled)) {
254: return true;
255: }
256: $strict = !is_numeric($key);
257:
258: return in_array((string)$key, $disabled, $strict);
259: }
260:
261: /**
262: * {@inheritDoc}
263: */
264: public function secureFields(array $data)
265: {
266: return [$data['name']];
267: }
268: }
269: