form-customizer.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit; // Exit if accessed directly
  4. }
  5. if ( ! class_exists( 'acf_form_customizer' ) ) :
  6. class acf_form_customizer {
  7. /*
  8. * __construct
  9. *
  10. * This function will setup the class functionality
  11. *
  12. * @type function
  13. * @date 5/03/2014
  14. * @since 5.0.0
  15. *
  16. * @param n/a
  17. * @return n/a
  18. */
  19. function __construct() {
  20. // vars
  21. $this->preview_values = array();
  22. $this->preview_fields = array();
  23. $this->preview_errors = array();
  24. // actions
  25. add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) );
  26. add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ), 1, 1 );
  27. add_action( 'customize_save', array( $this, 'customize_save' ), 1, 1 );
  28. // save
  29. add_filter( 'widget_update_callback', array( $this, 'save_widget' ), 10, 4 );
  30. }
  31. /*
  32. * admin_enqueue_scripts
  33. *
  34. * This action is run after post query but before any admin script / head actions.
  35. * It is a good place to register all actions.
  36. *
  37. * @type action (admin_enqueue_scripts)
  38. * @date 26/01/13
  39. * @since 3.6.0
  40. *
  41. * @param N/A
  42. * @return N/A
  43. */
  44. function customize_controls_init() {
  45. // load acf scripts
  46. acf_enqueue_scripts(
  47. array(
  48. 'context' => 'customize_controls',
  49. )
  50. );
  51. // actions
  52. add_action( 'acf/input/admin_footer', array( $this, 'admin_footer' ), 1 );
  53. }
  54. /*
  55. * save_widget
  56. *
  57. * This function will hook into the widget update filter and save ACF data
  58. *
  59. * @type function
  60. * @date 27/05/2015
  61. * @since 5.2.3
  62. *
  63. * @param $instance (array) widget settings
  64. * @param $new_instance (array) widget settings
  65. * @param $old_instance (array) widget settings
  66. * @param $widget (object) widget info
  67. * @return $instance
  68. */
  69. function save_widget( $instance, $new_instance, $old_instance, $widget ) {
  70. // bail early if not valid (customize + acf values + nonce)
  71. if ( ! isset( $_POST['wp_customize'] ) || ! isset( $new_instance['acf'] ) || ! acf_verify_nonce( 'widget' ) ) {
  72. return $instance;
  73. }
  74. // vars
  75. $data = array(
  76. 'post_id' => "widget_{$widget->id}",
  77. 'values' => array(),
  78. 'fields' => array(),
  79. );
  80. // append values
  81. $data['values'] = $new_instance['acf'];
  82. // append fields (name => key relationship) - used later in 'acf/get_field_reference' for customizer previews
  83. foreach ( $data['values'] as $k => $v ) {
  84. // get field
  85. $field = acf_get_field( $k );
  86. // continue if no field
  87. if ( ! $field ) {
  88. continue;
  89. }
  90. // update
  91. $data['fields'][ $field['name'] ] = $field['key'];
  92. }
  93. // append data to instance
  94. $instance['acf'] = $data;
  95. // return
  96. return $instance;
  97. }
  98. /*
  99. * settings
  100. *
  101. * This function will return an array of cutomizer settings that include ACF data
  102. * similar to `$customizer->settings();`
  103. *
  104. * @type function
  105. * @date 22/03/2016
  106. * @since 5.3.2
  107. *
  108. * @param $customizer (object)
  109. * @return $value (mixed)
  110. */
  111. function settings( $customizer ) {
  112. // vars
  113. $data = array();
  114. $settings = $customizer->settings();
  115. // bail early if no settings
  116. if ( empty( $settings ) ) {
  117. return false;
  118. }
  119. // loop over settings
  120. foreach ( $settings as $setting ) {
  121. // vars
  122. $id = $setting->id;
  123. // verify settings type
  124. if ( substr( $id, 0, 6 ) == 'widget' || substr( $id, 0, 7 ) == 'nav_menu' ) {
  125. // allow
  126. } else {
  127. continue;
  128. }
  129. // get value
  130. $value = $setting->post_value();
  131. // bail early if no acf
  132. if ( ! is_array( $value ) || ! isset( $value['acf'] ) ) {
  133. continue;
  134. }
  135. // set data
  136. $setting->acf = $value['acf'];
  137. // append
  138. $data[] = $setting;
  139. }
  140. // bail early if no settings
  141. if ( empty( $data ) ) {
  142. return false;
  143. }
  144. // return
  145. return $data;
  146. }
  147. /*
  148. * customize_preview_init
  149. *
  150. * This function is called when customizer preview is initialized
  151. *
  152. * @type function
  153. * @date 22/03/2016
  154. * @since 5.3.2
  155. *
  156. * @param $customizer (object)
  157. * @return n/a
  158. */
  159. function customize_preview_init( $customizer ) {
  160. // get customizer settings (widgets)
  161. $settings = $this->settings( $customizer );
  162. // bail early if no settings
  163. if ( empty( $settings ) ) {
  164. return;
  165. }
  166. // append values
  167. foreach ( $settings as $setting ) {
  168. // get acf data
  169. $data = $setting->acf;
  170. // append acf_value to preview_values
  171. $this->preview_values[ $data['post_id'] ] = $data['values'];
  172. $this->preview_fields[ $data['post_id'] ] = $data['fields'];
  173. }
  174. // bail early if no preview_values
  175. if ( empty( $this->preview_values ) ) {
  176. return;
  177. }
  178. // add filters
  179. add_filter( 'acf/pre_load_value', array( $this, 'pre_load_value' ), 10, 3 );
  180. add_filter( 'acf/pre_load_reference', array( $this, 'pre_load_reference' ), 10, 3 );
  181. }
  182. /**
  183. * pre_load_value
  184. *
  185. * Used to inject preview value
  186. *
  187. * @date 2/2/18
  188. * @since 5.6.5
  189. *
  190. * @param type $var Description. Default.
  191. * @return type Description.
  192. */
  193. function pre_load_value( $value, $post_id, $field ) {
  194. // check
  195. if ( isset( $this->preview_values[ $post_id ][ $field['key'] ] ) ) {
  196. return $this->preview_values[ $post_id ][ $field['key'] ];
  197. }
  198. // return
  199. return $value;
  200. }
  201. /**
  202. * pre_load_reference
  203. *
  204. * Used to inject preview value
  205. *
  206. * @date 2/2/18
  207. * @since 5.6.5
  208. *
  209. * @param type $var Description. Default.
  210. * @return type Description.
  211. */
  212. function pre_load_reference( $field_key, $field_name, $post_id ) {
  213. // check
  214. if ( isset( $this->preview_fields[ $post_id ][ $field_name ] ) ) {
  215. return $this->preview_fields[ $post_id ][ $field_name ];
  216. }
  217. // return
  218. return $field_key;
  219. }
  220. /*
  221. * customize_save
  222. *
  223. * This function is called when customizer saves a widget.
  224. * Normally, the widget_update_callback filter would be used, but the customizer disables this and runs a custom action
  225. * class-customizer-settings.php will save the widget data via the function set_root_value which uses update_option
  226. *
  227. * @type function
  228. * @date 22/03/2016
  229. * @since 5.3.2
  230. *
  231. * @param $customizer (object)
  232. * @return n/a
  233. */
  234. function customize_save( $customizer ) {
  235. // get customizer settings (widgets)
  236. $settings = $this->settings( $customizer );
  237. // bail early if no settings
  238. if ( empty( $settings ) ) {
  239. return;
  240. }
  241. // append values
  242. foreach ( $settings as $setting ) {
  243. // get acf data
  244. $data = $setting->acf;
  245. // save acf data
  246. acf_save_post( $data['post_id'], $data['values'] );
  247. // remove [acf] data from saved widget array
  248. $id_data = $setting->id_data();
  249. add_filter( 'pre_update_option_' . $id_data['base'], array( $this, 'pre_update_option' ), 10, 3 );
  250. }
  251. }
  252. /*
  253. * pre_update_option
  254. *
  255. * this function will remove the [acf] data from widget insance
  256. *
  257. * @type function
  258. * @date 22/03/2016
  259. * @since 5.3.2
  260. *
  261. * @param $post_id (int)
  262. * @return $post_id (int)
  263. */
  264. function pre_update_option( $value, $option, $old_value ) {
  265. // bail early if no value
  266. if ( empty( $value ) ) {
  267. return $value;
  268. }
  269. // loop over widgets
  270. // WP saves all widgets (of the same type) as an array of widgets
  271. foreach ( $value as $i => $widget ) {
  272. // bail early if no acf
  273. if ( ! isset( $widget['acf'] ) ) {
  274. continue;
  275. }
  276. // remove widget
  277. unset( $value[ $i ]['acf'] );
  278. }
  279. // return
  280. return $value;
  281. }
  282. /*
  283. * admin_footer
  284. *
  285. * This function will add some custom HTML to the footer of the edit page
  286. *
  287. * @type function
  288. * @date 11/06/2014
  289. * @since 5.0.0
  290. *
  291. * @param n/a
  292. * @return n/a
  293. */
  294. function admin_footer() {
  295. ?>
  296. <script type="text/javascript">
  297. (function($) {
  298. // customizer saves widget on any input change, so unload is not needed
  299. acf.unload.active = 0;
  300. // hack customizer function to remove bug caused by WYSIWYG field using aunique ID
  301. // customizer compares returned AJAX HTML with the HTML of the widget form.
  302. // the _getInputsSignature() function is used to generate a string based of input name + id.
  303. // because ACF generates a unique ID on the WYSIWYG field, this string will not match causing the preview function to bail.
  304. // 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.
  305. // source: wp-admin/js/customize-widgets.js
  306. // vars
  307. var WidgetControl = wp.customize.Widgets.WidgetControl.prototype;
  308. // backup functions
  309. WidgetControl.__getInputsSignature = WidgetControl._getInputsSignature;
  310. WidgetControl.__setInputState = WidgetControl._setInputState;
  311. // modify __getInputsSignature
  312. WidgetControl._getInputsSignature = function( inputs ) {
  313. // vars
  314. var signature = this.__getInputsSignature( inputs );
  315. safe = [];
  316. // split
  317. signature = signature.split(';');
  318. // loop
  319. for( var i in signature ) {
  320. // vars
  321. var bit = signature[i];
  322. // bail early if acf is found
  323. if( bit.indexOf('acf') !== -1 ) continue;
  324. // append
  325. safe.push( bit );
  326. }
  327. // update
  328. signature = safe.join(';');
  329. // return
  330. return signature;
  331. };
  332. // modify _setInputState
  333. // this function deosn't seem to run on widget title/content, only custom fields
  334. // either way, this function is not needed and will break ACF fields
  335. WidgetControl._setInputState = function( input, state ) {
  336. return true;
  337. };
  338. })(jQuery);
  339. </script>
  340. <?php
  341. }
  342. }
  343. new acf_form_customizer();
  344. endif;
  345. ?>