form-front.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit; // Exit if accessed directly
  4. }
  5. if ( ! class_exists( 'acf_form_front' ) ) :
  6. class acf_form_front {
  7. /** @var array An array of registered form settings */
  8. private $forms = array();
  9. /** @var array An array of default fields */
  10. public $fields = array();
  11. /*
  12. * __construct
  13. *
  14. * This function will setup the class functionality
  15. *
  16. * @type function
  17. * @date 5/03/2014
  18. * @since 5.0.0
  19. *
  20. * @param n/a
  21. * @return n/a
  22. */
  23. function __construct() {
  24. // vars
  25. $this->fields = array(
  26. '_post_title' => array(
  27. 'prefix' => 'acf',
  28. 'name' => '_post_title',
  29. 'key' => '_post_title',
  30. 'label' => __( 'Title', 'acf' ),
  31. 'type' => 'text',
  32. 'required' => true,
  33. ),
  34. '_post_content' => array(
  35. 'prefix' => 'acf',
  36. 'name' => '_post_content',
  37. 'key' => '_post_content',
  38. 'label' => __( 'Content', 'acf' ),
  39. 'type' => 'wysiwyg',
  40. ),
  41. '_validate_email' => array(
  42. 'prefix' => 'acf',
  43. 'name' => '_validate_email',
  44. 'key' => '_validate_email',
  45. 'label' => __( 'Validate Email', 'acf' ),
  46. 'type' => 'text',
  47. 'value' => '',
  48. 'wrapper' => array( 'style' => 'display:none !important;' ),
  49. ),
  50. );
  51. // actions
  52. add_action( 'acf/validate_save_post', array( $this, 'validate_save_post' ), 1 );
  53. // filters
  54. add_filter( 'acf/pre_save_post', array( $this, 'pre_save_post' ), 5, 2 );
  55. }
  56. /*
  57. * validate_form
  58. *
  59. * description
  60. *
  61. * @type function
  62. * @date 28/2/17
  63. * @since 5.5.8
  64. *
  65. * @param $post_id (int)
  66. * @return $post_id (int)
  67. */
  68. function validate_form( $args ) {
  69. // defaults
  70. // Todo: Allow message and button text to be generated by CPT settings.
  71. $args = wp_parse_args(
  72. $args,
  73. array(
  74. 'id' => 'acf-form',
  75. 'post_id' => false,
  76. 'new_post' => false,
  77. 'field_groups' => false,
  78. 'fields' => false,
  79. 'post_title' => false,
  80. 'post_content' => false,
  81. 'form' => true,
  82. 'form_attributes' => array(),
  83. 'return' => add_query_arg( 'updated', 'true', acf_get_current_url() ),
  84. 'html_before_fields' => '',
  85. 'html_after_fields' => '',
  86. 'submit_value' => __( 'Update', 'acf' ),
  87. 'updated_message' => __( 'Post updated', 'acf' ),
  88. 'label_placement' => 'top',
  89. 'instruction_placement' => 'label',
  90. 'field_el' => 'div',
  91. 'uploader' => 'wp',
  92. 'honeypot' => true,
  93. 'html_updated_message' => '<div id="message" class="updated"><p>%s</p></div>', // 5.5.10
  94. 'html_submit_button' => '<input type="submit" class="acf-button button button-primary button-large" value="%s" />', // 5.5.10
  95. 'html_submit_spinner' => '<span class="acf-spinner"></span>', // 5.5.10
  96. 'kses' => true, // 5.6.5
  97. )
  98. );
  99. $args['form_attributes'] = wp_parse_args(
  100. $args['form_attributes'],
  101. array(
  102. 'id' => $args['id'],
  103. 'class' => 'acf-form',
  104. 'action' => '',
  105. 'method' => 'post',
  106. )
  107. );
  108. // filter post_id
  109. $args['post_id'] = acf_get_valid_post_id( $args['post_id'] );
  110. // new post?
  111. if ( $args['post_id'] === 'new_post' ) {
  112. $args['new_post'] = wp_parse_args(
  113. $args['new_post'],
  114. array(
  115. 'post_type' => 'post',
  116. 'post_status' => 'draft',
  117. )
  118. );
  119. }
  120. // filter
  121. $args = apply_filters( 'acf/validate_form', $args );
  122. // return
  123. return $args;
  124. }
  125. /*
  126. * add_form
  127. *
  128. * description
  129. *
  130. * @type function
  131. * @date 28/2/17
  132. * @since 5.5.8
  133. *
  134. * @param $post_id (int)
  135. * @return $post_id (int)
  136. */
  137. function add_form( $args = array() ) {
  138. // validate
  139. $args = $this->validate_form( $args );
  140. // append
  141. $this->forms[ $args['id'] ] = $args;
  142. }
  143. /*
  144. * get_form
  145. *
  146. * description
  147. *
  148. * @type function
  149. * @date 28/2/17
  150. * @since 5.5.8
  151. *
  152. * @param $post_id (int)
  153. * @return $post_id (int)
  154. */
  155. function get_form( $id = '' ) {
  156. // bail early if not set
  157. if ( ! isset( $this->forms[ $id ] ) ) {
  158. return false;
  159. }
  160. // return
  161. return $this->forms[ $id ];
  162. }
  163. /*
  164. * validate_save_post
  165. *
  166. * This function will validate fields from the above array
  167. *
  168. * @type function
  169. * @date 7/09/2016
  170. * @since 5.4.0
  171. *
  172. * @param $post_id (int)
  173. * @return $post_id (int)
  174. */
  175. function validate_save_post() {
  176. // register field if isset in $_POST
  177. foreach ( $this->fields as $k => $field ) {
  178. // bail early if no in $_POST
  179. if ( ! isset( $_POST['acf'][ $k ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
  180. continue;
  181. }
  182. // register
  183. acf_add_local_field( $field );
  184. }
  185. // honeypot
  186. if ( ! empty( $_POST['acf']['_validate_email'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Data not used; presence indicates spam.
  187. acf_add_validation_error( '', __( 'Spam Detected', 'acf' ) );
  188. }
  189. }
  190. /*
  191. * pre_save_post
  192. *
  193. * description
  194. *
  195. * @type function
  196. * @date 7/09/2016
  197. * @since 5.4.0
  198. *
  199. * @param $post_id (int)
  200. * @return $post_id (int)
  201. */
  202. function pre_save_post( $post_id, $form ) {
  203. // vars
  204. $save = array(
  205. 'ID' => 0,
  206. );
  207. // determine save data
  208. if ( is_numeric( $post_id ) ) {
  209. // update post
  210. $save['ID'] = $post_id;
  211. } elseif ( $post_id == 'new_post' ) {
  212. // merge in new post data
  213. $save = array_merge( $save, $form['new_post'] );
  214. } else {
  215. // not post
  216. return $post_id;
  217. }
  218. // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified in check_submit_form().
  219. // save post_title
  220. if ( isset( $_POST['acf']['_post_title'] ) ) {
  221. $save['post_title'] = acf_extract_var( $_POST['acf'], '_post_title' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved.
  222. }
  223. // save post_content
  224. if ( isset( $_POST['acf']['_post_content'] ) ) {
  225. $save['post_content'] = acf_extract_var( $_POST['acf'], '_post_content' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved.
  226. }
  227. // phpcs:enable WordPress.Security.NonceVerification.Missing
  228. // honeypot
  229. if ( ! empty( $_POST['acf']['_validate_email'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Data not used; presence indicates spam.
  230. return false;
  231. }
  232. // validate
  233. if ( count( $save ) == 1 ) {
  234. return $post_id;
  235. }
  236. // save
  237. if ( $save['ID'] ) {
  238. wp_update_post( $save );
  239. } else {
  240. $post_id = wp_insert_post( $save );
  241. }
  242. // return
  243. return $post_id;
  244. }
  245. /*
  246. * enqueue
  247. *
  248. * This function will enqueue a form
  249. *
  250. * @type function
  251. * @date 7/09/2016
  252. * @since 5.4.0
  253. *
  254. * @param $post_id (int)
  255. * @return $post_id (int)
  256. */
  257. function enqueue_form() {
  258. // check
  259. $this->check_submit_form();
  260. // load acf scripts
  261. acf_enqueue_scripts();
  262. }
  263. /*
  264. * check_submit_form
  265. *
  266. * This function will maybe submit form data
  267. *
  268. * @type function
  269. * @date 3/3/17
  270. * @since 5.5.10
  271. *
  272. * @param n/a
  273. * @return n/a
  274. */
  275. function check_submit_form() {
  276. // Verify nonce.
  277. if ( ! acf_verify_nonce( 'acf_form' ) ) {
  278. return false;
  279. }
  280. // Confirm form was submit.
  281. if ( ! isset( $_POST['_acf_form'] ) ) {
  282. return false;
  283. }
  284. // Load registered form using id.
  285. $form = $this->get_form( acf_sanitize_request_args( $_POST['_acf_form'] ) );
  286. // Fallback to encrypted JSON.
  287. if ( ! $form ) {
  288. $form = json_decode( acf_decrypt( sanitize_text_field( $_POST['_acf_form'] ) ), true );
  289. if ( ! $form ) {
  290. return false;
  291. }
  292. }
  293. // Run kses on all $_POST data.
  294. if ( $form['kses'] && isset( $_POST['acf'] ) ) {
  295. $_POST['acf'] = wp_kses_post_deep( $_POST['acf'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- False positive.
  296. }
  297. // Validate data and show errors.
  298. // Todo: Return WP_Error and show above form, keeping input values.
  299. acf_validate_save_post( true );
  300. // Submit form.
  301. $this->submit_form( $form );
  302. }
  303. /*
  304. * submit_form
  305. *
  306. * This function will submit form data
  307. *
  308. * @type function
  309. * @date 3/3/17
  310. * @since 5.5.10
  311. *
  312. * @param n/a
  313. * @return n/a
  314. */
  315. function submit_form( $form ) {
  316. // filter
  317. $form = apply_filters( 'acf/pre_submit_form', $form );
  318. // vars
  319. $post_id = acf_maybe_get( $form, 'post_id', 0 );
  320. // add global for backwards compatibility
  321. $GLOBALS['acf_form'] = $form;
  322. // allow for custom save
  323. $post_id = apply_filters( 'acf/pre_save_post', $post_id, $form );
  324. // save
  325. acf_save_post( $post_id );
  326. // restore form (potentially modified)
  327. $form = $GLOBALS['acf_form'];
  328. // action
  329. do_action( 'acf/submit_form', $form, $post_id );
  330. // vars
  331. $return = acf_maybe_get( $form, 'return', '' );
  332. // redirect
  333. if ( $return ) {
  334. // update %placeholders%
  335. $return = str_replace( '%post_id%', $post_id, $return );
  336. $return = str_replace( '%post_url%', get_permalink( $post_id ), $return );
  337. // redirect
  338. wp_redirect( $return );
  339. exit;
  340. }
  341. }
  342. /*
  343. * render
  344. *
  345. * description
  346. *
  347. * @type function
  348. * @date 7/09/2016
  349. * @since 5.4.0
  350. *
  351. * @param $post_id (int)
  352. * @return $post_id (int)
  353. */
  354. function render_form( $args = array() ) {
  355. // Vars.
  356. $is_registered = false;
  357. $field_groups = array();
  358. $fields = array();
  359. // Allow form settings to be directly provided.
  360. if ( is_array( $args ) ) {
  361. $args = $this->validate_form( $args );
  362. // Otherwise, lookup registered form.
  363. } else {
  364. $is_registered = true;
  365. $args = $this->get_form( $args );
  366. if ( ! $args ) {
  367. return false;
  368. }
  369. }
  370. // Extract vars.
  371. $post_id = $args['post_id'];
  372. // Prevent ACF from loading values for "new_post".
  373. if ( $post_id === 'new_post' ) {
  374. $post_id = false;
  375. }
  376. // Set uploader type.
  377. acf_update_setting( 'uploader', $args['uploader'] );
  378. // Register local fields.
  379. foreach ( $this->fields as $k => $field ) {
  380. acf_add_local_field( $field );
  381. }
  382. // Append post_title field.
  383. if ( $args['post_title'] ) {
  384. $_post_title = acf_get_field( '_post_title' );
  385. $_post_title['value'] = $post_id ? get_post_field( 'post_title', $post_id ) : '';
  386. $fields[] = $_post_title;
  387. }
  388. // Append post_content field.
  389. if ( $args['post_content'] ) {
  390. $_post_content = acf_get_field( '_post_content' );
  391. $_post_content['value'] = $post_id ? get_post_field( 'post_content', $post_id ) : '';
  392. $fields[] = $_post_content;
  393. }
  394. // Load specific fields.
  395. if ( $args['fields'] ) {
  396. // Lookup fields using $strict = false for better compatibility with field names.
  397. foreach ( $args['fields'] as $selector ) {
  398. $fields[] = acf_maybe_get_field( $selector, $post_id, false );
  399. }
  400. // Load specific field groups.
  401. } elseif ( $args['field_groups'] ) {
  402. foreach ( $args['field_groups'] as $selector ) {
  403. $field_groups[] = acf_get_field_group( $selector );
  404. }
  405. // Load fields for the given "new_post" args.
  406. } elseif ( $args['post_id'] == 'new_post' ) {
  407. $field_groups = acf_get_field_groups( $args['new_post'] );
  408. // Load fields for the given "post_id" arg.
  409. } else {
  410. $field_groups = acf_get_field_groups(
  411. array(
  412. 'post_id' => $args['post_id'],
  413. )
  414. );
  415. }
  416. // load fields from the found field groups.
  417. if ( $field_groups ) {
  418. foreach ( $field_groups as $field_group ) {
  419. $_fields = acf_get_fields( $field_group );
  420. if ( $_fields ) {
  421. foreach ( $_fields as $_field ) {
  422. $fields[] = $_field;
  423. }
  424. }
  425. }
  426. }
  427. // Add honeypot field.
  428. if ( $args['honeypot'] ) {
  429. $fields[] = acf_get_field( '_validate_email' );
  430. }
  431. // Display updated_message
  432. if ( ! empty( $_GET['updated'] ) && $args['updated_message'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used as a flag; data not used.
  433. printf( $args['html_updated_message'], $args['updated_message'] );
  434. }
  435. // display form
  436. if ( $args['form'] ) : ?>
  437. <form <?php echo acf_esc_attrs( $args['form_attributes'] ); ?>>
  438. <?php
  439. endif;
  440. // Render hidde form data.
  441. acf_form_data(
  442. array(
  443. 'screen' => 'acf_form',
  444. 'post_id' => $args['post_id'],
  445. 'form' => $is_registered ? $args['id'] : acf_encrypt( json_encode( $args ) ),
  446. )
  447. );
  448. ?>
  449. <div class="acf-fields acf-form-fields -<?php echo esc_attr( $args['label_placement'] ); ?>">
  450. <?php echo $args['html_before_fields']; ?>
  451. <?php acf_render_fields( $fields, $post_id, $args['field_el'], $args['instruction_placement'] ); ?>
  452. <?php echo $args['html_after_fields']; ?>
  453. </div>
  454. <?php if ( $args['form'] ) : ?>
  455. <div class="acf-form-submit">
  456. <?php printf( $args['html_submit_button'], $args['submit_value'] ); ?>
  457. <?php echo $args['html_submit_spinner']; ?>
  458. </div>
  459. </form>
  460. <?php endif;
  461. }
  462. }
  463. // initialize
  464. acf()->form_front = new acf_form_front();
  465. endif; // class_exists check
  466. /*
  467. * Functions
  468. *
  469. * alias of acf()->form->functions
  470. *
  471. * @type function
  472. * @date 11/06/2014
  473. * @since 5.0.0
  474. *
  475. * @param n/a
  476. * @return n/a
  477. */
  478. function acf_form_head() {
  479. acf()->form_front->enqueue_form();
  480. }
  481. function acf_form( $args = array() ) {
  482. acf()->form_front->render_form( $args );
  483. }
  484. function acf_get_form( $id = '' ) {
  485. return acf()->form_front->get_form( $id );
  486. }
  487. function acf_register_form( $args ) {
  488. acf()->form_front->add_form( $args );
  489. }
  490. ?>