增强套件,可改进"高级自定义字段"管理
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

790 lines
26 KiB

  1. <?php
  2. if(!defined('ABSPATH'))
  3. exit;
  4. if(!class_exists('acfe_form_front')):
  5. class acfe_form_front{
  6. function __construct(){
  7. // vars
  8. $this->fields = array(
  9. '_validate_email' => array(
  10. 'prefix' => 'acf',
  11. 'name' => '_validate_email',
  12. 'key' => '_validate_email',
  13. 'label' => __('Validate Email', 'acf'),
  14. 'type' => 'text',
  15. 'value' => '',
  16. 'wrapper' => array('style' => 'display:none !important;')
  17. )
  18. );
  19. // Submit
  20. add_action('wp', array($this, 'check_submit_form'));
  21. // Shortcode
  22. add_shortcode('acfe_form', array($this, 'add_shortcode'));
  23. // Validation
  24. add_action('acf/validate_save_post', array($this, 'validate_save_post'), 1);
  25. }
  26. function validate_save_post(){
  27. if(!acfe_is_front())
  28. return;
  29. if(acf_maybe_get_POST('_acf_screen') !== 'acfe_form')
  30. return;
  31. $form = acfe_form_decrypt_args();
  32. if(!$form)
  33. return;
  34. $post_id = acf_maybe_get($form, 'post_id', false);
  35. $form_name = acf_maybe_get($form, 'name');
  36. $form_id = acf_maybe_get($form, 'ID');
  37. if(!$form_name || !$form_id)
  38. return;
  39. foreach($this->fields as $k => $field){
  40. // bail early if no in $_POST
  41. if(!isset($_POST['acf'][ $k ]))
  42. continue;
  43. // register
  44. acf_add_local_field($field);
  45. }
  46. // Honeypot
  47. if(!empty($acf['_validate_email'])){
  48. acf_add_validation_error('', __('Spam Detected', 'acf'));
  49. }
  50. // Validation
  51. acfe_setup_meta($_POST['acf'], 'acfe/form/validation', true);
  52. $rows = array();
  53. // Actions
  54. if(have_rows('acfe_form_actions', $form_id)):
  55. while(have_rows('acfe_form_actions', $form_id)): the_row();
  56. $action = get_row_layout();
  57. $alias = get_sub_field('acfe_form_custom_alias');
  58. // Custom Action
  59. if($action === 'custom'){
  60. $action = get_sub_field('acfe_form_custom_action');
  61. $alias = '';
  62. }
  63. $rows[] = array(
  64. 'action' => $action,
  65. 'alias' => $alias,
  66. );
  67. endwhile;
  68. endif;
  69. // Do Action
  70. foreach($rows as $row){
  71. $action = $row['action'];
  72. $alias = $row['alias'];
  73. do_action('acfe/form/validation/' . $action, $form, $post_id, $alias);
  74. do_action('acfe/form/validation/' . $action . '/form=' . $form_name, $form, $post_id, $alias);
  75. if(!empty($alias))
  76. do_action('acfe/form/validation/' . $action . '/action=' . $alias, $form, $post_id, $alias);
  77. }
  78. do_action('acfe/form/validation', $form, $post_id);
  79. do_action('acfe/form/validation/form=' . $form_name, $form, $post_id);
  80. acfe_reset_meta();
  81. }
  82. function check_submit_form(){
  83. // Verify nonce.
  84. if(!acf_verify_nonce('acfe_form'))
  85. return;
  86. $form = acfe_form_decrypt_args();
  87. if(!$form)
  88. return;
  89. // ACF
  90. $_POST['acf'] = isset($_POST['acf']) ? $_POST['acf'] : array();
  91. // Run kses on all $_POST data.
  92. if($form['kses'] && isset($_POST['acf'])){
  93. $_POST['acf'] = wp_kses_post_deep($_POST['acf']);
  94. }
  95. // Validate data and show errors.
  96. acf_validate_save_post(true);
  97. // Submit form.
  98. $this->submit_form($form);
  99. }
  100. function submit_form($form){
  101. // vars
  102. $post_id = acf_maybe_get($form, 'post_id', false);
  103. $form_name = acf_maybe_get($form, 'name');
  104. $form_id = acf_maybe_get($form, 'ID');
  105. // Upload
  106. acf_save_post(false);
  107. // Unset Files
  108. if(isset($_FILES))
  109. unset($_FILES);
  110. /*
  111. * Fix Elementor + YOAST infinite loop
  112. *
  113. * https://github.com/elementor/elementor/issues/10998
  114. * https://github.com/Yoast/wordpress-seo/issues/14643
  115. */
  116. remove_shortcode('acfe_form');
  117. acfe_setup_meta($_POST['acf'], 'acfe/form/submit', true);
  118. // Actions
  119. if(have_rows('acfe_form_actions', $form_id)):
  120. while(have_rows('acfe_form_actions', $form_id)): the_row();
  121. $action = get_row_layout();
  122. $alias = get_sub_field('acfe_form_custom_alias');
  123. do_action('acfe/form/make/' . $action, $form, $post_id, $alias);
  124. endwhile;
  125. endif;
  126. do_action('acfe/form/submit', $form, $post_id);
  127. do_action('acfe/form/submit/form=' . $form_name, $form, $post_id);
  128. acfe_reset_meta();
  129. add_shortcode('acfe_form', array($this, 'add_shortcode'));
  130. // vars
  131. $return = acf_maybe_get($form, 'return', '');
  132. // redirect
  133. if($return){
  134. _deprecated_function('ACF Extended - Dynamic Forms: "Redirection" setting', '0.8.7.5', "the new Redirect Action (See documentation: https://www.acf-extended.com/features/modules/dynamic-forms)");
  135. $return = acfe_form_map_field_value($return, $post_id, $form);
  136. // redirect
  137. wp_redirect($return);
  138. exit;
  139. }
  140. }
  141. function validate_form($param){
  142. $form_id = false;
  143. $form_name = false;
  144. $param_array = array();
  145. if(is_array($param)){
  146. $param_array = $param;
  147. if(acf_maybe_get($param, 'id')){
  148. $param = acf_maybe_get($param, 'id');
  149. }elseif(acf_maybe_get($param, 'ID')){
  150. $param = acf_maybe_get($param, 'ID');
  151. }elseif(acf_maybe_get($param, 'name')){
  152. $param = acf_maybe_get($param, 'name');
  153. }else{
  154. return false;
  155. }
  156. }
  157. // ID
  158. if(is_numeric($param)){
  159. if(get_post_type($param) !== 'acfe-form')
  160. return false;
  161. // Form
  162. $form_id = $param;
  163. $form_name = get_field('acfe_form_name', $form_id);
  164. }
  165. // Name
  166. elseif(is_string($param)){
  167. $form = get_page_by_path($param, OBJECT, 'acfe-form');
  168. if(!$form)
  169. return false;
  170. // Form
  171. $form_id = $form->ID;
  172. $form_name = get_field('acfe_form_name', $form_id);
  173. }
  174. // Bail early
  175. if(!$form_name || !$form_id)
  176. return false;
  177. // Filters
  178. $register = true;
  179. $register = apply_filters("acfe/form/register", $register, $form_name, $form_id);
  180. $register = apply_filters("acfe/form/register/name={$form_name}", $register, $form_name, $form_id);
  181. $register = apply_filters("acfe/form/register/id={$form_id}", $register, $form_name, $form_id);
  182. if($register === false)
  183. return false;
  184. // Unset
  185. acfe_unset($param_array, 'id');
  186. acfe_unset($param_array, 'ID');
  187. acfe_unset($param_array, 'name');
  188. // Form Attributes
  189. $form_attributes = get_field('acfe_form_attributes', $form_id);
  190. $fields_attributes = get_field('acfe_form_fields_attributes', $form_id);
  191. // Defaults
  192. $defaults = array(
  193. // General
  194. 'ID' => $form_id,
  195. 'name' => $form_name,
  196. 'title' => get_the_title($form_id),
  197. // Settings
  198. 'post_id' => acf_get_valid_post_id(),
  199. 'field_groups' => get_field('acfe_form_field_groups', $form_id),
  200. 'field_groups_rules' => get_field('acfe_form_field_groups_rules', $form_id),
  201. 'post_field_groups' => get_field('acfe_form_post_field_groups', $form_id), // Deprecated
  202. 'form' => get_field('acfe_form_form_element', $form_id),
  203. 'html_before_fields' => get_field('acfe_form_html_before_fields', $form_id),
  204. 'custom_html_enabled' => get_field('acfe_form_custom_html_enable', $form_id),
  205. 'custom_html' => get_field('acfe_form_custom_html', $form_id),
  206. 'html_after_fields' => get_field('acfe_form_html_after_fields', $form_id),
  207. 'form_submit' => get_field('acfe_form_form_submit', $form_id),
  208. 'submit_value' => get_field('acfe_form_submit_value', $form_id),
  209. 'html_submit_button' => get_field('acfe_form_html_submit_button', $form_id),
  210. 'html_submit_spinner' => get_field('acfe_form_html_submit_spinner', $form_id),
  211. // Submission
  212. 'hide_error' => get_field('acfe_form_hide_error', $form_id),
  213. 'hide_unload' => get_field('acfe_form_hide_unload', $form_id),
  214. 'hide_revalidation' => get_field('acfe_form_hide_revalidation', $form_id),
  215. 'errors_position' => get_field('acfe_form_errors_position', $form_id),
  216. 'errors_class' => get_field('acfe_form_errors_class', $form_id),
  217. 'updated_message' => get_field('acfe_form_updated_message', $form_id),
  218. 'html_updated_message' => get_field('acfe_form_html_updated_message', $form_id),
  219. 'updated_hide_form' => get_field('acfe_form_updated_hide_form', $form_id),
  220. 'return' => get_field('acfe_form_return', $form_id), // Deprecated
  221. // Advanced
  222. 'honeypot' => get_field('acfe_form_honeypot', $form_id),
  223. 'kses' => get_field('acfe_form_kses', $form_id),
  224. 'uploader' => get_field('acfe_form_uploader', $form_id),
  225. 'field_el' => get_field('acfe_form_form_field_el', $form_id),
  226. 'label_placement' => get_field('acfe_form_label_placement', $form_id),
  227. 'instruction_placement' => get_field('acfe_form_instruction_placement', $form_id),
  228. // Mapping
  229. 'map' => array(),
  230. // Form Attributes
  231. 'form_attributes' => array(
  232. 'id' => acf_maybe_get($form_attributes, 'acfe_form_attributes_id'),
  233. 'class' => 'acfe-form ' . acf_maybe_get($form_attributes, 'acfe_form_attributes_class'),
  234. 'action' => '',
  235. 'method' => 'post',
  236. 'data-fields-class' => '',
  237. 'data-hide-error' => '',
  238. 'data-hide-unload' => '',
  239. 'data-hide-revalidation'=> '',
  240. 'data-errors-position' => '',
  241. 'data-errors-class' => '',
  242. ),
  243. // Fields Attributes
  244. 'fields_attributes' => array(
  245. 'wrapper_class' => acf_maybe_get($fields_attributes, 'acfe_form_fields_wrapper_class'),
  246. 'class' => acf_maybe_get($fields_attributes, 'acfe_form_fields_class'),
  247. ),
  248. );
  249. // Override
  250. $args = wp_parse_args($param_array, $defaults);
  251. if(acf_maybe_get($param_array, 'form_attributes'))
  252. $args['form_attributes'] = wp_parse_args($param_array['form_attributes'], $defaults['form_attributes']);
  253. if(acf_maybe_get($param_array, 'fields_attributes'))
  254. $args['fields_attributes'] = wp_parse_args($param_array['fields_attributes'], $defaults['fields_attributes']);
  255. // Advanced Override
  256. $args['form_attributes']['data-fields-class'] = $args['fields_attributes']['class'];
  257. $args['form_attributes']['data-hide-error'] = $args['hide_error'];
  258. $args['form_attributes']['data-hide-unload'] = $args['hide_unload'];
  259. $args['form_attributes']['data-hide-revalidation'] = $args['hide_revalidation'];
  260. $args['form_attributes']['data-errors-position'] = $args['errors_position'];
  261. $args['form_attributes']['data-errors-class'] = $args['errors_class'];
  262. if(acf_maybe_get_POST('acf')){
  263. acfe_setup_meta($_POST['acf'], 'acfe/form/load', true);
  264. }
  265. // Args
  266. $args = apply_filters('acfe/form/load', $args, $args['post_id']);
  267. $args = apply_filters('acfe/form/load/form=' . $form_name, $args, $args['post_id']);
  268. // Load
  269. if(have_rows('acfe_form_actions', $form_id)):
  270. while(have_rows('acfe_form_actions', $form_id)): the_row();
  271. $action = get_row_layout();
  272. $alias = get_sub_field('acfe_form_custom_alias');
  273. // Custom Action
  274. if($action === 'custom'){
  275. $action = get_sub_field('acfe_form_custom_action');
  276. $alias = '';
  277. }
  278. $args = apply_filters('acfe/form/load/' . $action, $args, $args['post_id'], $alias);
  279. $args = apply_filters('acfe/form/load/' . $action . '/form=' . $form_name, $args, $args['post_id'], $alias);
  280. if(!empty($alias))
  281. $args = apply_filters('acfe/form/load/' . $action . '/action=' . $alias, $args, $args['post_id'], $alias);
  282. endwhile;
  283. endif;
  284. if(acf_maybe_get_POST('acf')){
  285. acfe_reset_meta();
  286. }
  287. return $args;
  288. }
  289. /*
  290. * ACFE Form: render_form
  291. *
  292. */
  293. function render_form($args = array()){
  294. $args = $this->validate_form($args);
  295. // bail early if no args
  296. if(!$args)
  297. return false;
  298. // load acf scripts
  299. acf_enqueue_scripts();
  300. // Check Flexible Preview & Block Type Preview
  301. $is_dynamic_preview = acfe_is_dynamic_preview();
  302. if($is_dynamic_preview){
  303. // Disabled required + fields names
  304. add_filter('acf/prepare_field', array($this, 'disable_fields'));
  305. }
  306. // Register local fields.
  307. foreach($this->fields as $k => $field){
  308. acf_add_local_field($field);
  309. }
  310. // honeypot
  311. if($args['honeypot']){
  312. $fields[] = acf_get_field('_validate_email');
  313. }
  314. // Updated message
  315. if(acfe_is_form_success($args['name'])){
  316. // Trigger Success JS
  317. echo '<div class="acfe-form-success" data-form-name="' . $args['name'] . '" data-form-id="' . $args['ID'] . '"></div>';
  318. if(!empty($args['updated_message'])){
  319. $message = $args['updated_message'];
  320. if(acf_maybe_get_POST('acf')){
  321. $message = acfe_form_map_field_value($args['updated_message'], $args['post_id'], $args);
  322. }
  323. if(!empty($args['html_updated_message'])){
  324. printf($args['html_updated_message'], wp_unslash($message));
  325. }else{
  326. echo $message;
  327. }
  328. }
  329. // Hide form
  330. if($args['updated_hide_form']){
  331. return false;
  332. }
  333. }
  334. if(!empty($args['fields_attributes']['wrapper_class']) || !empty($args['fields_attributes']['class']) || $args['label_placement'] === 'hidden'){
  335. add_filter('acf/prepare_field', function($field) use($args){
  336. if(!$field)
  337. return $field;
  338. if(!empty($args['fields_attributes']['wrapper_class']))
  339. $field['wrapper']['class'] .= ' ' . $args['fields_attributes']['wrapper_class'];
  340. if(!empty($args['fields_attributes']['class']))
  341. $field['class'] .= ' ' . $args['fields_attributes']['class'];
  342. if($args['label_placement'] === 'hidden')
  343. $field['label'] = false;
  344. return $field;
  345. });
  346. }
  347. if(!empty($args['map'])){
  348. foreach($args['map'] as $field_key => $array){
  349. add_filter('acf/prepare_field/key=' . $field_key, function($field) use($array){
  350. if(!$field)
  351. return $field;
  352. $field = array_merge($field, $array);
  353. return $field;
  354. });
  355. }
  356. }
  357. // uploader (always set incase of multiple forms on the page)
  358. acf_disable_filter('acfe/form/uploader');
  359. if($args['uploader'] !== 'default'){
  360. acf_enable_filter('acfe/form/uploader');
  361. acf_update_setting('uploader', $args['uploader']);
  362. }
  363. // Remove <form> in Dynamic Preview
  364. if($is_dynamic_preview)
  365. $args['form'] = false;
  366. $wrapper = $args['form'] ? 'form' : 'div';
  367. ?>
  368. <?php do_action('acfe/form/render/before_form'); ?>
  369. <<?php echo $wrapper; ?> <?php acf_esc_attr_e($args['form_attributes']); ?>>
  370. <?php do_action('acfe/form/render/before_fields'); ?>
  371. <?php
  372. if(!$is_dynamic_preview){
  373. // render post data
  374. acf_form_data(array(
  375. 'screen' => 'acfe_form',
  376. 'post_id' => $args['post_id'],
  377. 'form' => acf_encrypt(json_encode($args))
  378. ));
  379. }
  380. $label_placement = false;
  381. if($args['label_placement'] !== 'hidden')
  382. $label_placement = '-' . $args['label_placement'];
  383. ?>
  384. <div class="acf-fields acf-form-fields <?php echo $label_placement; ?>">
  385. <?php
  386. // html before fields
  387. echo $args['html_before_fields'];
  388. // Custom HTML
  389. if(!empty($args['custom_html_enabled']) && !empty($args['custom_html'])){
  390. echo acfe_form_render_fields($args['custom_html'], $args['post_id'], $args);
  391. }
  392. // Normal Render
  393. else{
  394. // vars
  395. $field_groups = array();
  396. $fields = array();
  397. // Post Field groups (Deprecated)
  398. if($args['post_field_groups']){
  399. // Override Field Groups
  400. $post_field_groups = acf_get_field_groups(array(
  401. 'post_id' => $args['post_field_groups']
  402. ));
  403. $args['field_groups'] = wp_list_pluck($post_field_groups, 'key');
  404. }
  405. // Field groups
  406. if($args['field_groups']){
  407. foreach($args['field_groups'] as $selector){
  408. // Bypass Author Module
  409. if($selector === 'group_acfe_author')
  410. continue;
  411. $field_groups[] = acf_get_field_group($selector);
  412. }
  413. }
  414. // Apply Field Groups Rules
  415. if($args['field_groups_rules']){
  416. if(!empty($field_groups)){
  417. $post_id = get_the_ID();
  418. $filter = array(
  419. 'post_id' => $post_id,
  420. 'post_type' => get_post_type($post_id),
  421. );
  422. $filtered = array();
  423. foreach($field_groups as $field_group){
  424. // Deleted field group
  425. if(!isset($field_group['location']))
  426. continue;
  427. // Force active
  428. $field_group['active'] = true;
  429. if(acf_get_field_group_visibility($field_group, $filter)){
  430. $filtered[] = $field_group;
  431. }
  432. }
  433. $field_groups = $filtered;
  434. }
  435. }
  436. //load fields based on field groups
  437. if(!empty($field_groups)){
  438. foreach($field_groups as $field_group){
  439. $field_group_fields = acf_get_fields($field_group);
  440. if(!empty($field_group_fields)){
  441. foreach(array_keys($field_group_fields) as $i){
  442. $fields[] = acf_extract_var($field_group_fields, $i);
  443. }
  444. }
  445. }
  446. }
  447. acf_render_fields($fields, acf_uniqid('acfe_form'), $args['field_el'], $args['instruction_placement']);
  448. }
  449. // html after fields
  450. echo $args['html_after_fields'];
  451. ?>
  452. </div>
  453. <?php if($args['form_submit']): ?>
  454. <div class="acf-form-submit">
  455. <?php printf($args['html_submit_button'], $args['submit_value']); ?>
  456. <?php echo $args['html_submit_spinner']; ?>
  457. </div>
  458. <?php endif; ?>
  459. <?php do_action('acfe/form/render/after_fields'); ?>
  460. </<?php echo $wrapper; ?>>
  461. <?php do_action('acfe/form/render/after_form'); ?>
  462. <?php
  463. if($is_dynamic_preview){
  464. remove_filter('acf/prepare_field', array($this, 'disable_fields'));
  465. }
  466. return false;
  467. }
  468. function add_shortcode($atts){
  469. $atts = shortcode_atts(array(
  470. 'name' => false,
  471. 'id' => false,
  472. 'ID' => false,
  473. ), $atts, 'acfe_form');
  474. if(!empty($atts['name'])){
  475. ob_start();
  476. acfe_form($atts['name']);
  477. return ob_get_clean();
  478. }
  479. if(!empty($atts['id'])){
  480. ob_start();
  481. acfe_form($atts['id']);
  482. return ob_get_clean();
  483. }
  484. if(!empty($atts['ID'])){
  485. ob_start();
  486. acfe_form($atts['ID']);
  487. return ob_get_clean();
  488. }
  489. return false;
  490. }
  491. function disable_fields($field){
  492. $field['name'] = '';
  493. $field['required'] = false;
  494. return $field;
  495. }
  496. }
  497. acf_new_instance('acfe_form_front');
  498. endif;
  499. function acfe_form($args = array()){
  500. acf_get_instance('acfe_form_front')->render_form($args);
  501. }