786 lines
15 KiB
PHP
786 lines
15 KiB
PHP
<?php
|
|
|
|
/*
|
|
* ACF Widget Form Class
|
|
*
|
|
* All the logic for adding fields to widgets
|
|
*
|
|
* @class acf_form_widget
|
|
* @package ACF
|
|
* @subpackage Forms
|
|
*/
|
|
|
|
if( ! class_exists('acf_form_widget') ) :
|
|
|
|
class acf_form_widget {
|
|
|
|
|
|
/*
|
|
* __construct
|
|
*
|
|
* This function will setup the class functionality
|
|
*
|
|
* @type function
|
|
* @date 5/03/2014
|
|
* @since 5.0.0
|
|
*
|
|
* @param n/a
|
|
* @return n/a
|
|
*/
|
|
|
|
function __construct() {
|
|
|
|
// vars
|
|
$this->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);
|
|
add_action('customize_save', array($this, 'customize_save'), 1, 1);
|
|
add_action('customize_preview_init', array($this, 'customize_preview_init'), 1, 1);
|
|
|
|
|
|
// filters
|
|
add_filter('widget_update_callback', array($this, 'widget_update_callback'), 10, 4);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* admin_enqueue_scripts
|
|
*
|
|
* This action is run after post query but before any admin script / head actions.
|
|
* It is a good place to register all actions.
|
|
*
|
|
* @type action (admin_enqueue_scripts)
|
|
* @date 26/01/13
|
|
* @since 3.6.0
|
|
*
|
|
* @param N/A
|
|
* @return N/A
|
|
*/
|
|
|
|
function admin_enqueue_scripts() {
|
|
|
|
// validate screen
|
|
if( acf_is_screen('widgets') || acf_is_screen('customize') ) {
|
|
|
|
// valid
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
// load acf scripts
|
|
acf_enqueue_scripts();
|
|
|
|
|
|
// actions
|
|
add_action('acf/input/admin_footer', array($this, 'admin_footer'), 1);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* edit_widget
|
|
*
|
|
* This function will render the fields for a widget form
|
|
*
|
|
* @type function
|
|
* @date 11/06/2014
|
|
* @since 5.0.0
|
|
*
|
|
* @param $widget (object)
|
|
* @param $return (null)
|
|
* @param $instance (object)
|
|
* @return $post_id (int)
|
|
*/
|
|
|
|
function edit_widget( $widget, $return, $instance ) {
|
|
|
|
// vars
|
|
$post_id = 0;
|
|
|
|
|
|
// get id
|
|
if( $widget->number !== '__i__' ) {
|
|
|
|
$post_id = "widget_{$widget->id}";
|
|
|
|
}
|
|
|
|
|
|
// get field groups
|
|
$field_groups = acf_get_field_groups(array(
|
|
'widget' => $widget->id_base
|
|
));
|
|
|
|
|
|
// render
|
|
if( !empty($field_groups) ) {
|
|
|
|
// render post data
|
|
acf_form_data(array(
|
|
'post_id' => $post_id,
|
|
'nonce' => 'widget'
|
|
));
|
|
|
|
|
|
foreach( $field_groups as $field_group ) {
|
|
|
|
$fields = acf_get_fields( $field_group );
|
|
|
|
acf_render_fields( $post_id, $fields, 'div', 'field' );
|
|
|
|
}
|
|
|
|
|
|
if( $widget->updated ): ?>
|
|
<script type="text/javascript">
|
|
(function($) {
|
|
|
|
acf.do_action('append', $('[id^="widget"][id$="<?php echo $widget->id; ?>"]') );
|
|
|
|
})(jQuery);
|
|
</script>
|
|
<?php endif;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* widget_update_callback
|
|
*
|
|
* This function will hook into the widget update filter and save ACF data
|
|
*
|
|
* @type function
|
|
* @date 27/05/2015
|
|
* @since 5.2.3
|
|
*
|
|
* @param $instance (array) widget settings
|
|
* @param $new_instance (array) widget settings
|
|
* @param $old_instance (array) widget settings
|
|
* @param $widget (object) widget info
|
|
* @return $instance
|
|
*/
|
|
|
|
function widget_update_callback( $instance, $new_instance, $old_instance, $widget ) {
|
|
|
|
// bail early if empty
|
|
if( empty($_POST['acf']) ) return $instance;
|
|
|
|
|
|
// bail early if no nonce
|
|
if( !acf_verify_nonce('widget') ) return $instance;
|
|
|
|
|
|
// customizer autosave preview
|
|
if( !empty($_POST['wp_customize']) ) {
|
|
|
|
return $this->customizer_widget_update_callback($instance, $new_instance, $old_instance, $widget);
|
|
|
|
}
|
|
|
|
|
|
// bail early if not valid
|
|
if( !acf_validate_save_post() ) return $instance;
|
|
|
|
|
|
// save
|
|
acf_save_post( "widget_{$widget->id}" );
|
|
|
|
|
|
// return
|
|
return $instance;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* customizer_widget_update_callback
|
|
*
|
|
* customizer specific verison of above function
|
|
*
|
|
* @type function
|
|
* @date 27/05/2015
|
|
* @since 5.2.3
|
|
*
|
|
* @param $instance (array) widget settings
|
|
* @param $new_instance (array) widget settings
|
|
* @param $old_instance (array) widget settings
|
|
* @param $widget (object) widget info
|
|
* @return $instance
|
|
*/
|
|
|
|
function customizer_widget_update_callback( $instance, $new_instance, $old_instance, $widget ) {
|
|
|
|
// bail early if not valid
|
|
if( !acf_validate_save_post() ) {
|
|
|
|
// vars
|
|
$errors = acf_get_validation_errors();
|
|
$total = count($errors);
|
|
|
|
|
|
// message
|
|
$message = __('Validation failed', 'acf') . '. ';
|
|
$message .= sprintf(_n( '1 field requires attention.', '%d fields require attention.', $total, 'acf' ), $total);
|
|
|
|
|
|
// add missing WP JS to remove spinning
|
|
// also set state to 'saved' which disables the save button
|
|
$message .= '<script type="text/javascript">';
|
|
$message .= '(function($) {';
|
|
$message .= '$(".customize-control.previewer-loading").removeClass("previewer-loading"); ';
|
|
$message .= 'wp.customize.state("saved").set( true ); ';
|
|
$message .= '})(jQuery);';
|
|
$message .= '</script>';
|
|
|
|
|
|
// return JSON error
|
|
wp_send_json_error(array(
|
|
'message' => $message
|
|
));
|
|
|
|
} else {
|
|
|
|
$instance['acf'] = array(
|
|
'ID' => 'widget_' . $widget->id,
|
|
'values' => false,
|
|
'reference' => array()
|
|
);
|
|
|
|
|
|
// append acf $_POST data to instance
|
|
// this allows preview JS data to contain acf values
|
|
$instance['acf']['values'] = $_POST['acf'];
|
|
|
|
|
|
// backup name => key reference
|
|
// this will allow the customizer preview to correctly load the field when attempting to run acf_load_value and acf_format_value functions on newly added widgets
|
|
foreach( $_POST['acf'] as $k => $v ) {
|
|
|
|
// get field
|
|
$field = acf_get_field( $k );
|
|
|
|
|
|
// continue if no field
|
|
if( !$field ) continue;
|
|
|
|
|
|
// update
|
|
$instance['acf']['reference'][ $field['name'] ] = $field['key'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// return
|
|
return $instance;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* get_customizer_widgets
|
|
*
|
|
* This function will return an array of widget settings
|
|
*
|
|
* @type function
|
|
* @date 22/03/2016
|
|
* @since 5.3.2
|
|
*
|
|
* @param $customizer (object)
|
|
* @return $value (mixed)
|
|
*/
|
|
|
|
function get_customizer_widgets( $customizer ) {
|
|
|
|
// vars
|
|
$widgets = array();
|
|
$settings = $customizer->settings();
|
|
|
|
|
|
// bail ealry if no settings
|
|
if( empty($settings) ) return false;
|
|
|
|
|
|
// loop over settings
|
|
foreach( array_keys($settings) as $i ) {
|
|
|
|
// vars
|
|
$setting = $settings[ $i ];
|
|
|
|
|
|
// bail ealry if not widget
|
|
if( substr($setting->id, 0, 7) !== 'widget_' ) continue;
|
|
|
|
|
|
// get value
|
|
$value = $setting->post_value();
|
|
|
|
|
|
// set data
|
|
$setting->acf = acf_maybe_get($value, 'acf');
|
|
|
|
|
|
// append
|
|
$widgets[] = $setting;
|
|
|
|
}
|
|
|
|
|
|
// bail ealry if no preview values
|
|
if( empty($widgets) ) return false;
|
|
|
|
|
|
// return
|
|
return $widgets;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* customize_preview_init
|
|
*
|
|
* This function is called when customizer preview is initialized
|
|
*
|
|
* @type function
|
|
* @date 22/03/2016
|
|
* @since 5.3.2
|
|
*
|
|
* @param $customizer (object)
|
|
* @return n/a
|
|
*/
|
|
|
|
function customize_preview_init( $customizer ) {
|
|
|
|
// get widgets
|
|
$widgets = $this->get_customizer_widgets($customizer);
|
|
|
|
|
|
// bail ealry if no widgets
|
|
if( empty($widgets) ) return;
|
|
|
|
|
|
// append values
|
|
foreach( $widgets as $widget ) {
|
|
|
|
// bail early if no acf
|
|
if( empty($widget->acf) ) continue;
|
|
|
|
|
|
// append acf_value to preview_values
|
|
$this->preview_values[ $widget->acf['ID'] ] = $widget->acf['values'];
|
|
$this->preview_reference[ $widget->acf['ID'] ] = $widget->acf['reference'];
|
|
|
|
}
|
|
|
|
|
|
// bail ealry if no preview_values
|
|
if( empty($this->preview_values) ) return;
|
|
|
|
|
|
// add filter
|
|
add_filter('acf/load_value', array($this, 'load_value'), 10, 3);
|
|
add_filter('acf/get_field_reference', array($this, 'get_field_reference'), 10, 3);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* get_field_reference
|
|
*
|
|
* This function will return a field_key for a given field name + post_id
|
|
* Normally, ACF would lookup the DB fro this connection, but a new preview widget has not yet saved anything to the DB
|
|
*
|
|
* @type function
|
|
* @date 12/05/2016
|
|
* @since 5.3.8
|
|
*
|
|
* @param $field_key (string)
|
|
* @param $field_name (string)
|
|
* @param $post_id (mixed)
|
|
* @return $field_key
|
|
*/
|
|
|
|
function get_field_reference( $field_key, $field_name, $post_id ) {
|
|
|
|
// look for reference
|
|
if( isset($this->preview_reference[ $post_id ][ $field_name ]) ) {
|
|
|
|
$field_key = $this->preview_reference[ $post_id ][ $field_name ];
|
|
|
|
}
|
|
|
|
|
|
// return
|
|
return $field_key;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* load_value
|
|
*
|
|
* This function will override a field's value with the preview_value set in previous functions
|
|
*
|
|
* @type function
|
|
* @date 22/03/2016
|
|
* @since 5.3.2
|
|
*
|
|
* @param $post_id (int)
|
|
* @return $post_id (int)
|
|
*/
|
|
|
|
function load_value( $value, $post_id, $field ) {
|
|
|
|
// acf/load_value filter is run first (before type, name, key)
|
|
// use this filter to append to the key filter and ensure this is run last
|
|
// could use set_cache in the future to remove this load_filter completley
|
|
// but don't want to clog up cache with multiple widget's values
|
|
|
|
|
|
// look for value
|
|
if( isset($this->preview_values[ $post_id ][ $field['key'] ]) ) {
|
|
|
|
// add filter to override the $value
|
|
add_filter('acf/load_value/key='.$field['key'], array($this, 'load_value_2'), 99, 3);
|
|
|
|
|
|
// return null and prevent any DB logic from field type functions
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
// return
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
function load_value_2( $value, $post_id, $field ) {
|
|
|
|
// look for value
|
|
$value = $this->preview_values[ $post_id ][ $field['key'] ];
|
|
|
|
|
|
// remove this filter (only run once)
|
|
remove_filter('acf/load_value/key='.$field['key'], array($this, 'load_value_2'), 99, 3);
|
|
|
|
|
|
// return
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* customize_save
|
|
*
|
|
* This function is called when customizer saves a widget.
|
|
* Normally, the widget_update_callback filter would be used, but the customizer disables this and runs a custom action
|
|
* class-customizer-settings.php will save the widget data via the function set_root_value which uses update_option
|
|
*
|
|
* @type function
|
|
* @date 22/03/2016
|
|
* @since 5.3.2
|
|
*
|
|
* @param $customizer (object)
|
|
* @return n/a
|
|
*/
|
|
|
|
function customize_save( $customizer ) {
|
|
|
|
// get widgets
|
|
$widgets = $this->get_customizer_widgets($customizer);
|
|
|
|
|
|
// bail ealry if no widgets
|
|
if( empty($widgets) ) return;
|
|
|
|
|
|
// append values
|
|
foreach( $widgets as $widget ) {
|
|
|
|
// bail early if no acf_value
|
|
if( !$widget->acf ) continue;
|
|
|
|
// fake post data
|
|
$_POST['acf'] = $widget->acf['values'];
|
|
|
|
|
|
// save
|
|
acf_save_post( $widget->acf['ID'] );
|
|
|
|
|
|
// get widget base
|
|
$id_data = $widget->id_data();
|
|
|
|
|
|
// remove [acf] data from saved widget array
|
|
add_filter('pre_update_option_' . $id_data['base'], array($this, 'pre_update_option'), 10, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* pre_update_option
|
|
*
|
|
* this function will remove the [acf] data from widget insance
|
|
*
|
|
* @type function
|
|
* @date 22/03/2016
|
|
* @since 5.3.2
|
|
*
|
|
* @param $post_id (int)
|
|
* @return $post_id (int)
|
|
*/
|
|
|
|
function pre_update_option( $value, $option, $old_value ) {
|
|
|
|
// bail ealry if no value
|
|
if( empty($value) ) return $value;
|
|
|
|
|
|
// loop over widgets
|
|
// WP saves all widgets (of the same type) as an array of widgets
|
|
foreach( $value as $i => $widget ) {
|
|
|
|
// bail ealry if no acf
|
|
if( !isset($widget['acf']) ) continue;
|
|
|
|
|
|
// remove widget
|
|
unset($value[ $i ]['acf']);
|
|
|
|
}
|
|
|
|
|
|
// return
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* admin_footer
|
|
*
|
|
* This function will add some custom HTML to the footer of the edit page
|
|
*
|
|
* @type function
|
|
* @date 11/06/2014
|
|
* @since 5.0.0
|
|
*
|
|
* @param n/a
|
|
* @return n/a
|
|
*/
|
|
|
|
function admin_footer() {
|
|
|
|
?>
|
|
<script type="text/javascript">
|
|
(function($) {
|
|
|
|
acf.add_filter('get_fields', function( $fields ){
|
|
|
|
// widgets
|
|
$fields = $fields.not('#available-widgets .acf-field');
|
|
|
|
|
|
// customizer
|
|
$fields = $fields.not('.widget-tpl .acf-field');
|
|
|
|
|
|
// return
|
|
return $fields;
|
|
|
|
});
|
|
|
|
|
|
$('#widgets-right').on('click', '.widget-control-save', function( e ){
|
|
|
|
// vars
|
|
var $form = $(this).closest('form');
|
|
|
|
|
|
// bail early if not active
|
|
if( !acf.validation.active ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
// ignore validation (only ignore once)
|
|
if( acf.validation.ignore ) {
|
|
|
|
acf.validation.ignore = 0;
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
// bail early if this form does not contain ACF data
|
|
if( !$form.find('#acf-form-data').exists() ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
// stop WP JS validation
|
|
e.stopImmediatePropagation();
|
|
|
|
|
|
// store submit trigger so it will be clicked if validation is passed
|
|
acf.validation.$trigger = $(this);
|
|
|
|
|
|
// run validation
|
|
acf.validation.fetch( $form );
|
|
|
|
|
|
// stop all other click events on this input
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
$(document).on('click', '.widget-top', function(){
|
|
|
|
var $el = $(this).parent().children('.widget-inside');
|
|
|
|
setTimeout(function(){
|
|
|
|
acf.get_fields('', $el).each(function(){
|
|
|
|
acf.do_action('show_field', $(this));
|
|
|
|
});
|
|
|
|
}, 250);
|
|
|
|
});
|
|
|
|
$(document).on('widget-added', function( e, $widget ){
|
|
|
|
// - use delay to avoid rendering issues with customizer (ensures div is visible)
|
|
setTimeout(function(){
|
|
|
|
acf.do_action('append', $widget );
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
$(document).on('widget-saved widget-updated', function( e, $widget ){
|
|
|
|
// unlock form
|
|
acf.validation.toggle( $widget, 'unlock' );
|
|
|
|
|
|
// submit
|
|
acf.do_action('submit', $widget );
|
|
|
|
});
|
|
|
|
<?php if( acf_is_screen('customize') ): ?>
|
|
|
|
// customizer saves widget on any input change, so unload is not needed
|
|
acf.unload.active = 0;
|
|
|
|
|
|
// hack customizer function to remove bug caused by WYSIWYG field using aunique ID
|
|
// customizer compares returned AJAX HTML with the HTML of the widget form.
|
|
// the _getInputsSignature() function is used to generate a string based of input name + id.
|
|
// because ACF generates a unique ID on the WYSIWYG field, this string will not match causing the preview function to bail.
|
|
// an attempt was made to remove the WYSIWYG unique ID, but this caused multiple issues in the wp-admin and altimately doesn't make sense with the tinymce rule that all editors must have a unique ID.
|
|
// source: wp-admin/js/customize-widgets.js
|
|
|
|
// vars
|
|
var WidgetControl = wp.customize.Widgets.WidgetControl.prototype;
|
|
|
|
|
|
// backup functions
|
|
WidgetControl.__getInputsSignature = WidgetControl._getInputsSignature;
|
|
WidgetControl.__setInputState = WidgetControl._setInputState;
|
|
|
|
|
|
// modify __getInputsSignature
|
|
WidgetControl._getInputsSignature = function( inputs ) {
|
|
|
|
// vars
|
|
var signature = this.__getInputsSignature( inputs );
|
|
safe = [];
|
|
|
|
|
|
// split
|
|
signature = signature.split(';');
|
|
|
|
|
|
// loop
|
|
for( var i in signature ) {
|
|
|
|
// vars
|
|
var bit = signature[i];
|
|
|
|
|
|
// bail ealry if acf is found
|
|
if( bit.indexOf('acf') !== -1 ) continue;
|
|
|
|
|
|
// append
|
|
safe.push( bit );
|
|
|
|
}
|
|
|
|
|
|
// update
|
|
signature = safe.join(';');
|
|
|
|
|
|
// return
|
|
return signature;
|
|
|
|
};
|
|
|
|
|
|
// modify _setInputState
|
|
// this function deosn't seem to run on widget title/content, only custom fields
|
|
// either way, this function is not needed and will break ACF fields
|
|
WidgetControl._setInputState = function( input, state ) {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
<?php endif; ?>
|
|
|
|
})(jQuery);
|
|
</script>
|
|
<?php
|
|
|
|
}
|
|
|
|
}
|
|
|
|
new acf_form_widget();
|
|
|
|
endif;
|
|
|
|
?>
|