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