JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
/**
* Helper functions to work with forms and form data.
*
* @since 1.8.0
*/
/**
* Helper function to trigger displaying a form.
*
* @since 1.0.2
*
* @param mixed $form_id Form ID.
* @param bool $title Form title.
* @param bool $desc Form description.
*/
function wpforms_display( $form_id = false, $title = false, $desc = false ) {
$frontend = wpforms()->obj( 'frontend' );
if ( empty( $frontend ) ) {
return;
}
$frontend->output( $form_id, $title, $desc );
}
/**
* Return URL to form preview page.
*
* @since 1.5.1
*
* @param int $form_id Form ID.
* @param bool $new_window New window flag.
*
* @return string
*/
function wpforms_get_form_preview_url( $form_id, $new_window = false ) {
$url = add_query_arg(
[
'wpforms_form_preview' => absint( $form_id ),
],
home_url()
);
if ( $new_window ) {
$url = add_query_arg(
[
'new_window' => 1,
],
$url
);
}
return $url;
}
/**
* Perform json_decode and unslash.
*
* IMPORTANT: This function decodes the result of wpforms_encode() properly only if
* wp_insert_post() or wp_update_post() were used after the data is encoded.
* Both wp_insert_post() and wp_update_post() remove excessive slashes added by wpforms_encode().
*
* Using wpforms_decode() on wpforms_encode() result directly
* (without using wp_insert_post() or wp_update_post() first) always returns null or false.
*
* The json_decode failure returns an empty array.
*
* @since 1.0.0
*
* @param string $data Data to decode.
*
* @return array|false|null Empty array if json_decode fails, false if $data is empty, or decoded data.
*/
function wpforms_decode( $data ) {
if ( empty( $data ) ) {
return false;
}
$decoded_data = json_decode( $data, true );
// If json_decode fails, return an empty array to prevent possible fatal errors due to type mismatch.
if ( json_last_error() !== JSON_ERROR_NONE ) {
return [];
}
return wp_unslash( $decoded_data );
}
/**
* Perform json_encode and wp_slash.
*
* IMPORTANT: This function adds excessive slashes to prevent data damage
* by wp_insert_post() or wp_update_post() that use wp_unslash() on all the incoming data.
*
* Decoding the result of this function by wpforms_decode() directly
* (without using wp_insert_post() or wp_update_post() first) always returns null or false.
*
* @since 1.3.1.3
*
* @param mixed $data Data to encode.
*
* @return string|false
*/
function wpforms_encode( $data = false ) {
if ( empty( $data ) ) {
return false;
}
return wp_slash( wp_json_encode( $data ) );
}
/**
* Decode json-encoded string if it is in JSON format.
*
* @since 1.7.5
*
* @param string $encoded_string A string.
* @param bool $associative Decode to the associative array if true. Decode to object if false.
*
* @return array|string
*/
function wpforms_json_decode( $encoded_string, $associative = false ) {
$encoded_string = html_entity_decode( $encoded_string );
if ( ! wpforms_is_json( $encoded_string ) ) {
return $encoded_string;
}
return json_decode( $encoded_string, $associative );
}
/**
* Get the value of a specific WPForms setting.
*
* @since 1.0.0
*
* @param string $key Setting name.
* @param mixed $default_value Default value to return if the setting is not available.
* @param string $option Option key, defaults to `wpforms_settings` in the `wp_options` table.
*
* @return mixed
*/
function wpforms_setting( $key, $default_value = false, $option = 'wpforms_settings' ) {
$key = wpforms_sanitize_key( $key );
$options = get_option( $option, false );
$value = is_array( $options ) && ! empty( $options[ $key ] ) ? wp_unslash( $options[ $key ] ) : $default_value;
/**
* Allows plugin setting to be modified.
*
* @since 1.7.8
*
* @param mixed $value Setting value.
* @param string $key Setting key.
* @param mixed $default_value Setting default value.
* @param string $option Settings option name.
*/
return apply_filters( 'wpforms_setting', $value, $key, $default_value, $option );
}
/**
* Update the plugin settings option and allow it to be filterable.
*
* The purpose of this function is to save settings when the "Save Settings" button is clicked.
* If you are programmatically saving setting in the database in cases not triggered by user,
* use update_option() instead.
*
* @since 1.6.6
*
* @param array $settings A plugin settings array that is saved into option table.
*
* @return bool
*/
function wpforms_update_settings( $settings ) {
$old_settings = (array) get_option( 'wpforms_settings', [] );
/**
* Allows plugin settings to be modified before persisting in the database.
*
* @since 1.6.6
*
* @param array $settings An array of plugin settings to modify.
*/
$settings = (array) apply_filters( 'wpforms_update_settings', $settings );
$updated = update_option( 'wpforms_settings', $settings );
/**
* Fires after the plugin settings were persisted in the database.
*
* The `$updated` parameter allows to check whether the update was actually successful.
*
* @since 1.6.1
* @since 1.8.4 The `$old_settings` parameter was added.
*
* @param array $settings An array of plugin settings.
* @param bool $updated Whether an option was updated or not.
* @param array $old_settings An old array of plugin settings.
*/
do_action( 'wpforms_settings_updated', $settings, $updated, $old_settings );
return $updated;
}
/**
* Check an if form provided contains the specified field type.
*
* @since 1.0.5
*
* @param array|string $type Field type or types.
* @param array|object $form Form data object.
* @param bool $multiple Whether to check multiple field types.
*
* @return bool
*/
function wpforms_has_field_type( $type, $form, $multiple = false ) {
$form_data = '';
$field = false;
$type = (array) $type;
if ( $multiple ) {
foreach ( $form as $single_form ) {
$field = wpforms_has_field_type( $type, $single_form );
if ( $field ) {
break;
}
}
return $field;
}
if ( is_object( $form ) && ! empty( $form->post_content ) ) {
$form_data = wpforms_decode( $form->post_content );
} elseif ( is_array( $form ) ) {
$form_data = $form;
}
if ( empty( $form_data['fields'] ) ) {
return false;
}
foreach ( $form_data['fields'] as $single_field ) {
if ( ! empty( $single_field['type'] ) && in_array( $single_field['type'], $type, true ) ) {
$field = true;
break;
}
}
return $field;
}
/**
* Check if the form provided contains a field which a specific setting.
*
* @since 1.4.5
*
* @param string $setting Setting key.
* @param object|array $form Form data.
* @param bool $multiple Whether to check multiple settings.
*
* @return bool
*/
function wpforms_has_field_setting( $setting, $form, $multiple = false ) {
$form_data = '';
$field = false;
if ( $multiple ) {
foreach ( $form as $single_form ) {
$field = wpforms_has_field_setting( $setting, $single_form );
if ( $field ) {
break;
}
}
return $field;
}
if ( is_object( $form ) && ! empty( $form->post_content ) ) {
$form_data = wpforms_decode( $form->post_content );
} elseif ( is_array( $form ) ) {
$form_data = $form;
}
if ( empty( $form_data['fields'] ) ) {
return false;
}
foreach ( $form_data['fields'] as $single_field ) {
if ( ! empty( $single_field[ $setting ] ) ) {
$field = true;
break;
}
}
return $field;
}
/**
* Retrieve actual fields from a form.
*
* Non-posting elements such as section divider, page break, and HTML are
* automatically excluded. Optionally, a whitelist can be provided.
*
* @since 1.0.0
*
* @param mixed $form Form data.
* @param array $allowlist A list of allowed fields.
*
* @return mixed boolean false or array
*/
function wpforms_get_form_fields( $form = false, $allowlist = [] ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
// Accept form (post) object or form ID.
if ( is_object( $form ) ) {
$form = wpforms_decode( $form->post_content );
} elseif ( is_numeric( $form ) ) {
$form = wpforms()->obj( 'form' )->get(
absint( $form ),
[
'content_only' => true,
]
);
}
$allowed_form_fields = [
'address',
'checkbox',
'date-time',
'email',
'file-upload',
'gdpr-checkbox',
'hidden',
'likert_scale',
'name',
'net_promoter_score',
'number',
'number-slider',
'payment-checkbox',
'payment-multiple',
'payment-select',
'payment-single',
'payment-total',
'phone',
'radio',
'ranking',
'rating',
'richtext',
'select',
'signature',
'text',
'textarea',
'url',
];
/**
* Filter the list of allowed form fields.
*
* @since 1.0.0
*
* @param array $allowed_form_fields List of allowed form fields.
*/
$allowed_form_fields = (array) apply_filters( 'wpforms_get_form_fields_allowed', $allowed_form_fields );
if ( ! is_array( $form ) || empty( $form['fields'] ) ) {
return false;
}
$allowlist = ! empty( $allowlist ) ? $allowlist : $allowed_form_fields;
$form_fields = $form['fields'];
foreach ( $form_fields as $id => $form_field ) {
// Remove repeater field and its children.
if ( $form_field['type'] === 'repeater' ) {
foreach ( (array) $form_field['columns'] as $column ) {
$column_fields = $column['fields'] ?? [];
foreach ( $column_fields as $field_id ) {
unset( $form_fields[ $field_id ] );
}
}
}
if ( ! in_array( $form_field['type'], $allowlist, true ) ) {
unset( $form_fields[ $id ] );
}
}
return $form_fields;
}
/**
* Conditional logic form fields supported.
*
* @since 1.5.2
*
* @return array
*/
function wpforms_get_conditional_logic_form_fields_supported() {
$fields_supported = [
'checkbox',
'email',
'hidden',
'net_promoter_score',
'number',
'number-slider',
'payment-checkbox',
'payment-multiple',
'payment-select',
'radio',
'rating',
'richtext',
'select',
'text',
'textarea',
'url',
];
/**
* Filter the list of form fields supported by conditional logic.
*
* @since 1.8.0
*
* @param array $fields_supported List of form fields supported by conditional logic.
*/
return apply_filters( 'wpforms_get_conditional_logic_form_fields_supported', $fields_supported );
}
/**
* Get meta key value for a form field.
*
* @since 1.3.1
* @since 1.5.0 More strict parameters. Always return an array.
*
* @param string $key Meta key.
* @param string $value Meta value to check against.
* @param array $form_data Form data array.
*
* @return array Empty array, when no data is found.
*/
function wpforms_get_form_fields_by_meta( $key, $value, $form_data ) {
$found = [];
if ( empty( $key ) || empty( $value ) || empty( $form_data['fields'] ) ) {
return $found;
}
foreach ( $form_data['fields'] as $id => $field ) {
if ( ! empty( $field['meta'][ $key ] ) && $value === $field['meta'][ $key ] ) {
$found[ $id ] = $field;
}
}
return $found;
}
/**
* Retrieve the full config for CAPTCHA.
*
* @since 1.6.4
*
* @return array
*/
function wpforms_get_captcha_settings() {
$allowed_captcha_list = [ 'hcaptcha', 'recaptcha', 'turnstile' ];
$captcha_provider = wpforms_setting( 'captcha-provider', 'recaptcha' );
if ( ! in_array( $captcha_provider, $allowed_captcha_list, true ) ) {
return [
'provider' => 'none',
];
}
return [
'provider' => $captcha_provider,
'site_key' => sanitize_text_field( wpforms_setting( "{$captcha_provider}-site-key", '' ) ),
'secret_key' => sanitize_text_field( wpforms_setting( "{$captcha_provider}-secret-key", '' ) ),
'recaptcha_type' => wpforms_setting( 'recaptcha-type', 'v2' ),
'theme' => sanitize_text_field( wpforms_setting( "{$captcha_provider}-theme", '' ) ),
];
}
/**
* Process smart tags.
*
* @since 1.7.1
* @since 1.8.7 Added `$context` parameter.
* @since 1.9.9.2 Added `$context_data` parameter.
*
* @param string $content Content.
* @param array $form_data Form data.
* @param array $fields List of fields.
* @param string $entry_id Entry ID.
* @param string $context Context.
* @param array $context_data Context data.
*
* @return string|mixed
*/
function wpforms_process_smart_tags( $content, $form_data, $fields = [], $entry_id = '', $context = '', array $context_data = [] ) {
// Skip it if variables have invalid format.
if ( ! is_string( $content ) || ! is_array( $form_data ) || ! is_array( $fields ) ) {
return $content;
}
/**
* Process smart tags.
*
* @since 1.4.0
* @since 1.8.7 Added $context parameter.
*
* @param string $content Content.
* @param array $form_data Form data.
* @param array $fields List of fields.
* @param string $entry_id Entry ID.
* @param string $context Context.
* @param array $context_data Context data.
*
* @return string
*/
return (string) apply_filters( 'wpforms_process_smart_tags', $content, $form_data, $fields, $entry_id, $context, $context_data );
}
/**
* Get all smart tags in the content.
* This function has been moved from the existing \WPForms\SmartTags\SmartTags::class.
*
* @since 1.10.0
*
* @param string $content Content.
*
* @return array
*/
function wpforms_get_all_smart_tags( $content ) {
/**
* A smart tag should start and end with a curly brace.
* ([a-z0-9_]+) a smart tag name and also the first capturing group.
* Lowercase letters, digits, and an underscore.
* (|[ =][^\n}]*) - second capturing group:
* | no characters at all or the following:
* [ =][^\n}]* space or equal sign and any number of any characters except new line and closing curly brace.
*/
preg_match_all( '~{([a-z0-9_]+)(|[ =][^\n}]*)}~', $content, $smart_tags );
if ( empty( $smart_tags[0] ) ) {
return [];
}
return array_combine( $smart_tags[0], $smart_tags[1] );
}
/**
* Check if form data slashing enabled.
*
* @since 1.9.0
*
* @return bool
*/
function wpforms_is_form_data_slashing_enabled() {
static $enabled = null;
if ( $enabled !== null ) {
return $enabled;
}
/**
* Filter to enable form data slashing.
*
* @since 1.9.0
*
* @param bool $enabled Form data slashing enabled.
*/
$enabled = (bool) apply_filters( 'wpforms_enable_form_data_slashing', $enabled );
$enabled = defined( 'WPFORMS_ENABLE_FORM_DATA_SLASHING' ) ? WPFORMS_ENABLE_FORM_DATA_SLASHING : $enabled;
return $enabled;
}
/**
* Check is frontend JS should be loaded in the header.
*
* @since 1.9.0
*
* @return bool
*/
function wpforms_is_frontend_js_header_force_load(): bool {
/**
* Allow loading JS in header on various pages.
*
* @since 1.9.0
*
* @param bool $force_load Force loading JS in header, default `false`.
*/
return (bool) apply_filters( 'wpforms_frontend_js_header_force_load', false );
}