MultiCheckboxWidget.php 9.1 KB

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