diff --git a/assets/images/icons/icon-add-alt.svg b/assets/images/icons/icon-add-alt.svg new file mode 100644 index 00000000..461e36bf --- /dev/null +++ b/assets/images/icons/icon-add-alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-duplicate.svg b/assets/images/icons/icon-duplicate.svg new file mode 100644 index 00000000..2ee9555d --- /dev/null +++ b/assets/images/icons/icon-duplicate.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-edit.svg b/assets/images/icons/icon-edit.svg new file mode 100644 index 00000000..e2ee7fc0 --- /dev/null +++ b/assets/images/icons/icon-edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-hidden-alt.svg b/assets/images/icons/icon-hidden-alt.svg new file mode 100644 index 00000000..4de62f9e --- /dev/null +++ b/assets/images/icons/icon-hidden-alt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/icons/icon-more-vertical.svg b/assets/images/icons/icon-more-vertical.svg new file mode 100644 index 00000000..19dea137 --- /dev/null +++ b/assets/images/icons/icon-more-vertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-trash-alt.svg b/assets/images/icons/icon-trash-alt.svg new file mode 100644 index 00000000..64d74fe8 --- /dev/null +++ b/assets/images/icons/icon-trash-alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-visible.svg b/assets/images/icons/icon-visible.svg new file mode 100644 index 00000000..5c117f45 --- /dev/null +++ b/assets/images/icons/icon-visible.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/src/js/_acf-field-date-picker.js b/assets/src/js/_acf-field-date-picker.js index b57de98b..0fe1952e 100644 --- a/assets/src/js/_acf-field-date-picker.js +++ b/assets/src/js/_acf-field-date-picker.js @@ -47,7 +47,32 @@ // add date picker acf.newDatePicker( $inputText, args ); - // action + // Check if default to today is enabled and field is empty + if ( + $inputText.data( 'default-to-today' ) === 1 && + ! $input.val() + ) { + // Get current date + const currentDate = new Date(); + + // Format display date + const displayDate = $.datepicker.formatDate( + args.dateFormat, + currentDate + ); + + // Set the display input value (what user sees) + $inputText.val( `${ displayDate }` ); + + // Format hidden field date (for database storage) + const hiddenDate = $.datepicker.formatDate( + 'yymmdd', + currentDate + ); + + // Set the hidden input value (what gets saved) + $input.val( `${ hiddenDate }` ); + } acf.doAction( 'date_picker_init', $inputText, args, this ); }, diff --git a/assets/src/js/_acf-field-date-time-picker.js b/assets/src/js/_acf-field-date-time-picker.js index d06de6c4..2fba2338 100644 --- a/assets/src/js/_acf-field-date-time-picker.js +++ b/assets/src/js/_acf-field-date-time-picker.js @@ -34,6 +34,43 @@ // add date time picker acf.newDateTimePicker( $inputText, args ); + // Check if default to today is enabled and field is empty + if ( + $inputText.data( 'default-to-today' ) === 1 && + ! $input.val() + ) { + // Get current date + const currentDate = new Date(); + + // Format display date and time + const displayDate = $.datepicker.formatDate( + args.dateFormat, + currentDate + ); + const displayTime = $.datepicker.formatTime( args.timeFormat, { + hour: currentDate.getHours(), + minute: currentDate.getMinutes(), + second: currentDate.getSeconds(), + } ); + + // Set the display input value (what user sees) + $inputText.val( `${ displayDate } ${ displayTime }` ); + + // Format hidden field date and time (for database storage) + const hiddenDate = $.datepicker.formatDate( + 'yy-mm-dd', + currentDate + ); + const hiddenTime = $.datepicker.formatTime( 'hh:mm:ss', { + hour: currentDate.getHours(), + minute: currentDate.getMinutes(), + second: currentDate.getSeconds(), + } ); + + // Set the hidden input value (what gets saved) + $input.val( `${ hiddenDate } ${ hiddenTime }` ); + } + // action acf.doAction( 'date_time_picker_init', $inputText, args, this ); }, diff --git a/assets/src/js/_acf-field-icon-picker.js b/assets/src/js/_acf-field-icon-picker.js index ecf52aa9..9213efa6 100644 --- a/assets/src/js/_acf-field-icon-picker.js +++ b/assets/src/js/_acf-field-icon-picker.js @@ -58,7 +58,7 @@ // Initialize the state of the icon picker. let typeAndValue = { type: this.$typeInput().val(), - value: this.$valueInput().val() + value: this.$valueInput().val(), }; // Store the type and value object. @@ -135,7 +135,7 @@ initializeIconLists( typeAndValue ) { const self = this; - this.$( '.acf-icon-list' ).each( function( i ) { + this.$( '.acf-icon-list' ).each( function ( i ) { const tabName = $( this ).data( 'parent-tab' ); const icons = self.getIconsList( tabName ) || []; self.set( tabName, icons ); @@ -143,7 +143,11 @@ if ( typeAndValue.type === tabName ) { // Select the correct icon. - self.selectIcon( $( this ), typeAndValue.value, false ).then( () => { + self.selectIcon( + $( this ), + typeAndValue.value, + false + ).then( () => { // Scroll to the selected icon. self.scrollToSelectedIcon(); } ); @@ -152,13 +156,9 @@ }, alignIconListTabsToCurrentValue( typeAndValue ) { - const icons = this.$( '.acf-icon-list' ).filter( - function () { - return ( - $( this ).data( 'parent-tab' ) !== typeAndValue.type - ); - } - ); + const icons = this.$( '.acf-icon-list' ).filter( function () { + return $( this ).data( 'parent-tab' ) !== typeAndValue.type; + } ); const self = this; icons.each( function () { self.unselectIcon( $( this ) ); @@ -179,12 +179,8 @@ icon.key ) } acf-icon-picker-list-icon" role="radio" data-icon="${ acf.strEscape( icon.key - ) }" style="${ style }" title="${ acf.strEscape( - icon.label - ) }"> - + ) }" style="${ style }" title="${ acf.strEscape( icon.label ) }"> + ' + this.get( 'text' ) + '

' ); + this.html( '

' + acf.strEscape( this.get( 'text' ) ) + '

' ); // close if ( this.get( 'dismiss' ) ) { - this.$el.append( '' ); + this.$el.append( + '' + ); this.$el.addClass( '-dismiss' ); } @@ -144,14 +146,29 @@ $( this ).remove(); } else { $( this ).show(); - $( this ).on( 'click', '.notice-dismiss', function ( e ) { - dismissed = acf.getPreference( 'dismissed-notices' ); - if ( ! dismissed || typeof dismissed != 'object' ) { - dismissed = []; + $( this ).on( + 'click', + '.notice-dismiss', + function ( e ) { + dismissed = + acf.getPreference( 'dismissed-notices' ); + if ( + ! dismissed || + typeof dismissed != 'object' + ) { + dismissed = []; + } + dismissed.push( + $( this ) + .closest( '.acf-admin-notice' ) + .data( 'persist-id' ) + ); + acf.setPreference( + 'dismissed-notices', + dismissed + ); } - dismissed.push( $( this ).closest( '.acf-admin-notice' ).data( 'persist-id' ) ); - acf.setPreference( 'dismissed-notices', dismissed ); - } ); + ); } } } ); diff --git a/assets/src/js/_acf-popup.js b/assets/src/js/_acf-popup.js index 5fcf42d2..685cdd87 100644 --- a/assets/src/js/_acf-popup.js +++ b/assets/src/js/_acf-popup.js @@ -7,12 +7,13 @@ height: 0, loading: false, openedBy: null, + confirmRemove: false, }, events: { 'click [data-event="close"]': 'onClickClose', 'click .acf-close-popup': 'onClickClose', - 'keydown': 'onPressEscapeClose', + keydown: 'onPressEscapeClose', }, setup: function ( props ) { @@ -31,7 +32,9 @@ return [ ' - render(); } /** @@ -696,9 +530,8 @@ public function render_field_presentation_settings( $field ) { /** - * This filter is applied to the $value after it is loaded from the db + * Filters the $value after it is loaded from the database. * - * @type filter * @since ACF 3.6 * * @param mixed $value The value found in the database. @@ -712,20 +545,20 @@ public function load_value( $value, $post_id, $field ) { return $value; } - // value must be an array - $value = acf_get_array( $value ); - - // vars - $rows = array(); - - // sort layouts into names - $layouts = array(); + $value = acf_get_array( $value ); + $disabled_layouts = $this->get_disabled_layouts( $post_id, $field ); + $rows = array(); + $layouts = array(); foreach ( $field['layouts'] as $k => $layout ) { $layouts[ $layout['name'] ] = $layout['sub_fields']; } // loop through rows foreach ( $value as $i => $l ) { + // If the layout is disabled, prevent it from showing up on the frontend. + if ( $this->should_disable_layout( $i, $disabled_layouts ) ) { + continue; + } // append to $values $rows[ $i ] = array(); @@ -764,6 +597,48 @@ public function load_value( $value, $post_id, $field ) { return $rows; } + /** + * Checks if a layout should be disabled based on the provided index and disabled layouts. + * + * @since ACF 6.5 + * + * @param integer|string $layout_index The index of the layout to check. + * @param array $disabled_layouts The array of disabled layout indices. + * @return boolean + */ + private function should_disable_layout( $layout_index, $disabled_layouts = array() ): bool { + // No disabled layouts provided, so no need to disable. + if ( ! is_array( $disabled_layouts ) || empty( $disabled_layouts ) ) { + return false; + } + + // The layout is not in the disabled list, so no need to disable. + if ( ! in_array( $layout_index, $disabled_layouts, true ) ) { + return false; + } + + if ( is_admin() ) { + $args = acf_request_args( + array( + 'action' => '', + 'query' => '', + ) + ); + + // If this is a block preview, disable the layout. + if ( ( 'acf/ajax/fetch-block' === $args['action'] && ! empty( $args['query']['preview'] ) ) || + acf_get_data( 'acf_doing_block_preview' ) ) { + return true; + } + + // Editing a layout in the admin, so don't disable it. + return false; + } + + // The layout has been disabled, and we're on the frontend. + return true; + } + /** * This filter is applied to the $value after it is loaded from the db and before it is returned to the template @@ -1003,6 +878,86 @@ public function get_layout( $name, $field ) { return false; } + /** + * Retrieves layout meta for the Flexible Content field saved to the provided post. + * + * @since ACF 6.5 + * + * @param integer|string $post_id The ID of the post being edited. + * @param array $field The Flexible Content field array. + * @return array + */ + public function get_layout_meta( $post_id, $field ) { + $field_name = $field['name']; + + // Enables compatibility with nested Flexible Content fields during render. + if ( ! empty( $field['_prepare'] ) ) { + $field_name = acf_get_field_type( 'repeater' )->get_field_name_from_input_name( $field_name ); + } + + // Bail early if we don't have a field name to check. + if ( empty( $field_name ) ) { + return array(); + } + + // Return the cached meta if we have it. + if ( ! empty( $this->layout_meta[ $field_name ] ) ) { + return $this->layout_meta[ $field_name ]; + } + + $layout_meta = acf_get_metadata_by_field( + $post_id, + array( + 'name' => '_' . $field_name . '_layout_meta', + ) + ); + + if ( empty( $layout_meta ) || ! is_array( $layout_meta ) ) { + return array(); + } + + $this->layout_meta[ $field_name ] = $layout_meta; + + return $this->layout_meta[ $field_name ]; + } + + /** + * Returns an array of layouts that have been disabled for the current field. + * + * @since ACF 6.5 + * + * @param integer|string $post_id The ID of the post being edited. + * @param array $field The Flexible Content field array. + * @return array + */ + public function get_disabled_layouts( $post_id, $field ): array { + $layout_meta = $this->get_layout_meta( $post_id, $field ); + + if ( empty( $layout_meta['disabled'] ) || ! is_array( $layout_meta['disabled'] ) ) { + return array(); + } + + return $layout_meta['disabled']; + } + + /** + * Returns an array of layouts that have been renamed for the current field. + * + * @since ACF 6.5 + * + * @param integer|string $post_id The ID of the post being edited. + * @param array $field The Flexible Content field array. + * @return array + */ + public function get_renamed_layouts( $post_id, $field ): array { + $layout_meta = $this->get_layout_meta( $post_id, $field ); + + if ( empty( $layout_meta['renamed'] ) || ! is_array( $layout_meta['renamed'] ) ) { + return array(); + } + + return $layout_meta['renamed']; + } /** * This function will delete a value row @@ -1094,9 +1049,8 @@ public function update_row( $row, $i, $field, $post_id ) { } /** - * This filter is applied to the $value before it is updated in the db + * Filters the $value before it is updated in the database. * - * @type filter * @since ACF 3.6 * * @param mixed $value The value which will be saved in the database. @@ -1106,15 +1060,17 @@ public function update_row( $row, $i, $field, $post_id ) { */ public function update_value( $value, $post_id, $field ) { - // bail early if no layouts - if ( empty( $field['layouts'] ) ) { + // Bail early if no layouts or field name. + if ( empty( $field['layouts'] ) || empty( $field['name'] ) ) { return $value; } // vars - $new_value = array(); - $old_value = acf_get_metadata_by_field( $post_id, $field ); - $old_value = is_array( $old_value ) ? $old_value : array(); + $new_value = array(); + $disabled_layouts = array(); + $renamed_layouts = array(); + $old_value = acf_get_metadata_by_field( $post_id, $field ); + $old_value = is_array( $old_value ) ? $old_value : array(); // update if ( ! empty( $value ) ) { @@ -1139,6 +1095,16 @@ public function update_value( $value, $post_id, $field ) { $this->delete_row( $i, $field, $post_id ); } + if ( ! empty( $row['acf_fc_layout_disabled'] ) ) { + $disabled_layouts[] = $i; + } + unset( $row['acf_fc_layout_disabled'] ); + + if ( ! empty( $row['acf_fc_layout_custom_label'] ) ) { + $renamed_layouts[ $i ] = $row['acf_fc_layout_custom_label']; + } + unset( $row['acf_fc_layout_custom_label'] ); + // update row $this->update_row( $row, $i, $field, $post_id ); @@ -1151,6 +1117,18 @@ public function update_value( $value, $post_id, $field ) { $old_count = empty( $old_value ) ? 0 : count( $old_value ); $new_count = empty( $new_value ) ? 0 : count( $new_value ); + // Update layout meta. + acf_update_metadata_by_field( + $post_id, + array( + 'name' => '_' . $field['name'] . '_layout_meta', + ), + array( + 'disabled' => $disabled_layouts, + 'renamed' => $renamed_layouts, + ) + ); + // remove old rows if ( $old_count > $new_count ) { @@ -1346,39 +1324,8 @@ public function ajax_layout_title() { * @return string The layout title, optionally filtered. */ public function get_layout_title( $field, $layout, $i, $value ) { - - // vars - $rows = array(); - $rows[ $i ] = $value; - - // add loop - acf_add_loop( - array( - 'selector' => $field['name'], - 'name' => $field['name'], - 'value' => $rows, - 'field' => $field, - 'i' => $i, - 'post_id' => 0, - ) - ); - - // vars - $title = $layout['label']; - - // filters - $title = apply_filters( 'acf/fields/flexible_content/layout_title', $title, $field, $layout, $i ); - $title = apply_filters( 'acf/fields/flexible_content/layout_title/name=' . $field['_name'], $title, $field, $layout, $i ); - $title = apply_filters( 'acf/fields/flexible_content/layout_title/key=' . $field['key'], $title, $field, $layout, $i ); - - // remove loop - acf_remove_loop(); - - // prepend order - $order = is_numeric( $i ) ? $i + 1 : 0; - $title = '' . $order . ' ' . acf_esc_html( $title ); - - return $title; + $layout = new Layout( $field, $layout, $i, $value ); + return $layout->get_title(); } diff --git a/includes/fields/class-acf-field-google-map.php b/includes/fields/class-acf-field-google-map.php index b0270f98..ea280835 100644 --- a/includes/fields/class-acf-field-google-map.php +++ b/includes/fields/class-acf-field-google-map.php @@ -1,7 +1,6 @@
-
+
+ + + >

@@ -173,7 +191,7 @@ function ( $tab ) use ( $field ) { switch ( $name ) { case 'dashicons': - $this->render_icon_list_tab( $name ); + $this->render_icon_list_tab( $name, $field ); break; case 'media_library': ?> @@ -228,18 +246,7 @@ class="acf-icon-picker-media-library-preview" break; default: do_action( 'acf/fields/icon_picker/tab/' . $name, $field ); - - $custom_icons = apply_filters( 'acf/fields/icon_picker/' . $name . '/icons', array(), $field ); - - if ( is_array( $custom_icons ) && ! empty( $custom_icons ) ) { - $this->render_icon_list_tab( $name ); - - acf_localize_data( - array( - 'iconPickerIcons_' . $name => $custom_icons, - ) - ); - } + $this->render_icon_list_tab( $name, $field ); } echo '

'; diff --git a/includes/fields/class-acf-field-oembed.php b/includes/fields/class-acf-field-oembed.php index 7f5340a9..9df09be1 100644 --- a/includes/fields/class-acf-field-oembed.php +++ b/includes/fields/class-acf-field-oembed.php @@ -1,7 +1,6 @@ true, ); + /** + * Default values for the field. + * + * @var array + */ + public $default_values = array(); + + /** + * Whether the field has rows. + * + * @var string + */ + public $have_rows = ''; + + /** + * The width of the field. + * + * @var string + */ + public $width = ''; + + /** + * The height of the field. + * + * @var string + */ + public $height = ''; + /** * Initializes the `acf_field` class. To initialize a field type that is * extending this class, use the `initialize()` method in the child class instead. diff --git a/includes/forms/form-customizer.php b/includes/forms/form-customizer.php index 370311b4..c87f8da3 100644 --- a/includes/forms/form-customizer.php +++ b/includes/forms/form-customizer.php @@ -23,14 +23,14 @@ class ACF_Form_Customizer { * * @var array */ - public $preview_values; + public $preview_values = array(); /** * Fields to be used in the preview. * * @var array */ - public $preview_fields; + public $preview_fields = array(); /** @@ -38,7 +38,7 @@ class ACF_Form_Customizer { * * @var array */ - public $preview_errors; + public $preview_errors = array(); /** * This function will setup the class functionality @@ -52,11 +52,6 @@ class ACF_Form_Customizer { */ public function __construct() { - // vars - $this->preview_values = array(); - $this->preview_fields = array(); - $this->preview_errors = array(); - // actions add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) ); add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ), 1, 1 ); @@ -381,75 +376,75 @@ public function admin_footer() { ?> preview_values = array(); - $this->preview_reference = array(); - $this->preview_errors = array(); - // actions add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); add_action( 'in_widget_form', array( $this, 'edit_widget' ), 10, 3 ); @@ -185,9 +179,9 @@ function edit_widget( $widget, $return, $instance ) { if ( $widget->updated ) : ?> loops = array(); - } - /** * This function will return true if no loops exist diff --git a/includes/revisions.php b/includes/revisions.php index 90263a87..899cfeac 100644 --- a/includes/revisions.php +++ b/includes/revisions.php @@ -5,11 +5,14 @@ } if ( ! class_exists( 'acf_revisions' ) ) : - #[AllowDynamicProperties] class acf_revisions { - // vars - var $cache = array(); + /** + * An array to cache post IDs for revisions. + * + * @var array + */ + public $cache = array(); /** * Constructs the acf_revisions class. diff --git a/includes/validation.php b/includes/validation.php index 93e7b196..9a1440e4 100644 --- a/includes/validation.php +++ b/includes/validation.php @@ -5,7 +5,6 @@ } if ( ! class_exists( 'acf_validation' ) ) : - #[AllowDynamicProperties] /** * Validation Class */ @@ -28,9 +27,6 @@ class acf_validation { */ public function __construct() { - // vars - $this->errors = array(); - // ajax add_action( 'wp_ajax_acf/validate_save_post', array( $this, 'ajax_validate_save_post' ) ); add_action( 'wp_ajax_nopriv_acf/validate_save_post', array( $this, 'ajax_validate_save_post' ) ); diff --git a/secure-custom-fields.php b/secure-custom-fields.php index b9c286c6..5113b176 100644 --- a/secure-custom-fields.php +++ b/secure-custom-fields.php @@ -56,6 +56,48 @@ class ACF { */ public $instances = array(); + /** + * The loop instance. + * + * @var acf_loop + */ + public $loop; + + /** + * The revisions instance. + * + * @var acf_revisions + */ + public $revisions; + + /** + * The fields instance. + * + * @var acf_fields + */ + public $fields; + + /** + * The form front instance. + * + * @var acf_form_front + */ + public $form_front; + + /** + * The validation instance. + * + * @var acf_validation + */ + public $validation; + + /** + * The admin tools instance. + * + * @var acf_admin_tools + */ + public $admin_tools; + /** * A dummy constructor to ensure ACF is only setup once. *