class-acf-field-flexible-content.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851
  1. <?php
  2. if ( ! class_exists( 'acf_field_flexible_content' ) ) :
  3. class acf_field_flexible_content 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 = 'flexible_content';
  19. $this->label = __( 'Flexible Content', 'acf' );
  20. $this->category = 'layout';
  21. $this->defaults = array(
  22. 'layouts' => array(),
  23. 'min' => '',
  24. 'max' => '',
  25. 'button_label' => __( 'Add Row', 'acf' ),
  26. );
  27. // ajax
  28. $this->add_action( 'wp_ajax_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
  29. $this->add_action( 'wp_ajax_nopriv_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
  30. // filters
  31. $this->add_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_any_field_for_export' ) );
  32. $this->add_filter( 'acf/clone_field', array( $this, 'clone_any_field' ), 10, 2 );
  33. $this->add_filter( 'acf/validate_field', array( $this, 'validate_any_field' ) );
  34. // field filters
  35. $this->add_field_filter( 'acf/get_sub_field', array( $this, 'get_sub_field' ), 10, 3 );
  36. $this->add_field_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_field_for_export' ) );
  37. $this->add_field_filter( 'acf/prepare_field_for_import', array( $this, 'prepare_field_for_import' ) );
  38. }
  39. /*
  40. * input_admin_enqueue_scripts
  41. *
  42. * description
  43. *
  44. * @type function
  45. * @date 16/12/2015
  46. * @since 5.3.2
  47. *
  48. * @param $post_id (int)
  49. * @return $post_id (int)
  50. */
  51. function input_admin_enqueue_scripts() {
  52. // localize
  53. acf_localize_text(
  54. array(
  55. // identifiers
  56. 'layout' => __( 'layout', 'acf' ),
  57. 'layouts' => __( 'layouts', 'acf' ),
  58. 'Fields' => __( 'Fields', 'acf' ),
  59. // min / max
  60. 'This field requires at least {min} {label} {identifier}' => __( 'This field requires at least {min} {label} {identifier}', 'acf' ),
  61. 'This field has a limit of {max} {label} {identifier}' => __( 'This field has a limit of {max} {label} {identifier}', 'acf' ),
  62. // popup badge
  63. '{available} {label} {identifier} available (max {max})' => __( '{available} {label} {identifier} available (max {max})', 'acf' ),
  64. '{required} {label} {identifier} required (min {min})' => __( '{required} {label} {identifier} required (min {min})', 'acf' ),
  65. // field settings
  66. 'Flexible Content requires at least 1 layout' => __( 'Flexible Content requires at least 1 layout', 'acf' ),
  67. )
  68. );
  69. }
  70. /*
  71. * get_valid_layout
  72. *
  73. * This function will fill in the missing keys to create a valid layout
  74. *
  75. * @type function
  76. * @date 3/10/13
  77. * @since 1.1.0
  78. *
  79. * @param $layout (array)
  80. * @return $layout (array)
  81. */
  82. function get_valid_layout( $layout = array() ) {
  83. // parse
  84. $layout = wp_parse_args(
  85. $layout,
  86. array(
  87. 'key' => uniqid( 'layout_' ),
  88. 'name' => '',
  89. 'label' => '',
  90. 'display' => 'block',
  91. 'sub_fields' => array(),
  92. 'min' => '',
  93. 'max' => '',
  94. )
  95. );
  96. // return
  97. return $layout;
  98. }
  99. /*
  100. * load_field()
  101. *
  102. * This filter is appied to the $field after it is loaded from the database
  103. *
  104. * @type filter
  105. * @since 3.6
  106. * @date 23/01/13
  107. *
  108. * @param $field - the field array holding all the field options
  109. *
  110. * @return $field - the field array holding all the field options
  111. */
  112. function load_field( $field ) {
  113. // bail early if no field layouts
  114. if ( empty( $field['layouts'] ) ) {
  115. return $field;
  116. }
  117. // vars
  118. $sub_fields = acf_get_fields( $field );
  119. // loop through layouts, sub fields and swap out the field key with the real field
  120. foreach ( array_keys( $field['layouts'] ) as $i ) {
  121. // extract layout
  122. $layout = acf_extract_var( $field['layouts'], $i );
  123. // validate layout
  124. $layout = $this->get_valid_layout( $layout );
  125. // append sub fields
  126. if ( ! empty( $sub_fields ) ) {
  127. foreach ( array_keys( $sub_fields ) as $k ) {
  128. // check if 'parent_layout' is empty
  129. if ( empty( $sub_fields[ $k ]['parent_layout'] ) ) {
  130. // parent_layout did not save for this field, default it to first layout
  131. $sub_fields[ $k ]['parent_layout'] = $layout['key'];
  132. }
  133. // append sub field to layout,
  134. if ( $sub_fields[ $k ]['parent_layout'] == $layout['key'] ) {
  135. $layout['sub_fields'][] = acf_extract_var( $sub_fields, $k );
  136. }
  137. }
  138. }
  139. // append back to layouts
  140. $field['layouts'][ $i ] = $layout;
  141. }
  142. // return
  143. return $field;
  144. }
  145. /*
  146. * get_sub_field
  147. *
  148. * This function will return a specific sub field
  149. *
  150. * @type function
  151. * @date 29/09/2016
  152. * @since 5.4.0
  153. *
  154. * @param $sub_field
  155. * @param $selector (string)
  156. * @param $field (array)
  157. * @return $post_id (int)
  158. */
  159. function get_sub_field( $sub_field, $id, $field ) {
  160. // Get active layout.
  161. $active = get_row_layout();
  162. // Loop over layouts.
  163. if ( $field['layouts'] ) {
  164. foreach ( $field['layouts'] as $layout ) {
  165. // Restict to active layout if within a have_rows() loop.
  166. if ( $active && $active !== $layout['name'] ) {
  167. continue;
  168. }
  169. // Check sub fields.
  170. if ( $layout['sub_fields'] ) {
  171. $sub_field = acf_search_fields( $id, $layout['sub_fields'] );
  172. if ( $sub_field ) {
  173. break;
  174. }
  175. }
  176. }
  177. }
  178. // return
  179. return $sub_field;
  180. }
  181. /*
  182. * render_field()
  183. *
  184. * Create the HTML interface for your field
  185. *
  186. * @param $field - an array holding all the field's data
  187. *
  188. * @type action
  189. * @since 3.6
  190. * @date 23/01/13
  191. */
  192. function render_field( $field ) {
  193. // defaults
  194. if ( empty( $field['button_label'] ) ) {
  195. $field['button_label'] = $this->defaults['button_label'];
  196. }
  197. // sort layouts into names
  198. $layouts = array();
  199. foreach ( $field['layouts'] as $k => $layout ) {
  200. $layouts[ $layout['name'] ] = $layout;
  201. }
  202. // vars
  203. $div = array(
  204. 'class' => 'acf-flexible-content',
  205. 'data-min' => $field['min'],
  206. 'data-max' => $field['max'],
  207. );
  208. // empty
  209. if ( empty( $field['value'] ) ) {
  210. $div['class'] .= ' -empty';
  211. }
  212. // no value message
  213. $no_value_message = __( 'Click the "%s" button below to start creating your layout', 'acf' );
  214. $no_value_message = apply_filters( 'acf/fields/flexible_content/no_value_message', $no_value_message, $field );
  215. $no_value_message = sprintf( $no_value_message, $field['button_label'] );
  216. ?>
  217. <div <?php echo acf_esc_attrs( $div ); ?>>
  218. <?php acf_hidden_input( array( 'name' => $field['name'] ) ); ?>
  219. <div class="no-value-message">
  220. <?php echo acf_esc_html( $no_value_message ); ?>
  221. </div>
  222. <div class="clones">
  223. <?php foreach ( $layouts as $layout ) : ?>
  224. <?php $this->render_layout( $field, $layout, 'acfcloneindex', array() ); ?>
  225. <?php endforeach; ?>
  226. </div>
  227. <div class="values">
  228. <?php
  229. if ( ! empty( $field['value'] ) ) :
  230. foreach ( $field['value'] as $i => $value ) :
  231. // validate
  232. if ( empty( $layouts[ $value['acf_fc_layout'] ] ) ) {
  233. continue;
  234. }
  235. // render
  236. $this->render_layout( $field, $layouts[ $value['acf_fc_layout'] ], $i, $value );
  237. endforeach;
  238. endif;
  239. ?>
  240. </div>
  241. <div class="acf-actions">
  242. <a class="acf-button button button-primary" href="#" data-name="add-layout"><?php echo acf_esc_html( $field['button_label'] ); ?></a>
  243. </div>
  244. <script type="text-html" class="tmpl-popup"><ul>
  245. <?php
  246. foreach ( $layouts as $layout ) :
  247. $atts = array(
  248. 'href' => '#',
  249. 'data-layout' => $layout['name'],
  250. 'data-min' => $layout['min'],
  251. 'data-max' => $layout['max'],
  252. );
  253. ?>
  254. <li><a <?php echo acf_esc_attrs( $atts ); ?>><?php echo acf_esc_html( $layout['label'] ); ?></a></li>
  255. <?php
  256. endforeach;
  257. ?>
  258. </ul>
  259. </script>
  260. </div>
  261. <?php
  262. }
  263. /*
  264. * render_layout
  265. *
  266. * description
  267. *
  268. * @type function
  269. * @date 19/11/2013
  270. * @since 5.0.0
  271. *
  272. * @param $post_id (int)
  273. * @return $post_id (int)
  274. */
  275. function render_layout( $field, $layout, $i, $value ) {
  276. // vars
  277. $order = 0;
  278. $el = 'div';
  279. $sub_fields = $layout['sub_fields'];
  280. $id = ( $i === 'acfcloneindex' ) ? 'acfcloneindex' : "row-$i";
  281. $prefix = $field['name'] . '[' . $id . ']';
  282. // div
  283. $div = array(
  284. 'class' => 'layout',
  285. 'data-id' => $id,
  286. 'data-layout' => $layout['name'],
  287. );
  288. // clone
  289. if ( is_numeric( $i ) ) {
  290. $order = $i + 1;
  291. } else {
  292. $div['class'] .= ' acf-clone';
  293. }
  294. // display
  295. if ( $layout['display'] == 'table' ) {
  296. $el = 'td';
  297. }
  298. // title
  299. $title = $this->get_layout_title( $field, $layout, $i, $value );
  300. // remove row
  301. reset_rows();
  302. ?>
  303. <div <?php echo acf_esc_attr( $div ); ?>>
  304. <?php
  305. acf_hidden_input(
  306. array(
  307. 'name' => $prefix . '[acf_fc_layout]',
  308. 'value' => $layout['name'],
  309. )
  310. );
  311. ?>
  312. <div class="acf-fc-layout-handle" title="<?php _e( 'Drag to reorder', 'acf' ); ?>" data-name="collapse-layout"><?php echo acf_esc_html( $title ); ?></div>
  313. <div class="acf-fc-layout-controls">
  314. <a class="acf-icon -plus small light acf-js-tooltip" href="#" data-name="add-layout" title="<?php _e( 'Add layout', 'acf' ); ?>"></a>
  315. <a class="acf-icon -duplicate small light acf-js-tooltip" href="#" data-name="duplicate-layout" title="<?php _e( 'Duplicate layout', 'acf' ); ?>"></a>
  316. <a class="acf-icon -minus small light acf-js-tooltip" href="#" data-name="remove-layout" title="<?php _e( 'Remove layout', 'acf' ); ?>"></a>
  317. <a class="acf-icon -collapse small -clear acf-js-tooltip" href="#" data-name="collapse-layout" title="<?php _e( 'Click to toggle', 'acf' ); ?>"></a>
  318. </div>
  319. <?php if ( ! empty( $sub_fields ) ) : ?>
  320. <?php if ( $layout['display'] == 'table' ) : ?>
  321. <table class="acf-table">
  322. <thead>
  323. <tr>
  324. <?php
  325. foreach ( $sub_fields as $sub_field ) :
  326. // Set prefix to generate correct "for" attribute on <label>.
  327. $sub_field['prefix'] = $prefix;
  328. // Prepare field (allow sub fields to be removed).
  329. $sub_field = acf_prepare_field( $sub_field );
  330. if ( ! $sub_field ) {
  331. continue;
  332. }
  333. // Define attrs.
  334. $attrs = array();
  335. $attrs['class'] = 'acf-th';
  336. $attrs['data-name'] = $sub_field['_name'];
  337. $attrs['data-type'] = $sub_field['type'];
  338. $attrs['data-key'] = $sub_field['key'];
  339. if ( $sub_field['wrapper']['width'] ) {
  340. $attrs['data-width'] = $sub_field['wrapper']['width'];
  341. $attrs['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
  342. }
  343. ?>
  344. <th <?php echo acf_esc_attrs( $attrs ); ?>>
  345. <?php acf_render_field_label( $sub_field ); ?>
  346. <?php acf_render_field_instructions( $sub_field ); ?>
  347. </th>
  348. <?php endforeach; ?>
  349. </tr>
  350. </thead>
  351. <tbody>
  352. <tr class="acf-row">
  353. <?php else : ?>
  354. <div class="acf-fields
  355. <?php
  356. if ( $layout['display'] == 'row' ) :
  357. ?>
  358. -left<?php endif; ?>">
  359. <?php endif; ?>
  360. <?php
  361. // loop though sub fields
  362. foreach ( $sub_fields as $sub_field ) {
  363. // add value
  364. if ( isset( $value[ $sub_field['key'] ] ) ) {
  365. // this is a normal value
  366. $sub_field['value'] = $value[ $sub_field['key'] ];
  367. } elseif ( isset( $sub_field['default_value'] ) ) {
  368. // no value, but this sub field has a default value
  369. $sub_field['value'] = $sub_field['default_value'];
  370. }
  371. // update prefix to allow for nested values
  372. $sub_field['prefix'] = $prefix;
  373. // render input
  374. acf_render_field_wrap( $sub_field, $el );
  375. }
  376. ?>
  377. <?php if ( $layout['display'] == 'table' ) : ?>
  378. </tr>
  379. </tbody>
  380. </table>
  381. <?php else : ?>
  382. </div>
  383. <?php endif; ?>
  384. <?php endif; ?>
  385. </div>
  386. <?php
  387. }
  388. /**
  389. * Renders the flexible content field layouts in the field group editor.
  390. *
  391. * @since 3.6
  392. * @date 23/01/13
  393. *
  394. * @param array $field An array holding all the field's data.
  395. */
  396. public function render_field_settings( $field ) {
  397. $layout_open = apply_filters( 'acf/fields/flexible_content/layout_default_expanded', false );
  398. // Load default layout.
  399. if ( empty( $field['layouts'] ) ) {
  400. $layout_open = true;
  401. $field['layouts'] = array(
  402. array(),
  403. );
  404. }
  405. $field_settings_class = $layout_open ? 'open' : '';
  406. $toggle_class = $layout_open ? 'open' : 'closed';
  407. $field_settings_style = $layout_open ? '' : 'display: none;';
  408. // loop through layouts
  409. foreach ( $field['layouts'] as $layout ) {
  410. // get valid layout
  411. $layout = $this->get_valid_layout( $layout );
  412. // vars
  413. $layout_prefix = "{$field['prefix']}[layouts][{$layout['key']}]";
  414. ?>
  415. <div class="acf-field acf-field-setting-fc_layout" data-name="fc_layout" data-setting="flexible_content" data-layout-label="<?php echo esc_attr( $layout['label'] ); ?>" data-id="<?php echo esc_attr( $layout['key'] ); ?>">
  416. <div class="acf-label acf-field-settings-fc_head">
  417. <div class="acf-fc_draggable">
  418. <label class="reorder-layout"><?php esc_attr_e( 'Layout', 'acf' ); ?>
  419. <span class="acf-fc-layout-name"></span></label>
  420. </div>
  421. <ul class="acf-bl acf-fl-actions">
  422. <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-delete"><i class="acf-icon acf-icon-trash delete-layout " href="#" title="<?php esc_attr_e( 'Delete Layout', 'acf' ); ?>"></i></button></li>
  423. <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-duplicate"><i class="acf-icon -duplicate duplicate-layout" href="#" title="<?php esc_attr_e( 'Duplicate Layout', 'acf' ); ?>"></i></button></li>
  424. <li class="acf-fc-add-layout"><button class="add-layout acf-btn acf-btn-primary add-field" href="#" title="<?php esc_attr_e( 'Add New Layout', 'acf' ); ?>"><i class="acf-icon acf-icon-plus"></i><?php esc_html_e( 'Add Layout', 'acf' ); ?></button></li>
  425. <li><button type="button" class="acf-toggle-fc-layout" aria-expanded="true"></li>
  426. <li><span class="toggle-indicator <?php echo esc_attr( $toggle_class ); ?>" aria-hidden="true"></span></li>
  427. </ul>
  428. </div>
  429. <div class="acf-input acf-field-layout-settings <?php echo esc_attr( $field_settings_class ); ?>" style="<?php echo esc_attr( $field_settings_style ); ?>">
  430. <?php
  431. acf_hidden_input(
  432. array(
  433. 'id' => acf_idify( $layout_prefix . '[key]' ),
  434. 'name' => $layout_prefix . '[key]',
  435. 'class' => 'layout-key',
  436. 'value' => $layout['key'],
  437. )
  438. );
  439. ?>
  440. <ul class="acf-fc-meta acf-bl">
  441. <li class="acf-fc-meta-label acf-fc-meta-left">
  442. <?php
  443. acf_render_field(
  444. array(
  445. 'type' => 'text',
  446. 'name' => 'label',
  447. 'class' => 'layout-label',
  448. 'prefix' => $layout_prefix,
  449. 'value' => $layout['label'],
  450. 'prepend' => __( 'Label', 'acf' ),
  451. )
  452. );
  453. ?>
  454. </li>
  455. <li class="acf-fc-meta-name acf-fc-meta-right">
  456. <?php
  457. acf_render_field(
  458. array(
  459. 'type' => 'text',
  460. 'name' => 'name',
  461. 'class' => 'layout-name',
  462. 'prefix' => $layout_prefix,
  463. 'value' => $layout['name'],
  464. 'prepend' => __( 'Name', 'acf' ),
  465. )
  466. );
  467. ?>
  468. </li>
  469. <li class="acf-fc-meta-display acf-fc-meta-left">
  470. <div class="acf-input-prepend"><?php esc_html_e( 'Layout', 'acf' ); ?></div>
  471. <div class="acf-input-wrap">
  472. <?php
  473. acf_render_field(
  474. array(
  475. 'type' => 'select',
  476. 'name' => 'display',
  477. 'prefix' => $layout_prefix,
  478. 'value' => $layout['display'],
  479. 'class' => 'acf-is-prepended',
  480. 'choices' => array(
  481. 'table' => __( 'Table', 'acf' ),
  482. 'block' => __( 'Block', 'acf' ),
  483. 'row' => __( 'Row', 'acf' ),
  484. ),
  485. )
  486. );
  487. ?>
  488. </div>
  489. </li>
  490. <li class="acf-fc-meta-min">
  491. <?php
  492. acf_render_field(
  493. array(
  494. 'type' => 'text',
  495. 'name' => 'min',
  496. 'prefix' => $layout_prefix,
  497. 'value' => $layout['min'],
  498. 'prepend' => __( 'Min', 'acf' ),
  499. )
  500. );
  501. ?>
  502. </li>
  503. <li class="acf-fc-meta-max">
  504. <?php
  505. acf_render_field(
  506. array(
  507. 'type' => 'text',
  508. 'name' => 'max',
  509. 'prefix' => $layout_prefix,
  510. 'value' => $layout['max'],
  511. 'prepend' => __( 'Max', 'acf' ),
  512. )
  513. );
  514. ?>
  515. </li>
  516. </ul>
  517. <div class="acf-input-sub">
  518. <?php
  519. // vars
  520. $args = array(
  521. 'fields' => $layout['sub_fields'],
  522. 'parent' => $field['ID'],
  523. 'is_subfield' => true,
  524. );
  525. acf_get_view( 'field-group-fields', $args );
  526. ?>
  527. </div>
  528. </div>
  529. </div>
  530. <?php
  531. }
  532. // endforeach
  533. }
  534. /**
  535. * Renders the field settings used in the "Presentation" tab.
  536. *
  537. * @since 6.0
  538. *
  539. * @param array $field The field settings array.
  540. * @return void
  541. */
  542. function render_field_presentation_settings( $field ) {
  543. // min
  544. acf_render_field_setting(
  545. $field,
  546. array(
  547. 'label' => __( 'Minimum Layouts', 'acf' ),
  548. 'instructions' => '',
  549. 'type' => 'number',
  550. 'name' => 'min',
  551. )
  552. );
  553. // max
  554. acf_render_field_setting(
  555. $field,
  556. array(
  557. 'label' => __( 'Maximum Layouts', 'acf' ),
  558. 'instructions' => '',
  559. 'type' => 'number',
  560. 'name' => 'max',
  561. )
  562. );
  563. // add new row label
  564. acf_render_field_setting(
  565. $field,
  566. array(
  567. 'label' => __( 'Button Label', 'acf' ),
  568. 'instructions' => '',
  569. 'type' => 'text',
  570. 'name' => 'button_label',
  571. )
  572. );
  573. }
  574. /*
  575. * load_value()
  576. *
  577. * This filter is applied to the $value after it is loaded from the db
  578. *
  579. * @type filter
  580. * @since 3.6
  581. * @date 23/01/13
  582. *
  583. * @param $value (mixed) the value found in the database
  584. * @param $post_id (mixed) the $post_id from which the value was loaded
  585. * @param $field (array) the field array holding all the field options
  586. * @return $value
  587. */
  588. function load_value( $value, $post_id, $field ) {
  589. // bail early if no value
  590. if ( empty( $value ) || empty( $field['layouts'] ) ) {
  591. return $value;
  592. }
  593. // value must be an array
  594. $value = acf_get_array( $value );
  595. // vars
  596. $rows = array();
  597. // sort layouts into names
  598. $layouts = array();
  599. foreach ( $field['layouts'] as $k => $layout ) {
  600. $layouts[ $layout['name'] ] = $layout['sub_fields'];
  601. }
  602. // loop through rows
  603. foreach ( $value as $i => $l ) {
  604. // append to $values
  605. $rows[ $i ] = array();
  606. $rows[ $i ]['acf_fc_layout'] = $l;
  607. // bail early if layout deosnt contain sub fields
  608. if ( empty( $layouts[ $l ] ) ) {
  609. continue;
  610. }
  611. // get layout
  612. $layout = $layouts[ $l ];
  613. // loop through sub fields
  614. foreach ( array_keys( $layout ) as $j ) {
  615. // get sub field
  616. $sub_field = $layout[ $j ];
  617. // bail early if no name (tab)
  618. if ( acf_is_empty( $sub_field['name'] ) ) {
  619. continue;
  620. }
  621. // update full name
  622. $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
  623. // get value
  624. $sub_value = acf_get_value( $post_id, $sub_field );
  625. // add value
  626. $rows[ $i ][ $sub_field['key'] ] = $sub_value;
  627. }
  628. // foreach
  629. }
  630. // foreach
  631. // return
  632. return $rows;
  633. }
  634. /*
  635. * format_value()
  636. *
  637. * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
  638. *
  639. * @type filter
  640. * @since 3.6
  641. * @date 23/01/13
  642. *
  643. * @param $value (mixed) the value which was loaded from the database
  644. * @param $post_id (mixed) the $post_id from which the value was loaded
  645. * @param $field (array) the field array holding all the field options
  646. *
  647. * @return $value (mixed) the modified value
  648. */
  649. function format_value( $value, $post_id, $field ) {
  650. // bail early if no value
  651. if ( empty( $value ) || empty( $field['layouts'] ) ) {
  652. return false;
  653. }
  654. // sort layouts into names
  655. $layouts = array();
  656. foreach ( $field['layouts'] as $k => $layout ) {
  657. $layouts[ $layout['name'] ] = $layout['sub_fields'];
  658. }
  659. // loop over rows
  660. foreach ( array_keys( $value ) as $i ) {
  661. // get layout name
  662. $l = $value[ $i ]['acf_fc_layout'];
  663. // bail early if layout deosnt exist
  664. if ( empty( $layouts[ $l ] ) ) {
  665. continue;
  666. }
  667. // get layout
  668. $layout = $layouts[ $l ];
  669. // loop through sub fields
  670. foreach ( array_keys( $layout ) as $j ) {
  671. // get sub field
  672. $sub_field = $layout[ $j ];
  673. // bail early if no name (tab)
  674. if ( acf_is_empty( $sub_field['name'] ) ) {
  675. continue;
  676. }
  677. // extract value
  678. $sub_value = acf_extract_var( $value[ $i ], $sub_field['key'] );
  679. // update $sub_field name
  680. $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
  681. // format value
  682. $sub_value = acf_format_value( $sub_value, $post_id, $sub_field );
  683. // append to $row
  684. $value[ $i ][ $sub_field['_name'] ] = $sub_value;
  685. }
  686. }
  687. // return
  688. return $value;
  689. }
  690. /*
  691. * validate_value
  692. *
  693. * description
  694. *
  695. * @type function
  696. * @date 11/02/2014
  697. * @since 5.0.0
  698. *
  699. * @param $post_id (int)
  700. * @return $post_id (int)
  701. */
  702. function validate_value( $valid, $value, $field, $input ) {
  703. // vars
  704. $count = 0;
  705. // check if is value (may be empty string)
  706. if ( is_array( $value ) ) {
  707. // remove acfcloneindex
  708. if ( isset( $value['acfcloneindex'] ) ) {
  709. unset( $value['acfcloneindex'] );
  710. }
  711. // count
  712. $count = count( $value );
  713. }
  714. // validate required
  715. if ( $field['required'] && ! $count ) {
  716. $valid = false;
  717. }
  718. // validate min
  719. $min = (int) $field['min'];
  720. if ( $min && $count < $min ) {
  721. // vars
  722. $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
  723. $identifier = _n( 'layout', 'layouts', $min );
  724. // replace
  725. $error = str_replace( '{min}', $min, $error );
  726. $error = str_replace( '{label}', '', $error );
  727. $error = str_replace( '{identifier}', $identifier, $error );
  728. // return
  729. return $error;
  730. }
  731. // find layouts
  732. $layouts = array();
  733. foreach ( array_keys( $field['layouts'] ) as $i ) {
  734. // vars
  735. $layout = $field['layouts'][ $i ];
  736. // add count
  737. $layout['count'] = 0;
  738. // append
  739. $layouts[ $layout['name'] ] = $layout;
  740. }
  741. // validate value
  742. if ( $count ) {
  743. // loop rows
  744. foreach ( $value as $i => $row ) {
  745. // get layout
  746. $l = $row['acf_fc_layout'];
  747. // bail if layout doesn't exist
  748. if ( ! isset( $layouts[ $l ] ) ) {
  749. continue;
  750. }
  751. // increase count
  752. $layouts[ $l ]['count']++;
  753. // bail if no sub fields
  754. if ( empty( $layouts[ $l ]['sub_fields'] ) ) {
  755. continue;
  756. }
  757. // loop sub fields
  758. foreach ( $layouts[ $l ]['sub_fields'] as $sub_field ) {
  759. // get sub field key
  760. $k = $sub_field['key'];
  761. // bail if no value
  762. if ( ! isset( $value[ $i ][ $k ] ) ) {
  763. continue;
  764. }
  765. // validate
  766. acf_validate_value( $value[ $i ][ $k ], $sub_field, "{$input}[{$i}][{$k}]" );
  767. }
  768. // end loop sub fields
  769. }
  770. // end loop rows
  771. }
  772. // validate layouts
  773. foreach ( $layouts as $layout ) {
  774. // validate min / max
  775. $min = (int) $layout['min'];
  776. $count = $layout['count'];
  777. $label = $layout['label'];
  778. if ( $min && $count < $min ) {
  779. // vars
  780. $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
  781. $identifier = _n( 'layout', 'layouts', $min );
  782. // replace
  783. $error = str_replace( '{min}', $min, $error );
  784. $error = str_replace( '{label}', '"' . $label . '"', $error );
  785. $error = str_replace( '{identifier}', $identifier, $error );
  786. // return
  787. return $error;
  788. }
  789. }
  790. // return
  791. return $valid;
  792. }
  793. /**
  794. * This function will return a specific layout by name from a field
  795. *
  796. * @date 15/2/17
  797. * @since 5.5.8
  798. *
  799. * @param string $name
  800. * @param array $field
  801. * @return array|false
  802. */
  803. function get_layout( $name, $field ) {
  804. // bail early if no layouts
  805. if ( ! isset( $field['layouts'] ) ) {
  806. return false;
  807. }
  808. // loop
  809. foreach ( $field['layouts'] as $layout ) {
  810. // match
  811. if ( $layout['name'] === $name ) {
  812. return $layout;
  813. }
  814. }
  815. // return
  816. return false;
  817. }
  818. /**
  819. * This function will delete a value row
  820. *
  821. * @date 15/2/17
  822. * @since 5.5.8
  823. *
  824. * @param int $i
  825. * @param array $field
  826. * @param mixed $post_id
  827. * @return bool
  828. */
  829. function delete_row( $i, $field, $post_id ) {
  830. // vars
  831. $value = acf_get_metadata( $post_id, $field['name'] );
  832. // bail early if no value
  833. if ( ! is_array( $value ) || ! isset( $value[ $i ] ) ) {
  834. return false;
  835. }
  836. // get layout
  837. $layout = $this->get_layout( $value[ $i ], $field );
  838. // bail early if no layout
  839. if ( ! $layout || empty( $layout['sub_fields'] ) ) {
  840. return false;
  841. }
  842. // loop
  843. foreach ( $layout['sub_fields'] as $sub_field ) {
  844. // modify name for delete
  845. $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
  846. // delete value
  847. acf_delete_value( $post_id, $sub_field );
  848. }
  849. // return
  850. return true;
  851. }
  852. /**
  853. * This function will update a value row
  854. *
  855. * @date 15/2/17
  856. * @since 5.5.8
  857. *
  858. * @param array $row
  859. * @param int $i
  860. * @param array $field
  861. * @param mixed $post_id
  862. * @return bool
  863. */
  864. function update_row( $row, $i, $field, $post_id ) {
  865. // bail early if no layout reference
  866. if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
  867. return false;
  868. }
  869. // get layout
  870. $layout = $this->get_layout( $row['acf_fc_layout'], $field );
  871. // bail early if no layout
  872. if ( ! $layout || empty( $layout['sub_fields'] ) ) {
  873. return false;
  874. }
  875. foreach ( $layout['sub_fields'] as $sub_field ) {
  876. $value = null;
  877. if ( array_key_exists( $sub_field['key'], $row ) ) {
  878. $value = $row[ $sub_field['key'] ];
  879. } elseif ( array_key_exists( $sub_field['name'], $row ) ) {
  880. $value = $row[ $sub_field['name'] ];
  881. } else {
  882. // Value does not exist.
  883. continue;
  884. }
  885. // modify name for save
  886. $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
  887. // update field
  888. acf_update_value( $value, $post_id, $sub_field );
  889. }
  890. return true;
  891. }
  892. /*
  893. * update_value()
  894. *
  895. * This filter is appied to the $value before it is updated in the db
  896. *
  897. * @type filter
  898. * @since 3.6
  899. * @date 23/01/13
  900. *
  901. * @param $value - the value which will be saved in the database
  902. * @param $field - the field array holding all the field options
  903. * @param $post_id - the $post_id of which the value will be saved
  904. *
  905. * @return $value - the modified value
  906. */
  907. function update_value( $value, $post_id, $field ) {
  908. // bail early if no layouts
  909. if ( empty( $field['layouts'] ) ) {
  910. return $value;
  911. }
  912. // vars
  913. $new_value = array();
  914. $old_value = acf_get_metadata( $post_id, $field['name'] );
  915. $old_value = is_array( $old_value ) ? $old_value : array();
  916. // update
  917. if ( ! empty( $value ) ) {
  918. $i = -1;
  919. // remove acfcloneindex
  920. if ( isset( $value['acfcloneindex'] ) ) {
  921. unset( $value['acfcloneindex'] );
  922. }
  923. // loop through rows
  924. foreach ( $value as $row ) {
  925. $i++;
  926. // bail early if no layout reference
  927. if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
  928. continue;
  929. }
  930. // delete old row if layout has changed
  931. if ( isset( $old_value[ $i ] ) && $old_value[ $i ] !== $row['acf_fc_layout'] ) {
  932. $this->delete_row( $i, $field, $post_id );
  933. }
  934. // update row
  935. $this->update_row( $row, $i, $field, $post_id );
  936. // append to order
  937. $new_value[] = $row['acf_fc_layout'];
  938. }
  939. }
  940. // vars
  941. $old_count = empty( $old_value ) ? 0 : count( $old_value );
  942. $new_count = empty( $new_value ) ? 0 : count( $new_value );
  943. // remove old rows
  944. if ( $old_count > $new_count ) {
  945. // loop
  946. for ( $i = $new_count; $i < $old_count; $i++ ) {
  947. $this->delete_row( $i, $field, $post_id );
  948. }
  949. }
  950. // save false for empty value
  951. if ( empty( $new_value ) ) {
  952. $new_value = '';
  953. }
  954. // return
  955. return $new_value;
  956. }
  957. /*
  958. * delete_value
  959. *
  960. * description
  961. *
  962. * @type function
  963. * @date 1/07/2015
  964. * @since 5.2.3
  965. *
  966. * @param $post_id (int)
  967. * @return $post_id (int)
  968. */
  969. function delete_value( $post_id, $key, $field ) {
  970. // vars
  971. $old_value = acf_get_metadata( $post_id, $field['name'] );
  972. $old_value = is_array( $old_value ) ? $old_value : array();
  973. // bail early if no rows or no sub fields
  974. if ( empty( $old_value ) ) {
  975. return;
  976. }
  977. // loop
  978. foreach ( array_keys( $old_value ) as $i ) {
  979. $this->delete_row( $i, $field, $post_id );
  980. }
  981. }
  982. /*
  983. * update_field()
  984. *
  985. * This filter is appied to the $field before it is saved to the database
  986. *
  987. * @type filter
  988. * @since 3.6
  989. * @date 23/01/13
  990. *
  991. * @param $field - the field array holding all the field options
  992. * @param $post_id - the field group ID (post_type = acf)
  993. *
  994. * @return $field - the modified field
  995. */
  996. function update_field( $field ) {
  997. // loop
  998. if ( ! empty( $field['layouts'] ) ) {
  999. foreach ( $field['layouts'] as &$layout ) {
  1000. unset( $layout['sub_fields'] );
  1001. }
  1002. }
  1003. // return
  1004. return $field;
  1005. }
  1006. /*
  1007. * delete_field
  1008. *
  1009. * description
  1010. *
  1011. * @type function
  1012. * @date 4/04/2014
  1013. * @since 5.0.0
  1014. *
  1015. * @param $post_id (int)
  1016. * @return $post_id (int)
  1017. */
  1018. function delete_field( $field ) {
  1019. if ( ! empty( $field['layouts'] ) ) {
  1020. // loop through layouts
  1021. foreach ( $field['layouts'] as $layout ) {
  1022. // loop through sub fields
  1023. if ( ! empty( $layout['sub_fields'] ) ) {
  1024. foreach ( $layout['sub_fields'] as $sub_field ) {
  1025. acf_delete_field( $sub_field['ID'] );
  1026. }
  1027. // foreach
  1028. }
  1029. // if
  1030. }
  1031. // foreach
  1032. }
  1033. // if
  1034. }
  1035. /*
  1036. * duplicate_field()
  1037. *
  1038. * This filter is appied to the $field before it is duplicated and saved to the database
  1039. *
  1040. * @type filter
  1041. * @since 3.6
  1042. * @date 23/01/13
  1043. *
  1044. * @param $field - the field array holding all the field options
  1045. *
  1046. * @return $field - the modified field
  1047. */
  1048. function duplicate_field( $field ) {
  1049. // vars
  1050. $sub_fields = array();
  1051. if ( ! empty( $field['layouts'] ) ) {
  1052. // loop through layouts
  1053. foreach ( $field['layouts'] as $layout ) {
  1054. // extract sub fields
  1055. $extra = acf_extract_var( $layout, 'sub_fields' );
  1056. // merge
  1057. if ( ! empty( $extra ) ) {
  1058. $sub_fields = array_merge( $sub_fields, $extra );
  1059. }
  1060. }
  1061. // foreach
  1062. }
  1063. // if
  1064. // save field to get ID
  1065. $field = acf_update_field( $field );
  1066. // duplicate sub fields
  1067. acf_duplicate_fields( $sub_fields, $field['ID'] );
  1068. // return
  1069. return $field;
  1070. }
  1071. /*
  1072. * ajax_layout_title
  1073. *
  1074. * description
  1075. *
  1076. * @type function
  1077. * @date 2/03/2016
  1078. * @since 5.3.2
  1079. *
  1080. * @param $post_id (int)
  1081. * @return $post_id (int)
  1082. */
  1083. function ajax_layout_title() {
  1084. $options = acf_parse_args(
  1085. $_POST, // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
  1086. array(
  1087. 'post_id' => 0,
  1088. 'i' => 0,
  1089. 'field_key' => '',
  1090. 'nonce' => '',
  1091. 'layout' => '',
  1092. 'value' => array(),
  1093. )
  1094. );
  1095. // load field
  1096. $field = acf_get_field( $options['field_key'] );
  1097. if ( ! $field ) {
  1098. die();
  1099. }
  1100. // vars
  1101. $layout = $this->get_layout( $options['layout'], $field );
  1102. if ( ! $layout ) {
  1103. die();
  1104. }
  1105. // title
  1106. $title = $this->get_layout_title( $field, $layout, $options['i'], $options['value'] );
  1107. // echo
  1108. echo $title;
  1109. die;
  1110. }
  1111. function get_layout_title( $field, $layout, $i, $value ) {
  1112. // vars
  1113. $rows = array();
  1114. $rows[ $i ] = $value;
  1115. // add loop
  1116. acf_add_loop(
  1117. array(
  1118. 'selector' => $field['name'],
  1119. 'name' => $field['name'],
  1120. 'value' => $rows,
  1121. 'field' => $field,
  1122. 'i' => $i,
  1123. 'post_id' => 0,
  1124. )
  1125. );
  1126. // vars
  1127. $title = $layout['label'];
  1128. // filters
  1129. $title = apply_filters( 'acf/fields/flexible_content/layout_title', $title, $field, $layout, $i );
  1130. $title = apply_filters( 'acf/fields/flexible_content/layout_title/name=' . $field['_name'], $title, $field, $layout, $i );
  1131. $title = apply_filters( 'acf/fields/flexible_content/layout_title/key=' . $field['key'], $title, $field, $layout, $i );
  1132. // remove loop
  1133. acf_remove_loop();
  1134. // prepend order
  1135. $order = is_numeric( $i ) ? $i + 1 : 0;
  1136. $title = '<span class="acf-fc-layout-order">' . $order . '</span> ' . acf_esc_html( $title );
  1137. // return
  1138. return $title;
  1139. }
  1140. /*
  1141. * clone_any_field
  1142. *
  1143. * This function will update clone field settings based on the origional field
  1144. *
  1145. * @type function
  1146. * @date 28/06/2016
  1147. * @since 5.3.8
  1148. *
  1149. * @param $clone (array)
  1150. * @param $field (array)
  1151. * @return $clone
  1152. */
  1153. function clone_any_field( $field, $clone_field ) {
  1154. // remove parent_layout
  1155. // - allows a sub field to be rendered as a normal field
  1156. unset( $field['parent_layout'] );
  1157. // attempt to merger parent_layout
  1158. if ( isset( $clone_field['parent_layout'] ) ) {
  1159. $field['parent_layout'] = $clone_field['parent_layout'];
  1160. }
  1161. // return
  1162. return $field;
  1163. }
  1164. /*
  1165. * prepare_field_for_export
  1166. *
  1167. * description
  1168. *
  1169. * @type function
  1170. * @date 11/03/2014
  1171. * @since 5.0.0
  1172. *
  1173. * @param $post_id (int)
  1174. * @return $post_id (int)
  1175. */
  1176. function prepare_field_for_export( $field ) {
  1177. // loop
  1178. if ( ! empty( $field['layouts'] ) ) {
  1179. foreach ( $field['layouts'] as &$layout ) {
  1180. $layout['sub_fields'] = acf_prepare_fields_for_export( $layout['sub_fields'] );
  1181. }
  1182. }
  1183. // return
  1184. return $field;
  1185. }
  1186. function prepare_any_field_for_export( $field ) {
  1187. // remove parent_layout
  1188. unset( $field['parent_layout'] );
  1189. // return
  1190. return $field;
  1191. }
  1192. /*
  1193. * prepare_field_for_import
  1194. *
  1195. * description
  1196. *
  1197. * @type function
  1198. * @date 11/03/2014
  1199. * @since 5.0.0
  1200. *
  1201. * @param $post_id (int)
  1202. * @return $post_id (int)
  1203. */
  1204. function prepare_field_for_import( $field ) {
  1205. // Bail early if no layouts
  1206. if ( empty( $field['layouts'] ) ) {
  1207. return $field;
  1208. }
  1209. // Storage for extracted fields.
  1210. $extra = array();
  1211. // Loop over layouts.
  1212. foreach ( $field['layouts'] as &$layout ) {
  1213. // Ensure layout is valid.
  1214. $layout = $this->get_valid_layout( $layout );
  1215. // Extract sub fields.
  1216. $sub_fields = acf_extract_var( $layout, 'sub_fields' );
  1217. // Modify and append sub fields to $extra.
  1218. if ( $sub_fields ) {
  1219. foreach ( $sub_fields as $i => $sub_field ) {
  1220. // Update atttibutes
  1221. $sub_field['parent'] = $field['key'];
  1222. $sub_field['parent_layout'] = $layout['key'];
  1223. $sub_field['menu_order'] = $i;
  1224. // Append to extra.
  1225. $extra[] = $sub_field;
  1226. }
  1227. }
  1228. }
  1229. // Merge extra sub fields.
  1230. if ( $extra ) {
  1231. array_unshift( $extra, $field );
  1232. return $extra;
  1233. }
  1234. // Return field.
  1235. return $field;
  1236. }
  1237. /*
  1238. * validate_any_field
  1239. *
  1240. * This function will add compatibility for the 'column_width' setting
  1241. *
  1242. * @type function
  1243. * @date 30/1/17
  1244. * @since 5.5.6
  1245. *
  1246. * @param $field (array)
  1247. * @return $field
  1248. */
  1249. function validate_any_field( $field ) {
  1250. // width has changed
  1251. if ( isset( $field['column_width'] ) ) {
  1252. $field['wrapper']['width'] = acf_extract_var( $field, 'column_width' );
  1253. }
  1254. // return
  1255. return $field;
  1256. }
  1257. /*
  1258. * translate_field
  1259. *
  1260. * This function will translate field settings
  1261. *
  1262. * @type function
  1263. * @date 8/03/2016
  1264. * @since 5.3.2
  1265. *
  1266. * @param $field (array)
  1267. * @return $field
  1268. */
  1269. function translate_field( $field ) {
  1270. // translate
  1271. $field['button_label'] = acf_translate( $field['button_label'] );
  1272. // loop
  1273. if ( ! empty( $field['layouts'] ) ) {
  1274. foreach ( $field['layouts'] as &$layout ) {
  1275. $layout['label'] = acf_translate( $layout['label'] );
  1276. }
  1277. }
  1278. // return
  1279. return $field;
  1280. }
  1281. /**
  1282. * Additional validation for the flexible content field when submitted via REST.
  1283. *
  1284. * @param bool $valid
  1285. * @param int $value
  1286. * @param array $field
  1287. *
  1288. * @return bool|WP_Error
  1289. */
  1290. public function validate_rest_value( $valid, $value, $field ) {
  1291. $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
  1292. $data = array(
  1293. 'param' => $param,
  1294. 'value' => $value,
  1295. );
  1296. if ( ! is_array( $value ) && is_null( $value ) ) {
  1297. $error = sprintf( __( '%s must be of type array or null.', 'acf' ), $param );
  1298. return new WP_Error( 'rest_invalid_param', $error, $param );
  1299. }
  1300. $layouts_to_update = array_count_values( array_column( $value, 'acf_fc_layout' ) );
  1301. foreach ( $field['layouts'] as $layout ) {
  1302. $num_layouts = isset( $layouts_to_update[ $layout['name'] ] ) ? $layouts_to_update[ $layout['name'] ] : 0;
  1303. if ( '' !== $layout['min'] && $num_layouts < (int) $layout['min'] ) {
  1304. $error = sprintf(
  1305. _n(
  1306. '%1$s must contain at least %2$s %3$s layout.',
  1307. '%1$s must contain at least %2$s %3$s layouts.',
  1308. $layout['min'],
  1309. 'acf'
  1310. ),
  1311. $param,
  1312. number_format_i18n( $layout['min'] ),
  1313. $layout['name']
  1314. );
  1315. return new WP_Error( 'rest_invalid_param', $error, $data );
  1316. }
  1317. if ( '' !== $layout['max'] && $num_layouts > (int) $layout['max'] ) {
  1318. $error = sprintf(
  1319. _n(
  1320. '%1$s must contain at most %2$s %3$s layout.',
  1321. '%1$s must contain at most %2$s %3$s layouts.',
  1322. $layout['max'],
  1323. 'acf'
  1324. ),
  1325. $param,
  1326. number_format_i18n( $layout['max'] ),
  1327. $layout['name']
  1328. );
  1329. return new WP_Error( 'rest_invalid_param', $error, $data );
  1330. }
  1331. }
  1332. return $valid;
  1333. }
  1334. /**
  1335. * Return the schema array for the REST API.
  1336. *
  1337. * @param array $field
  1338. * @return array
  1339. */
  1340. public function get_rest_schema( array $field ) {
  1341. $schema = array(
  1342. 'type' => array( 'array', 'null' ),
  1343. 'required' => ! empty( $field['required'] ),
  1344. 'items' => array(
  1345. 'oneOf' => array(),
  1346. ),
  1347. );
  1348. // Loop through layouts building up a schema for each.
  1349. foreach ( $field['layouts'] as $layout ) {
  1350. $layout_schema = array(
  1351. 'type' => 'object',
  1352. 'properties' => array(
  1353. 'acf_fc_layout' => array(
  1354. 'type' => 'string',
  1355. 'required' => true,
  1356. // By using a pattern match against the layout name, data sent in must match an available
  1357. // layout on the flexible field. If it doesn't, a 400 Bad Request response will result.
  1358. 'pattern' => '^' . $layout['name'] . '$',
  1359. ),
  1360. ),
  1361. );
  1362. foreach ( $layout['sub_fields'] as $sub_field ) {
  1363. if ( $sub_field_schema = acf_get_field_rest_schema( $sub_field ) ) {
  1364. $layout_schema['properties'][ $sub_field['name'] ] = $sub_field_schema;
  1365. }
  1366. }
  1367. $schema['items']['oneOf'][] = $layout_schema;
  1368. }
  1369. if ( ! empty( $field['min'] ) ) {
  1370. $schema['minItems'] = (int) $field['min'];
  1371. }
  1372. if ( ! empty( $field['max'] ) ) {
  1373. $schema['maxItems'] = (int) $field['max'];
  1374. }
  1375. return $schema;
  1376. }
  1377. /**
  1378. * Apply basic formatting to prepare the value for default REST output.
  1379. *
  1380. * @param mixed $value
  1381. * @param int|string $post_id
  1382. * @param array $field
  1383. * @return array|mixed
  1384. */
  1385. public function format_value_for_rest( $value, $post_id, array $field ) {
  1386. if ( empty( $value ) || ! is_array( $value ) || empty( $field['layouts'] ) ) {
  1387. return null;
  1388. }
  1389. // Create a map of layout sub fields mapped to layout names.
  1390. foreach ( $field['layouts'] as $layout ) {
  1391. $layouts[ $layout['name'] ] = $layout['sub_fields'];
  1392. }
  1393. // Loop through each layout and within that, each sub field to process sub fields individually.
  1394. foreach ( $value as &$layout ) {
  1395. $name = $layout['acf_fc_layout'];
  1396. if ( empty( $layouts[ $name ] ) ) {
  1397. continue;
  1398. }
  1399. foreach ( $layouts[ $name ] as $sub_field ) {
  1400. // Bail early if the field has no name (tab).
  1401. if ( acf_is_empty( $sub_field['name'] ) ) {
  1402. continue;
  1403. }
  1404. // Extract the sub field 'field_key'=>'value' pair from the $layout and format it.
  1405. $sub_value = acf_extract_var( $layout, $sub_field['key'] );
  1406. $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
  1407. // Add the sub field value back to the $layout but mapped to the field name instead
  1408. // of the key reference.
  1409. $layout[ $sub_field['name'] ] = $sub_value;
  1410. }
  1411. }
  1412. return $value;
  1413. }
  1414. }
  1415. // initialize
  1416. acf_register_field_type( 'acf_field_flexible_content' );
  1417. endif; // class_exists check
  1418. ?>