class-acf-field-group.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <?php
  2. if ( ! class_exists( 'acf_field__group' ) ) :
  3. class acf_field__group 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 = 'group';
  19. $this->label = __( 'Group', 'acf' );
  20. $this->category = 'layout';
  21. $this->defaults = array(
  22. 'sub_fields' => array(),
  23. 'layout' => 'block',
  24. );
  25. $this->have_rows = 'single';
  26. // field filters
  27. $this->add_field_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_field_for_export' ) );
  28. $this->add_field_filter( 'acf/prepare_field_for_import', array( $this, 'prepare_field_for_import' ) );
  29. }
  30. /*
  31. * load_field()
  32. *
  33. * This filter is appied to the $field after it is loaded from the database
  34. *
  35. * @type filter
  36. * @since 3.6
  37. * @date 23/01/13
  38. *
  39. * @param $field - the field array holding all the field options
  40. *
  41. * @return $field - the field array holding all the field options
  42. */
  43. function load_field( $field ) {
  44. // vars
  45. $sub_fields = acf_get_fields( $field );
  46. // append
  47. if ( $sub_fields ) {
  48. $field['sub_fields'] = $sub_fields;
  49. }
  50. // return
  51. return $field;
  52. }
  53. /*
  54. * load_value()
  55. *
  56. * This filter is applied to the $value after it is loaded from the db
  57. *
  58. * @type filter
  59. * @since 3.6
  60. * @date 23/01/13
  61. *
  62. * @param $value (mixed) the value found in the database
  63. * @param $post_id (mixed) the $post_id from which the value was loaded
  64. * @param $field (array) the field array holding all the field options
  65. * @return $value
  66. */
  67. function load_value( $value, $post_id, $field ) {
  68. // bail early if no sub fields
  69. if ( empty( $field['sub_fields'] ) ) {
  70. return $value;
  71. }
  72. // modify names
  73. $field = $this->prepare_field_for_db( $field );
  74. // load sub fields
  75. $value = array();
  76. // loop
  77. foreach ( $field['sub_fields'] as $sub_field ) {
  78. // load
  79. $value[ $sub_field['key'] ] = acf_get_value( $post_id, $sub_field );
  80. }
  81. // return
  82. return $value;
  83. }
  84. /*
  85. * format_value()
  86. *
  87. * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
  88. *
  89. * @type filter
  90. * @since 3.6
  91. * @date 23/01/13
  92. *
  93. * @param $value (mixed) the value which was loaded from the database
  94. * @param $post_id (mixed) the $post_id from which the value was loaded
  95. * @param $field (array) the field array holding all the field options
  96. *
  97. * @return $value (mixed) the modified value
  98. */
  99. function format_value( $value, $post_id, $field ) {
  100. // bail early if no value
  101. if ( empty( $value ) ) {
  102. return false;
  103. }
  104. // modify names
  105. $field = $this->prepare_field_for_db( $field );
  106. // loop
  107. foreach ( $field['sub_fields'] as $sub_field ) {
  108. // extract value
  109. $sub_value = acf_extract_var( $value, $sub_field['key'] );
  110. // format value
  111. $sub_value = acf_format_value( $sub_value, $post_id, $sub_field );
  112. // append to $row
  113. $value[ $sub_field['_name'] ] = $sub_value;
  114. }
  115. // return
  116. return $value;
  117. }
  118. /*
  119. * update_value()
  120. *
  121. * This filter is appied to the $value before it is updated in the db
  122. *
  123. * @type filter
  124. * @since 3.6
  125. * @date 23/01/13
  126. *
  127. * @param $value - the value which will be saved in the database
  128. * @param $field - the field array holding all the field options
  129. * @param $post_id - the $post_id of which the value will be saved
  130. *
  131. * @return $value - the modified value
  132. */
  133. function update_value( $value, $post_id, $field ) {
  134. // bail early if no value
  135. if ( ! acf_is_array( $value ) ) {
  136. return null;
  137. }
  138. // bail early if no sub fields
  139. if ( empty( $field['sub_fields'] ) ) {
  140. return null;
  141. }
  142. // modify names
  143. $field = $this->prepare_field_for_db( $field );
  144. // loop
  145. foreach ( $field['sub_fields'] as $sub_field ) {
  146. // vars
  147. $v = false;
  148. // key (backend)
  149. if ( isset( $value[ $sub_field['key'] ] ) ) {
  150. $v = $value[ $sub_field['key'] ];
  151. // name (frontend)
  152. } elseif ( isset( $value[ $sub_field['_name'] ] ) ) {
  153. $v = $value[ $sub_field['_name'] ];
  154. // empty
  155. } else {
  156. // input is not set (hidden by conditioanl logic)
  157. continue;
  158. }
  159. // update value
  160. acf_update_value( $v, $post_id, $sub_field );
  161. }
  162. // return
  163. return '';
  164. }
  165. /*
  166. * prepare_field_for_db
  167. *
  168. * This function will modify sub fields ready for update / load
  169. *
  170. * @type function
  171. * @date 4/11/16
  172. * @since 5.5.0
  173. *
  174. * @param $field (array)
  175. * @return $field
  176. */
  177. function prepare_field_for_db( $field ) {
  178. // bail early if no sub fields
  179. if ( empty( $field['sub_fields'] ) ) {
  180. return $field;
  181. }
  182. // loop
  183. foreach ( $field['sub_fields'] as &$sub_field ) {
  184. // prefix name
  185. $sub_field['name'] = $field['name'] . '_' . $sub_field['_name'];
  186. }
  187. // return
  188. return $field;
  189. }
  190. /*
  191. * render_field()
  192. *
  193. * Create the HTML interface for your field
  194. *
  195. * @param $field - an array holding all the field's data
  196. *
  197. * @type action
  198. * @since 3.6
  199. * @date 23/01/13
  200. */
  201. function render_field( $field ) {
  202. // bail early if no sub fields
  203. if ( empty( $field['sub_fields'] ) ) {
  204. return;
  205. }
  206. // load values
  207. foreach ( $field['sub_fields'] as &$sub_field ) {
  208. // add value
  209. if ( isset( $field['value'][ $sub_field['key'] ] ) ) {
  210. // this is a normal value
  211. $sub_field['value'] = $field['value'][ $sub_field['key'] ];
  212. } elseif ( isset( $sub_field['default_value'] ) ) {
  213. // no value, but this sub field has a default value
  214. $sub_field['value'] = $sub_field['default_value'];
  215. }
  216. // update prefix to allow for nested values
  217. $sub_field['prefix'] = $field['name'];
  218. // restore required
  219. if ( $field['required'] ) {
  220. $sub_field['required'] = 0;
  221. }
  222. }
  223. // render
  224. if ( $field['layout'] == 'table' ) {
  225. $this->render_field_table( $field );
  226. } else {
  227. $this->render_field_block( $field );
  228. }
  229. }
  230. /*
  231. * render_field_block
  232. *
  233. * description
  234. *
  235. * @type function
  236. * @date 12/07/2016
  237. * @since 5.4.0
  238. *
  239. * @param $post_id (int)
  240. * @return $post_id (int)
  241. */
  242. function render_field_block( $field ) {
  243. // vars
  244. $label_placement = ( $field['layout'] == 'block' ) ? 'top' : 'left';
  245. // html
  246. echo '<div class="acf-fields -' . $label_placement . ' -border">';
  247. foreach ( $field['sub_fields'] as $sub_field ) {
  248. acf_render_field_wrap( $sub_field );
  249. }
  250. echo '</div>';
  251. }
  252. /*
  253. * render_field_table
  254. *
  255. * description
  256. *
  257. * @type function
  258. * @date 12/07/2016
  259. * @since 5.4.0
  260. *
  261. * @param $post_id (int)
  262. * @return $post_id (int)
  263. */
  264. function render_field_table( $field ) {
  265. ?>
  266. <table class="acf-table">
  267. <thead>
  268. <tr>
  269. <?php
  270. foreach ( $field['sub_fields'] as $sub_field ) :
  271. // prepare field (allow sub fields to be removed)
  272. $sub_field = acf_prepare_field( $sub_field );
  273. // bail early if no field
  274. if ( ! $sub_field ) {
  275. continue;
  276. }
  277. // vars
  278. $atts = array();
  279. $atts['class'] = 'acf-th';
  280. $atts['data-name'] = $sub_field['_name'];
  281. $atts['data-type'] = $sub_field['type'];
  282. $atts['data-key'] = $sub_field['key'];
  283. // Add custom width
  284. if ( $sub_field['wrapper']['width'] ) {
  285. $atts['data-width'] = $sub_field['wrapper']['width'];
  286. $atts['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
  287. }
  288. ?>
  289. <th <?php echo acf_esc_attrs( $atts ); ?>>
  290. <?php acf_render_field_label( $sub_field ); ?>
  291. <?php acf_render_field_instructions( $sub_field ); ?>
  292. </th>
  293. <?php endforeach; ?>
  294. </tr>
  295. </thead>
  296. <tbody>
  297. <tr class="acf-row">
  298. <?php
  299. foreach ( $field['sub_fields'] as $sub_field ) {
  300. acf_render_field_wrap( $sub_field, 'td' );
  301. }
  302. ?>
  303. </tr>
  304. </tbody>
  305. </table>
  306. <?php
  307. }
  308. /*
  309. * render_field_settings()
  310. *
  311. * Create extra options for your field. This is rendered when editing a field.
  312. * The value of $field['name'] can be used (like bellow) to save extra data to the $field
  313. *
  314. * @type action
  315. * @since 3.6
  316. * @date 23/01/13
  317. *
  318. * @param $field - an array holding all the field's data
  319. */
  320. function render_field_settings( $field ) {
  321. // vars
  322. $args = array(
  323. 'fields' => $field['sub_fields'],
  324. 'parent' => $field['ID'],
  325. 'is_subfield' => true,
  326. );
  327. ?>
  328. <div class="acf-field acf-field-setting-sub_fields" data-setting="group" data-name="sub_fields">
  329. <div class="acf-label">
  330. <label><?php _e( 'Sub Fields', 'acf' ); ?></label>
  331. </div>
  332. <div class="acf-input acf-input-sub">
  333. <?php
  334. acf_get_view( 'field-group-fields', $args );
  335. ?>
  336. </div>
  337. </div>
  338. <?php
  339. // layout
  340. acf_render_field_setting(
  341. $field,
  342. array(
  343. 'label' => __( 'Layout', 'acf' ),
  344. 'instructions' => __( 'Specify the style used to render the selected fields', 'acf' ),
  345. 'type' => 'radio',
  346. 'name' => 'layout',
  347. 'layout' => 'horizontal',
  348. 'choices' => array(
  349. 'block' => __( 'Block', 'acf' ),
  350. 'table' => __( 'Table', 'acf' ),
  351. 'row' => __( 'Row', 'acf' ),
  352. ),
  353. )
  354. );
  355. }
  356. /*
  357. * validate_value
  358. *
  359. * description
  360. *
  361. * @type function
  362. * @date 11/02/2014
  363. * @since 5.0.0
  364. *
  365. * @param $post_id (int)
  366. * @return $post_id (int)
  367. */
  368. function validate_value( $valid, $value, $field, $input ) {
  369. // bail early if no $value
  370. if ( empty( $value ) ) {
  371. return $valid;
  372. }
  373. // bail early if no sub fields
  374. if ( empty( $field['sub_fields'] ) ) {
  375. return $valid;
  376. }
  377. // loop
  378. foreach ( $field['sub_fields'] as $sub_field ) {
  379. // get sub field
  380. $k = $sub_field['key'];
  381. // bail early if value not set (conditional logic?)
  382. if ( ! isset( $value[ $k ] ) ) {
  383. continue;
  384. }
  385. // required
  386. if ( $field['required'] ) {
  387. $sub_field['required'] = 1;
  388. }
  389. // validate
  390. acf_validate_value( $value[ $k ], $sub_field, "{$input}[{$k}]" );
  391. }
  392. // return
  393. return $valid;
  394. }
  395. /*
  396. * duplicate_field()
  397. *
  398. * This filter is appied to the $field before it is duplicated and saved to the database
  399. *
  400. * @type filter
  401. * @since 3.6
  402. * @date 23/01/13
  403. *
  404. * @param $field - the field array holding all the field options
  405. *
  406. * @return $field - the modified field
  407. */
  408. function duplicate_field( $field ) {
  409. // get sub fields
  410. $sub_fields = acf_extract_var( $field, 'sub_fields' );
  411. // save field to get ID
  412. $field = acf_update_field( $field );
  413. // duplicate sub fields
  414. acf_duplicate_fields( $sub_fields, $field['ID'] );
  415. // return
  416. return $field;
  417. }
  418. /**
  419. * prepare_field_for_export
  420. *
  421. * Prepares the field for export.
  422. *
  423. * @date 11/03/2014
  424. * @since 5.0.0
  425. *
  426. * @param array $field The field settings.
  427. * @return array
  428. */
  429. function prepare_field_for_export( $field ) {
  430. // Check for sub fields.
  431. if ( ! empty( $field['sub_fields'] ) ) {
  432. $field['sub_fields'] = acf_prepare_fields_for_export( $field['sub_fields'] );
  433. }
  434. return $field;
  435. }
  436. /**
  437. * prepare_field_for_import
  438. *
  439. * Returns a flat array of fields containing all sub fields ready for import.
  440. *
  441. * @date 11/03/2014
  442. * @since 5.0.0
  443. *
  444. * @param array $field The field settings.
  445. * @return array
  446. */
  447. function prepare_field_for_import( $field ) {
  448. // Check for sub fields.
  449. if ( ! empty( $field['sub_fields'] ) ) {
  450. $sub_fields = acf_extract_var( $field, 'sub_fields' );
  451. // Modify sub fields.
  452. foreach ( $sub_fields as $i => $sub_field ) {
  453. $sub_fields[ $i ]['parent'] = $field['key'];
  454. $sub_fields[ $i ]['menu_order'] = $i;
  455. }
  456. // Return array of [field, sub_1, sub_2, ...].
  457. return array_merge( array( $field ), $sub_fields );
  458. }
  459. return $field;
  460. }
  461. /*
  462. * delete_value
  463. *
  464. * Called when deleting this field's value.
  465. *
  466. * @date 1/07/2015
  467. * @since 5.2.3
  468. *
  469. * @param mixed $post_id The post ID being saved
  470. * @param string $meta_key The field name as seen by the DB
  471. * @param array $field The field settings
  472. * @return void
  473. */
  474. function delete_value( $post_id, $meta_key, $field ) {
  475. // bail early if no sub fields
  476. if ( empty( $field['sub_fields'] ) ) {
  477. return null;
  478. }
  479. // modify names
  480. $field = $this->prepare_field_for_db( $field );
  481. // loop
  482. foreach ( $field['sub_fields'] as $sub_field ) {
  483. acf_delete_value( $post_id, $sub_field );
  484. }
  485. }
  486. /**
  487. * delete_field
  488. *
  489. * Called when deleting a field of this type.
  490. *
  491. * @date 8/11/18
  492. * @since 5.8.0
  493. *
  494. * @param arra $field The field settings.
  495. * @return void
  496. */
  497. function delete_field( $field ) {
  498. // loop over sub fields and delete them
  499. if ( $field['sub_fields'] ) {
  500. foreach ( $field['sub_fields'] as $sub_field ) {
  501. acf_delete_field( $sub_field['ID'] );
  502. }
  503. }
  504. }
  505. /**
  506. * Return the schema array for the REST API.
  507. *
  508. * @param array $field
  509. * @return array
  510. */
  511. public function get_rest_schema( array $field ) {
  512. $schema = array(
  513. 'type' => array( 'object', 'null' ),
  514. 'properties' => array(),
  515. 'required' => ! empty( $field['required'] ),
  516. );
  517. foreach ( $field['sub_fields'] as $sub_field ) {
  518. if ( $sub_field_schema = acf_get_field_rest_schema( $sub_field ) ) {
  519. $schema['properties'][ $sub_field['name'] ] = $sub_field_schema;
  520. }
  521. }
  522. return $schema;
  523. }
  524. /**
  525. * Apply basic formatting to prepare the value for default REST output.
  526. *
  527. * @param mixed $value
  528. * @param int|string $post_id
  529. * @param array $field
  530. * @return array|mixed
  531. */
  532. public function format_value_for_rest( $value, $post_id, array $field ) {
  533. if ( empty( $value ) || ! is_array( $value ) || empty( $field['sub_fields'] ) ) {
  534. return $value;
  535. }
  536. // Loop through each row and within that, each sub field to process sub fields individually.
  537. foreach ( $field['sub_fields'] as $sub_field ) {
  538. // Extract the sub field 'field_key'=>'value' pair from the $value and format it.
  539. $sub_value = acf_extract_var( $value, $sub_field['key'] );
  540. $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
  541. // Add the sub field value back to the $value but mapped to the field name instead
  542. // of the key reference.
  543. $value[ $sub_field['name'] ] = $sub_value;
  544. }
  545. return $value;
  546. }
  547. }
  548. // initialize
  549. acf_register_field_type( 'acf_field__group' );
  550. endif; // class_exists check
  551. ?>