Нет описания

class-frontend.php 57KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891
  1. <?php
  2. /**
  3. * Form front-end rendering.
  4. *
  5. * @since 1.0.0
  6. */
  7. class WPForms_Frontend {
  8. /**
  9. * Store form data to be referenced later.
  10. *
  11. * @since 1.0.0
  12. * @var array
  13. */
  14. public $forms;
  15. /**
  16. * Store information for multi-page forms.
  17. *
  18. * Forms that do not contain pages return false, otherwise returns an array
  19. * that contains the number of total pages and page counter used when
  20. * displaying pagebreak fields.
  21. *
  22. * @since 1.3.7
  23. *
  24. * @var array
  25. */
  26. public $pages = false;
  27. /**
  28. * Store a form confirmation message.
  29. *
  30. * @since 1.4.8
  31. * @todo Remove in favor of \WPForms_Process::$confirmation_message().
  32. *
  33. * @var string
  34. */
  35. public $confirmation_message = '';
  36. /**
  37. * If the active form confirmation should auto scroll.
  38. *
  39. * @since 1.4.9
  40. *
  41. * @var bool
  42. */
  43. public $confirmation_message_scroll = false;
  44. /**
  45. * Whether ChoiceJS library has already been enqueued on the front end.
  46. * This lib is used in different fields that can enqueue it separately,
  47. * and we use this property to avoid config duplication.
  48. *
  49. * @since 1.6.3
  50. *
  51. * @var bool
  52. */
  53. public $is_choicesjs_enqueued = false;
  54. /**
  55. * Primary class constructor.
  56. *
  57. * @since 1.0.0
  58. */
  59. public function __construct() {
  60. $this->forms = [];
  61. // Filters.
  62. add_filter( 'amp_skip_post', [ $this, 'amp_skip_post' ] );
  63. // Actions.
  64. add_action( 'wpforms_frontend_output_success', [ $this, 'confirmation' ], 10, 3 );
  65. add_action( 'wpforms_frontend_output', [ $this, 'head' ], 5, 5 );
  66. add_action( 'wpforms_frontend_output', [ $this, 'fields' ], 10, 5 );
  67. add_action( 'wpforms_display_field_before', [ $this, 'field_container_open' ], 5, 2 );
  68. add_action( 'wpforms_display_field_before', [ $this, 'field_label' ], 15, 2 );
  69. add_action( 'wpforms_display_field_before', [ $this, 'field_description' ], 20, 2 );
  70. add_action( 'wpforms_display_field_after', [ $this, 'field_error' ], 3, 2 );
  71. add_action( 'wpforms_display_field_after', [ $this, 'field_description' ], 5, 2 );
  72. add_action( 'wpforms_display_field_after', [ $this, 'field_container_close' ], 15, 2 );
  73. add_action( 'wpforms_frontend_output', [ $this, 'recaptcha' ], 20, 5 );
  74. add_action( 'wpforms_frontend_output', [ $this, 'foot' ], 25, 5 );
  75. add_action( 'wp_enqueue_scripts', [ $this, 'assets_header' ] );
  76. add_action( 'wp_enqueue_scripts', [ $this, 'recaptcha_noconflict' ], 9999 );
  77. add_action( 'wp_footer', [ $this, 'missing_assets_error_js' ] );
  78. add_action( 'wp_footer', [ $this, 'assets_footer' ], 15 );
  79. add_action( 'wp_footer', [ $this, 'recaptcha_noconflict' ], 19 );
  80. add_action( 'wp_footer', [ $this, 'footer_end' ], 99 );
  81. // Register shortcode.
  82. add_shortcode( 'wpforms', [ $this, 'shortcode' ] );
  83. }
  84. /**
  85. * Get the amp-state ID for a given form.
  86. *
  87. * @param int $form_id Form ID.
  88. * @return string State ID.
  89. */
  90. protected function get_form_amp_state_id( $form_id ) {
  91. return sprintf( 'wpforms_form_state_%d', $form_id );
  92. }
  93. /**
  94. * Disable AMP if query param is detected.
  95. *
  96. * This allows the full form to be accessible for Pro users or sites
  97. * that do not have SSL.
  98. *
  99. * @since 1.5.3
  100. *
  101. * @param bool $skip Skip AMP mode, display full post.
  102. *
  103. * @return bool
  104. */
  105. public function amp_skip_post( $skip ) {
  106. return isset( $_GET['nonamp'] ) ? true : $skip;
  107. }
  108. /**
  109. * Primary function to render a form on the frontend.
  110. *
  111. * @since 1.0.0
  112. *
  113. * @param int $id Form ID.
  114. * @param bool $title Whether to display form title.
  115. * @param bool $description Whether to display form description.
  116. */
  117. public function output( $id, $title = false, $description = false ) {
  118. if ( empty( $id ) ) {
  119. return;
  120. }
  121. // Grab the form data, if not found then we bail.
  122. $form = wpforms()->form->get( (int) $id );
  123. if ( empty( $form ) ) {
  124. return;
  125. }
  126. // Basic information.
  127. $form_data = apply_filters( 'wpforms_frontend_form_data', wpforms_decode( $form->post_content ) );
  128. $form_id = absint( $form->ID );
  129. $settings = $form_data['settings'];
  130. $action = esc_url_raw( remove_query_arg( 'wpforms' ) );
  131. $classes = (int) wpforms_setting( 'disable-css', '1' ) === 1 ? [ 'wpforms-container-full' ] : [];
  132. $errors = empty( wpforms()->process->errors[ $form_id ] ) ? array() : wpforms()->process->errors[ $form_id ];
  133. $title = filter_var( $title, FILTER_VALIDATE_BOOLEAN );
  134. $description = filter_var( $description, FILTER_VALIDATE_BOOLEAN );
  135. // If the form does not contain any fields - do not proceed.
  136. if ( empty( $form_data['fields'] ) ) {
  137. echo '<!-- WPForms: no fields, form hidden -->';
  138. return;
  139. }
  140. // We need to stop output processing in case we are on AMP page.
  141. if ( wpforms_is_amp( false ) && ( ! current_theme_supports( 'amp' ) || apply_filters( 'wpforms_amp_pro', wpforms()->pro ) || ! is_ssl() || ! defined( 'AMP__VERSION' ) || version_compare( AMP__VERSION, '1.2', '<' ) ) ) {
  142. $full_page_url = home_url( add_query_arg( 'nonamp', '1' ) . '#wpforms-' . absint( $form->ID ) );
  143. /**
  144. * Allow modifying the text or url for the full page on the AMP pages.
  145. *
  146. * @since 1.4.1.1
  147. * @since 1.7.1 Added $form_id, $full_page_url, and $form_data arguments.
  148. *
  149. * @param int $form_id Form id.
  150. * @param array $form_data Form data and settings.
  151. *
  152. * @return string
  153. */
  154. $text = (string) apply_filters(
  155. 'wpforms_frontend_shortcode_amp_text',
  156. sprintf( /* translators: %s - URL to a non-amp version of a page with the form. */
  157. __( '<a href="%s">Go to the full page</a> to view and submit the form.', 'wpforms-lite' ),
  158. esc_url( $full_page_url )
  159. ),
  160. $form_id,
  161. $full_page_url,
  162. $form_data
  163. );
  164. printf(
  165. '<p class="wpforms-shortcode-amp-text">%s</p>',
  166. wp_kses_post( $text )
  167. );
  168. return;
  169. }
  170. // Add url query var wpforms_form_id to track post_max_size overflows.
  171. if ( in_array( 'file-upload', wp_list_pluck( $form_data['fields'], 'type' ), true ) ) {
  172. $action = add_query_arg( 'wpforms_form_id', $form_id, $action );
  173. }
  174. // Before output hook.
  175. do_action( 'wpforms_frontend_output_before', $form_data, $form );
  176. // Check for return hash.
  177. if (
  178. ! empty( $_GET['wpforms_return'] ) &&
  179. wpforms()->process->valid_hash &&
  180. absint( wpforms()->process->form_data['id'] ) === $form_id
  181. ) {
  182. do_action( 'wpforms_frontend_output_success', wpforms()->process->form_data, wpforms()->process->fields, wpforms()->process->entry_id );
  183. wpforms_debug_data( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
  184. return;
  185. }
  186. // Check for error-free completed form.
  187. if (
  188. empty( $errors ) &&
  189. ! empty( $form_data ) &&
  190. ! empty( $_POST['wpforms']['id'] ) &&
  191. absint( $_POST['wpforms']['id'] ) === $form_id
  192. ) {
  193. do_action( 'wpforms_frontend_output_success', $form_data, false, false );
  194. wpforms_debug_data( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
  195. return;
  196. }
  197. // Allow filter to return early if some condition is not met.
  198. if ( ! apply_filters( 'wpforms_frontend_load', true, $form_data, null ) ) {
  199. do_action( 'wpforms_frontend_not_loaded', $form_data, $form );
  200. return;
  201. }
  202. // All checks have passed, so calculate multi-page details for the form.
  203. $pages = wpforms_get_pagebreak_details( $form_data );
  204. if ( $pages ) {
  205. $this->pages = $pages;
  206. } else {
  207. $this->pages = false;
  208. }
  209. // Allow final action to be customized - 3rd param ($form) has been deprecated.
  210. $action = apply_filters( 'wpforms_frontend_form_action', $action, $form_data, null );
  211. // Allow form container classes to be filtered and user defined classes.
  212. $classes = apply_filters( 'wpforms_frontend_container_class', $classes, $form_data );
  213. if ( ! empty( $settings['form_class'] ) ) {
  214. $classes = array_merge( $classes, explode( ' ', $settings['form_class'] ) );
  215. }
  216. $classes = wpforms_sanitize_classes( $classes, true );
  217. $form_classes = array( 'wpforms-validate', 'wpforms-form' );
  218. if ( ! empty( $form_data['settings']['ajax_submit'] ) && ! wpforms_is_amp() ) {
  219. $form_classes[] = 'wpforms-ajax-form';
  220. }
  221. $form_atts = array(
  222. 'id' => sprintf( 'wpforms-form-%d', absint( $form_id ) ),
  223. 'class' => $form_classes,
  224. 'data' => array(
  225. 'formid' => absint( $form_id ),
  226. ),
  227. 'atts' => array(
  228. 'method' => 'post',
  229. 'enctype' => 'multipart/form-data',
  230. 'action' => esc_url( $action ),
  231. ),
  232. );
  233. if ( wpforms_is_amp() ) {
  234. // Set submitting state.
  235. if ( ! isset( $form_atts['atts']['on'] ) ) {
  236. $form_atts['atts']['on'] = '';
  237. } else {
  238. $form_atts['atts']['on'] .= ';';
  239. }
  240. $form_atts['atts']['on'] .= sprintf(
  241. 'submit:AMP.setState( %1$s ); submit-success:AMP.setState( %2$s ); submit-error:AMP.setState( %2$s );',
  242. wp_json_encode(
  243. array(
  244. $this->get_form_amp_state_id( $form_id ) => array(
  245. 'submitting' => true,
  246. ),
  247. )
  248. ),
  249. wp_json_encode(
  250. array(
  251. $this->get_form_amp_state_id( $form_id ) => array(
  252. 'submitting' => false,
  253. ),
  254. )
  255. )
  256. );
  257. // Upgrade the form to be an amp-form to avoid sanitizer conversion.
  258. if ( isset( $form_atts['atts']['action'] ) ) {
  259. $form_atts['atts']['action-xhr'] = $form_atts['atts']['action'];
  260. unset( $form_atts['atts']['action'] );
  261. $form_atts['atts']['verify-xhr'] = $form_atts['atts']['action-xhr'];
  262. }
  263. }
  264. $form_atts = apply_filters( 'wpforms_frontend_form_atts', $form_atts, $form_data );
  265. // Begin to build the output.
  266. do_action( 'wpforms_frontend_output_container_before', $form_data, $form );
  267. printf( '<div class="wpforms-container %s" id="wpforms-%d">', esc_attr( $classes ), absint( $form_id ) );
  268. do_action( 'wpforms_frontend_output_form_before', $form_data, $form );
  269. echo '<form ' . wpforms_html_attributes( $form_atts['id'], $form_atts['class'], $form_atts['data'], $form_atts['atts'] ) . '>';
  270. if ( wpforms_is_amp() ) {
  271. $state = array(
  272. 'submitting' => false,
  273. );
  274. printf(
  275. '<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
  276. $this->get_form_amp_state_id( $form_id ),
  277. wp_json_encode( $state )
  278. );
  279. }
  280. do_action( 'wpforms_frontend_output', $form_data, null, $title, $description, $errors );
  281. echo '</form>';
  282. do_action( 'wpforms_frontend_output_form_after', $form_data, $form );
  283. echo '</div> <!-- .wpforms-container -->';
  284. do_action( 'wpforms_frontend_output_container_after', $form_data, $form );
  285. // Add form to class property that tracks all forms in a page.
  286. $this->forms[ $form_id ] = $form_data;
  287. // Optional debug information if WPFORMS_DEBUG is defined.
  288. wpforms_debug_data( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
  289. // After output hook.
  290. do_action( 'wpforms_frontend_output_after', $form_data, $form );
  291. }
  292. /**
  293. * Display form confirmation message.
  294. *
  295. * @since 1.0.0
  296. *
  297. * @param array $form_data Form data and settings.
  298. * @param array $fields Sanitized field data.
  299. * @param int $entry_id Entry id.
  300. */
  301. public function confirmation( $form_data, $fields = array(), $entry_id = 0 ) {
  302. $class = intval( wpforms_setting( 'disable-css', '1' ) ) === 1 ? 'wpforms-confirmation-container-full' : 'wpforms-confirmation-container';
  303. // In AMP, just print template.
  304. if ( wpforms_is_amp() ) {
  305. $this->assets_confirmation();
  306. printf( '<div submit-success><template type="amp-mustache"><div class="%s {{#redirecting}}wpforms-redirection-message{{/redirecting}}">{{{message}}}</div></template></div>', esc_attr( $class ) );
  307. return;
  308. }
  309. if ( empty( $fields ) ) {
  310. $fields = ! empty( $_POST['wpforms']['complete'] ) ? $_POST['wpforms']['complete'] : array();
  311. }
  312. if ( empty( $entry_id ) ) {
  313. $entry_id = ! empty( $_POST['wpforms']['entry_id'] ) ? $_POST['wpforms']['entry_id'] : 0;
  314. }
  315. $confirmation = wpforms()->get( 'process' )->get_current_confirmation();
  316. $confirmation_message = wpforms()->get( 'process' )->get_confirmation_message( $form_data, $fields, $entry_id );
  317. // Only display if a confirmation message has been configured.
  318. if ( empty( $confirmation ) || empty( $confirmation_message ) ) {
  319. return;
  320. }
  321. // Load confirmation specific assets.
  322. $this->assets_confirmation();
  323. /**
  324. * Fires once before the confirmation message.
  325. *
  326. * @since 1.6.9
  327. *
  328. * @param array $confirmation Current confirmation data.
  329. * @param array $form_data Form data and settings.
  330. * @param array $fields Sanitized field data.
  331. * @param int $entry_id Entry id.
  332. */
  333. do_action( 'wpforms_frontend_confirmation_message_before', $confirmation, $form_data, $fields, $entry_id );
  334. $class .= $this->confirmation_message_scroll ? ' wpforms-confirmation-scroll' : '';
  335. printf(
  336. '<div class="%s" id="wpforms-confirmation-%d">%s</div>',
  337. $class,
  338. absint( $form_data['id'] ),
  339. $confirmation_message
  340. );
  341. /**
  342. * Fires once after the confirmation message.
  343. *
  344. * @since 1.6.9
  345. *
  346. * @param array $confirmation Current confirmation data.
  347. * @param array $form_data Form data and settings.
  348. * @param array $fields Sanitized field data.
  349. * @param int $entry_id Entry id.
  350. */
  351. do_action( 'wpforms_frontend_confirmation_message_after', $confirmation, $form_data, $fields, $entry_id );
  352. }
  353. /**
  354. * Form head area, for displaying form title and description if enabled.
  355. *
  356. * @since 1.0.0
  357. *
  358. * @param array $form_data Form data and settings.
  359. * @param null $deprecated Deprecated in v1.3.7, previously was $form object.
  360. * @param bool $title Whether to display form title.
  361. * @param bool $description Whether to display form description.
  362. * @param array $errors List of all errors filled in WPForms_Process::process().
  363. */
  364. public function head( $form_data, $deprecated, $title, $description, $errors ) {
  365. $settings = $form_data['settings'];
  366. // Output title and/or description.
  367. if ( true === $title || true === $description ) {
  368. echo '<div class="wpforms-head-container">';
  369. if ( true === $title && ! empty( $settings['form_title'] ) ) {
  370. echo '<div class="wpforms-title">' . esc_html( $settings['form_title'] ) . '</div>';
  371. }
  372. if ( true === $description && ! empty( $settings['form_desc'] ) ) {
  373. echo '<div class="wpforms-description">';
  374. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
  375. echo wpforms_process_smart_tags( $settings['form_desc'], $form_data );
  376. echo '</div>';
  377. }
  378. echo '</div>';
  379. }
  380. // Output <noscript> error message.
  381. $noscript_msg = apply_filters( 'wpforms_frontend_noscript_error_message', __( 'Please enable JavaScript in your browser to complete this form.', 'wpforms-lite' ), $form_data );
  382. if ( ! empty( $noscript_msg ) && ! empty( $form_data['fields'] ) && ! wpforms_is_amp() ) {
  383. echo '<noscript class="wpforms-error-noscript">' . esc_html( $noscript_msg ) . '</noscript>';
  384. }
  385. // Output header errors if they exist.
  386. if ( ! empty( $errors['header'] ) ) {
  387. $this->form_error( 'header', $errors['header'] );
  388. }
  389. }
  390. /**
  391. * Form field area.
  392. *
  393. * @since 1.0.0
  394. *
  395. * @param array $form_data Form data and settings.
  396. * @param null $deprecated Deprecated in v1.3.7, previously was $form object.
  397. * @param bool $title Whether to display form title.
  398. * @param bool $description Whether to display form description.
  399. * @param array $errors List of all errors filled in WPForms_Process::process().
  400. */
  401. public function fields( $form_data, $deprecated, $title, $description, $errors ) {
  402. // Obviously we need to have form fields to proceed.
  403. if ( empty( $form_data['fields'] ) ) {
  404. return;
  405. }
  406. // Form fields area.
  407. echo '<div class="wpforms-field-container">';
  408. /**
  409. * Core actions on this hook:
  410. * Priority / Description
  411. * 20 Pagebreak markup (open first page)
  412. */
  413. do_action( 'wpforms_display_fields_before', $form_data );
  414. // Loop through all the fields we have.
  415. foreach ( $form_data['fields'] as $field ) :
  416. if ( ! has_action( "wpforms_display_field_{$field['type']}" ) ) {
  417. continue;
  418. }
  419. /**
  420. *
  421. * Modify Field before render.
  422. *
  423. * @since 1.4.0
  424. *
  425. * @param array $field Current field.
  426. * @param array $form_data Form data and settings.
  427. */
  428. $field = apply_filters( 'wpforms_field_data', $field, $form_data );
  429. if ( empty( $field ) ) {
  430. continue;
  431. }
  432. // Get field attributes. Deprecated; Customizations should use
  433. // field properties instead.
  434. $attributes = $this->get_field_attributes( $field, $form_data );
  435. // Add properties to the field so it's available everywhere.
  436. $field['properties'] = $this->get_field_properties( $field, $form_data, $attributes );
  437. /**
  438. * Core actions on this hook:
  439. * Priority / Description
  440. * 5 Field opening container markup.
  441. * 15 Field label.
  442. * 20 Field description (depending on position).
  443. */
  444. do_action( 'wpforms_display_field_before', $field, $form_data );
  445. /**
  446. * Individual field classes use this hook to display the actual
  447. * field form elements.
  448. * See `field_display` methods in /includes/fields.
  449. */
  450. do_action( "wpforms_display_field_{$field['type']}", $field, $attributes, $form_data );
  451. /**
  452. * Core actions on this hook:
  453. * Priority / Description
  454. * 3 Field error messages.
  455. * 5 Field description (depending on position).
  456. * 15 Field closing container markup.
  457. * 20 Pagebreak markup (close previous page, open next)
  458. */
  459. do_action( 'wpforms_display_field_after', $field, $form_data );
  460. endforeach;
  461. /**
  462. * Core actions on this hook:
  463. * Priority / Description
  464. * 5 Pagebreak markup (close last page)
  465. */
  466. do_action( 'wpforms_display_fields_after', $form_data );
  467. echo '</div>';
  468. }
  469. /**
  470. * Return base attributes for a specific field. This is deprecated and
  471. * exists for backwards-compatibility purposes. Use field properties instead.
  472. *
  473. * @since 1.3.7
  474. *
  475. * @param array $field Field data and settings.
  476. * @param array $form_data Form data and settings.
  477. *
  478. * @return array
  479. */
  480. public function get_field_attributes( $field, $form_data ) {
  481. $form_id = absint( $form_data['id'] );
  482. $field_id = absint( $field['id'] );
  483. $attributes = array(
  484. 'field_class' => array( 'wpforms-field', 'wpforms-field-' . sanitize_html_class( $field['type'] ) ),
  485. 'field_id' => array( sprintf( 'wpforms-%d-field_%d-container', $form_id, $field_id ) ),
  486. 'field_style' => '',
  487. 'label_class' => array( 'wpforms-field-label' ),
  488. 'label_id' => '',
  489. 'description_class' => array( 'wpforms-field-description' ),
  490. 'description_id' => array(),
  491. 'input_id' => array( sprintf( 'wpforms-%d-field_%d', $form_id, $field_id ) ),
  492. 'input_class' => array(),
  493. 'input_data' => array(),
  494. );
  495. // Check user field defined classes.
  496. if ( ! empty( $field['css'] ) ) {
  497. $attributes['field_class'] = array_merge( $attributes['field_class'], wpforms_sanitize_classes( $field['css'], true ) );
  498. }
  499. // Check for input column layouts.
  500. if ( ! empty( $field['input_columns'] ) ) {
  501. if ( '2' === $field['input_columns'] ) {
  502. $attributes['field_class'][] = 'wpforms-list-2-columns';
  503. } elseif ( '3' === $field['input_columns'] ) {
  504. $attributes['field_class'][] = 'wpforms-list-3-columns';
  505. } elseif ( 'inline' === $field['input_columns'] ) {
  506. $attributes['field_class'][] = 'wpforms-list-inline';
  507. }
  508. }
  509. // Check label visibility.
  510. if ( ! empty( $field['label_hide'] ) ) {
  511. $attributes['label_class'][] = 'wpforms-label-hide';
  512. }
  513. // Check size.
  514. if ( ! empty( $field['size'] ) ) {
  515. $attributes['input_class'][] = 'wpforms-field-' . sanitize_html_class( $field['size'] );
  516. }
  517. // Check if required.
  518. if ( ! empty( $field['required'] ) ) {
  519. $attributes['input_class'][] = 'wpforms-field-required';
  520. }
  521. // Check if there are errors.
  522. if ( ! empty( wpforms()->process->errors[ $form_id ][ $field_id ] ) ) {
  523. $attributes['input_class'][] = 'wpforms-error';
  524. }
  525. // This filter is deprecated, filter the properties (below) instead.
  526. $attributes = apply_filters( 'wpforms_field_atts', $attributes, $field, $form_data );
  527. return $attributes;
  528. }
  529. /**
  530. * Return base properties for a specific field.
  531. *
  532. * @since 1.3.7
  533. *
  534. * @param array $field Field data and settings.
  535. * @param array $form_data Form data and settings.
  536. * @param array $attributes List of field attributes.
  537. *
  538. * @return array
  539. */
  540. public function get_field_properties( $field, $form_data, $attributes = array() ) {
  541. if ( empty( $attributes ) ) {
  542. $attributes = $this->get_field_attributes( $field, $form_data );
  543. }
  544. // This filter is for backwards compatibility purposes.
  545. $types = array( 'text', 'textarea', 'name', 'number', 'email', 'hidden', 'url', 'html', 'divider', 'password', 'phone', 'address', 'select', 'checkbox', 'radio' );
  546. if ( in_array( $field['type'], $types, true ) ) {
  547. $field = apply_filters( "wpforms_{$field['type']}_field_display", $field, $attributes, $form_data );
  548. } elseif ( 'credit-card' === $field['type'] ) {
  549. $field = apply_filters( 'wpforms_creditcard_field_display', $field, $attributes, $form_data );
  550. } elseif ( in_array( $field['type'], array( 'payment-multiple', 'payment-single', 'payment-checkbox' ), true ) ) {
  551. $filter_field_type = str_replace( '-', '_', $field['type'] );
  552. $field = apply_filters( 'wpforms_' . $filter_field_type . '_field_display', $field, $attributes, $form_data );
  553. }
  554. $form_id = absint( $form_data['id'] );
  555. $field_id = absint( $field['id'] );
  556. $error = ! empty( wpforms()->process->errors[ $form_id ][ $field_id ] ) ? wpforms()->process->errors[ $form_id ][ $field_id ] : '';
  557. $properties = array(
  558. 'container' => array(
  559. 'attr' => array(
  560. 'style' => $attributes['field_style'],
  561. ),
  562. 'class' => $attributes['field_class'],
  563. 'data' => array(),
  564. 'id' => implode( '', array_slice( $attributes['field_id'], 0 ) ),
  565. ),
  566. 'label' => array(
  567. 'attr' => array(
  568. 'for' => sprintf( 'wpforms-%d-field_%d', $form_id, $field_id ),
  569. ),
  570. 'class' => $attributes['label_class'],
  571. 'data' => array(),
  572. 'disabled' => ! empty( $field['label_disable'] ) ? true : false,
  573. 'hidden' => ! empty( $field['label_hide'] ) ? true : false,
  574. 'id' => $attributes['label_id'],
  575. 'required' => ! empty( $field['required'] ) ? true : false,
  576. 'value' => ! empty( $field['label'] ) ? $field['label'] : '',
  577. ),
  578. 'inputs' => array(
  579. 'primary' => array(
  580. 'attr' => array(
  581. 'name' => "wpforms[fields][{$field_id}]",
  582. 'value' => isset( $field['default_value'] ) ? wpforms_process_smart_tags( $field['default_value'], $form_data ) : '',
  583. 'placeholder' => isset( $field['placeholder'] ) ? $field['placeholder'] : '',
  584. ),
  585. 'class' => $attributes['input_class'],
  586. 'data' => $attributes['input_data'],
  587. 'id' => implode( array_slice( $attributes['input_id'], 0 ) ),
  588. 'required' => ! empty( $field['required'] ) ? 'required' : '',
  589. ),
  590. ),
  591. 'error' => array(
  592. 'attr' => array(
  593. 'for' => sprintf( 'wpforms-%d-field_%d', $form_id, $field_id ),
  594. ),
  595. 'class' => array( 'wpforms-error' ),
  596. 'data' => array(),
  597. 'id' => '',
  598. 'value' => $error,
  599. ),
  600. 'description' => array(
  601. 'attr' => array(),
  602. 'class' => $attributes['description_class'],
  603. 'data' => array(),
  604. 'id' => implode( '', array_slice( $attributes['description_id'], 0 ) ),
  605. 'position' => 'after',
  606. 'value' => ! empty( $field['description'] ) ? wpforms_process_smart_tags( $field['description'], $form_data ) : '',
  607. ),
  608. );
  609. $properties = apply_filters( "wpforms_field_properties_{$field['type']}", $properties, $field, $form_data );
  610. $properties = apply_filters( 'wpforms_field_properties', $properties, $field, $form_data );
  611. return $properties;
  612. }
  613. /**
  614. * Display the opening container markup for each field.
  615. *
  616. * @since 1.3.7
  617. *
  618. * @param array $field Field data and settings.
  619. * @param array $form_data Form data and settings.
  620. */
  621. public function field_container_open( $field, $form_data ) {
  622. $container = $field['properties']['container'];
  623. $container['data']['field-id'] = absint( $field['id'] );
  624. printf(
  625. '<div %s>',
  626. wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
  627. );
  628. }
  629. /**
  630. * Display the label for each field.
  631. *
  632. * @since 1.3.7
  633. *
  634. * @param array $field Field data and settings.
  635. * @param array $form_data Form data and settings.
  636. */
  637. public function field_label( $field, $form_data ) {
  638. $label = $field['properties']['label'];
  639. // If the label is empty or disabled don't proceed.
  640. if ( empty( $label['value'] ) || $label['disabled'] ) {
  641. return;
  642. }
  643. $required = $label['required'] ? wpforms_get_field_required_label() : '';
  644. printf( '<label %s>%s%s</label>',
  645. wpforms_html_attributes( $label['id'], $label['class'], $label['data'], $label['attr'] ),
  646. esc_html( $label['value'] ),
  647. $required
  648. );
  649. }
  650. /**
  651. * Display any errors for each field.
  652. *
  653. * @since 1.3.7
  654. *
  655. * @param array $field Field data and settings.
  656. * @param array $form_data Form data and settings.
  657. */
  658. public function field_error( $field, $form_data ) {
  659. $error = $field['properties']['error'];
  660. // If there are no errors don't proceed.
  661. // Advanced fields with multiple inputs (address, name, etc) errors
  662. // will be an array and are handled within the respective field class.
  663. if ( empty( $error['value'] ) || is_array( $error['value'] ) ) {
  664. return;
  665. }
  666. printf( '<label %s>%s</label>',
  667. wpforms_html_attributes( $error['id'], $error['class'], $error['data'], $error['attr'] ),
  668. esc_html( $error['value'] )
  669. );
  670. }
  671. /**
  672. * Display the description for each field.
  673. *
  674. * @since 1.3.7
  675. *
  676. * @param array $field Field data and settings.
  677. * @param array $form_data Form data and settings.
  678. */
  679. public function field_description( $field, $form_data ) {
  680. $action = current_action();
  681. $description = $field['properties']['description'];
  682. // If the description is empty don't proceed.
  683. if ( empty( $description['value'] ) ) {
  684. return;
  685. }
  686. // Determine positioning.
  687. if ( 'wpforms_display_field_before' === $action && 'before' !== $description['position'] ) {
  688. return;
  689. }
  690. if ( 'wpforms_display_field_after' === $action && 'after' !== $description['position'] ) {
  691. return;
  692. }
  693. if ( 'before' === $description['position'] ) {
  694. $description['class'][] = 'before';
  695. }
  696. printf( '<div %s>%s</div>',
  697. wpforms_html_attributes( $description['id'], $description['class'], $description['data'], $description['attr'] ),
  698. do_shortcode( $description['value'] )
  699. );
  700. }
  701. /**
  702. * Display the closing container markup for each field.
  703. *
  704. * @since 1.3.7
  705. *
  706. * @param array $field Field data and settings.
  707. * @param array $form_data Form data and settings.
  708. */
  709. public function field_container_close( $field, $form_data ) {
  710. echo '</div>';
  711. }
  712. /**
  713. * Anti-spam honeypot output if configured.
  714. *
  715. * @since 1.0.0
  716. *
  717. * @param array $form_data Form data and settings.
  718. * @param null $deprecated Deprecated in v1.3.7, previously was $form object.
  719. * @param bool $title Whether to display form title.
  720. * @param bool $description Whether to display form description.
  721. * @param array $errors List of all errors filled in WPForms_Process::process().
  722. */
  723. public function honeypot( $form_data, $deprecated, $title, $description, $errors ) {
  724. if (
  725. empty( $form_data['settings']['honeypot'] ) ||
  726. '1' !== $form_data['settings']['honeypot']
  727. ) {
  728. return;
  729. }
  730. $names = array( 'Name', 'Phone', 'Comment', 'Message', 'Email', 'Website' );
  731. echo '<div class="wpforms-field wpforms-field-hp">';
  732. echo '<label for="wpforms-' . $form_data['id'] . '-field-hp" class="wpforms-field-label">' . $names[ array_rand( $names ) ] . '</label>'; // phpcs:ignore
  733. echo '<input type="text" name="wpforms[hp]" id="wpforms-' . $form_data['id'] . '-field-hp" class="wpforms-field-medium">'; // phpcs:ignore
  734. echo '</div>';
  735. }
  736. /**
  737. * CAPTCHA output if configured.
  738. *
  739. * @since 1.0.0
  740. * @since 1.6.4 Added hCaptcha support.
  741. *
  742. * @param array $form_data Form data and settings.
  743. * @param null $deprecated Deprecated in v1.3.7, previously was $form object.
  744. * @param bool $title Whether to display form title.
  745. * @param bool $description Whether to display form description.
  746. * @param array $errors List of all errors filled in WPForms_Process::process().
  747. */
  748. public function recaptcha( $form_data, $deprecated, $title, $description, $errors ) {
  749. // Check that CAPTCHA is configured in the settings.
  750. $captcha_settings = wpforms_get_captcha_settings();
  751. if (
  752. empty( $captcha_settings['provider'] ) ||
  753. 'none' === $captcha_settings['provider'] ||
  754. empty( $captcha_settings['site_key'] ) ||
  755. empty( $captcha_settings['secret_key'] )
  756. ) {
  757. return;
  758. }
  759. // Check that the CAPTCHA is configured for the specific form.
  760. if (
  761. ! isset( $form_data['settings']['recaptcha'] ) ||
  762. '1' !== $form_data['settings']['recaptcha']
  763. ) {
  764. return;
  765. }
  766. $is_recaptcha_v3 = 'recaptcha' === $captcha_settings['provider'] && 'v3' === $captcha_settings['recaptcha_type'];
  767. if ( wpforms_is_amp() ) {
  768. if ( $is_recaptcha_v3 ) {
  769. printf(
  770. '<amp-recaptcha-input name="wpforms[recaptcha]" data-sitekey="%s" data-action="%s" layout="nodisplay"></amp-recaptcha-input>',
  771. esc_attr( $captcha_settings['site_key'] ),
  772. esc_attr( 'wpforms_' . $form_data['id'] )
  773. );
  774. } elseif ( is_super_admin() ) {
  775. $captcha_provider = 'hcaptcha' === $captcha_settings['provider'] ? esc_html__( 'hCaptcha', 'wpforms-lite' ) : esc_html__( 'Google reCAPTCHA v2', 'wpforms-lite' );
  776. echo '<div class="wpforms-notice wpforms-warning" style="margin: 20px 0;">';
  777. printf(
  778. wp_kses(
  779. /* translators: %1$s - CAPTCHA provider name; %2$s - URL to reCAPTCHA documentation. */
  780. __( '%1$s is not supported by AMP and is currently disabled.<br><a href="%2$s" rel="noopener noreferrer" target="_blank">Upgrade to reCAPTCHA v3</a> for full AMP support. <br><em>Please note: this message is only displayed to site administrators.</em>', 'wpforms-lite' ),
  781. array(
  782. 'a' => array(
  783. 'href' => array(),
  784. 'rel' => array(),
  785. 'target' => array(),
  786. ),
  787. 'br' => array(),
  788. 'em' => array(),
  789. )
  790. ),
  791. $captcha_provider, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
  792. 'https://wpforms.com/docs/setup-captcha-wpforms/'
  793. );
  794. echo '</div>';
  795. }
  796. return; // Only v3 is supported in AMP.
  797. }
  798. if ( $is_recaptcha_v3 ) {
  799. echo '<input type="hidden" name="wpforms[recaptcha]" value="">';
  800. return;
  801. }
  802. $data = apply_filters(
  803. 'wpforms_frontend_recaptcha',
  804. [
  805. 'sitekey' => $captcha_settings['site_key'],
  806. ],
  807. $form_data
  808. );
  809. if ( $captcha_settings['provider'] === 'recaptcha' && $captcha_settings['recaptcha_type'] === 'invisible' ) {
  810. $data['size'] = 'invisible';
  811. }
  812. printf(
  813. '<div class="wpforms-recaptcha-container wpforms-is-%s"%s>',
  814. sanitize_html_class( $captcha_settings['provider'] ),
  815. $this->pages ? ' style="display:none;"' : ''
  816. );
  817. echo '<div ' . wpforms_html_attributes( '', [ 'g-recaptcha' ], $data ) . '></div>';
  818. if ( $captcha_settings['provider'] === 'hcaptcha' || $captcha_settings['recaptcha_type'] !== 'invisible' ) {
  819. echo '<input type="text" name="g-recaptcha-hidden" class="wpforms-recaptcha-hidden" style="position:absolute!important;clip:rect(0,0,0,0)!important;height:1px!important;width:1px!important;border:0!important;overflow:hidden!important;padding:0!important;margin:0!important;" required>';
  820. }
  821. if ( ! empty( $errors['recaptcha'] ) ) {
  822. $this->form_error( 'recaptcha', $errors['recaptcha'] );
  823. }
  824. echo '</div>';
  825. }
  826. /**
  827. * Form footer area.
  828. *
  829. * @since 1.0.0
  830. *
  831. * @param array $form_data Form data and settings.
  832. * @param null $deprecated Deprecated in v1.3.7, previously was $form object.
  833. * @param bool $title Whether to display form title.
  834. * @param bool $description Whether to display form description.
  835. * @param array $errors List of all errors filled in WPForms_Process::process().
  836. */
  837. public function foot( $form_data, $deprecated, $title, $description, $errors ) {
  838. $form_id = absint( $form_data['id'] );
  839. $settings = $form_data['settings'];
  840. $submit = apply_filters( 'wpforms_field_submit', $settings['submit_text'], $form_data );
  841. $attrs = [
  842. 'aria-live' => 'assertive',
  843. 'value' => 'wpforms-submit',
  844. ];
  845. $data_attrs = [];
  846. $classes = [ 'wpforms-submit' ];
  847. // Check for submit button alt-text.
  848. if ( ! empty( $settings['submit_text_processing'] ) ) {
  849. if ( wpforms_is_amp() ) {
  850. $attrs['[text]'] = sprintf(
  851. '%s.submitting ? %s : %s',
  852. $this->get_form_amp_state_id( $form_id ),
  853. wp_json_encode( $settings['submit_text_processing'], JSON_UNESCAPED_UNICODE ),
  854. wp_json_encode( $submit, JSON_UNESCAPED_UNICODE )
  855. );
  856. } else {
  857. $data_attrs['alt-text'] = $settings['submit_text_processing'];
  858. $data_attrs['submit-text'] = $submit;
  859. }
  860. }
  861. // Check user defined submit button classes.
  862. if ( ! empty( $settings['submit_class'] ) ) {
  863. $submit_classes = is_array( $settings['submit_class'] ) ? $settings['submit_class'] : array_filter( explode( ' ', $settings['submit_class'] ) );
  864. $classes = array_merge( $classes, $submit_classes );
  865. }
  866. // AMP submit error template.
  867. if ( wpforms_is_amp() ) {
  868. echo '<div submit-error><template type="amp-mustache"><div class="wpforms-error-container"><p>{{{message}}}</p></div></template></div>';
  869. }
  870. // Output footer errors if they exist.
  871. if ( ! empty( $errors['footer'] ) ) {
  872. $this->form_error( 'footer', $errors['footer'] );
  873. }
  874. // Submit button area.
  875. printf( '<div class="wpforms-submit-container"%s>', $this->pages ? ' style="display:none;"' : '' );
  876. echo '<input type="hidden" name="wpforms[id]" value="' . absint( $form_id ) . '">';
  877. if ( is_user_logged_in() ) {
  878. ?>
  879. <input
  880. type="hidden"
  881. name="wpforms[nonce]"
  882. value="<?php echo esc_attr( wp_create_nonce( "wpforms::form_{$form_id}" ) ); ?>"
  883. />
  884. <?php
  885. }
  886. echo '<input type="hidden" name="wpforms[author]" value="' . absint( get_the_author_meta( 'ID' ) ) . '">';
  887. if ( is_singular() ) {
  888. echo '<input type="hidden" name="wpforms[post_id]" value="' . absint( get_the_ID() ) . '">';
  889. }
  890. do_action( 'wpforms_display_submit_before', $form_data );
  891. printf(
  892. '<button type="submit" name="wpforms[submit]" %s>%s</button>',
  893. wpforms_html_attributes(
  894. sprintf( 'wpforms-submit-%d', absint( $form_id ) ),
  895. $classes,
  896. $data_attrs,
  897. $attrs
  898. ),
  899. esc_html( $submit )
  900. );
  901. if ( ! empty( $settings['ajax_submit'] ) && ! wpforms_is_amp() ) {
  902. /**
  903. * Filter submit spinner image src attribute.
  904. *
  905. * @since 1.5.4.1
  906. * @deprecated 1.6.7.3
  907. *
  908. * @param string $src Spinner image source.
  909. * @param array $form_data Form data and settings.
  910. */
  911. $src = apply_filters_deprecated(
  912. 'wpforms_display_sumbit_spinner_src',
  913. [
  914. WPFORMS_PLUGIN_URL . 'assets/images/submit-spin.svg',
  915. $form_data,
  916. ],
  917. '1.6.7.3',
  918. 'wpforms_display_submit_spinner_src'
  919. );
  920. /**
  921. * Filter submit spinner image src attribute.
  922. *
  923. * @since 1.6.7.3
  924. *
  925. * @param string $src Spinner image source.
  926. * @param array $form_data Form data and settings.
  927. */
  928. $src = apply_filters(
  929. 'wpforms_display_submit_spinner_src',
  930. $src,
  931. $form_data
  932. );
  933. printf(
  934. '<img src="%s" class="wpforms-submit-spinner" style="display: none;" width="26" height="26" alt="">',
  935. esc_url( $src )
  936. );
  937. }
  938. /**
  939. * Runs right after form Submit button rendering.
  940. *
  941. * @since 1.5.0
  942. *
  943. * @param array $form_data Form data.
  944. */
  945. do_action( 'wpforms_display_submit_after', $form_data );
  946. echo '</div>';
  947. // Load the success template in AMP.
  948. if ( wpforms_is_amp() ) {
  949. $this->confirmation( $form_data, $form_data['fields'] );
  950. }
  951. }
  952. /**
  953. * Display form error.
  954. *
  955. * @since 1.5.3
  956. *
  957. * @param string $type Error type.
  958. * @param string $error Error text.
  959. */
  960. public function form_error( $type, $error ) {
  961. switch ( $type ) {
  962. case 'header':
  963. case 'footer':
  964. echo '<div class="wpforms-error-container">' . wpforms_sanitize_error( $error ) . '</div>';
  965. break;
  966. case 'recaptcha':
  967. echo '<label id="wpforms-field_recaptcha-error" class="wpforms-error">' . wpforms_sanitize_error( $error ) . '</label>';
  968. break;
  969. }
  970. }
  971. /**
  972. * Determine if we should load assets globally.
  973. * If false assets will load conditionally (default).
  974. *
  975. * @since 1.2.4
  976. *
  977. * @return bool
  978. */
  979. public function assets_global() {
  980. return apply_filters( 'wpforms_global_assets', wpforms_setting( 'global-assets', false ) );
  981. }
  982. /**
  983. * Load the necessary CSS for single pages/posts earlier if possible.
  984. *
  985. * If we are viewing a singular page, then we can check the content early
  986. * to see if the shortcode was used. If not we fallback and load the assets
  987. * later on during the page (widgets, archives, etc).
  988. *
  989. * @since 1.0.0
  990. */
  991. public function assets_header() {
  992. if ( ! is_singular() ) {
  993. return;
  994. }
  995. global $post;
  996. if (
  997. has_shortcode( $post->post_content, 'wpforms' ) ||
  998. ( function_exists( 'has_block' ) && has_block( 'wpforms/form-selector' ) )
  999. ) {
  1000. $this->assets_css();
  1001. }
  1002. }
  1003. /**
  1004. * Load the CSS assets for frontend output.
  1005. *
  1006. * @since 1.0.0
  1007. */
  1008. public function assets_css() {
  1009. do_action( 'wpforms_frontend_css', $this->forms );
  1010. $min = wpforms_get_min_suffix();
  1011. // jQuery date/time library CSS.
  1012. if (
  1013. $this->assets_global() ||
  1014. true === wpforms_has_field_type( 'date-time', $this->forms, true )
  1015. ) {
  1016. wp_enqueue_style(
  1017. 'wpforms-jquery-timepicker',
  1018. WPFORMS_PLUGIN_URL . 'assets/css/jquery.timepicker.css',
  1019. [],
  1020. '1.11.5'
  1021. );
  1022. wp_enqueue_style(
  1023. 'wpforms-flatpickr',
  1024. WPFORMS_PLUGIN_URL . 'assets/css/flatpickr.min.css',
  1025. [],
  1026. '4.6.9'
  1027. );
  1028. }
  1029. // Load CSS per global setting.
  1030. if ( (int) wpforms_setting( 'disable-css', '1' ) === 1 ) {
  1031. wp_enqueue_style(
  1032. 'wpforms-full',
  1033. WPFORMS_PLUGIN_URL . "assets/css/wpforms-full{$min}.css",
  1034. [],
  1035. WPFORMS_VERSION
  1036. );
  1037. }
  1038. if ( (int) wpforms_setting( 'disable-css', '1' ) === 2 ) {
  1039. wp_enqueue_style(
  1040. 'wpforms-base',
  1041. WPFORMS_PLUGIN_URL . "assets/css/wpforms-base{$min}.css",
  1042. [],
  1043. WPFORMS_VERSION
  1044. );
  1045. }
  1046. }
  1047. /**
  1048. * Load the JS assets for frontend output.
  1049. *
  1050. * @since 1.0.0
  1051. */
  1052. public function assets_js() {
  1053. if ( wpforms_is_amp() ) {
  1054. return;
  1055. }
  1056. do_action( 'wpforms_frontend_js', $this->forms );
  1057. $min = wpforms_get_min_suffix();
  1058. // Load jQuery validation library - https://jqueryvalidation.org/.
  1059. wp_enqueue_script(
  1060. 'wpforms-validation',
  1061. WPFORMS_PLUGIN_URL . 'assets/js/jquery.validate.min.js',
  1062. [ 'jquery' ],
  1063. '1.19.3',
  1064. true
  1065. );
  1066. // Load jQuery date/time libraries.
  1067. // TODO: should be moved out of here.
  1068. if (
  1069. $this->assets_global() ||
  1070. true === wpforms_has_field_type( 'date-time', $this->forms, true )
  1071. ) {
  1072. wp_enqueue_script(
  1073. 'wpforms-flatpickr',
  1074. WPFORMS_PLUGIN_URL . 'assets/js/flatpickr.min.js',
  1075. [ 'jquery' ],
  1076. '4.6.9',
  1077. true
  1078. );
  1079. wp_enqueue_script(
  1080. 'wpforms-jquery-timepicker',
  1081. WPFORMS_PLUGIN_URL . 'assets/js/jquery.timepicker.min.js',
  1082. [ 'jquery' ],
  1083. '1.11.5',
  1084. true
  1085. );
  1086. }
  1087. // Load jQuery input mask library - https://github.com/RobinHerbots/jquery.inputmask.
  1088. if (
  1089. $this->assets_global() ||
  1090. true === wpforms_has_field_type( [ 'phone', 'address' ], $this->forms, true ) ||
  1091. true === wpforms_has_field_setting( 'input_mask', $this->forms, true )
  1092. ) {
  1093. wp_enqueue_script(
  1094. 'wpforms-maskedinput',
  1095. WPFORMS_PLUGIN_URL . 'assets/js/jquery.inputmask.min.js',
  1096. [ 'jquery' ],
  1097. '5.0.6',
  1098. true
  1099. );
  1100. }
  1101. // Load mailcheck <https://github.com/mailcheck/mailcheck> and punycode libraries.
  1102. if (
  1103. $this->assets_global() ||
  1104. true === wpforms_has_field_type( [ 'email' ], $this->forms, true )
  1105. ) {
  1106. wp_enqueue_script(
  1107. 'wpforms-mailcheck',
  1108. WPFORMS_PLUGIN_URL . 'assets/js/mailcheck.min.js',
  1109. false,
  1110. '1.1.2',
  1111. true
  1112. );
  1113. wp_enqueue_script(
  1114. 'wpforms-punycode',
  1115. WPFORMS_PLUGIN_URL . "assets/js/punycode{$min}.js",
  1116. [],
  1117. '1.0.0',
  1118. true
  1119. );
  1120. }
  1121. // Load CC payment library - https://github.com/stripe/jquery.payment/.
  1122. // TODO: should be moved out of here.
  1123. if (
  1124. $this->assets_global() ||
  1125. true === wpforms_has_field_type( 'credit-card', $this->forms, true )
  1126. ) {
  1127. wp_enqueue_script(
  1128. 'wpforms-payment',
  1129. WPFORMS_PLUGIN_URL . 'assets/js/jquery.payment.min.js',
  1130. [ 'jquery' ],
  1131. WPFORMS_VERSION,
  1132. true
  1133. );
  1134. }
  1135. // Load base JS.
  1136. wp_enqueue_script(
  1137. 'wpforms',
  1138. WPFORMS_PLUGIN_URL . 'assets/js/wpforms.js',
  1139. [ 'jquery' ],
  1140. WPFORMS_VERSION,
  1141. true
  1142. );
  1143. $this->assets_recaptcha();
  1144. }
  1145. /**
  1146. * Load the assets needed for the CAPTCHA.
  1147. *
  1148. * @since 1.6.2
  1149. * @since 1.6.4 Added hCaptcha support.
  1150. */
  1151. public function assets_recaptcha() {
  1152. // Kill switch for CAPTCHA.
  1153. if ( (bool) apply_filters( 'wpforms_frontend_recaptcha_disable', false ) ) {
  1154. return;
  1155. }
  1156. // Load CAPTCHA support if form supports it.
  1157. $captcha_settings = wpforms_get_captcha_settings();
  1158. if (
  1159. empty( $captcha_settings['provider'] ) ||
  1160. 'none' === $captcha_settings['provider'] ||
  1161. empty( $captcha_settings['site_key'] ) ||
  1162. empty( $captcha_settings['secret_key'] )
  1163. ) {
  1164. return;
  1165. }
  1166. // Whether at least 1 form on a page has CAPTCHA enabled.
  1167. $captcha = false;
  1168. foreach ( $this->forms as $form ) {
  1169. if ( ! empty( $form['settings']['recaptcha'] ) ) {
  1170. $captcha = true;
  1171. break;
  1172. }
  1173. }
  1174. // Return early.
  1175. if ( ! $captcha && ! $this->assets_global() ) {
  1176. return;
  1177. }
  1178. $is_recaptcha_v3 = 'recaptcha' === $captcha_settings['provider'] && 'v3' === $captcha_settings['recaptcha_type'];
  1179. $captcha_api = 'hcaptcha' === $captcha_settings['provider'] ? 'https://hcaptcha.com/1/api.js?onload=wpformsRecaptchaLoad&render=explicit' : apply_filters( 'wpforms_frontend_recaptcha_url', 'https://www.google.com/recaptcha/api.js?onload=wpformsRecaptchaLoad&render=explicit' ); // BC: reCAPTCHA v3 don't filtered.
  1180. $captcha_api = $is_recaptcha_v3 ? 'https://www.google.com/recaptcha/api.js?render=' . $captcha_settings['site_key'] : $captcha_api;
  1181. /**
  1182. * Filter the CAPTCHA API URL.
  1183. *
  1184. * @since 1.6.4
  1185. *
  1186. * @param string $captcha_api The CAPTCHA API URL.
  1187. */
  1188. $captcha_api = apply_filters( 'wpforms_frontend_captcha_api', $captcha_api );
  1189. wp_enqueue_script(
  1190. 'wpforms-recaptcha',
  1191. $captcha_api,
  1192. $is_recaptcha_v3 ? [] : [ 'jquery' ],
  1193. null,
  1194. true
  1195. );
  1196. /**
  1197. * Filter the string containing the CAPTCHA javascript to be added.
  1198. *
  1199. * @since 1.6.4
  1200. *
  1201. * @param string $captcha_inline The CAPTCHA javascript.
  1202. */
  1203. $captcha_inline = apply_filters( 'wpforms_frontend_captcha_inline_script', $this->get_captcha_inline_script( $captcha_settings ) );
  1204. wp_add_inline_script( 'wpforms-recaptcha', $captcha_inline );
  1205. }
  1206. /**
  1207. * Retrieve the string containing the CAPTCHA inline javascript.
  1208. *
  1209. * @since 1.6.4
  1210. *
  1211. * @param array $captcha_settings The CAPTCHA settings.
  1212. *
  1213. * @return string
  1214. */
  1215. protected function get_captcha_inline_script( $captcha_settings ) {
  1216. // IE11 polyfills for native `matches()` and `closest()` methods.
  1217. $polyfills = // language=JavaScript PhpStorm.
  1218. 'if (!Element.prototype.matches) {
  1219. Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
  1220. }
  1221. if (!Element.prototype.closest) {
  1222. Element.prototype.closest = function (s) {
  1223. var el = this;
  1224. do {
  1225. if (Element.prototype.matches.call(el, s)) { return el; }
  1226. el = el.parentElement || el.parentNode;
  1227. } while (el !== null && el.nodeType === 1);
  1228. return null;
  1229. };
  1230. }
  1231. ';
  1232. // Native equivalent for jQuery's `trigger()` method.
  1233. $dispatch = // language=JavaScript PhpStorm.
  1234. 'var wpformsDispatchEvent = function (el, ev, custom) {
  1235. var e = document.createEvent(custom ? "CustomEvent" : "HTMLEvents");
  1236. custom ? e.initCustomEvent(ev, true, true, false) : e.initEvent(ev, true, true);
  1237. el.dispatchEvent(e);
  1238. };
  1239. ';
  1240. // Captcha callback, used by hCaptcha and checkbox reCaptcha v2.
  1241. $callback = // language=JavaScript PhpStorm.
  1242. 'var wpformsRecaptchaCallback = function (el) {
  1243. var hdn = el.parentNode.querySelector(".wpforms-recaptcha-hidden");
  1244. var err = el.parentNode.querySelector("#g-recaptcha-hidden-error");
  1245. hdn.value = "1";
  1246. wpformsDispatchEvent(hdn, "change", false);
  1247. hdn.classList.remove("wpforms-error");
  1248. err && hdn.parentNode.removeChild(err);
  1249. };
  1250. ';
  1251. if ( $captcha_settings['provider'] === 'hcaptcha' ) {
  1252. $data = $dispatch;
  1253. $data .= $callback;
  1254. $data .= // language=JavaScript PhpStorm.
  1255. 'var wpformsRecaptchaLoad = function () {
  1256. Array.prototype.forEach.call(document.querySelectorAll(".g-recaptcha"), function (el) {
  1257. var captchaID = hcaptcha.render(el, {
  1258. callback: function () {
  1259. wpformsRecaptchaCallback(el);
  1260. }
  1261. });
  1262. el.setAttribute("data-recaptcha-id", captchaID);
  1263. });
  1264. wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
  1265. };
  1266. ';
  1267. return $data;
  1268. }
  1269. if ( $captcha_settings['recaptcha_type'] === 'v3' ) {
  1270. $data = $dispatch;
  1271. $data .= // language=JavaScript PhpStorm.
  1272. 'var wpformsRecaptchaLoad = function () {
  1273. grecaptcha.execute("' . $captcha_settings['site_key'] . '", {action: "wpforms"}).then(function (token) {
  1274. Array.prototype.forEach.call(document.getElementsByName("wpforms[recaptcha]"), function (el) {
  1275. el.value = token;
  1276. });
  1277. });
  1278. wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
  1279. };
  1280. grecaptcha.ready(wpformsRecaptchaLoad);
  1281. ';
  1282. } elseif ( $captcha_settings['recaptcha_type'] === 'invisible' ) {
  1283. $data = $polyfills;
  1284. $data .= $dispatch;
  1285. $data .= // language=JavaScript PhpStorm.
  1286. 'var wpformsRecaptchaLoad = function () {
  1287. Array.prototype.forEach.call(document.querySelectorAll(".g-recaptcha"), function (el) {
  1288. try {
  1289. var recaptchaID = grecaptcha.render(el, {
  1290. callback: function () {
  1291. wpformsRecaptchaCallback(el);
  1292. }
  1293. }, true);
  1294. el.closest("form").querySelector("button[type=submit]").recaptchaID = recaptchaID;
  1295. } catch (error) {}
  1296. });
  1297. wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
  1298. };
  1299. var wpformsRecaptchaCallback = function (el) {
  1300. var $form = el.closest("form");
  1301. if (typeof wpforms.formSubmit === "function") {
  1302. wpforms.formSubmit($form);
  1303. } else {
  1304. $form.querySelector("button[type=submit]").recaptchaID = false;
  1305. $form.submit();
  1306. }
  1307. };
  1308. ';
  1309. } else {
  1310. $data = $dispatch;
  1311. $data .= $callback;
  1312. $data .= // language=JavaScript PhpStorm.
  1313. 'var wpformsRecaptchaLoad = function () {
  1314. Array.prototype.forEach.call(document.querySelectorAll(".g-recaptcha"), function (el) {
  1315. try {
  1316. var recaptchaID = grecaptcha.render(el, {
  1317. callback: function () {
  1318. wpformsRecaptchaCallback(el);
  1319. }
  1320. });
  1321. el.setAttribute("data-recaptcha-id", recaptchaID);
  1322. } catch (error) {}
  1323. });
  1324. wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
  1325. };
  1326. ';
  1327. }
  1328. return $data;
  1329. }
  1330. /**
  1331. * Load the necessary assets for the confirmation message.
  1332. *
  1333. * @since 1.1.2
  1334. */
  1335. public function assets_confirmation() {
  1336. $min = wpforms_get_min_suffix();
  1337. // Base CSS only.
  1338. if ( (int) wpforms_setting( 'disable-css', '1' ) === 1 ) {
  1339. wp_enqueue_style(
  1340. 'wpforms-full',
  1341. WPFORMS_PLUGIN_URL . "assets/css/wpforms-full{$min}.css",
  1342. [],
  1343. WPFORMS_VERSION
  1344. );
  1345. }
  1346. // Special confirmation JS.
  1347. if ( ! wpforms_is_amp() ) {
  1348. wp_enqueue_script(
  1349. 'wpforms-confirmation',
  1350. WPFORMS_PLUGIN_URL . 'assets/js/wpforms-confirmation.js',
  1351. [ 'jquery' ],
  1352. WPFORMS_VERSION,
  1353. true
  1354. );
  1355. }
  1356. do_action( 'wpforms_frontend_confirmation' );
  1357. }
  1358. /**
  1359. * Load the assets in footer if needed (archives, widgets, etc).
  1360. *
  1361. * @since 1.0.0
  1362. */
  1363. public function assets_footer() {
  1364. if ( empty( $this->forms ) && ! $this->assets_global() ) {
  1365. return;
  1366. }
  1367. $this->assets_css();
  1368. $this->assets_js();
  1369. do_action( 'wpforms_wp_footer', $this->forms );
  1370. }
  1371. /**
  1372. * Get strings to localize.
  1373. *
  1374. * @since 1.6.0
  1375. *
  1376. * @return array Array of strings to localize.
  1377. */
  1378. public function get_strings() {
  1379. // Define base strings.
  1380. $strings = [
  1381. 'val_required' => wpforms_setting( 'validation-required', esc_html__( 'This field is required.', 'wpforms-lite' ) ),
  1382. 'val_email' => wpforms_setting( 'validation-email', esc_html__( 'Please enter a valid email address.', 'wpforms-lite' ) ),
  1383. 'val_email_suggestion' => wpforms_setting(
  1384. 'validation-email-suggestion',
  1385. sprintf( /* translators: %s - suggested email address. */
  1386. esc_html__( 'Did you mean %s?', 'wpforms-lite' ),
  1387. '{suggestion}'
  1388. )
  1389. ),
  1390. 'val_email_suggestion_title' => esc_attr__( 'Click to accept this suggestion.', 'wpforms-lite' ),
  1391. 'val_email_restricted' => wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) ),
  1392. 'val_number' => wpforms_setting( 'validation-number', esc_html__( 'Please enter a valid number.', 'wpforms-lite' ) ),
  1393. 'val_number_positive' => wpforms_setting( 'validation-number-positive', esc_html__( 'Please enter a valid positive number.', 'wpforms-lite' ) ),
  1394. 'val_confirm' => wpforms_setting( 'validation-confirm', esc_html__( 'Field values do not match.', 'wpforms-lite' ) ),
  1395. 'val_checklimit' => wpforms_setting( 'validation-check-limit', esc_html__( 'You have exceeded the number of allowed selections: {#}.', 'wpforms-lite' ) ),
  1396. 'val_limit_characters' => wpforms_setting(
  1397. 'validation-character-limit',
  1398. sprintf( /* translators: %1$s - characters count, %2$s - characters limit. */
  1399. esc_html__( '%1$s of %2$s max characters.', 'wpforms-lite' ),
  1400. '{count}',
  1401. '{limit}'
  1402. )
  1403. ),
  1404. 'val_limit_words' => wpforms_setting(
  1405. 'validation-word-limit',
  1406. sprintf( /* translators: %1$s - words count, %2$s - words limit. */
  1407. esc_html__( '%1$s of %2$s max words.', 'wpforms-lite' ),
  1408. '{count}',
  1409. '{limit}'
  1410. )
  1411. ),
  1412. 'val_recaptcha_fail_msg' => wpforms_setting( 'recaptcha-fail-msg', esc_html__( 'Google reCAPTCHA verification failed, please try again later.', 'wpforms-lite' ) ),
  1413. 'val_empty_blanks' => wpforms_setting( 'validation-input-mask-incomplete', esc_html__( 'Please fill out all blanks.', 'wpforms-lite' ) ),
  1414. 'uuid_cookie' => false,
  1415. 'locale' => wpforms_get_language_code(),
  1416. 'wpforms_plugin_url' => WPFORMS_PLUGIN_URL,
  1417. 'gdpr' => wpforms_setting( 'gdpr' ),
  1418. 'ajaxurl' => admin_url( 'admin-ajax.php' ),
  1419. 'mailcheck_enabled' => (bool) apply_filters( 'wpforms_mailcheck_enabled', true ),
  1420. 'mailcheck_domains' => array_map( 'sanitize_text_field', (array) apply_filters( 'wpforms_mailcheck_domains', array() ) ),
  1421. 'mailcheck_toplevel_domains' => array_map( 'sanitize_text_field', (array) apply_filters( 'wpforms_mailcheck_toplevel_domains', array( 'dev' ) ) ),
  1422. 'is_ssl' => is_ssl(),
  1423. ];
  1424. // Include payment related strings if needed.
  1425. if ( function_exists( 'wpforms_get_currencies' ) ) {
  1426. $currency = wpforms_get_currency();
  1427. $currencies = wpforms_get_currencies();
  1428. $strings['currency_code'] = $currency;
  1429. $strings['currency_thousands'] = isset( $currencies[ $currency ]['thousands_separator'] ) ? $currencies[ $currency ]['thousands_separator'] : ',';
  1430. $strings['currency_decimals'] = wpforms_get_currency_decimals( $currencies[ $currency ] );
  1431. $strings['currency_decimal'] = isset( $currencies[ $currency ]['decimal_separator'] ) ? $currencies[ $currency ]['decimal_separator'] : '.';
  1432. $strings['currency_symbol'] = isset( $currencies[ $currency ]['symbol'] ) ? $currencies[ $currency ]['symbol'] : '$';
  1433. $strings['currency_symbol_pos'] = isset( $currencies[ $currency ]['symbol_pos'] ) ? $currencies[ $currency ]['symbol_pos'] : 'left';
  1434. }
  1435. $strings = apply_filters( 'wpforms_frontend_strings', $strings );
  1436. foreach ( (array) $strings as $key => $value ) {
  1437. if ( ! is_scalar( $value ) ) {
  1438. continue;
  1439. }
  1440. $strings[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
  1441. }
  1442. return $strings;
  1443. }
  1444. /**
  1445. * Hook at fires at a later priority in wp_footer.
  1446. *
  1447. * @since 1.0.5
  1448. * @since 1.7.0 Load wpforms_settings on the confirmation page for a non-ajax form.
  1449. */
  1450. public function footer_end() {
  1451. if (
  1452. ( empty( $this->forms ) && empty( $_POST['wpforms'] ) && ! $this->assets_global() ) || // phpcs:ignore WordPress.Security.NonceVerification.Missing
  1453. wpforms_is_amp()
  1454. ) {
  1455. return;
  1456. }
  1457. $strings = $this->get_strings();
  1458. /*
  1459. * Below we do our own implementation of wp_localize_script in an effort
  1460. * to be better compatible with caching plugins which were causing
  1461. * conflicts.
  1462. */
  1463. echo "<script type='text/javascript'>\n";
  1464. echo "/* <![CDATA[ */\n";
  1465. echo 'var wpforms_settings = ' . wp_json_encode( $strings ) . "\n";
  1466. echo "/* ]]> */\n";
  1467. echo "</script>\n";
  1468. do_action( 'wpforms_wp_footer_end', $this->forms );
  1469. }
  1470. /**
  1471. * Google reCAPTCHA no-conflict mode.
  1472. *
  1473. * When enabled in the WPForms settings, forcefully remove all other
  1474. * reCAPTCHA enqueues to prevent conflicts. Filter can be used to target
  1475. * specific pages, etc.
  1476. *
  1477. * @since 1.4.5
  1478. * @since 1.6.4 Added hCaptcha support.
  1479. */
  1480. public function recaptcha_noconflict() {
  1481. $captcha_settings = wpforms_get_captcha_settings();
  1482. if (
  1483. empty( wpforms_setting( 'recaptcha-noconflict' ) ) ||
  1484. empty( $captcha_settings['provider'] ) ||
  1485. 'none' === $captcha_settings['provider'] ||
  1486. ! apply_filters( 'wpforms_frontend_recaptcha_noconflict', true )
  1487. ) {
  1488. return;
  1489. }
  1490. $scripts = wp_scripts();
  1491. $urls = [ 'google.com/recaptcha', 'gstatic.com/recaptcha', 'hcaptcha.com/1' ];
  1492. foreach ( $scripts->queue as $handle ) {
  1493. // Skip the WPForms javascript-assets.
  1494. if (
  1495. ! isset( $scripts->registered[ $handle ] ) ||
  1496. false !== strpos( $scripts->registered[ $handle ]->handle, 'wpforms' )
  1497. ) {
  1498. return;
  1499. }
  1500. foreach ( $urls as $url ) {
  1501. if ( false !== strpos( $scripts->registered[ $handle ]->src, $url ) ) {
  1502. wp_dequeue_script( $handle );
  1503. wp_deregister_script( $handle );
  1504. break;
  1505. }
  1506. }
  1507. }
  1508. }
  1509. /**
  1510. * Shortcode wrapper for the outputting a form.
  1511. *
  1512. * @since 1.0.0
  1513. *
  1514. * @param array $atts Shortcode attributes provided by a user.
  1515. *
  1516. * @return string
  1517. */
  1518. public function shortcode( $atts ) {
  1519. $defaults = array(
  1520. 'id' => false,
  1521. 'title' => false,
  1522. 'description' => false,
  1523. );
  1524. $atts = shortcode_atts( $defaults, shortcode_atts( $defaults, $atts, 'output' ), 'wpforms' );
  1525. ob_start();
  1526. $this->output( $atts['id'], $atts['title'], $atts['description'] );
  1527. return ob_get_clean();
  1528. }
  1529. /**
  1530. * Inline a script to check if our main js is loaded and display a warning message otherwise.
  1531. *
  1532. * @since 1.6.4.1
  1533. */
  1534. public function missing_assets_error_js() {
  1535. /**
  1536. * Disable missing assets error js checking.
  1537. *
  1538. * @since 1.6.6
  1539. *
  1540. * @param bool False by default, set to True to disable checking.
  1541. */
  1542. $skip = (bool) apply_filters( 'wpforms_frontend_missing_assets_error_js_disable', false );
  1543. if ( $skip || ! wpforms_current_user_can() ) {
  1544. return;
  1545. }
  1546. if ( empty( $this->forms ) && ! $this->assets_global() ) {
  1547. return;
  1548. }
  1549. if ( wpforms_is_amp() ) {
  1550. return;
  1551. }
  1552. printf( $this->get_missing_assets_error_script(), $this->get_missing_assets_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
  1553. }
  1554. /**
  1555. * Get missing assets error script.
  1556. *
  1557. * @since 1.6.4.1
  1558. *
  1559. * @return string
  1560. */
  1561. private function get_missing_assets_error_script() {
  1562. return "<script>
  1563. ( function() {
  1564. function wpforms_js_error_loading() {
  1565. if ( typeof window.wpforms !== 'undefined' ) {
  1566. return;
  1567. }
  1568. var forms = document.querySelectorAll( '.wpforms-form' );
  1569. if ( ! forms.length ) {
  1570. return;
  1571. }
  1572. var error = document.createElement( 'div' );
  1573. error.classList.add( 'wpforms-error-container' );
  1574. error.innerHTML = '%s';
  1575. forms.forEach( function( form ) {
  1576. if ( ! form.querySelector( '.wpforms-error-container' ) ) {
  1577. form.insertBefore( error.cloneNode( true ), form.firstChild );
  1578. }
  1579. } );
  1580. };
  1581. if ( document.readyState === 'loading' ) {
  1582. document.addEventListener( 'DOMContentLoaded', wpforms_js_error_loading );
  1583. } else {
  1584. wpforms_js_error_loading();
  1585. }
  1586. }() );
  1587. </script>";
  1588. }
  1589. /**
  1590. * Get missing assets error message.
  1591. *
  1592. * @since 1.6.4.1
  1593. *
  1594. * @return string
  1595. */
  1596. private function get_missing_assets_error_message() {
  1597. $message = wp_kses(
  1598. sprintf( /* translators: %s - URL to the troubleshooting guide. */
  1599. __( 'Heads up! WPForms has detected an issue with JavaScript on this page. JavaScript is required for this form to work properly, so this form may not work as expected. See our <a href="%s" target="_blank" rel="noopener noreferrer">troubleshooting guide</a> to learn more or contact support.', 'wpforms-lite' ),
  1600. 'https://wpforms.com/docs/getting-support-wpforms/'
  1601. ),
  1602. [
  1603. 'a' => [
  1604. 'href' => [],
  1605. 'target' => [],
  1606. 'rel' => [],
  1607. ],
  1608. ]
  1609. );
  1610. $message .= '<p>';
  1611. $message .= esc_html__( 'This message is only displayed to site administrators.', 'wpforms-lite' );
  1612. $message .= '</p>';
  1613. return $message;
  1614. }
  1615. }