diff --git a/assets/src/js/_acf-field-select.js b/assets/src/js/_acf-field-select.js index 5d9a23e6..61146a40 100644 --- a/assets/src/js/_acf-field-select.js +++ b/assets/src/js/_acf-field-select.js @@ -37,6 +37,7 @@ multiple: this.get( 'multiple' ), placeholder: this.get( 'placeholder' ), allowNull: this.get( 'allow_null' ), + tags: this.get( 'create_options' ), ajaxAction: ajaxAction, } ); } diff --git a/assets/src/js/_acf-select2.js b/assets/src/js/_acf-select2.js index 620a3acd..0c5f3a3e 100644 --- a/assets/src/js/_acf-select2.js +++ b/assets/src/js/_acf-select2.js @@ -19,6 +19,7 @@ multiple: false, field: false, ajax: false, + tags: false, ajaxAction: '', ajaxData: function ( data ) { return data; @@ -220,7 +221,6 @@ if ( field.get( 'nonce' ) ) { ajaxData.nonce = field.get( 'nonce' ); } - } // callback @@ -324,6 +324,11 @@ data: [], }; + if ( this.get( 'tags' ) ) { + options.tags = true; + options.tokenSeparators = [ ',' ]; + } + // Clear empty templateSelections, templateResults, or dropdownCssClass. if ( ! options.templateSelection ) { delete options.templateSelection; diff --git a/includes/fields/class-acf-field-select.php b/includes/fields/class-acf-field-select.php index a6238ea2..bd9f810b 100644 --- a/includes/fields/class-acf-field-select.php +++ b/includes/fields/class-acf-field-select.php @@ -27,14 +27,16 @@ function initialize() { $this->doc_url = 'https://developer.wordpress.org/secure-custom-fields/features/fields/select/'; $this->tutorial_url = 'https://developer.wordpress.org/secure-custom-fields/features/fields/select/select-tutorial/'; $this->defaults = array( - 'multiple' => 0, - 'allow_null' => 0, - 'choices' => array(), - 'default_value' => '', - 'ui' => 0, - 'ajax' => 0, - 'placeholder' => '', - 'return_format' => 'value', + 'multiple' => 0, + 'allow_null' => 0, + 'choices' => array(), + 'default_value' => '', + 'ui' => 0, + 'ajax' => 0, + 'placeholder' => '', + 'return_format' => 'value', + 'create_options' => 0, + 'save_options' => 0, ); // ajax @@ -204,38 +206,35 @@ public function get_ajax_query( $options = array() ) { /** - * Create the HTML interface for your field + * Creates the HTML interface for the field. * - * @param $field - an array holding all the field's data + * @param array $field An array holding all the field's data. + * @return void * - * @type action * @since ACF 3.6 - * @date 23/01/13 */ function render_field( $field ) { - // convert $value = acf_get_array( $field['value'] ); $choices = acf_get_array( $field['choices'] ); - // placeholder if ( empty( $field['placeholder'] ) ) { $field['placeholder'] = _x( 'Select', 'verb', 'secure-custom-fields' ); } - // add empty value (allows '' to be selected) + // Add empty value (allows '' to be selected). if ( empty( $value ) ) { $value = array( '' ); } - // prepend empty choice + // Prepend empty choice // - only for single selects // - have tried array_merge but this causes keys to re-index if is numeric (post ID's) if ( isset( $field['allow_null'] ) && $field['allow_null'] && isset( $field['multiple'] ) && ! $field['multiple'] ) { $choices = array( '' => "- {$field['placeholder']} -" ) + $choices; } - // clean up choices if using ajax + // Clean up choices if using ajax. if ( isset( $field['ui'] ) && $field['ui'] && isset( $field['ajax'] ) && $field['ajax'] ) { $minimal = array(); foreach ( $value as $key ) { @@ -246,7 +245,6 @@ function render_field( $field ) { $choices = $minimal; } - // vars $select = array( 'id' => acf_maybe_get( $field, 'id', '' ), 'class' => acf_maybe_get( $field, 'class', '' ), @@ -262,7 +260,6 @@ function render_field( $field ) { $select['aria-label'] = $field['aria-label']; } - // multiple if ( isset( $field['multiple'] ) && $field['multiple'] ) { $select['multiple'] = 'multiple'; $select['size'] = 5; @@ -274,6 +271,10 @@ function render_field( $field ) { } } + if ( ! empty( $field['create_options'] ) && $field['ui'] ) { + $select['data-create_options'] = true; + } + // special atts if ( ! empty( $field['readonly'] ) ) { $select['readonly'] = 'readonly'; @@ -308,22 +309,33 @@ function render_field( $field ) { $select['value'] = $value; $select['choices'] = $choices; + if ( ! empty( $field['create_options'] ) && $field['ui'] && is_array( $field['value'] ) ) { + foreach ( $field['value'] as $value ) { + // Already exists in choices. + if ( isset( $field['choices'][ $value ] ) ) { + continue; + } + + $option = esc_attr( $value ); + + $select['choices'][ $option ] = $option; + } + } + // render acf_select_input( $select ); } /** - * 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 + * Renders the field settings used in the "General" tab. * - * @type action - * @since ACF 3.6 - * @date 23/01/13 + * @since ACF 3.6 * - * @param $field - an array holding all the field's data + * @param array $field An array holding all the field's data. + * @return void */ - function render_field_settings( $field ) { + public function render_field_settings( $field ) { // encode choices (convert from array) $field['choices'] = acf_encode_choices( $field['choices'] ); @@ -388,7 +400,7 @@ function render_field_settings( $field ) { * @param array $field The field settings array. * @return void */ - function render_field_validation_settings( $field ) { + public function render_field_validation_settings( $field ) { acf_render_field_setting( $field, array( @@ -409,7 +421,7 @@ function render_field_validation_settings( $field ) { * @param array $field The field settings array. * @return void */ - function render_field_presentation_settings( $field ) { + public function render_field_presentation_settings( $field ) { acf_render_field_setting( $field, array( @@ -436,21 +448,70 @@ function render_field_presentation_settings( $field ) { ), ) ); + + acf_render_field_setting( + $field, + array( + 'label' => __( 'Create Options', 'secure-custom-fields' ), + 'instructions' => __( 'Allow content editors to create new options by typing in the Select input. Multiple options can be created from a comma separated string.', 'secure-custom-fields' ), + 'name' => 'create_options', + 'type' => 'true_false', + 'ui' => 1, + 'conditions' => array( + array( + 'field' => 'ui', + 'operator' => '==', + 'value' => 1, + ), + array( + 'field' => 'multiple', + 'operator' => '==', + 'value' => 1, + ), + ), + ) + ); + + acf_render_field_setting( + $field, + array( + 'label' => __( 'Save Options', 'secure-custom-fields' ), + 'instructions' => __( 'Save created options back to the "Choices" setting in the field definition.', 'secure-custom-fields' ), + 'name' => 'save_options', + 'type' => 'true_false', + 'ui' => 1, + 'conditions' => array( + array( + 'field' => 'ui', + 'operator' => '==', + 'value' => 1, + ), + array( + 'field' => 'multiple', + 'operator' => '==', + 'value' => 1, + ), + array( + 'field' => 'create_options', + 'operator' => '==', + 'value' => 1, + ), + ), + ) + ); } /** - * This filter is applied to the $value after it is loaded from the db + * Filters the $value after it is loaded from the db. * - * @type filter - * @since ACF 3.6 - * @date 23/01/13 + * @since ACF 3.6 * - * @param $value (mixed) the value found in the database - * @param $post_id (mixed) the post_id from which the value was loaded - * @param $field (array) the field array holding all the field options - * @return $value + * @param mixed $value The value found in the database. + * @param integer|string $post_id The post_id from which the value was loaded. + * @param array $field The field array holding all the field options. + * @return mixed */ - function load_value( $value, $post_id, $field ) { + public function load_value( $value, $post_id, $field ) { // Return an array when field is set for multiple. if ( $field['multiple'] ) { @@ -466,21 +527,16 @@ function load_value( $value, $post_id, $field ) { /** + * Filters the $field before it is saved to the database. * - * This filter is applied to the $field before it is saved to the database - * - * @type filter - * @since ACF 3.6 - * @date 23/01/13 - * - * @param $field - the field array holding all the field options - * @param $post_id - the field group ID (post_type = acf) + * @since ACF 3.6 * - * @return $field - the modified field + * @param array $field The field array holding all the field options. + * @return array */ - function update_field( $field ) { + public function update_field( $field ) { - // decode choices (convert to array) + // Decode choices (convert to array). $field['choices'] = acf_decode_choices( $field['choices'] ); $field['default_value'] = acf_decode_choices( $field['default_value'], true ); @@ -489,25 +545,22 @@ function update_field( $field ) { $field['default_value'] = acf_unarray( $field['default_value'] ); } - // return return $field; } /** - * This filter is applied to the $value before it is updated in the db + * Filters the $value before it is updated in the db. * - * @type filter - * @since ACF 3.6 - * @date 23/01/13 + * @since ACF 3.6 * - * @param $value - the value which will be saved in the database - * @param $post_id - the post_id of which the value will be saved - * @param $field - the field array holding all the field options + * @param mixed $value The value which will be saved in the database. + * @param integer|string $post_id The post_id of which the value will be saved. + * @param array $field The field array holding all the field options. * - * @return $value - the modified value + * @return mixed */ - function update_value( $value, $post_id, $field ) { + public function update_value( $value, $post_id, $field ) { // Bail early if no value. if ( empty( $value ) ) { @@ -520,45 +573,64 @@ function update_value( $value, $post_id, $field ) { $value = array_map( 'strval', $value ); } - // return + // Save custom options back to the field definition if configured. + if ( ! empty( $field['save_options'] ) && is_array( $value ) ) { + // Get the raw field, using the ID if present or the key otherwise (i.e. when using JSON). + $selector = $field['ID'] ? $field['ID'] : $field['key']; + $field = acf_get_field( $selector ); + + // Bail if we don't have a valid field or field ID (JSON only). + if ( empty( $field['ID'] ) ) { + return $value; + } + + foreach ( $value as $v ) { + // Ignore if the option already exists. + if ( isset( $field['choices'][ $v ] ) ) { + continue; + } + + // Unslash (fixes serialize single quote issue) and sanitize. + $v = wp_unslash( $v ); + $v = sanitize_text_field( $v ); + + // Append to the field choices. + $field['choices'][ $v ] = $v; + } + + acf_update_field( $field ); + } return $value; } /** - * This function will translate field settings + * Translates the field settings. * - * @type function - * @date 8/03/2016 - * @since ACF 5.3.2 + * @since ACF 5.3.2 * - * @param $field (array) - * @return $field + * @param array $field The main field array. + * @return array */ - function translate_field( $field ) { + public function translate_field( $field ) { - // translate $field['choices'] = acf_translate( $field['choices'] ); - - // return return $field; } /** - * This filter is applied to the $value after it is loaded from the db and before it is returned to the template + * Filters the $value after it is loaded from the db, and before it is returned to the template. * - * @type filter - * @since ACF 3.6 - * @date 23/01/13 + * @since ACF 3.6 * - * @param $value (mixed) the value which was loaded from the database - * @param $post_id (mixed) the post_id from which the value was loaded - * @param $field (array) the field array holding all the field options + * @param mixed $value The value which was loaded from the database. + * @param integer|string $post_id The post_id from which the value was loaded. + * @param array $field The field array holding all the field options. * - * @return $value (mixed) the modified value + * @return mixed */ - function format_value( $value, $post_id, $field ) { + public function format_value( $value, $post_id, $field ) { if ( is_array( $value ) ) { foreach ( $value as $i => $val ) { $value[ $i ] = $this->format_value_single( $val, $post_id, $field ); @@ -569,35 +641,33 @@ function format_value( $value, $post_id, $field ) { return $value; } - - function format_value_single( $value, $post_id, $field ) { - - // bail early if is empty + /** + * Formats the value when the select is not a multi-select. + * + * @since 3.6 + * + * @param mixed $value The value to format. + * @param integer|string $post_id The post_id from which the value was loaded. + * @param array $field The field array holding all the field options. + * @return mixed + */ + public function format_value_single( $value, $post_id, $field ) { + // Bail early if is empty. if ( acf_is_empty( $value ) ) { return $value; } - // vars $label = acf_maybe_get( $field['choices'], $value, $value ); - // value - $return_format = isset( $field['return_format'] ) ? $field['return_format'] : 'value'; - - if ( 'value' === $return_format ) { - // do nothing - return $value; - } elseif ( 'label' === $return_format ) { - // label + if ( 'label' === $field['return_format'] ) { $value = $label; - } elseif ( 'array' === $return_format ) { - // array + } elseif ( 'array' === $field['return_format'] ) { $value = array( 'value' => $value, 'label' => $label, ); } - // return return $value; }