12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103 |
- <?php
- if ( ! class_exists( 'acf_field_repeater' ) ) :
- class acf_field_repeater extends acf_field {
- /**
- * If we're currently rendering fields.
- *
- * @var bool
- */
- public $is_rendering = false;
- /**
- * The post/page ID that we're rendering for.
- *
- * @var mixed
- */
- public $post_id = false;
- /**
- * This function will set up the field type data
- *
- * @date 5/03/2014
- * @since 5.0.0
- */
- public function initialize() {
- $this->name = 'repeater';
- $this->label = __( 'Repeater', 'acf' );
- $this->category = 'layout';
- $this->defaults = array(
- 'sub_fields' => array(),
- 'min' => 0,
- 'max' => 0,
- 'rows_per_page' => 20,
- 'layout' => 'table',
- 'button_label' => '',
- 'collapsed' => '',
- );
- // field filters
- $this->add_field_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_field_for_export' ) );
- $this->add_field_filter( 'acf/prepare_field_for_import', array( $this, 'prepare_field_for_import' ) );
- // filters
- $this->add_filter( 'acf/validate_field', array( $this, 'validate_any_field' ) );
- $this->add_filter( 'acf/pre_render_fields', array( $this, 'pre_render_fields' ), 10, 2 );
- add_action( 'wp_ajax_acf/ajax/query_repeater', array( $this, 'ajax_get_rows' ) );
- }
- /**
- * Localizes text for the repeater field.
- *
- * @date 16/12/2015
- * @since 5.3.2
- */
- public function input_admin_enqueue_scripts() {
- acf_localize_text(
- array(
- 'Minimum rows reached ({min} rows)' => __( 'Minimum rows reached ({min} rows)', 'acf' ),
- 'Maximum rows reached ({max} rows)' => __( 'Maximum rows reached ({max} rows)', 'acf' ),
- 'Error loading page' => __( 'Error loading page', 'acf' ),
- 'Order will be assigned upon save' => __( 'Order will be assigned upon save', 'acf' ),
- )
- );
- }
- /**
- * Filters the field array after it's loaded from the database.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field The field array holding all the field options.
- * @return array
- */
- public function load_field( $field ) {
- $field['min'] = (int) $field['min'];
- $field['max'] = (int) $field['max'];
- $sub_fields = acf_get_fields( $field );
- if ( $sub_fields ) {
- $field['sub_fields'] = array_map(
- function( $sub_field ) use ( $field ) {
- $sub_field['parent_repeater'] = $field['key'];
- return $sub_field;
- },
- $sub_fields
- );
- }
- if ( empty( $field['rows_per_page'] ) || (int) $field['rows_per_page'] < 1 ) {
- $field['rows_per_page'] = 20;
- }
- if ( '' === $field['button_label'] ) {
- $field['button_label'] = __( 'Add Row', 'acf' );
- }
- return $field;
- }
- /**
- * Runs on the "acf/pre_render_fields" filter. Used to signify
- * that we're currently rendering a repeater field.
- *
- * @since 6.0.0
- *
- * @param array $fields The main field array.
- * @param mixed $post_id The post ID for the field being rendered.
- * @return array
- */
- public function pre_render_fields( $fields, $post_id = false ) {
- if ( is_admin() ) {
- $this->is_rendering = true;
- $this->post_id = $post_id;
- }
- return $fields;
- }
- /**
- * Create the HTML interface for your field
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field An array holding all the field's data.
- */
- public function render_field( $field ) {
- $field['orig_name'] = $this->get_field_name_from_input_name( $field['name'] );
- $field['total_rows'] = (int) acf_get_metadata( $this->post_id, $field['orig_name'] );
- $table = new ACF_Repeater_Table( $field );
- $table->render();
- }
- /**
- * Create extra options for your field. This is rendered when editing a field.
- * The value of $field['name'] can be used (like bellow) to save extra data to the $field
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field An array holding all the field's data.
- */
- function render_field_settings( $field ) {
- $args = array(
- 'fields' => $field['sub_fields'],
- 'parent' => $field['ID'],
- 'is_subfield' => true,
- );
- $supports_pagination = ( empty( $field['parent_repeater'] ) && empty( $field['parent_layout'] ) );
- ?>
- <div class="acf-field acf-field-setting-sub_fields" data-setting="repeater" data-name="sub_fields">
- <div class="acf-label">
- <label><?php _e( 'Sub Fields', 'acf' ); ?></label>
- <p class="description"></p>
- </div>
- <div class="acf-input acf-input-sub">
- <?php
- acf_get_view( 'field-group-fields', $args );
- ?>
- </div>
- </div>
- <?php
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Layout', 'acf' ),
- 'instructions' => '',
- 'class' => 'acf-repeater-layout',
- 'type' => 'radio',
- 'name' => 'layout',
- 'layout' => 'horizontal',
- 'choices' => array(
- 'table' => __( 'Table', 'acf' ),
- 'block' => __( 'Block', 'acf' ),
- 'row' => __( 'Row', 'acf' ),
- ),
- )
- );
- if ( $supports_pagination ) {
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Pagination', 'acf' ),
- 'instructions' => __( 'Useful for fields with a large number of rows.', 'acf' ),
- 'class' => 'acf-repeater-pagination',
- 'type' => 'true_false',
- 'name' => 'pagination',
- 'ui' => 1,
- )
- );
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Rows Per Page', 'acf' ),
- 'instructions' => __( 'Set the number of rows to be displayed on a page.', 'acf' ),
- 'class' => 'acf-repeater-pagination-num-rows',
- 'type' => 'number',
- 'name' => 'rows_per_page',
- 'placeholder' => 20,
- 'ui' => 1,
- 'min' => 1,
- 'conditions' => array(
- 'field' => 'pagination',
- 'operator' => '==',
- 'value' => 1,
- ),
- )
- );
- }
- }
- /**
- * Renders the field settings used in the "Validation" tab.
- *
- * @since 6.0
- *
- * @param array $field The field settings array.
- * @return void
- */
- function render_field_validation_settings( $field ) {
- $field['min'] = empty( $field['min'] ) ? '' : $field['min'];
- $field['max'] = empty( $field['max'] ) ? '' : $field['max'];
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Minimum Rows', 'acf' ),
- 'instructions' => '',
- 'type' => 'number',
- 'name' => 'min',
- 'placeholder' => '0',
- )
- );
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Maximum Rows', 'acf' ),
- 'instructions' => '',
- 'type' => 'number',
- 'name' => 'max',
- 'placeholder' => '0',
- )
- );
- }
- /**
- * Renders the field settings used in the "Presentation" tab.
- *
- * @since 6.0
- *
- * @param array $field The field settings array.
- * @return void
- */
- function render_field_presentation_settings( $field ) {
- $choices = array();
- if ( $field['collapsed'] ) {
- $sub_field = acf_get_field( $field['collapsed'] );
- if ( $sub_field ) {
- $choices[ $sub_field['key'] ] = $sub_field['label'];
- }
- }
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Collapsed', 'acf' ),
- 'instructions' => __( 'Select a sub field to show when row is collapsed', 'acf' ),
- 'type' => 'select',
- 'name' => 'collapsed',
- 'allow_null' => 1,
- 'choices' => $choices,
- )
- );
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Button Label', 'acf' ),
- 'instructions' => '',
- 'type' => 'text',
- 'name' => 'button_label',
- 'placeholder' => __( 'Add Row', 'acf' ),
- )
- );
- }
- /**
- * Filters the field $value after it is loaded from the database.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param mixed $value The value found in the database.
- * @param mixed $post_id The $post_id from which the value was loaded.
- * @param array $field The field array holding all the field options.
- * @return array $value
- */
- public function load_value( $value, $post_id, $field ) {
- // Bail early if we don't have enough info to load the field.
- if ( empty( $value ) || ! is_numeric( $value ) || empty( $field['sub_fields'] ) ) {
- return false;
- }
- $value = (int) $value;
- $rows = array();
- $offset = 0;
- // Ensure pagination is disabled inside blocks.
- if ( acf_get_data( 'acf_inside_rest_call' ) || doing_action( 'wp_ajax_acf/ajax/fetch-block' ) ) {
- $field['pagination'] = false;
- }
- if ( ! empty( $field['pagination'] ) && $this->is_rendering ) {
- $rows_per_page = isset( $field['rows_per_page'] ) ? (int) $field['rows_per_page'] : 20;
- if ( $rows_per_page < 1 ) {
- $rows_per_page = 20;
- }
- if ( doing_action( 'wp_ajax_acf/ajax/query_repeater' ) ) {
- $offset = ( intval( $_POST['paged'] ) - 1 ) * $rows_per_page; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
- $value = min( $value, $offset + $rows_per_page );
- } else {
- $value = min( $value, $rows_per_page );
- }
- }
- for ( $i = $offset; $i < $value; $i++ ) {
- $rows[ $i ] = array();
- foreach ( array_keys( $field['sub_fields'] ) as $j ) {
- $sub_field = $field['sub_fields'][ $j ];
- // Bail early if no name (tab field).
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // Update $sub_field name and value.
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- $sub_value = acf_get_value( $post_id, $sub_field );
- $rows[ $i ][ $sub_field['key'] ] = $sub_value;
- }
- }
- return $rows;
- }
- /**
- * This filter is applied to the $value after it is loaded from the db,
- * and before it is returned to the template.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param mixed $value The value which was loaded from the database.
- * @param mixed $post_id The $post_id from which the value was loaded.
- * @param array $field The field array holding all the field options.
- *
- * @return array $value The modified value.
- */
- function format_value( $value, $post_id, $field ) {
- // bail early if no value
- if ( empty( $value ) ) {
- return false;
- }
- // bail early if not array
- if ( ! is_array( $value ) ) {
- return false;
- }
- // bail early if no sub fields
- if ( empty( $field['sub_fields'] ) ) {
- return false;
- }
- // loop over rows
- foreach ( array_keys( $value ) as $i ) {
- // loop through sub fields
- foreach ( array_keys( $field['sub_fields'] ) as $j ) {
- // get sub field
- $sub_field = $field['sub_fields'][ $j ];
- // bail early if no name (tab)
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // extract value
- $sub_value = acf_extract_var( $value[ $i ], $sub_field['key'] );
- // update $sub_field name
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // format value
- $sub_value = acf_format_value( $sub_value, $post_id, $sub_field );
- // append to $row
- $value[ $i ][ $sub_field['_name'] ] = $sub_value;
- }
- }
- return $value;
- }
- /**
- * Validates values for the repeater field
- *
- * @date 11/02/2014
- * @since 5.0.0
- *
- * @param bool $valid If the field is valid.
- * @param mixed $value The value to validate.
- * @param array $field The main field array.
- * @param string $input The input element's name attribute.
- *
- * @return bool
- */
- function validate_value( $valid, $value, $field, $input ) {
- // vars
- $count = 0;
- // check if is value (may be empty string)
- if ( is_array( $value ) ) {
- // remove acfcloneindex
- if ( isset( $value['acfcloneindex'] ) ) {
- unset( $value['acfcloneindex'] );
- }
- // count
- $count = count( $value );
- }
- // validate required
- if ( $field['required'] && ! $count ) {
- $valid = false;
- }
- // min
- $min = (int) $field['min'];
- if ( empty( $field['pagination'] ) && $min && $count < $min ) {
- // create error
- $error = __( 'Minimum rows reached ({min} rows)', 'acf' );
- $error = str_replace( '{min}', $min, $error );
- // return
- return $error;
- }
- // validate value
- if ( $count ) {
- // bail early if no sub fields
- if ( ! $field['sub_fields'] ) {
- return $valid;
- }
- // loop rows
- foreach ( $value as $i => $row ) {
- // Skip rows that were deleted in paginated repeaters.
- if ( false !== strpos( $i, '_deleted' ) ) {
- continue;
- }
- // loop sub fields
- foreach ( $field['sub_fields'] as $sub_field ) {
- // vars
- $k = $sub_field['key'];
- // test sub field exists
- if ( ! isset( $row[ $k ] ) ) {
- continue;
- }
- // validate
- acf_validate_value( $row[ $k ], $sub_field, "{$input}[{$i}][{$k}]" );
- }
- // end loop sub fields
- }
- // end loop rows
- }
- return $valid;
- }
- /**
- * This function will update a value row.
- *
- * @date 15/2/17
- * @since 5.5.8
- *
- * @param array $row
- * @param int $i
- * @param array $field
- * @param mixed $post_id
- * @return boolean
- */
- function update_row( $row, $i, $field, $post_id ) {
- // bail early if no layout reference
- if ( ! is_array( $row ) ) {
- return false;
- }
- // bail early if no layout
- if ( empty( $field['sub_fields'] ) ) {
- return false;
- }
- foreach ( $field['sub_fields'] as $sub_field ) {
- $value = null;
- if ( array_key_exists( $sub_field['key'], $row ) ) {
- $value = $row[ $sub_field['key'] ];
- } elseif ( array_key_exists( $sub_field['name'], $row ) ) {
- $value = $row[ $sub_field['name'] ];
- } else {
- // Value does not exist.
- continue;
- }
- // modify name for save
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // update field
- acf_update_value( $value, $post_id, $sub_field );
- }
- return true;
- }
- /**
- * This function will delete a value row.
- *
- * @date 15/2/17
- * @since 5.5.8
- *
- * @param int $i
- * @param array $field
- * @param mixed $post_id
- * @return boolean
- */
- function delete_row( $i, $field, $post_id ) {
- // bail early if no sub fields
- if ( empty( $field['sub_fields'] ) ) {
- return false;
- }
- foreach ( $field['sub_fields'] as $sub_field ) {
- // modify name for delete
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // delete value
- acf_delete_value( $post_id, $sub_field );
- }
- return true;
- }
- /**
- * Filters the $value before it is updated in the database.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param mixed $value The value which will be saved in the database.
- * @param array $field The field array holding all the field options.
- * @param mixed $post_id The $post_id of which the value will be saved.
- *
- * @return mixed $value
- */
- function update_value( $value, $post_id, $field ) {
- // Bail early if no sub fields.
- if ( empty( $field['sub_fields'] ) ) {
- return $value;
- }
- if ( ! is_array( $value ) ) {
- $value = array();
- }
- if ( isset( $value['acfcloneindex'] ) ) {
- unset( $value['acfcloneindex'] );
- }
- $new_value = 0;
- $old_value = (int) acf_get_metadata( $post_id, $field['name'] );
- if ( ! empty( $field['pagination'] ) && did_action( 'acf/save_post' ) && ! isset( $_POST['_acf_form'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Value not used.
- $old_rows = acf_get_value( $post_id, $field );
- $old_rows = is_array( $old_rows ) ? $old_rows : array();
- $edited_rows = array();
- $deleted_rows = array();
- $reordered_rows = array();
- $new_rows = array();
- // Categorize the submitted values, so we know what to do with them.
- foreach ( $value as $key => $row ) {
- if ( ! is_array( $row ) ) {
- continue;
- }
- // Check if this is a new row.
- if ( false === strpos( $key, 'row' ) ) {
- unset( $row['acf_changed'] );
- // Check if this new row was inserted before an existing row.
- $reordered_row_num = isset( $row['acf_reordered'] ) ? (int) $row['acf_reordered'] : false;
- if ( false !== $reordered_row_num && $reordered_row_num <= $old_value ) {
- $reordered_rows[ $key ] = $reordered_row_num;
- } else {
- $new_rows[ $key ] = $row;
- }
- continue;
- }
- $row_num = (int) str_replace( 'row-', '', $key );
- if ( isset( $row['acf_deleted'] ) ) {
- $deleted_rows[] = $row_num;
- } elseif ( isset( $row['acf_reordered'] ) ) {
- $reordered_rows[ $row_num ] = (int) $row['acf_reordered'];
- } else {
- unset( $row['acf_changed'] );
- $edited_rows[ $row_num ] = $row;
- }
- }
- // Process any row deletions first, but don't remove their keys yet.
- foreach ( $deleted_rows as $deleted_row ) {
- $this->delete_row( $deleted_row, $field, $post_id );
- $old_rows[ $deleted_row ] = 'acf_deleted';
- }
- // Update any existing rows that were edited.
- foreach ( $edited_rows as $key => $row ) {
- if ( array_key_exists( $key, $old_rows ) ) {
- $old_rows[ $key ] = $row;
- }
- }
- $rows_to_move = array();
- $new_rows_to_move = array();
- foreach ( $reordered_rows as $old_order => $new_order ) {
- if ( is_int( $old_order ) ) {
- $rows_to_move[ $new_order ][] = $value[ 'row-' . $old_order ];
- unset( $old_rows[ $old_order ] );
- } else {
- $new_rows_to_move[ $new_order ][] = $value[ $old_order ];
- }
- }
- // Iterate over existing moved rows first.
- if ( ! empty( $rows_to_move ) ) {
- ksort( $rows_to_move );
- foreach ( $rows_to_move as $key => $values ) {
- array_splice( $old_rows, $key, 0, $values );
- }
- }
- // Iterate over inserted/duplicated rows in reverse order, so they're inserted into the correct spot.
- if ( ! empty( $new_rows_to_move ) ) {
- krsort( $new_rows_to_move );
- foreach ( $new_rows_to_move as $key => $values ) {
- array_splice( $old_rows, $key, 0, $values );
- }
- }
- // Append any new rows.
- foreach ( $new_rows as $new_row ) {
- $old_rows[] = $new_row;
- }
- // Update the rows in the database.
- $new_row_num = 0;
- foreach ( $old_rows as $key => $row ) {
- if ( 'acf_deleted' === $row ) {
- unset( $old_rows[ $key ] );
- continue;
- }
- $this->update_row( $row, $new_row_num, $field, $post_id );
- $new_row_num++;
- }
- // Calculate the total number of rows that will be saved after this update.
- $new_value = count( $old_rows );
- } else {
- $i = -1;
- foreach ( $value as $row ) {
- $i++;
- // Bail early if no row.
- if ( ! is_array( $row ) ) {
- continue;
- }
- $this->update_row( $row, $i, $field, $post_id );
- $new_value++;
- }
- }
- // Remove old rows.
- if ( $old_value > $new_value ) {
- for ( $i = $new_value; $i < $old_value; $i++ ) {
- $this->delete_row( $i, $field, $post_id );
- }
- }
- // Save empty string for empty value.
- if ( empty( $new_value ) ) {
- $new_value = '';
- }
- return $new_value;
- }
- /**
- * Deletes any subfields after the field has been deleted.
- *
- * @date 4/04/2014
- * @since 5.0.0
- *
- * @param array $field The main field array.
- * @return void
- */
- function delete_field( $field ) {
- // Bail early if no subfields.
- if ( empty( $field['sub_fields'] ) ) {
- return;
- }
- // Delete any subfields.
- foreach ( $field['sub_fields'] as $sub_field ) {
- acf_delete_field( $sub_field['ID'] );
- }
- }
- /**
- * Deletes a value from the database.
- *
- * @date 1/07/2015
- * @since 5.2.3
- *
- * @param int $post_id The post ID to delete the value from.
- * @param string $key The meta name/key (unused).
- * @param array $field The main field array.
- * @return void
- */
- function delete_value( $post_id, $key, $field ) {
- // Get the old value from the database.
- $old_value = (int) acf_get_metadata( $post_id, $field['name'] );
- // Bail early if no rows or no subfields.
- if ( ! $old_value || empty( $field['sub_fields'] ) ) {
- return;
- }
- for ( $i = 0; $i < $old_value; $i++ ) {
- $this->delete_row( $i, $field, $post_id );
- }
- }
- /**
- * This filter is applied to the $field before it is saved to the database.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field The field array holding all the field options.
- *
- * @return array
- */
- function update_field( $field ) {
- unset( $field['sub_fields'] );
- return $field;
- }
- /**
- * This filter is applied to the $field before it is duplicated and saved to the database.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field The field array holding all the field options.
- * @return array
- */
- function duplicate_field( $field ) {
- // get sub fields
- $sub_fields = acf_extract_var( $field, 'sub_fields' );
- // save field to get ID
- $field = acf_update_field( $field );
- // duplicate sub fields
- acf_duplicate_fields( $sub_fields, $field['ID'] );
- return $field;
- }
- /**
- * This function will translate field settings.
- *
- * @date 8/03/2016
- * @since 5.3.2
- *
- * @param array $field The main field array.
- * @return array
- */
- function translate_field( $field ) {
- $field['button_label'] = acf_translate( $field['button_label'] );
- return $field;
- }
- /**
- * This function will add compatibility for the 'column_width' setting
- *
- * @date 30/1/17
- * @since 5.5.6
- *
- * @param array $field The main field array.
- * @return array
- */
- function validate_any_field( $field ) {
- // width has changed
- if ( isset( $field['column_width'] ) ) {
- $field['wrapper']['width'] = acf_extract_var( $field, 'column_width' );
- }
- return $field;
- }
- /**
- * Prepares the field for export.
- *
- * @date 11/03/2014
- * @since 5.0.0
- *
- * @param array $field The field settings.
- * @return array
- */
- function prepare_field_for_export( $field ) {
- // Check for subfields.
- if ( ! empty( $field['sub_fields'] ) ) {
- $field['sub_fields'] = acf_prepare_fields_for_export( $field['sub_fields'] );
- }
- return $field;
- }
- /**
- * Returns a flat array of fields containing all subfields ready for import.
- *
- * @date 11/03/2014
- * @since 5.0.0
- *
- * @param array $field The field settings.
- * @return array
- */
- function prepare_field_for_import( $field ) {
- // Check for sub fields.
- if ( ! empty( $field['sub_fields'] ) ) {
- $sub_fields = acf_extract_var( $field, 'sub_fields' );
- // Modify sub fields.
- foreach ( $sub_fields as $i => $sub_field ) {
- $sub_fields[ $i ]['parent'] = $field['key'];
- $sub_fields[ $i ]['menu_order'] = $i;
- }
- // Return array of [field, sub_1, sub_2, ...].
- return array_merge( array( $field ), $sub_fields );
- }
- return $field;
- }
- /**
- * Additional validation for the repeater field when submitted via REST.
- *
- * @param bool $valid
- * @param int $value
- * @param array $field
- *
- * @return bool|WP_Error
- */
- public function validate_rest_value( $valid, $value, $field ) {
- if ( ! is_array( $value ) && is_null( $value ) ) {
- $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
- $data = array(
- 'param' => $param,
- 'value' => $value,
- );
- $error = sprintf( __( '%s must be of type array or null.', 'acf' ), $param );
- return new WP_Error( 'rest_invalid_param', $error, $param );
- }
- return $valid;
- }
- /**
- * Return the schema array for the REST API.
- *
- * @param array $field
- * @return array
- */
- public function get_rest_schema( array $field ) {
- $schema = array(
- 'type' => array( 'array', 'null' ),
- 'required' => ! empty( $field['required'] ),
- 'items' => array(
- 'type' => 'object',
- 'properties' => array(),
- ),
- );
- foreach ( $field['sub_fields'] as $sub_field ) {
- if ( $sub_field_schema = acf_get_field_rest_schema( $sub_field ) ) {
- $schema['items']['properties'][ $sub_field['name'] ] = $sub_field_schema;
- }
- }
- if ( ! empty( $field['min'] ) ) {
- $schema['minItems'] = (int) $field['min'];
- }
- if ( ! empty( $field['max'] ) ) {
- $schema['maxItems'] = (int) $field['max'];
- }
- return $schema;
- }
- /**
- * Apply basic formatting to prepare the value for default REST output.
- *
- * @param mixed $value
- * @param int|string $post_id
- * @param array $field
- * @return array|mixed
- */
- public function format_value_for_rest( $value, $post_id, array $field ) {
- if ( empty( $value ) || ! is_array( $value ) || empty( $field['sub_fields'] ) ) {
- return null;
- }
- // Loop through each row and within that, each sub field to process sub fields individually.
- foreach ( $value as &$row ) {
- foreach ( $field['sub_fields'] as $sub_field ) {
- // Bail early if the field has no name (tab).
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // Extract the sub field 'field_key'=>'value' pair from the $row and format it.
- $sub_value = acf_extract_var( $row, $sub_field['key'] );
- $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
- // Add the sub field value back to the $row but mapped to the field name instead
- // of the key reference.
- $row[ $sub_field['name'] ] = $sub_value;
- }
- }
- return $value;
- }
- /**
- * Takes the provided input name and turns it into a field name that
- * works with repeater fields that are subfields of other fields.
- *
- * @param string $input_name The name attribute used in the repeater.
- *
- * @return string|bool
- */
- public function get_field_name_from_input_name( $input_name ) {
- $parts = array();
- preg_match_all( '/\[([^\]]*)\]/', $input_name, $parts );
- if ( ! isset( $parts[1] ) ) {
- return false;
- }
- $field_keys = $parts[1];
- $name_parts = array();
- foreach ( $field_keys as $field_key ) {
- if ( ! acf_is_field_key( $field_key ) ) {
- if ( 'acfcloneindex' === $field_key ) {
- $name_parts[] = 'acfcloneindex';
- continue;
- }
- $row_num = str_replace( 'row-', '', $field_key );
- if ( is_numeric( $row_num ) ) {
- $name_parts[] = (int) $row_num;
- continue;
- }
- }
- $field = acf_get_field( $field_key );
- if ( $field ) {
- $name_parts[] = $field['name'];
- }
- }
- return implode( '_', $name_parts );
- }
- /**
- * Returns an array of rows used to populate the repeater table over AJAX.
- *
- * @since 6.0.0
- *
- * @return void|WP_Error
- */
- public function ajax_get_rows() {
- if ( ! acf_verify_ajax() ) {
- $error = array( 'error' => __( 'Invalid nonce.', 'acf' ) );
- wp_send_json_error( $error, 401 );
- }
- $args = acf_request_args(
- array(
- 'field_name' => '',
- 'field_key' => '',
- 'post_id' => 0,
- 'rows_per_page' => 0,
- 'refresh' => false,
- )
- );
- if ( '' === $args['field_name'] || '' === $args['field_key'] ) {
- $error = array( 'error' => __( 'Invalid field key or name.', 'acf' ) );
- wp_send_json_error( $error, 404 );
- }
- $field = acf_get_field( $args['field_key'] );
- $post_id = acf_get_valid_post_id( $args['post_id'] );
- $response = array();
- if ( ! $field || ! $post_id ) {
- $error = array( 'error' => __( 'There was an error retrieving the field.', 'acf' ) );
- wp_send_json_error( $error, 404 );
- }
- // Make sure we have a valid field.
- $field = acf_validate_field( $field );
- // Make sure that we only get a subset of the rows.
- $this->is_rendering = true;
- $args['rows_per_page'] = (int) $args['rows_per_page'];
- if ( $args['rows_per_page'] ) {
- $field['rows_per_page'] = $args['rows_per_page'];
- }
- /**
- * We have to swap out the field name with the one sent via JS,
- * as the repeater could be inside a subfield.
- */
- $field['name'] = $args['field_name'];
- $field['value'] = acf_get_value( $post_id, $field );
- $field = acf_prepare_field( $field );
- $repeater_table = new ACF_Repeater_Table( $field );
- $response['rows'] = $repeater_table->rows( true );
- if ( $args['refresh'] ) {
- $response['total_rows'] = (int) acf_get_metadata( $post_id, $args['field_name'] );
- }
- wp_send_json_success( $response );
- }
- }
- // initialize
- acf_register_field_type( 'acf_field_repeater' );
- endif; // class_exists check
|