class-acf-field-checkbox.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. <?php
  2. if ( ! class_exists( 'acf_field_checkbox' ) ) :
  3. class acf_field_checkbox extends acf_field {
  4. /*
  5. * __construct
  6. *
  7. * This function will setup the field type data
  8. *
  9. * @type function
  10. * @date 5/03/2014
  11. * @since 5.0.0
  12. *
  13. * @param n/a
  14. * @return n/a
  15. */
  16. function initialize() {
  17. // vars
  18. $this->name = 'checkbox';
  19. $this->label = __( 'Checkbox', 'acf' );
  20. $this->category = 'choice';
  21. $this->defaults = array(
  22. 'layout' => 'vertical',
  23. 'choices' => array(),
  24. 'default_value' => '',
  25. 'allow_custom' => 0,
  26. 'save_custom' => 0,
  27. 'toggle' => 0,
  28. 'return_format' => 'value',
  29. );
  30. }
  31. /*
  32. * render_field()
  33. *
  34. * Create the HTML interface for your field
  35. *
  36. * @param $field (array) the $field being rendered
  37. *
  38. * @type action
  39. * @since 3.6
  40. * @date 23/01/13
  41. *
  42. * @param $field (array) the $field being edited
  43. * @return n/a
  44. */
  45. function render_field( $field ) {
  46. // reset vars
  47. $this->_values = array();
  48. $this->_all_checked = true;
  49. // ensure array
  50. $field['value'] = acf_get_array( $field['value'] );
  51. $field['choices'] = acf_get_array( $field['choices'] );
  52. // hiden input
  53. acf_hidden_input( array( 'name' => $field['name'] ) );
  54. // vars
  55. $li = '';
  56. $ul = array(
  57. 'class' => 'acf-checkbox-list',
  58. );
  59. // append to class
  60. $ul['class'] .= ' ' . ( $field['layout'] == 'horizontal' ? 'acf-hl' : 'acf-bl' );
  61. $ul['class'] .= ' ' . $field['class'];
  62. // checkbox saves an array
  63. $field['name'] .= '[]';
  64. // choices
  65. if ( ! empty( $field['choices'] ) ) {
  66. // choices
  67. $li .= $this->render_field_choices( $field );
  68. // toggle
  69. if ( $field['toggle'] ) {
  70. $li = $this->render_field_toggle( $field ) . $li;
  71. }
  72. }
  73. // custom
  74. if ( $field['allow_custom'] ) {
  75. $li .= $this->render_field_custom( $field );
  76. }
  77. // return
  78. echo '<ul ' . acf_esc_attr( $ul ) . '>' . "\n" . $li . '</ul>' . "\n";
  79. }
  80. /*
  81. * render_field_choices
  82. *
  83. * description
  84. *
  85. * @type function
  86. * @date 15/7/17
  87. * @since 5.6.0
  88. *
  89. * @param $post_id (int)
  90. * @return $post_id (int)
  91. */
  92. function render_field_choices( $field ) {
  93. // walk
  94. return $this->walk( $field['choices'], $field );
  95. }
  96. /**
  97. * Validates values for the checkbox field
  98. *
  99. * @date 09/12/2022
  100. * @since 6.0.0
  101. *
  102. * @param bool $valid If the field is valid.
  103. * @param mixed $value The value to validate.
  104. * @param array $field The main field array.
  105. * @param string $input The input element's name attribute.
  106. *
  107. * @return bool
  108. */
  109. function validate_value( $valid, $value, $field, $input ) {
  110. if ( ! is_array( $value ) || empty( $field['allow_custom'] ) ) {
  111. return $valid;
  112. }
  113. foreach ( $value as $value ) {
  114. if ( empty( $value ) ) {
  115. return __( 'Checkbox custom values cannot be empty. Uncheck any empty values.', 'acf' );
  116. }
  117. }
  118. return $valid;
  119. }
  120. /*
  121. * render_field_toggle
  122. *
  123. * description
  124. *
  125. * @type function
  126. * @date 15/7/17
  127. * @since 5.6.0
  128. *
  129. * @param $post_id (int)
  130. * @return $post_id (int)
  131. */
  132. function render_field_toggle( $field ) {
  133. // vars
  134. $atts = array(
  135. 'type' => 'checkbox',
  136. 'class' => 'acf-checkbox-toggle',
  137. 'label' => __( 'Toggle All', 'acf' ),
  138. );
  139. // custom label
  140. if ( is_string( $field['toggle'] ) ) {
  141. $atts['label'] = $field['toggle'];
  142. }
  143. // checked
  144. if ( $this->_all_checked ) {
  145. $atts['checked'] = 'checked';
  146. }
  147. // return
  148. return '<li>' . acf_get_checkbox_input( $atts ) . '</li>' . "\n";
  149. }
  150. /*
  151. * render_field_custom
  152. *
  153. * description
  154. *
  155. * @type function
  156. * @date 15/7/17
  157. * @since 5.6.0
  158. *
  159. * @param $post_id (int)
  160. * @return $post_id (int)
  161. */
  162. function render_field_custom( $field ) {
  163. // vars
  164. $html = '';
  165. // loop
  166. foreach ( $field['value'] as $value ) {
  167. // ignore if already eixsts
  168. if ( isset( $field['choices'][ $value ] ) ) {
  169. continue;
  170. }
  171. // vars
  172. $esc_value = esc_attr( $value );
  173. $text_input = array(
  174. 'name' => $field['name'],
  175. 'value' => $value,
  176. );
  177. // bail early if choice already exists
  178. if ( in_array( $esc_value, $this->_values ) ) {
  179. continue;
  180. }
  181. // append
  182. $html .= '<li><input class="acf-checkbox-custom" type="checkbox" checked="checked" />' . acf_get_text_input( $text_input ) . '</li>' . "\n";
  183. }
  184. // append button
  185. $html .= '<li><a href="#" class="button acf-add-checkbox">' . esc_attr__( 'Add new choice', 'acf' ) . '</a></li>' . "\n";
  186. // return
  187. return $html;
  188. }
  189. function walk( $choices = array(), $args = array(), $depth = 0 ) {
  190. // bail early if no choices
  191. if ( empty( $choices ) ) {
  192. return '';
  193. }
  194. // defaults
  195. $args = wp_parse_args(
  196. $args,
  197. array(
  198. 'id' => '',
  199. 'type' => 'checkbox',
  200. 'name' => '',
  201. 'value' => array(),
  202. 'disabled' => array(),
  203. )
  204. );
  205. // vars
  206. $html = '';
  207. // sanitize values for 'selected' matching
  208. if ( $depth == 0 ) {
  209. $args['value'] = array_map( 'esc_attr', $args['value'] );
  210. $args['disabled'] = array_map( 'esc_attr', $args['disabled'] );
  211. }
  212. // loop
  213. foreach ( $choices as $value => $label ) {
  214. // open
  215. $html .= '<li>';
  216. // optgroup
  217. if ( is_array( $label ) ) {
  218. $html .= '<ul>' . "\n";
  219. $html .= $this->walk( $label, $args, $depth + 1 );
  220. $html .= '</ul>';
  221. // option
  222. } else {
  223. // vars
  224. $esc_value = esc_attr( $value );
  225. $atts = array(
  226. 'id' => $args['id'] . '-' . str_replace( ' ', '-', $value ),
  227. 'type' => $args['type'],
  228. 'name' => $args['name'],
  229. 'value' => $value,
  230. 'label' => $label,
  231. );
  232. // selected
  233. if ( in_array( $esc_value, $args['value'] ) ) {
  234. $atts['checked'] = 'checked';
  235. } else {
  236. $this->_all_checked = false;
  237. }
  238. // disabled
  239. if ( in_array( $esc_value, $args['disabled'] ) ) {
  240. $atts['disabled'] = 'disabled';
  241. }
  242. // store value added
  243. $this->_values[] = $esc_value;
  244. // append
  245. $html .= acf_get_checkbox_input( $atts );
  246. }
  247. // close
  248. $html .= '</li>' . "\n";
  249. }
  250. // return
  251. return $html;
  252. }
  253. /*
  254. * render_field_settings()
  255. *
  256. * Create extra options for your field. This is rendered when editing a field.
  257. * The value of $field['name'] can be used (like bellow) to save extra data to the $field
  258. *
  259. * @type action
  260. * @since 3.6
  261. * @date 23/01/13
  262. *
  263. * @param $field - an array holding all the field's data
  264. */
  265. function render_field_settings( $field ) {
  266. // Encode choices (convert from array).
  267. $field['choices'] = acf_encode_choices( $field['choices'] );
  268. $field['default_value'] = acf_encode_choices( $field['default_value'], false );
  269. acf_render_field_setting(
  270. $field,
  271. array(
  272. 'label' => __( 'Choices', 'acf' ),
  273. 'instructions' => __( 'Enter each choice on a new line.', 'acf' ) . '<br />' . __( 'For more control, you may specify both a value and label like this:', 'acf' ) . '<br /><span class="acf-field-setting-example">' . __( 'red : Red', 'acf' ) . '</span>',
  274. 'type' => 'textarea',
  275. 'name' => 'choices',
  276. )
  277. );
  278. acf_render_field_setting(
  279. $field,
  280. array(
  281. 'label' => __( 'Default Value', 'acf' ),
  282. 'instructions' => __( 'Enter each default value on a new line', 'acf' ),
  283. 'type' => 'textarea',
  284. 'name' => 'default_value',
  285. )
  286. );
  287. acf_render_field_setting(
  288. $field,
  289. array(
  290. 'label' => __( 'Return Value', 'acf' ),
  291. 'instructions' => __( 'Specify the returned value on front end', 'acf' ),
  292. 'type' => 'radio',
  293. 'name' => 'return_format',
  294. 'layout' => 'horizontal',
  295. 'choices' => array(
  296. 'value' => __( 'Value', 'acf' ),
  297. 'label' => __( 'Label', 'acf' ),
  298. 'array' => __( 'Both (Array)', 'acf' ),
  299. ),
  300. )
  301. );
  302. }
  303. /**
  304. * Renders the field settings used in the "Validation" tab.
  305. *
  306. * @since 6.0
  307. *
  308. * @param array $field The field settings array.
  309. * @return void
  310. */
  311. function render_field_validation_settings( $field ) {
  312. acf_render_field_setting(
  313. $field,
  314. array(
  315. 'label' => __( 'Allow Custom Values', 'acf' ),
  316. 'name' => 'allow_custom',
  317. 'type' => 'true_false',
  318. 'ui' => 1,
  319. 'instructions' => __( "Allow 'custom' values to be added", 'acf' ),
  320. )
  321. );
  322. acf_render_field_setting(
  323. $field,
  324. array(
  325. 'label' => __( 'Save Custom Values', 'acf' ),
  326. 'name' => 'save_custom',
  327. 'type' => 'true_false',
  328. 'ui' => 1,
  329. 'instructions' => __( "Save 'custom' values to the field's choices", 'acf' ),
  330. 'conditions' => array(
  331. 'field' => 'allow_custom',
  332. 'operator' => '==',
  333. 'value' => 1,
  334. ),
  335. )
  336. );
  337. }
  338. /**
  339. * Renders the field settings used in the "Presentation" tab.
  340. *
  341. * @since 6.0
  342. *
  343. * @param array $field The field settings array.
  344. * @return void
  345. */
  346. function render_field_presentation_settings( $field ) {
  347. acf_render_field_setting(
  348. $field,
  349. array(
  350. 'label' => __( 'Layout', 'acf' ),
  351. 'instructions' => '',
  352. 'type' => 'radio',
  353. 'name' => 'layout',
  354. 'layout' => 'horizontal',
  355. 'choices' => array(
  356. 'vertical' => __( 'Vertical', 'acf' ),
  357. 'horizontal' => __( 'Horizontal', 'acf' ),
  358. ),
  359. )
  360. );
  361. acf_render_field_setting(
  362. $field,
  363. array(
  364. 'label' => __( 'Add Toggle All', 'acf' ),
  365. 'instructions' => __( 'Prepend an extra checkbox to toggle all choices', 'acf' ),
  366. 'name' => 'toggle',
  367. 'type' => 'true_false',
  368. 'ui' => 1,
  369. )
  370. );
  371. }
  372. /*
  373. * update_field()
  374. *
  375. * This filter is appied to the $field before it is saved to the database
  376. *
  377. * @type filter
  378. * @since 3.6
  379. * @date 23/01/13
  380. *
  381. * @param $field - the field array holding all the field options
  382. * @param $post_id - the field group ID (post_type = acf)
  383. *
  384. * @return $field - the modified field
  385. */
  386. function update_field( $field ) {
  387. // Decode choices (convert to array).
  388. $field['choices'] = acf_decode_choices( $field['choices'] );
  389. $field['default_value'] = acf_decode_choices( $field['default_value'], true );
  390. return $field;
  391. }
  392. /*
  393. * update_value()
  394. *
  395. * This filter is appied to the $value before it is updated in the db
  396. *
  397. * @type filter
  398. * @since 3.6
  399. * @date 23/01/13
  400. *
  401. * @param $value - the value which will be saved in the database
  402. * @param $post_id - the $post_id of which the value will be saved
  403. * @param $field - the field array holding all the field options
  404. *
  405. * @return $value - the modified value
  406. */
  407. function update_value( $value, $post_id, $field ) {
  408. // bail early if is empty
  409. if ( empty( $value ) ) {
  410. return $value;
  411. }
  412. // select -> update_value()
  413. $value = acf_get_field_type( 'select' )->update_value( $value, $post_id, $field );
  414. // save_other_choice
  415. if ( $field['save_custom'] ) {
  416. // get raw $field (may have been changed via repeater field)
  417. // if field is local, it won't have an ID
  418. $selector = $field['ID'] ? $field['ID'] : $field['key'];
  419. $field = acf_get_field( $selector );
  420. if ( ! $field ) {
  421. return false;
  422. }
  423. // bail early if no ID (JSON only)
  424. if ( ! $field['ID'] ) {
  425. return $value;
  426. }
  427. // loop
  428. foreach ( $value as $v ) {
  429. // ignore if already eixsts
  430. if ( isset( $field['choices'][ $v ] ) ) {
  431. continue;
  432. }
  433. // unslash (fixes serialize single quote issue)
  434. $v = wp_unslash( $v );
  435. // sanitize (remove tags)
  436. $v = sanitize_text_field( $v );
  437. // append
  438. $field['choices'][ $v ] = $v;
  439. }
  440. // save
  441. acf_update_field( $field );
  442. }
  443. // return
  444. return $value;
  445. }
  446. /*
  447. * translate_field
  448. *
  449. * This function will translate field settings
  450. *
  451. * @type function
  452. * @date 8/03/2016
  453. * @since 5.3.2
  454. *
  455. * @param $field (array)
  456. * @return $field
  457. */
  458. function translate_field( $field ) {
  459. return acf_get_field_type( 'select' )->translate_field( $field );
  460. }
  461. /*
  462. * format_value()
  463. *
  464. * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
  465. *
  466. * @type filter
  467. * @since 3.6
  468. * @date 23/01/13
  469. *
  470. * @param $value (mixed) the value which was loaded from the database
  471. * @param $post_id (mixed) the $post_id from which the value was loaded
  472. * @param $field (array) the field array holding all the field options
  473. *
  474. * @return $value (mixed) the modified value
  475. */
  476. function format_value( $value, $post_id, $field ) {
  477. // Bail early if is empty.
  478. if ( acf_is_empty( $value ) ) {
  479. return array();
  480. }
  481. // Always convert to array of items.
  482. $value = acf_array( $value );
  483. // Return.
  484. return acf_get_field_type( 'select' )->format_value( $value, $post_id, $field );
  485. }
  486. /**
  487. * Return the schema array for the REST API.
  488. *
  489. * @param array $field
  490. * @return array
  491. */
  492. public function get_rest_schema( array $field ) {
  493. $schema = array(
  494. 'type' => array( 'string', 'array', 'null' ),
  495. 'required' => isset( $field['required'] ) && $field['required'],
  496. 'items' => array(
  497. 'type' => 'string',
  498. ),
  499. );
  500. if ( isset( $field['default_value'] ) && '' !== $field['default_value'] ) {
  501. $schema['default'] = $field['default_value'];
  502. }
  503. // If we allow custom values, nothing else to do here.
  504. if ( ! empty( $field['allow_custom'] ) ) {
  505. return $schema;
  506. }
  507. /**
  508. * If a user has defined keys for the checkboxes,
  509. * we should use the keys for the available options to POST to,
  510. * since they are what is displayed in GET requests.
  511. */
  512. $checkbox_keys = array_diff(
  513. array_keys( $field['choices'] ),
  514. array_values( $field['choices'] )
  515. );
  516. $schema['items']['enum'] = empty( $checkbox_keys ) ? $field['choices'] : $checkbox_keys;
  517. return $schema;
  518. }
  519. }
  520. // initialize
  521. acf_register_field_type( 'acf_field_checkbox' );
  522. endif; // class_exists check