Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Frontend;
|
||||
|
||||
/**
|
||||
* AMP class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class Amp {
|
||||
|
||||
/**
|
||||
* Whether the current page is in AMP mode or not.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_amp_mode;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
add_filter( 'amp_skip_post', [ $this, 'skip_post' ] );
|
||||
add_filter( 'wpforms_frontend_form_atts', [ $this, 'form_atts' ], -PHP_INT_MAX, 2 );
|
||||
add_action( 'wpforms_frontend_output', [ $this, 'output_state' ], -PHP_INT_MAX, 5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current page is in AMP mode or not.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return bool True if the current page is in AMP mode.
|
||||
*/
|
||||
public function is_amp(): bool {
|
||||
|
||||
if ( is_null( $this->is_amp_mode ) ) {
|
||||
$this->is_amp_mode = wpforms_is_amp();
|
||||
}
|
||||
|
||||
return $this->is_amp_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop AMP output.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return bool True if we need to stop the output.
|
||||
*/
|
||||
public function stop_output( $form_data ): bool {
|
||||
|
||||
// We need to stop output processing in case we are on AMP page.
|
||||
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
if ( ! $this->should_stop_output() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$form_id = ! empty( $form_data['id'] ) ? (int) $form_data['id'] : 0;
|
||||
$full_page_url = home_url( add_query_arg( 'nonamp', '1' ) . '#wpforms-' . $form_id );
|
||||
|
||||
/**
|
||||
* Allow modifying the text or url for the full page on the AMP pages.
|
||||
*
|
||||
* @since 1.4.1.1
|
||||
* @since 1.7.1 Added $form_id, $full_page_url, and $form_data arguments.
|
||||
*
|
||||
* @param string $text Text.
|
||||
* @param int $form_id Form id.
|
||||
* @param string $full_page_url Full page url.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
$text = (string) apply_filters(
|
||||
'wpforms_frontend_shortcode_amp_text',
|
||||
sprintf( /* translators: %s - URL to a non-amp version of a page with the form. */
|
||||
__( '<a href="%s">Go to the full page</a> to view and submit the form.', 'wpforms-lite' ),
|
||||
esc_url( $full_page_url )
|
||||
),
|
||||
$form_id,
|
||||
$full_page_url,
|
||||
$form_data
|
||||
);
|
||||
|
||||
printf(
|
||||
'<p class="wpforms-shortcode-amp-text">%s</p>',
|
||||
wp_kses_post( $text )
|
||||
);
|
||||
|
||||
return true;
|
||||
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether output should be stopped.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_stop_output(): bool {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters PRO status of the plugin.
|
||||
* Returning `true` means that AMP stop loading.
|
||||
*
|
||||
* @since 1.5.4.2
|
||||
*
|
||||
* @param bool $pro Pro status.
|
||||
*/
|
||||
if ( apply_filters( 'wpforms_amp_pro', wpforms()->is_pro() ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
! defined( 'AMP__VERSION' ) ||
|
||||
version_compare( AMP__VERSION, '1.2', '<' ) ||
|
||||
! is_ssl()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable AMP if query param is detected.
|
||||
*
|
||||
* This allows the full form to be accessible for Pro users or sites
|
||||
* that do not have SSL.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param bool $skip Skip AMP mode, display full post.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function skip_post( $skip ) {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return isset( $_GET['nonamp'] ) ? true : $skip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form attributes filter.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_atts Form attributes.
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function form_atts( $form_atts, $form_data ) {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return $form_atts;
|
||||
}
|
||||
|
||||
// Set submitting state.
|
||||
if ( ! isset( $form_atts['atts']['on'] ) ) {
|
||||
$form_atts['atts']['on'] = '';
|
||||
} else {
|
||||
$form_atts['atts']['on'] .= ';';
|
||||
}
|
||||
|
||||
$form_id = ! empty( $form_data['id'] ) ? (int) $form_data['id'] : 0;
|
||||
|
||||
$form_atts['atts']['on'] .= sprintf(
|
||||
'submit:AMP.setState( %1$s ); submit-success:AMP.setState( %2$s ); submit-error:AMP.setState( %2$s );',
|
||||
wp_json_encode(
|
||||
[
|
||||
$this->get_form_amp_state_id( $form_id ) => [ 'submitting' => true ],
|
||||
]
|
||||
),
|
||||
wp_json_encode(
|
||||
[
|
||||
$this->get_form_amp_state_id( $form_id ) => [ 'submitting' => false ],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// Upgrade the form to be an amp-form to avoid sanitizer conversion.
|
||||
if ( isset( $form_atts['atts']['action'] ) ) {
|
||||
$form_atts['atts']['action-xhr'] = $form_atts['atts']['action'];
|
||||
$form_atts['atts']['verify-xhr'] = $form_atts['atts']['action-xhr'];
|
||||
|
||||
unset( $form_atts['atts']['action'] );
|
||||
}
|
||||
|
||||
return $form_atts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amp-state ID for a given form.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
*
|
||||
* @return string State ID.
|
||||
*/
|
||||
private function get_form_amp_state_id( $form_id ) {
|
||||
|
||||
return sprintf( 'wpforms_form_state_%d', $form_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output AMP state.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
* @param null $deprecated Deprecated.
|
||||
* @param string $title Form title.
|
||||
* @param string $description Form description.
|
||||
* @param array $errors Errors.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function output_state( $form_data, $deprecated, $title, $description, $errors ) {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$state = [ 'submitting' => false ];
|
||||
|
||||
$form_id = ! empty( $form_data['id'] ) ? (int) $form_data['id'] : 0;
|
||||
|
||||
printf(
|
||||
'<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
|
||||
$this->get_form_amp_state_id( $form_id ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wp_json_encode( $state )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output submit success template.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return bool True if the template was printed.
|
||||
*/
|
||||
public function output_success_template( $form_data ) {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$frontend = wpforms()->obj( 'frontend' );
|
||||
|
||||
if ( ! $frontend ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$frontend->assets_confirmation( $form_data );
|
||||
|
||||
$class = (int) wpforms_setting( 'disable-css', '1' ) === 1 ?
|
||||
'wpforms-confirmation-container-full' :
|
||||
'wpforms-confirmation-container';
|
||||
|
||||
printf(
|
||||
'<div submit-success><template type="amp-mustache"><div class="%s {{#redirecting}}wpforms-redirection-message{{/redirecting}}">{{{message}}}</div></template></div>',
|
||||
esc_attr( $class )
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output submit error template.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return bool True if the template was printed.
|
||||
*/
|
||||
public function output_error_template() {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
echo '<div submit-error><template type="amp-mustache"><div class="wpforms-error-container"><p>{{{message}}}</p></div></template></div>';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text attribute.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param array $settings Form settings.
|
||||
* @param string $submit Submit button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_text_attr( $form_id, $settings, $submit ) {
|
||||
|
||||
return sprintf(
|
||||
'%s.submitting ? %s : %s',
|
||||
$this->get_form_amp_state_id( $form_id ),
|
||||
wp_json_encode( $settings['submit_text_processing'], JSON_UNESCAPED_UNICODE ),
|
||||
wp_json_encode( $submit, JSON_UNESCAPED_UNICODE )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output captcha.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param bool $is_recaptcha_v3 Whether we use v3.
|
||||
* @param array $captcha_settings Captcha settings.
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function output_captcha( $is_recaptcha_v3, $captcha_settings, $form_data ) {
|
||||
|
||||
if ( ! $this->is_amp() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $is_recaptcha_v3 ) {
|
||||
|
||||
printf(
|
||||
'<amp-recaptcha-input name="wpforms[recaptcha]" data-sitekey="%s" data-action="%s" layout="nodisplay"></amp-recaptcha-input>',
|
||||
esc_attr( $captcha_settings['site_key'] ),
|
||||
esc_attr( 'wpforms_' . $form_data['id'] )
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_super_admin() ) {
|
||||
|
||||
$captcha_provider = $captcha_settings['provider'] === 'hcaptcha' ? esc_html__( 'hCaptcha', 'wpforms-lite' ) : esc_html__( 'Google reCAPTCHA v2', 'wpforms-lite' );
|
||||
|
||||
echo '<div class="wpforms-notice wpforms-warning" style="margin: 20px 0;">';
|
||||
printf(
|
||||
wp_kses( /* translators: %1$s - CAPTCHA provider name, %2$s - URL to reCAPTCHA documentation. */
|
||||
__( '%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' ),
|
||||
[
|
||||
'a' => [
|
||||
'href' => [],
|
||||
'rel' => [],
|
||||
'target' => [],
|
||||
],
|
||||
'br' => [],
|
||||
'em' => [],
|
||||
]
|
||||
),
|
||||
$captcha_provider, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'https://wpforms.com/docs/setup-captcha-wpforms/'
|
||||
);
|
||||
echo '</div>';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,825 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Frontend;
|
||||
|
||||
use WPForms\Integrations\Gutenberg\ThemesData;
|
||||
use WPForms\Lite\Integrations\Gutenberg\ThemesData as LiteThemesData;
|
||||
use WPForms\Pro\Integrations\Gutenberg\ThemesData as ProThemesData;
|
||||
use WPForms\Pro\Integrations\Gutenberg\StockPhotos;
|
||||
|
||||
/**
|
||||
* CSS variables class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class CSSVars {
|
||||
|
||||
/**
|
||||
* Root vars and values.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const ROOT_VARS = [
|
||||
'field-border-radius' => '3px',
|
||||
'field-border-style' => 'solid',
|
||||
'field-border-size' => '1px',
|
||||
'field-background-color' => self::WHITE,
|
||||
'field-border-color' => 'rgba( 0, 0, 0, 0.25 )',
|
||||
'field-text-color' => 'rgba( 0, 0, 0, 0.7 )',
|
||||
'field-menu-color' => self::WHITE,
|
||||
|
||||
'label-color' => 'rgba( 0, 0, 0, 0.85 )',
|
||||
'label-sublabel-color' => 'rgba( 0, 0, 0, 0.55 )',
|
||||
'label-error-color' => '#d63637',
|
||||
|
||||
'button-border-radius' => '3px',
|
||||
'button-border-style' => 'none',
|
||||
'button-border-size' => '1px',
|
||||
'button-background-color' => '#066aab',
|
||||
'button-border-color' => '#066aab',
|
||||
'button-text-color' => self::WHITE,
|
||||
|
||||
'page-break-color' => '#066aab',
|
||||
|
||||
'background-image' => 'none',
|
||||
'background-position' => 'center center',
|
||||
'background-repeat' => 'no-repeat',
|
||||
'background-size' => 'cover',
|
||||
'background-width' => '100px',
|
||||
'background-height' => '100px',
|
||||
'background-color' => 'rgba( 0, 0, 0, 0 )',
|
||||
'background-url' => 'url()',
|
||||
|
||||
'container-padding' => '0px',
|
||||
'container-border-style' => 'none',
|
||||
'container-border-width' => '1px',
|
||||
'container-border-color' => '#000000',
|
||||
'container-border-radius' => '3px',
|
||||
];
|
||||
|
||||
/**
|
||||
* Container shadow vars and values.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const CONTAINER_SHADOW_SIZE = [
|
||||
'none' => [
|
||||
'box-shadow' => 'none',
|
||||
],
|
||||
'small' => [
|
||||
'box-shadow' => '0px 3px 5px 0px rgba(0, 0, 0, 0.1)',
|
||||
],
|
||||
'medium' => [
|
||||
'box-shadow' => '0px 10px 20px 0px rgba(0, 0, 0, 0.1)',
|
||||
],
|
||||
'large' => [
|
||||
'box-shadow' => '0px 30px 50px -10px rgba(0, 0, 0, 0.15)',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Field Size vars and values.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const FIELD_SIZE = [
|
||||
'small' => [
|
||||
'input-height' => '31px',
|
||||
'input-spacing' => '10px',
|
||||
'font-size' => '14px',
|
||||
'line-height' => '17px',
|
||||
'padding-h' => '9px',
|
||||
'checkbox-size' => '14px',
|
||||
'sublabel-spacing' => '5px',
|
||||
'icon-size' => '0.75',
|
||||
],
|
||||
'medium' => [
|
||||
'input-height' => '43px',
|
||||
'input-spacing' => '15px',
|
||||
'font-size' => '16px',
|
||||
'line-height' => '19px',
|
||||
'padding-h' => '14px',
|
||||
'checkbox-size' => '16px',
|
||||
'sublabel-spacing' => '5px',
|
||||
'icon-size' => '1',
|
||||
],
|
||||
'large' => [
|
||||
'input-height' => '50px',
|
||||
'input-spacing' => '20px',
|
||||
'font-size' => '18px',
|
||||
'line-height' => '21px',
|
||||
'padding-h' => '14px',
|
||||
'checkbox-size' => '18px',
|
||||
'sublabel-spacing' => '10px',
|
||||
'icon-size' => '1.25',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Label Size vars and values.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const LABEL_SIZE = [
|
||||
'small' => [
|
||||
'font-size' => '14px',
|
||||
'line-height' => '17px',
|
||||
'sublabel-font-size' => '13px',
|
||||
'sublabel-line-height' => '16px',
|
||||
],
|
||||
'medium' => [
|
||||
'font-size' => '16px',
|
||||
'line-height' => '19px',
|
||||
'sublabel-font-size' => '14px',
|
||||
'sublabel-line-height' => '17px',
|
||||
],
|
||||
'large' => [
|
||||
'font-size' => '18px',
|
||||
'line-height' => '21px',
|
||||
'sublabel-font-size' => '16px',
|
||||
'sublabel-line-height' => '19px',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Button Size vars and values.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const BUTTON_SIZE = [
|
||||
'small' => [
|
||||
'font-size' => '14px',
|
||||
'height' => '37px',
|
||||
'padding-h' => '15px',
|
||||
'margin-top' => '5px',
|
||||
],
|
||||
'medium' => [
|
||||
'font-size' => '17px',
|
||||
'height' => '41px',
|
||||
'padding-h' => '15px',
|
||||
'margin-top' => '10px',
|
||||
],
|
||||
'large' => [
|
||||
'font-size' => '20px',
|
||||
'height' => '48px',
|
||||
'padding-h' => '20px',
|
||||
'margin-top' => '15px',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Spare variables.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const SPARE_VARS = [ 'field-border-color' ];
|
||||
|
||||
/**
|
||||
* White color.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const WHITE = '#ffffff';
|
||||
|
||||
/**
|
||||
* Render engine.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $render_engine;
|
||||
|
||||
/**
|
||||
* CSS variables.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $css_vars;
|
||||
|
||||
/**
|
||||
* Flag to check if root CSS vars were output.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_root_vars_displayed;
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function init(): void {
|
||||
|
||||
$this->init_vars();
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS variables data.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
private function init_vars(): void {
|
||||
|
||||
$vars = [];
|
||||
|
||||
$vars[':root'] = array_merge(
|
||||
self::ROOT_VARS,
|
||||
$this->get_complex_vars( 'field-size', self::FIELD_SIZE['medium'] ),
|
||||
$this->get_complex_vars( 'label-size', self::LABEL_SIZE['medium'] ),
|
||||
$this->get_complex_vars( 'button-size', self::BUTTON_SIZE['medium'] ),
|
||||
$this->get_complex_vars( 'container-shadow-size', self::CONTAINER_SHADOW_SIZE['none'] )
|
||||
);
|
||||
|
||||
/**
|
||||
* Allows developers to modify default CSS variables which output on the frontend.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $vars CSS variables two-dimensional array.
|
||||
* The first level keys is the CSS selector.
|
||||
* Second level keys is the variable name without the `--wpforms-` prefix.
|
||||
*/
|
||||
$this->css_vars = apply_filters( 'wpforms_frontend_css_vars_init_vars', $vars );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complex CSS variables data.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $prefix CSS variable prefix.
|
||||
* @param array $values Values.
|
||||
*/
|
||||
public function get_complex_vars( string $prefix, array $values ): array {
|
||||
|
||||
$vars = [];
|
||||
|
||||
foreach ( $values as $key => $value ) {
|
||||
$vars[ "{$prefix}-{$key}" ] = $value;
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS variables data by selector.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $selector Selector.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_vars( string $selector = ':root' ): array {
|
||||
|
||||
if ( empty( $this->css_vars[ $selector ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->css_vars[ $selector ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Output root CSS variables.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @since 1.8.1.2 Added $force argument.
|
||||
* @deprecated 1.9.3
|
||||
*
|
||||
* @param bool $force Force output root variables.
|
||||
*
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
public function output_root( $force = false ): void {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.9.3 of the WPForms plugin' );
|
||||
|
||||
if ( ! empty( $this->is_root_vars_displayed ) && empty( $force ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output_selector_vars( ':root', $this->css_vars[':root'] );
|
||||
|
||||
$this->is_root_vars_displayed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root variables CSS.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_root_vars_css(): string {
|
||||
|
||||
return $this->get_selector_vars_css( ':root', $this->css_vars[':root'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output selector's CSS variables.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $selector Selector.
|
||||
* @param array $vars Variables data.
|
||||
* @param string $style_id Style tag ID attribute. Optional. Default is an empty string.
|
||||
* @param string|int $form_id Form ID. Optional. Default is an empty string.
|
||||
*/
|
||||
public function output_selector_vars( string $selector, array $vars, string $style_id = '', $form_id = '' ): void {
|
||||
|
||||
if ( empty( $this->render_engine ) ) {
|
||||
$this->render_engine = wpforms_get_render_engine();
|
||||
}
|
||||
|
||||
if ( $this->render_engine === 'classic' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is not full "Base and Form Theme Styling", skip.
|
||||
if ( (int) wpforms_setting( 'disable-css', '1' ) !== 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$style_id = empty( $style_id ) ? 'wpforms-css-vars-' . $selector : $style_id;
|
||||
|
||||
printf(
|
||||
'<style id="%1$s">
|
||||
%2$s
|
||||
</style>',
|
||||
sanitize_key( $style_id ),
|
||||
$this->get_selector_vars_css( $selector, $vars, $form_id ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output CSS vars for the form added as a shortcode.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $atts Shortcode attributes.
|
||||
*/
|
||||
public function output_css_vars_for_shortcode( array $atts ): void {
|
||||
|
||||
if ( empty( $atts['id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form_handler = wpforms()->obj( 'form' );
|
||||
|
||||
if ( ! $form_handler ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form_id = (int) $atts['id'];
|
||||
$form_data = $form_handler->get( $form_id, [ 'content_only' => true ] );
|
||||
|
||||
if ( empty( $form_data ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attr = isset( $form_data['settings']['themes'] ) ? (array) $form_data['settings']['themes'] : [];
|
||||
$attr = $this->maybe_override_attributes( $attr );
|
||||
|
||||
$css_vars = $this->get_customized_css_vars( $attr );
|
||||
$css_vars = $this->add_css_vars_units( $css_vars );
|
||||
|
||||
$selector = "#wpforms-{$form_id}";
|
||||
$style_id = "wpforms-css-vars-{$form_id}";
|
||||
|
||||
$this->output_selector_vars( $selector, $css_vars, $style_id, $form_id );
|
||||
$this->output_custom_css( $attr, $selector, $style_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output custom CSS.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $attr Attributes.
|
||||
* @param string $selector Selector.
|
||||
* @param string $style_id Style ID.
|
||||
*
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
private function output_custom_css( array $attr, string $selector, string $style_id ): void {
|
||||
|
||||
if ( wpforms_get_render_engine() === 'classic' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$custom_css = trim( $attr['customCss'] ?? '' );
|
||||
|
||||
if ( empty( $custom_css ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<style id="%1$s-custom-css">
|
||||
%2$s {
|
||||
%3$s
|
||||
}
|
||||
</style>',
|
||||
sanitize_key( $style_id ),
|
||||
$selector, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wp_strip_all_tags( $custom_css ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe override attributes with themes.json settings.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $attr Attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_override_attributes( array $attr ): array {
|
||||
|
||||
$theme_slug = (string) ( $attr['wpformsTheme'] ?? '' );
|
||||
|
||||
if ( empty( $theme_slug ) ) {
|
||||
return $attr;
|
||||
}
|
||||
|
||||
$attr = $this->normalize_background_url( $attr );
|
||||
|
||||
$theme_data = $this->get_themes_data_object()->get_theme( $theme_slug );
|
||||
$settings = $theme_data['settings'] ?? [];
|
||||
|
||||
return array_merge( $attr, $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize background URL.
|
||||
*
|
||||
* Check if the background URL is not wrapped in url() and add it if needed.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $attr Attributes.
|
||||
*/
|
||||
private function normalize_background_url( array $attr ): array {
|
||||
|
||||
if ( ! isset( $attr['backgroundUrl'] ) ) {
|
||||
return $attr;
|
||||
}
|
||||
|
||||
if ( strpos( $attr['backgroundUrl'], 'url(' ) === 0 ) {
|
||||
return $attr;
|
||||
}
|
||||
|
||||
$attr['backgroundUrl'] = 'url(' . $attr['backgroundUrl'] . ')';
|
||||
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get themes data object.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @return ThemesData
|
||||
*/
|
||||
private function get_themes_data_object(): ThemesData {
|
||||
|
||||
if ( wpforms()->is_pro() ) {
|
||||
return new ProThemesData( new StockPhotos() );
|
||||
}
|
||||
|
||||
return new LiteThemesData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSS vars units.
|
||||
*
|
||||
* Form builder saves values without pixels, we need to add them before outputting as CSS vars.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $css_vars CSS vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function add_css_vars_units( array $css_vars ): array {
|
||||
|
||||
$has_pixels = [
|
||||
'field-border-size',
|
||||
'field-border-radius',
|
||||
'button-border-size',
|
||||
'button-border-radius',
|
||||
'container-padding',
|
||||
'container-border-width',
|
||||
'container-border-radius',
|
||||
];
|
||||
|
||||
foreach ( $has_pixels as $key ) {
|
||||
if ( isset( $css_vars[ $key ] ) && is_numeric( $css_vars[ $key ] ) && $css_vars[ $key ] > 0 ) {
|
||||
$css_vars[ $key ] .= 'px';
|
||||
}
|
||||
}
|
||||
|
||||
return $css_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selector variables CSS.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param string $selector Selector.
|
||||
* @param array $vars Variables data.
|
||||
* @param string|int $form_id Form ID. Optional. Default is an empty string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_selector_vars_css( string $selector, array $vars, $form_id = '' ): string {
|
||||
|
||||
return sprintf(
|
||||
'%1$s {
|
||||
%2$s
|
||||
}',
|
||||
$selector, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wp_strip_all_tags( $this->get_vars_css( $vars, $form_id ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre print vars filter.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $vars Variables data.
|
||||
* @param string|int $form_id Form ID. Optional. Default is an empty string.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_pre_print_vars( array $vars, $form_id = '' ): array {
|
||||
|
||||
// Normalize the `background-url` variable.
|
||||
if ( isset( $vars['background-url'] ) ) {
|
||||
$vars['background-url'] = $vars['background-url'] === 'url()' ? 'none' : $vars['background-url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter CSS variables right before printing the CSS.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $vars CSS variables.
|
||||
* @param int $form_id Form ID. Optional. Default is an empty string.
|
||||
*/
|
||||
return (array) apply_filters( 'wpforms_frontend_css_vars_pre_print_filter', $vars, $form_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSS code from given vars data.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $vars Variables data.
|
||||
* @param string|int $form_id Form ID. Optional. Default is an empty string.
|
||||
*/
|
||||
private function get_vars_css( array $vars, $form_id = '' ): string {
|
||||
|
||||
$vars = $this->get_pre_print_vars( $vars, $form_id );
|
||||
$result = '';
|
||||
|
||||
foreach ( $vars as $name => $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $value === '0' ) {
|
||||
$value = '0px';
|
||||
}
|
||||
|
||||
$result .= "--wpforms-{$name}: {$value};\n";
|
||||
|
||||
if ( in_array( $name, self::SPARE_VARS, true ) ) {
|
||||
$result .= "--wpforms-{$name}-spare: {$value};\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customized CSS vars.
|
||||
*
|
||||
* @since 1.8.3
|
||||
*
|
||||
* @param array $attr Attributes passed by integration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_customized_css_vars( array $attr ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
$root_css_vars = $this->get_vars();
|
||||
$css_vars = [];
|
||||
|
||||
foreach ( $attr as $key => $value ) {
|
||||
|
||||
$var_name = strtolower( preg_replace( '/[A-Z]/', '-$0', $key ) );
|
||||
|
||||
// Skip an attribute that is not the CSS var or has the default value.
|
||||
if ( empty( $root_css_vars[ $var_name ] ) || $root_css_vars[ $var_name ] === $value ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$css_vars[ $var_name ] = $value;
|
||||
}
|
||||
|
||||
// Reset border size in case of border style is `none`.
|
||||
$css_vars = $this->maybe_reset_border( $css_vars, 'field-border' );
|
||||
$css_vars = $this->maybe_reset_border( $css_vars, 'button-border' );
|
||||
|
||||
// Set the button alternative background color and use border color for accent in case of transparent color.
|
||||
$button_bg_color = $css_vars['button-background-color'] ?? $root_css_vars['button-background-color'];
|
||||
|
||||
if ( $this->is_transparent_color( $button_bg_color ) ) {
|
||||
$css_vars['button-background-color-alt'] = $button_bg_color;
|
||||
|
||||
$border_color = $css_vars['button-border-color'] ?? $root_css_vars['button-border-color'];
|
||||
|
||||
$css_vars['button-background-color'] = $this->is_transparent_color( $border_color ) ? $root_css_vars['button-background-color'] : $border_color;
|
||||
$button_bg_color = $css_vars['button-background-color'];
|
||||
}
|
||||
|
||||
$button_bg_color = strtolower( $button_bg_color );
|
||||
|
||||
// Set the button alternative text color in case if the background and text color are identical.
|
||||
$button_text_color = strtolower( $css_vars['button-text-color'] ?? $root_css_vars['button-text-color'] );
|
||||
|
||||
if ( $button_bg_color === $button_text_color || $this->is_transparent_color( $button_text_color ) ) {
|
||||
$css_vars['button-text-color-alt'] = $this->get_contrast_color( $button_bg_color );
|
||||
}
|
||||
|
||||
$size_css_vars = $this->get_size_css_vars( $attr );
|
||||
|
||||
return array_merge( $css_vars, $size_css_vars );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset border size in case of border style is `none`.
|
||||
*
|
||||
* @since 1.9.7
|
||||
*
|
||||
* @param array $css_vars CSS vars.
|
||||
* @param string $key Key.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_reset_border( array $css_vars, string $key ): array {
|
||||
|
||||
$style_key = $key . '-style';
|
||||
$size_key = $key . '-size';
|
||||
|
||||
if ( isset( $css_vars[ $style_key ] ) && $css_vars[ $style_key ] === 'none' ) {
|
||||
$css_vars[ $size_key ] = '0px';
|
||||
}
|
||||
|
||||
return $css_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided color has transparency.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string $color The color to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_transparent_color( string $color ): bool {
|
||||
|
||||
$rgba = $this->get_color_as_rgb_array( $color );
|
||||
|
||||
$opacity_threshold = 0.33;
|
||||
$opacity = $rgba[3] ?? 1;
|
||||
|
||||
return $opacity < $opacity_threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contrast color relative to a given color.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string|array $color The color.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_contrast_color( $color ): string {
|
||||
|
||||
$rgba = is_array( $color ) ? $color : $this->get_color_as_rgb_array( $color );
|
||||
$avg = (int) ( ( ( array_sum( $rgba ) ) / 3 ) * ( $rgba[3] ?? 1 ) );
|
||||
|
||||
return $avg < 128 ? '#ffffff' : '#000000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size CSS vars.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @since 1.8.8 Removed $css_vars argument.
|
||||
*
|
||||
* @param array $attr Attributes passed by integration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_size_css_vars( array $attr ): array {
|
||||
|
||||
$size_items = [ 'field', 'label', 'button', 'container-shadow' ];
|
||||
$size_css_vars = [];
|
||||
|
||||
foreach ( $size_items as $item ) {
|
||||
|
||||
$item_attr = preg_replace_callback(
|
||||
'/-(\w)/',
|
||||
static function ( $matches ) {
|
||||
|
||||
return strtoupper( $matches[1] );
|
||||
},
|
||||
$item
|
||||
);
|
||||
|
||||
$item_attr .= 'Size';
|
||||
|
||||
$item_key = $item . '-size';
|
||||
$item_constant = 'self::' . str_replace( '-', '_', strtoupper( $item ) ) . '_SIZE';
|
||||
|
||||
if ( empty( $attr[ $item_attr ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$size_css_vars[] = $this->get_complex_vars( $item_key, constant( $item_constant )[ $attr[ $item_attr ] ] );
|
||||
}
|
||||
|
||||
return empty( $size_css_vars ) ? [] : array_merge( ...$size_css_vars );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color as an array of RGB(A) values.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string $color Color.
|
||||
*
|
||||
* @return array|bool Color as an array of RGBA values. False on error.
|
||||
*/
|
||||
private function get_color_as_rgb_array( string $color ) {
|
||||
|
||||
// Remove # from the beginning of the string and remove whitespaces.
|
||||
$color = preg_replace( '/^#/', '', strtolower( trim( $color ) ) );
|
||||
$color = str_replace( ' ', '', (string) $color );
|
||||
|
||||
if ( $color === 'transparent' ) {
|
||||
$color = 'rgba(0,0,0,0)';
|
||||
}
|
||||
|
||||
$rgba = $color;
|
||||
$rgb_array = [];
|
||||
|
||||
// Check if color is in HEX(A) format.
|
||||
$is_hex = preg_match( '/[0-9a-f]{6,8}$/', $rgba );
|
||||
|
||||
if ( $is_hex ) {
|
||||
// Search and split HEX(A) color into an array of char couples.
|
||||
preg_match_all( '/\w\w/', $rgba, $rgb_array );
|
||||
|
||||
$rgb_array = array_map(
|
||||
static function ( $value ) {
|
||||
|
||||
return hexdec( '0x' . $value );
|
||||
},
|
||||
$rgb_array[0] ?? []
|
||||
);
|
||||
$rgb_array[3] = ( $rgb_array[3] ?? 255 ) / 255;
|
||||
} else {
|
||||
$rgba = preg_replace( '/[^\d,.]/', '', $rgba );
|
||||
$rgb_array = explode( ',', $rgba );
|
||||
}
|
||||
|
||||
return $rgb_array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,670 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Frontend;
|
||||
|
||||
/**
|
||||
* Captcha class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class Captcha {
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
// Filters.
|
||||
add_filter( 'script_loader_tag', [ $this, 'set_defer_attribute' ], 10, 3 );
|
||||
|
||||
// Actions.
|
||||
add_action( 'send_headers', [ $this, 'send_headers' ] );
|
||||
add_action( 'wpforms_frontend_output', [ $this, 'recaptcha' ], 20, 5 );
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'recaptcha_noconflict' ], 9999 );
|
||||
add_action( 'wp_footer', [ $this, 'recaptcha_noconflict' ], 19 );
|
||||
add_action( 'wpforms_wp_footer', [ $this, 'assets_recaptcha' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send HTTP headers to prevent warning in the browser console.
|
||||
*
|
||||
* @since 1.9.8.3
|
||||
*/
|
||||
public function send_headers(): void {
|
||||
|
||||
if ( headers_sent() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$urls = '"https://www.google.com" "https://www.gstatic.com" "https://recaptcha.net" "https://challenges.cloudflare.com" "https://hcaptcha.com"';
|
||||
|
||||
header(
|
||||
'Permissions-Policy: ' .
|
||||
"private-state-token-redemption=(self $urls), " .
|
||||
"private-state-token-issuance=(self $urls)",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* CAPTCHA output if configured.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
* @param null $deprecated Deprecated in v1.3.7, previously was $form object.
|
||||
* @param bool $title Whether to display form title.
|
||||
* @param bool $description Whether to display form description.
|
||||
* @param array $errors List of all errors filled in WPForms_Process::process().
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function recaptcha( $form_data, $deprecated, $title, $description, $errors ) {
|
||||
|
||||
// Check that CAPTCHA is configured in the settings.
|
||||
$captcha_settings = $this->get_form_captcha_settings( $form_data );
|
||||
|
||||
if ( ! $captcha_settings ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$frontend = wpforms()->obj( 'frontend' );
|
||||
|
||||
$container_classes = [ 'wpforms-recaptcha-container', 'wpforms-is-' . $captcha_settings['provider'] ];
|
||||
|
||||
if ( $captcha_settings['provider'] === 'recaptcha' ) {
|
||||
$container_classes[] = 'wpforms-is-recaptcha-type-' . $captcha_settings['recaptcha_type'];
|
||||
}
|
||||
|
||||
printf(
|
||||
'<div class="%1$s" %2$s>',
|
||||
wpforms_sanitize_classes( $container_classes, true ),
|
||||
$frontend->pages ? 'style="display:none;"' : ''
|
||||
);
|
||||
|
||||
$this->print_recaptcha_fields( $captcha_settings, $form_data );
|
||||
|
||||
if ( ! empty( $errors['recaptcha'] ) ) {
|
||||
$frontend->form_error( 'recaptcha', $errors['recaptcha'] );
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a provider-specific captcha class.
|
||||
*
|
||||
* @since 1.9.8.3
|
||||
*
|
||||
* @param string $provider Captcha provider.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_captcha_class( string $provider ): string {
|
||||
|
||||
$classes = [
|
||||
'recaptcha' => 'g-recaptcha',
|
||||
'hcaptcha' => 'h-captcha',
|
||||
'turnstile' => 'wpforms-turnstile',
|
||||
];
|
||||
|
||||
return $classes[ $provider ] ?? 'g-recaptcha';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recaptcha data.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $captcha_settings Captcha settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_recaptcha_data( array $captcha_settings, array $form_data ): array {
|
||||
|
||||
/**
|
||||
* Filters captcha sitekey.
|
||||
*
|
||||
* @since 1.7.1
|
||||
*
|
||||
* @param array $sitekey Sitekey.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
$data = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
'wpforms_frontend_recaptcha',
|
||||
[ 'sitekey' => $captcha_settings['site_key'] ],
|
||||
$form_data
|
||||
);
|
||||
|
||||
$is_recaptcha = $captcha_settings['provider'] === 'recaptcha';
|
||||
$is_turnstile = $captcha_settings['provider'] === 'turnstile';
|
||||
|
||||
if ( $is_recaptcha && $captcha_settings['recaptcha_type'] === 'invisible' ) {
|
||||
$data['size'] = 'invisible';
|
||||
}
|
||||
|
||||
if ( ! $is_turnstile ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Turnstile action value.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $action Action value. Can only contain up to 32 alphanumeric characters including _ and -.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
$data['action'] = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
'wpforms_frontend_recaptcha_turnstile_action',
|
||||
sprintf(
|
||||
'FormID-%d',
|
||||
$form_data['id']
|
||||
),
|
||||
$form_data
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print recaptcha fields.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $captcha_settings Captcha settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
private function print_recaptcha_fields( array $captcha_settings, array $form_data ) {
|
||||
|
||||
$data = $this->get_recaptcha_data( $captcha_settings, $form_data );
|
||||
$is_recaptcha = $captcha_settings['provider'] === 'recaptcha';
|
||||
$is_recaptcha_v3 = $is_recaptcha && $captcha_settings['recaptcha_type'] === 'v3';
|
||||
|
||||
if ( $is_recaptcha_v3 ) {
|
||||
// The value adds via JS code.
|
||||
echo '<input type="hidden" name="wpforms[recaptcha]" value="">';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$captcha_class = $this->get_captcha_class( $captcha_settings['provider'] );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div ' . wpforms_html_attributes( '', [ $captcha_class ], $data ) . '></div>';
|
||||
|
||||
if ( $is_recaptcha && $captcha_settings['recaptcha_type'] === 'invisible' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<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;" data-rule-%1$s="1">',
|
||||
esc_attr( $captcha_settings['provider'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get captcha settings for form output.
|
||||
* Return null if captcha is disabled.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return array|null
|
||||
* @noinspection NullPointerExceptionInspection
|
||||
*/
|
||||
private function get_form_captcha_settings( $form_data ) {
|
||||
|
||||
$captcha_settings = wpforms_get_captcha_settings();
|
||||
|
||||
if (
|
||||
empty( $captcha_settings['provider'] ) ||
|
||||
$captcha_settings['provider'] === 'none' ||
|
||||
empty( $captcha_settings['site_key'] ) ||
|
||||
empty( $captcha_settings['secret_key'] )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check that the CAPTCHA is configured for the specific form.
|
||||
if (
|
||||
! isset( $form_data['settings']['recaptcha'] ) ||
|
||||
$form_data['settings']['recaptcha'] !== '1'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$is_recaptcha_v3 = $captcha_settings['provider'] === 'recaptcha' && $captcha_settings['recaptcha_type'] === 'v3';
|
||||
|
||||
if ( wpforms()->obj( 'amp' )->output_captcha( $is_recaptcha_v3, $captcha_settings, $form_data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $captcha_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google reCAPTCHA no-conflict mode.
|
||||
*
|
||||
* When enabled in the WPForms settings, forcefully remove all other
|
||||
* reCAPTCHA enqueues to prevent conflicts. Filter can be used to target
|
||||
* specific pages, etc.
|
||||
*
|
||||
* @since 1.4.5
|
||||
* @since 1.6.4 Added hCaptcha support.
|
||||
*/
|
||||
public function recaptcha_noconflict() {
|
||||
|
||||
$captcha_settings = wpforms_get_captcha_settings();
|
||||
|
||||
if (
|
||||
empty( $captcha_settings['provider'] ) ||
|
||||
$captcha_settings['provider'] === 'none' ||
|
||||
empty( wpforms_setting( 'recaptcha-noconflict' ) ) ||
|
||||
/**
|
||||
* Filters recaptcha no conflict flag.
|
||||
*
|
||||
* @since 1.6.4
|
||||
*
|
||||
* @param bool $recaptcha_no_conflict No conflict flag.
|
||||
*/
|
||||
! apply_filters( 'wpforms_frontend_recaptcha_noconflict', true ) // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scripts = wp_scripts();
|
||||
$urls = [ 'google.com/recaptcha', 'gstatic.com/recaptcha', 'hcaptcha.com/1' ];
|
||||
|
||||
foreach ( $scripts->queue as $handle ) {
|
||||
|
||||
// Skip the WPForms javascript-assets.
|
||||
if (
|
||||
! isset( $scripts->registered[ $handle ] ) ||
|
||||
false !== strpos( $scripts->registered[ $handle ]->handle, 'wpforms' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $urls as $url ) {
|
||||
if ( false !== strpos( $scripts->registered[ $handle ]->src, $url ) ) {
|
||||
wp_dequeue_script( $handle );
|
||||
wp_deregister_script( $handle );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the assets needed for the CAPTCHA.
|
||||
*
|
||||
* @since 1.6.2
|
||||
* @since 1.6.4 Added hCaptcha support.
|
||||
*
|
||||
* @param array $forms Forms being displayed.
|
||||
*/
|
||||
public function assets_recaptcha( $forms ) {
|
||||
|
||||
$captcha_settings = $this->get_assets_captcha_settings( $forms );
|
||||
|
||||
if ( ! $captcha_settings ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_recaptcha_v3 = $captcha_settings['provider'] === 'recaptcha' && $captcha_settings['recaptcha_type'] === 'v3';
|
||||
$recaptcha_url = $is_recaptcha_v3 ?
|
||||
'https://www.google.com/recaptcha/api.js?render=' . $captcha_settings['site_key'] :
|
||||
/**
|
||||
* For backward compatibility reason we have to filter only the v2 reCAPTCHA.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @param string $url The reCaptcha v2 URL.
|
||||
*/
|
||||
apply_filters( 'wpforms_frontend_recaptcha_url', 'https://www.google.com/recaptcha/api.js?onload=wpformsRecaptchaLoad&render=explicit' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
$captcha_api_array = [
|
||||
'hcaptcha' => 'https://hcaptcha.com/1/api.js?onload=wpformsRecaptchaLoad&render=explicit&recaptchacompat=off',
|
||||
'recaptcha' => $recaptcha_url,
|
||||
'turnstile' => 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=wpformsRecaptchaLoad&render=explicit',
|
||||
];
|
||||
/**
|
||||
* Filter the CAPTCHA API URL.
|
||||
*
|
||||
* @since 1.6.4
|
||||
*
|
||||
* @param string $captcha_api The CAPTCHA API URL.
|
||||
*/
|
||||
$captcha_api = apply_filters( 'wpforms_frontend_captcha_api', $captcha_api_array[ $captcha_settings['provider'] ] );
|
||||
$in_footer = ! wpforms_is_frontend_js_header_force_load();
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-recaptcha',
|
||||
$captcha_api,
|
||||
$is_recaptcha_v3 ? [] : [ 'jquery' ],
|
||||
null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
$in_footer
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the string containing the CAPTCHA JavaScript to be added.
|
||||
*
|
||||
* @since 1.6.4
|
||||
*
|
||||
* @param string $captcha_inline The CAPTCHA JavaScript.
|
||||
*/
|
||||
$captcha_inline = apply_filters(
|
||||
'wpforms_frontend_captcha_inline_script',
|
||||
$this->get_captcha_inline_script( $captcha_settings )
|
||||
);
|
||||
|
||||
wp_add_inline_script( 'wpforms-recaptcha', $captcha_inline );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get captcha settings for assets output.
|
||||
* Return null if captcha is disabled.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $forms Forms being displayed.
|
||||
*
|
||||
* @return array|null
|
||||
* @noinspection NullPointerExceptionInspection
|
||||
*/
|
||||
private function get_assets_captcha_settings( $forms ) {
|
||||
|
||||
/**
|
||||
* Filters disable captcha switch.
|
||||
*
|
||||
* @since 1.6.2
|
||||
*
|
||||
* @param bool $is_captcha_disabled Whether captcha is disabled.
|
||||
*/
|
||||
if ( apply_filters( 'wpforms_frontend_recaptcha_disable', false ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load CAPTCHA support if form supports it.
|
||||
$captcha_settings = wpforms_get_captcha_settings();
|
||||
|
||||
if (
|
||||
empty( $captcha_settings['provider'] ) ||
|
||||
$captcha_settings['provider'] === 'none' ||
|
||||
empty( $captcha_settings['site_key'] ) ||
|
||||
empty( $captcha_settings['secret_key'] )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whether at least 1 form on a page has CAPTCHA enabled.
|
||||
$captcha = false;
|
||||
|
||||
foreach ( $forms as $form ) {
|
||||
if ( ! empty( $form['settings']['recaptcha'] ) ) {
|
||||
$captcha = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return early.
|
||||
if ( ! $captcha && ! wpforms()->obj( 'frontend' )->assets_global() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $captcha_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the string containing the CAPTCHA inline javascript.
|
||||
*
|
||||
* @since 1.6.4
|
||||
*
|
||||
* @param array $captcha_settings The CAPTCHA settings.
|
||||
*
|
||||
* @return string
|
||||
* @noinspection JSUnusedLocalSymbols
|
||||
* @noinspection UnnecessaryLocalVariableJS
|
||||
* @noinspection JSUnresolvedVariable
|
||||
* @noinspection JSDeprecatedSymbols
|
||||
* @noinspection JSUnresolvedFunction
|
||||
*/
|
||||
protected function get_captcha_inline_script( $captcha_settings ) {
|
||||
|
||||
// IE11 polyfills for native `matches()` and `closest()` methods.
|
||||
$polyfills = /** @lang JavaScript */
|
||||
'if (!Element.prototype.matches) {
|
||||
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
|
||||
}
|
||||
if (!Element.prototype.closest) {
|
||||
Element.prototype.closest = function (s) {
|
||||
var el = this;
|
||||
do {
|
||||
if (Element.prototype.matches.call(el, s)) { return el; }
|
||||
el = el.parentElement || el.parentNode;
|
||||
} while (el !== null && el.nodeType === 1);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
';
|
||||
|
||||
// Native equivalent for jQuery's `trigger()` method.
|
||||
$dispatch = /** @lang JavaScript */
|
||||
'var wpformsDispatchEvent = function (el, ev, custom) {
|
||||
var e = document.createEvent(custom ? "CustomEvent" : "HTMLEvents");
|
||||
custom ? e.initCustomEvent(ev, true, true, false) : e.initEvent(ev, true, true);
|
||||
el.dispatchEvent(e);
|
||||
};
|
||||
';
|
||||
|
||||
// Update container class after changing Turnstile type.
|
||||
$turnstile_update_class = /** @lang JavaScript */
|
||||
'var turnstileUpdateContainer = function (el) {
|
||||
|
||||
let form = el.closest( "form" ),
|
||||
iframeWrapperHeight = el.offsetHeight;
|
||||
|
||||
parseInt(iframeWrapperHeight) === 0 ?
|
||||
form.querySelector(".wpforms-is-turnstile").classList.add( "wpforms-is-turnstile-invisible" ) :
|
||||
form.querySelector(".wpforms-is-turnstile").classList.remove( "wpforms-is-turnstile-invisible" );
|
||||
};
|
||||
';
|
||||
|
||||
// Captcha callback, used by hCaptcha and checkbox reCaptcha v2.
|
||||
$callback = /** @lang JavaScript */
|
||||
'var wpformsRecaptchaCallback = function (el) {
|
||||
var hdn = el.parentNode.querySelector(".wpforms-recaptcha-hidden");
|
||||
var err = el.parentNode.querySelector("#g-recaptcha-hidden-error");
|
||||
hdn.value = "1";
|
||||
wpformsDispatchEvent(hdn, "change", false);
|
||||
hdn.classList.remove("wpforms-error");
|
||||
err && hdn.parentNode.removeChild(err);
|
||||
};
|
||||
';
|
||||
|
||||
$sync = /** @lang JavaScript */
|
||||
'const wpformsRecaptchaSync = ( func ) => {
|
||||
return function() {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
|
||||
// Sync with jQuery ready event.
|
||||
jQuery( document ).ready( function() {
|
||||
func.apply( context, args );
|
||||
} );
|
||||
}
|
||||
};
|
||||
';
|
||||
|
||||
if ( $captcha_settings['provider'] === 'hcaptcha' ) {
|
||||
$data = $dispatch;
|
||||
$data .= $callback;
|
||||
|
||||
$data .= /** @lang JavaScript */
|
||||
'var wpformsRecaptchaLoad = function () {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".h-captcha"), function (el) {
|
||||
var captchaID = hcaptcha.render(el, {
|
||||
callback: function () {
|
||||
wpformsRecaptchaCallback(el);
|
||||
}
|
||||
});
|
||||
el.setAttribute("data-recaptcha-id", captchaID);
|
||||
});
|
||||
wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
|
||||
};
|
||||
';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $captcha_settings['provider'] === 'turnstile' ) {
|
||||
$data = $dispatch;
|
||||
$data .= $callback;
|
||||
$data .= $turnstile_update_class;
|
||||
|
||||
$data .= /** @lang JavaScript */
|
||||
'var wpformsRecaptchaLoad = function () {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".wpforms-turnstile"), function (el) {
|
||||
let form = el.closest( "form" ),
|
||||
formId = form.dataset.formid,
|
||||
captchaID = turnstile.render(el, {
|
||||
theme: "' . $captcha_settings['theme'] . '",
|
||||
callback: function () {
|
||||
turnstileUpdateContainer(el);
|
||||
wpformsRecaptchaCallback(el);
|
||||
},
|
||||
"timeout-callback": function() {
|
||||
turnstileUpdateContainer(el);
|
||||
}
|
||||
});
|
||||
el.setAttribute("data-recaptcha-id", captchaID);
|
||||
});
|
||||
|
||||
wpformsDispatchEvent( document, "wpformsRecaptchaLoaded", true );
|
||||
};
|
||||
';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $captcha_settings['recaptcha_type'] === 'v3' ) {
|
||||
$data = $dispatch;
|
||||
|
||||
$data .= /** @lang JavaScript */
|
||||
'var wpformsRecaptchaV3Execute = function ( callback ) {
|
||||
grecaptcha.execute( "' . $captcha_settings['site_key'] . '", { action: "wpforms" } ).then( function ( token ) {
|
||||
Array.prototype.forEach.call( document.getElementsByName( "wpforms[recaptcha]" ), function ( el ) {
|
||||
el.value = token;
|
||||
} );
|
||||
if ( typeof callback === "function" ) {
|
||||
return callback();
|
||||
}
|
||||
} );
|
||||
}
|
||||
grecaptcha.ready( function () {
|
||||
wpformsDispatchEvent( document, "wpformsRecaptchaLoaded", true );
|
||||
} );
|
||||
';
|
||||
} elseif ( $captcha_settings['recaptcha_type'] === 'invisible' ) {
|
||||
$data = $polyfills;
|
||||
$data .= $dispatch;
|
||||
$data .= $sync;
|
||||
|
||||
$data .= /** @lang JavaScript */
|
||||
'var wpformsRecaptchaLoad = wpformsRecaptchaSync( function () {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".g-recaptcha"), function (el) {
|
||||
try {
|
||||
var recaptchaID = grecaptcha.render(el, {
|
||||
"callback": function () {
|
||||
wpformsRecaptchaCallback(el);
|
||||
},
|
||||
"error-callback": function () {
|
||||
wpformsRecaptchaErrorCallback(el);
|
||||
}
|
||||
}, true);
|
||||
el.closest("form").querySelector("button[type=submit]").recaptchaID = recaptchaID;
|
||||
} catch (error) {}
|
||||
});
|
||||
wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
|
||||
} );
|
||||
var wpformsRecaptchaCallback = function (el) {
|
||||
var $form = el.closest("form");
|
||||
if (typeof wpforms.formSubmit === "function") {
|
||||
wpforms.formSubmit($form);
|
||||
} else {
|
||||
$form.querySelector("button[type=submit]").recaptchaID = false;
|
||||
$form.submit();
|
||||
}
|
||||
};
|
||||
var wpformsRecaptchaErrorCallback = function (el) {
|
||||
var $form = el.closest("form");
|
||||
$form.querySelector("button[type=submit]").dataset.captchaInvalid = true;
|
||||
};
|
||||
';
|
||||
} else {
|
||||
$data = $dispatch;
|
||||
$data .= $callback;
|
||||
|
||||
$data .= /** @lang JavaScript */
|
||||
'var wpformsRecaptchaLoad = function () {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".g-recaptcha"), function (el) {
|
||||
try {
|
||||
var recaptchaID = grecaptcha.render(el, {
|
||||
callback: function () {
|
||||
wpformsRecaptchaCallback(el);
|
||||
}
|
||||
});
|
||||
el.setAttribute("data-recaptcha-id", recaptchaID);
|
||||
} catch (error) {}
|
||||
});
|
||||
wpformsDispatchEvent(document, "wpformsRecaptchaLoaded", true);
|
||||
};
|
||||
';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloudflare Turnstile captcha requires defer attribute.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $tag HTML for the script tag.
|
||||
* @param string $handle Handle of script.
|
||||
* @param string $src Src of script.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function set_defer_attribute( $tag, $handle, $src ) {
|
||||
|
||||
$captcha_settings = wpforms_get_captcha_settings();
|
||||
|
||||
if ( $captcha_settings['provider'] !== 'turnstile' ) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
if ( $handle !== 'wpforms-recaptcha' ) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
return str_replace( ' src', ' defer src', $tag );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Frontend;
|
||||
|
||||
/**
|
||||
* Classic render engine class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class Classic {
|
||||
|
||||
/**
|
||||
* Current form data.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $form_data;
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function hooks() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Open form container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string|array $classes Form container classes.
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function form_container_open( $classes, $form_data ) {
|
||||
|
||||
printf(
|
||||
'<div class="wpforms-container %s" id="wpforms-%d">',
|
||||
wpforms_sanitize_classes( $classes, true ),
|
||||
absint( $form_data['id'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close form container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function form_container_close() {
|
||||
|
||||
echo '</div> <!-- .wpforms-container -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* The form has no fields.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function form_is_empty() {
|
||||
|
||||
echo '<!-- WPForms: no fields, form hidden -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* Noscript message.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $msg Noscript message.
|
||||
*/
|
||||
public function noscript( $msg ) {
|
||||
|
||||
printf(
|
||||
'<noscript class="wpforms-error-noscript">%s</noscript>',
|
||||
esc_html( $msg )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display form error.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $type Error type.
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
public function form_error( $type, $error ) {
|
||||
|
||||
switch ( $type ) {
|
||||
case 'header':
|
||||
case 'footer':
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="wpforms-error-container">' . wpautop( wpforms_sanitize_error( $error ) ) . '</div>';
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
break;
|
||||
|
||||
case 'header_styled':
|
||||
case 'footer_styled':
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="wpforms-error-container wpforms-error-styled-container"><div class="wpforms-error">' . wpautop( wpforms_sanitize_error( $error ) ) . '</div></div>';
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
break;
|
||||
|
||||
case 'recaptcha':
|
||||
echo '<label id="wpforms-field_recaptcha-error" class="wpforms-error">' . wpforms_sanitize_error( $error ) . '</label>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open fields area container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function fields_area_open() {
|
||||
|
||||
echo '<div class="wpforms-field-container">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Close fields area container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function fields_area_close() {
|
||||
|
||||
echo '</div><!-- .wpforms-field-container -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open container for each field.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_container_open( $field, $form_data ) {
|
||||
|
||||
$container = $field['properties']['container'];
|
||||
$container['data']['field-id'] = wpforms_validate_field_id( $field['id'] );
|
||||
|
||||
printf(
|
||||
'<div %s>',
|
||||
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close container markup for each field.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_container_close( $field, $form_data ) {
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open fieldset.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_fieldset_open( $field, $form_data ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Close fieldset.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_fieldset_close( $field, $form_data ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Field label.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_label( $field, $form_data ) {
|
||||
|
||||
if ( empty( $field['properties']['label'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$label = $field['properties']['label'];
|
||||
$required = $label['required'] ? wpforms_get_field_required_label() : '';
|
||||
|
||||
printf(
|
||||
'<label %s>%s%s</label>',
|
||||
wpforms_html_attributes( $label['id'], $label['class'], $label['data'], $label['attr'] ),
|
||||
esc_html( $label['value'] ),
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$required
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Field error.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_error( $field, $form_data ) {
|
||||
|
||||
if ( empty( $field['properties']['error'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $field['properties']['error'];
|
||||
|
||||
printf(
|
||||
'<label %s>%s</label>',
|
||||
wpforms_html_attributes( $error['id'], $error['class'], $error['data'], $error['attr'] ),
|
||||
esc_html( $error['value'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Field description.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function field_description( $field, $form_data ) {
|
||||
|
||||
if ( empty( $field['properties']['description'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$description = $field['properties']['description'];
|
||||
|
||||
printf(
|
||||
'<div %s>%s</div>',
|
||||
wpforms_html_attributes( $description['id'], $description['class'], $description['data'], $description['attr'] ),
|
||||
do_shortcode( $description['value'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $confirmation_message Confirmation message.
|
||||
* @param string $class CSS class.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
public function confirmation( $confirmation_message, $class, $form_data ) {
|
||||
|
||||
$form_id = isset( $form_data['id'] ) ? $form_data['id'] : 0;
|
||||
|
||||
printf(
|
||||
'<div class="%s" id="wpforms-confirmation-%d">%s</div>',
|
||||
wpforms_sanitize_classes( $class ),
|
||||
absint( $form_id ),
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$confirmation_message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form head container. Form title and description.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param bool $title Whether to display form title.
|
||||
* @param bool $description Whether to display form description.
|
||||
* @param array $form_data Form data.
|
||||
*/
|
||||
public function form_head_container( $title, $description, $form_data ) {
|
||||
|
||||
$settings = $form_data['settings'];
|
||||
|
||||
echo '<div class="wpforms-head-container">';
|
||||
|
||||
if ( $title === true && ! empty( $settings['form_title'] ) ) {
|
||||
echo '<div class="wpforms-title">' . esc_html( $settings['form_title'] ) . '</div>';
|
||||
}
|
||||
|
||||
if ( $description === true && ! empty( $settings['form_desc'] ) ) {
|
||||
echo '<div class="wpforms-description">';
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wpforms_process_smart_tags( $settings['form_desc'], $form_data, [], '', 'form-description' );
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open submit container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param int $pages Information for multi-page forms.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
*/
|
||||
public function submit_container_open( $pages, $form_data ) {
|
||||
|
||||
printf( '<div class="wpforms-submit-container" %s>', $pages ? 'style="display:none;"' : '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit button.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param int $form_id Form ID.
|
||||
* @param string $submit Submit text.
|
||||
* @param array $classes CSS classes.
|
||||
* @param array $data_attrs Data attributes.
|
||||
* @param array $attrs Other attributes.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function submit_button( $form_id, $submit, $classes, $data_attrs, $attrs, $form_data ) {
|
||||
|
||||
printf(
|
||||
'<button type="submit" name="wpforms[submit]" %s>%s</button>',
|
||||
wpforms_html_attributes(
|
||||
sprintf( 'wpforms-submit-%d', absint( $form_id ) ),
|
||||
$classes,
|
||||
$data_attrs,
|
||||
$attrs
|
||||
),
|
||||
esc_html( $submit )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit button.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $src Spinner image src attribute.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function submit_spinner( $src, $form_data ) {
|
||||
|
||||
printf(
|
||||
'<img src="%s" class="wpforms-submit-spinner" style="display: none;" width="26" height="26" alt="%s">',
|
||||
esc_url( $src ),
|
||||
esc_attr__( 'Loading', 'wpforms-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open submit container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function submit_container_close( $form_data ) {
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Frontend;
|
||||
|
||||
/**
|
||||
* Modern render engine class.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class Modern extends Classic {
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function hooks() {
|
||||
|
||||
add_filter( 'wpforms_field_properties', [ $this, 'field_properties' ], 10, 3 );
|
||||
add_filter( 'wpforms_get_field_required_label', [ $this, 'get_field_required_label' ], 10 );
|
||||
add_filter( 'wpforms_frontend_strings', [ $this, 'frontend_strings' ], 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open form container.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $classes Form container classes.
|
||||
* @param array $form_data Form data.
|
||||
*/
|
||||
public function form_container_open( $classes, $form_data ) {
|
||||
|
||||
$classes[] = 'wpforms-render-modern';
|
||||
|
||||
parent::form_container_open( $classes, $form_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Noscript message.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $msg Noscript message.
|
||||
*/
|
||||
public function noscript( $msg ) {
|
||||
|
||||
printf(
|
||||
'<noscript class="wpforms-error-noscript">%1$s</noscript><div id="wpforms-error-noscript" style="display: none;">%1$s</div>',
|
||||
esc_html( $msg )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display form error.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $type Error type.
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
public function form_error( $type, $error ) {
|
||||
|
||||
switch ( $type ) {
|
||||
case 'header':
|
||||
case 'footer':
|
||||
printf(
|
||||
'<div id="wpforms-%1$s-%2$s-error" class="wpforms-error-container" role="alert">
|
||||
<span class="wpforms-hidden" aria-hidden="false">%3$s</span>%4$s
|
||||
</div>',
|
||||
esc_attr( $this->form_data['id'] ?? 0 ),
|
||||
esc_attr( $type ),
|
||||
esc_html__( 'Form error message', 'wpforms-lite' ),
|
||||
wpautop( wpforms_sanitize_error( $error ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
break;
|
||||
|
||||
case 'header_styled':
|
||||
case 'footer_styled':
|
||||
printf(
|
||||
'<div id="wpforms-%1$s-%2$s-error" class="wpforms-error-container wpforms-error-styled-container" role="alert">
|
||||
<div class="wpforms-error"><span class="wpforms-hidden" aria-hidden="false">%3$s</span>%4$s</div>
|
||||
</div>',
|
||||
esc_attr( $this->form_data['id'] ),
|
||||
esc_attr( $type ),
|
||||
esc_html__( 'Form error message', 'wpforms-lite' ),
|
||||
wpautop( wpforms_sanitize_error( $error ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
break;
|
||||
|
||||
case 'header_styled':
|
||||
case 'footer_styled':
|
||||
printf(
|
||||
'<div id="wpforms-%1$s-%2$s-error" class="wpforms-error-container wpforms-error-styled-container" role="alert">
|
||||
<div class="wpforms-error"><span class="wpforms-hidden" aria-hidden="false">%3$s</span>%4$s</div>
|
||||
</div>',
|
||||
esc_attr( $this->form_data['id'] ),
|
||||
esc_attr( $type ),
|
||||
esc_html__( 'Form error message', 'wpforms-lite' ),
|
||||
wpautop( wpforms_sanitize_error( $error ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
break;
|
||||
|
||||
case 'header_styled':
|
||||
case 'footer_styled':
|
||||
printf(
|
||||
'<div id="wpforms-%1$s-%2$s-error" class="wpforms-error-container wpforms-error-styled-container" role="alert">
|
||||
<div class="wpforms-error"><span class="wpforms-hidden" aria-hidden="false">%3$s</span>%4$s</div>
|
||||
</div>',
|
||||
esc_attr( $this->form_data['id'] ),
|
||||
esc_attr( $type ),
|
||||
esc_html__( 'Form error message', 'wpforms-lite' ),
|
||||
wpautop( wpforms_sanitize_error( $error ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
break;
|
||||
|
||||
case 'recaptcha':
|
||||
printf(
|
||||
'<em id="wpforms-field_recaptcha-error" class="wpforms-error" role="alert">
|
||||
<span class="wpforms-hidden" aria-hidden="false">%1$s</span>%2$s
|
||||
</em>',
|
||||
esc_attr__( 'Recaptcha error message', 'wpforms-lite' ),
|
||||
wpforms_sanitize_error( $error ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field label markup.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
public function field_label( $field, $form_data ) {
|
||||
|
||||
// Do not need to output label if the field requires fieldset.
|
||||
if ( $this->is_field_requires_fieldset( $field ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $field['label_hide'] ) ) {
|
||||
$field['properties']['label']['attr']['aria-hidden'] = 'false';
|
||||
}
|
||||
|
||||
parent::field_label( $field, $form_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open fieldset markup.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
public function field_fieldset_open( $field, $form_data ) {
|
||||
|
||||
if ( ! $this->is_field_requires_fieldset( $field ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $field['label_hide'] ) ) {
|
||||
$field['properties']['label']['attr']['aria-hidden'] = 'false';
|
||||
}
|
||||
|
||||
$label = $field['properties']['label'];
|
||||
$required = $label['required'] ? wpforms_get_field_required_label() : '';
|
||||
|
||||
unset( $label['attr']['for'] );
|
||||
|
||||
printf(
|
||||
'<fieldset><legend %s>%s%s</legend>',
|
||||
wpforms_html_attributes( $label['id'], $label['class'], $label['data'], $label['attr'] ),
|
||||
esc_html( $label['value'] ),
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$required
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close fieldset markup.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*/
|
||||
public function field_fieldset_close( $field, $form_data ) {
|
||||
|
||||
if ( ! $this->is_field_requires_fieldset( $field ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '</fieldset>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the field requires fieldset markup.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
*/
|
||||
private function is_field_requires_fieldset( $field ) {
|
||||
|
||||
if ( empty( $field['type'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the field is requires fieldset+legend markup on the frontend.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param bool $requires_fieldset True if requires. Defaults to false.
|
||||
* @param array $field Field data.
|
||||
*/
|
||||
return (bool) apply_filters( "wpforms_frontend_modern_is_field_requires_fieldset_{$field['type']}", false, $field );
|
||||
}
|
||||
|
||||
/**
|
||||
* Field error.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $field Field data and settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @noinspection HtmlUnknownAttribute
|
||||
*/
|
||||
public function field_error( $field, $form_data ) {
|
||||
|
||||
if ( empty( $field['properties']['error'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $field['properties']['error'];
|
||||
|
||||
printf(
|
||||
'<em %1$s>%2$s</em>',
|
||||
wpforms_html_attributes( $error['id'], $error['class'], $error['data'], $error['attr'] ),
|
||||
esc_html( $error['value'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define additional field properties.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $properties Field properties.
|
||||
* @param array $field Field settings.
|
||||
* @param array $form_data Form data and settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function field_properties( $properties, $field, $form_data ) {
|
||||
|
||||
$field_id = "wpforms-{$form_data['id']}-field_{$field['id']}";
|
||||
$desc_id = "{$field_id}-description";
|
||||
|
||||
// Add `id` to field description.
|
||||
$properties['description']['id'] = $desc_id;
|
||||
|
||||
// Add attributes to error message.
|
||||
$properties['error']['attr']['role'] = 'alert';
|
||||
$properties['error']['attr']['aria-label'] = esc_html__( 'Error message', 'wpforms-lite' );
|
||||
$properties['error']['attr']['id'] = $properties['error']['attr']['for'] . '-error';
|
||||
$properties['error']['attr']['for'] = '';
|
||||
|
||||
foreach ( $properties['inputs'] as $input => $input_data ) {
|
||||
|
||||
// Add `aria-errormessage` to inputs (except hidden according to W3C requirements).
|
||||
if ( ! empty( $input_data['id'] ) && $field['type'] !== 'hidden' ) {
|
||||
$properties['inputs'][ $input ]['attr']['aria-errormessage'] = "{$input_data['id']}-error";
|
||||
}
|
||||
|
||||
// Add `aria-describedby` to inputs.
|
||||
if ( ! empty( $field['description'] ) ) {
|
||||
$properties['inputs'][ $input ]['attr']['aria-describedby'] = $desc_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required label (asterisk) markup.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param string $label_html Required label markup.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_field_required_label( $label_html ) {
|
||||
|
||||
return ' <span class="wpforms-required-label" aria-hidden="true">*</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify javascript `wpforms_settings` properties on the front end.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param array $strings Array `wpforms_settings` properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function frontend_strings( $strings ) {
|
||||
|
||||
$strings['isModernMarkupEnabled'] = wpforms_get_render_engine() === 'modern';
|
||||
$strings['formErrorMessagePrefix'] = esc_html__( 'Form error message', 'wpforms-lite' );
|
||||
$strings['errorMessagePrefix'] = esc_html__( 'Error message', 'wpforms-lite' );
|
||||
$strings['submitBtnDisabled'] = esc_html__( 'Submit button is disabled during form submission.', 'wpforms-lite' );
|
||||
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user