.*?\\1>@si', '', $value, - 1, $count ); } // Make sure we have allowed tags only. $value = wp_kses( $value, wpforms_get_allowed_html_tags_for_richtext_field() ); // Make sure that all tags are balanced. return force_balance_tags( $value ); } /** * Escaping for Rich Text field values. * * @since 1.7.0 * * @param string $value Text to escape. * * @return string Escaped text. */ function wpforms_esc_richtext_field( $value ) { return wpautop( wpforms_sanitize_richtext_field( $value ) ); } /** * Retrieve allowed HTML tags for Rich Text field. * * @since 1.7.0 * * @return array Array of allowed tags. */ function wpforms_get_allowed_html_tags_for_richtext_field() { $allowed_tags = array_fill_keys( [ 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'hr', 'br', 'code', 'pre', 'strong', 'b', 'em', 'i', 'blockquote', 'cite', 'q', 'del', 'span', 'small', 'table', 'thead', 'tbody', 'th', 'tr', 'td', 'abbr', 'address', 'sub', 'sup', 'ins', 'figure', 'figcaption', 'div', ], array_fill_keys( [ 'align', 'class', 'id', 'style', 'src', 'rel', 'alt', 'href', 'target', 'width', 'height', 'title', 'cite', 'start', 'reversed', 'datetime' ], [] ) ); /** * Allowed HTML tags for Rich Text field. * * @since 1.7.0 * * @param array $allowed_tags Allowed HTML tags. */ $tags = (array) apply_filters( 'wpforms_get_allowed_html_tags_for_richtext_field', $allowed_tags ); // Force unset iframes, script and style no matter when we get back // from apply_filters, as they are a huge security risk. unset( $tags['iframe'], $tags['script'], $tags['style'] ); return $tags; } /** * Sanitize an array, that consists of values as strings. * After that - merge all array values into multiline string. * * @since 1.4.1 * * @param array $array * * @return mixed If not an array is passed (or empty var) - return unmodified var. Otherwise - a merged array into multiline string. */ function wpforms_sanitize_array_combine( $array ) { if ( empty( $array ) || ! is_array( $array ) ) { return $array; } return implode( "\n", array_map( 'sanitize_text_field', $array ) ); } /** * Detect if we should use a light or dark color based on the color given. * * @since 1.2.5 * @link https://docs.woocommerce.com/wc-apidocs/source-function-wc_light_or_dark.html#608-627 * * @param mixed $color * @param string $dark (default: '#000000'). * @param string $light (default: '#FFFFFF'). * * @return string */ function wpforms_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) { $hex = str_replace( '#', '', $color ); $c_r = hexdec( substr( $hex, 0, 2 ) ); $c_g = hexdec( substr( $hex, 2, 2 ) ); $c_b = hexdec( substr( $hex, 4, 2 ) ); $brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000; return $brightness > 155 ? $dark : $light; } /** * Build and return either a taxonomy or post type object that is * nested to accommodate any hierarchy. * * @since 1.3.9 * @since 1.5.0 Return array only. Empty array of no data. * * @param array $args Object arguments to pass to data retrieval function. * @param bool $flat Preserve hierarchy or not. False by default - preserve it. * * @return array */ function wpforms_get_hierarchical_object( $args = [], $flat = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded if ( empty( $args['taxonomy'] ) && empty( $args['post_type'] ) ) { return []; } $children = []; $parents = []; $ref_parent = ''; $ref_name = ''; $number = 0; if ( ! empty( $args['post_type'] ) ) { $defaults = [ 'posts_per_page' => - 1, 'orderby' => 'title', 'order' => 'ASC', ]; $args = wp_parse_args( $args, $defaults ); $items = get_posts( $args ); $ref_parent = 'post_parent'; $ref_id = 'ID'; $ref_name = 'post_title'; $number = ! empty( $args['posts_per_page'] ) ? $args['posts_per_page'] : 0; } elseif ( ! empty( $args['taxonomy'] ) ) { $defaults = [ 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ]; $args = wp_parse_args( $args, $defaults ); $items = get_terms( $args ); $ref_parent = 'parent'; $ref_id = 'term_id'; $ref_name = 'name'; $number = ! empty( $args['number'] ) ? $args['number'] : 0; } if ( empty( $items ) || is_wp_error( $items ) ) { return []; } foreach ( $items as $item ) { if ( $item->{$ref_parent} ) { $children[ $item->{$ref_id} ] = $item; $children[ $item->{$ref_id} ]->ID = (int) $item->{$ref_id}; } else { $parents[ $item->{$ref_id} ] = $item; $parents[ $item->{$ref_id} ]->ID = (int) $item->{$ref_id}; } } $children_count = count( $children ); $is_limited = $number > 1; // We can't guarantee that all children have a parent if there is a limit in the request. // Hence, we have to make sure that there is a parent for every child. if ( $is_limited && $children_count ) { foreach ( $children as $child ) { if ( ! empty( $parents[ $child->{$ref_parent} ] ) || ! empty( $children[ $child->{$ref_parent} ] ) ) { continue; } do { $current_parent = ! empty( $args['post_type'] ) ? get_post( $child->{$ref_parent} ) : get_term( $child->{$ref_parent} ); if ( $current_parent->{$ref_parent} === 0 ) { $parents[ $current_parent->{$ref_id} ] = $current_parent; $parents[ $current_parent->{$ref_id} ]->ID = (int) $current_parent->{$ref_id}; } else { $children[ $current_parent->{$ref_id} ] = $current_parent; $children[ $current_parent->{$ref_id} ]->ID = (int) $current_parent->{$ref_id}; } } while ( $current_parent->{$ref_parent} > 0 ); } } while ( $children_count >= 1 ) { foreach ( $children as $child ) { _wpforms_get_hierarchical_object_search( $child, $parents, $children, $ref_parent ); // $children is modified by reference, so we need to recount to make sure we met the limits. $children_count = count( $children ); } } // Sort nested child objects alphabetically using natural order, applies only // to ordering by entry title or term name. if ( in_array( $args['orderby'], [ 'title', 'name' ], true ) ) { _wpforms_sort_hierarchical_object( $parents, $args['orderby'], $args['order'] ); } if ( $flat ) { $parents_flat = []; _wpforms_get_hierarchical_object_flatten( $parents, $parents_flat, $ref_name ); $parents = $parents_flat; } return $is_limited ? array_slice( $parents, 0, $number ) : $parents; } /** * Sort a nested array of objects. * * @since 1.6.5 * * @param array $objects An array of objects to sort. * @param string $orderby The object field to order by. * @param string $order Order direction. */ function _wpforms_sort_hierarchical_object( &$objects, $orderby, $order ) { // Map WP_Query/WP_Term_Query orderby to WP_Post/WP_Term property. $map = [ 'title' => 'post_title', 'name' => 'name', ]; foreach ( $objects as $object ) { if ( ! isset( $object->children ) ) { continue; } uasort( $object->children, static function ( $a, $b ) use ( $map, $orderby, $order ) { /** * This covers most cases and works for most languages. For some – e.g. European languages * that use extended latin charset (Polish, German etc) it will sort the objects into 2 * groups – base and extended, properly sorted within each group. Making it even more * robust requires either additional PHP extensions to be installed on the server * or using heavy (and slow) conversions and computations. */ return $order === 'ASC' ? strnatcasecmp( $a->{$map[ $orderby ]}, $b->{$map[ $orderby ]} ) : strnatcasecmp( $b->{$map[ $orderby ]}, $a->{$map[ $orderby ]} ); } ); _wpforms_sort_hierarchical_object( $object->children, $orderby, $order ); } } /** * Search a given array and find the parent of the provided object. * * @since 1.3.9 * * @param object $child Current child. * @param array $parents Parents list. * @param array $children Children list. * @param string $ref_parent Parent reference. */ function _wpforms_get_hierarchical_object_search( $child, &$parents, &$children, $ref_parent ) { foreach ( $parents as $id => $parent ) { if ( $parent->ID === $child->{$ref_parent} ) { if ( empty( $parent->children ) ) { $parents[ $id ]->children = array( $child->ID => $child, ); } else { $parents[ $id ]->children[ $child->ID ] = $child; } unset( $children[ $child->ID ] ); } elseif ( ! empty( $parent->children ) && is_array( $parent->children ) ) { _wpforms_get_hierarchical_object_search( $child, $parent->children, $children, $ref_parent ); } } } /** * Flatten a hierarchical object. * * @since 1.3.9 * * @param array $array Array to process. * @param array $output Processed output. * @param string $ref_name Name reference. * @param int $level Nesting level. */ function _wpforms_get_hierarchical_object_flatten( $array, &$output, $ref_name = 'name', $level = 0 ) { foreach ( $array as $key => $item ) { $indicator = apply_filters( 'wpforms_hierarchical_object_indicator', '—' ); $item->{$ref_name} = str_repeat( $indicator, $level ) . ' ' . $item->{$ref_name}; $item->depth = $level + 1; $output[ $item->ID ] = $item; if ( ! empty( $item->children ) ) { _wpforms_get_hierarchical_object_flatten( $item->children, $output, $ref_name, $level + 1 ); unset( $output[ $item->ID ]->children ); } } } /** * Return field choice properties for field configured with dynamic choices. * * @since 1.4.5 * * @param array $field Field settings. * @param int $form_id Form ID. * @param array $form_data Form data and settings. * * @return false|array */ function wpforms_get_field_dynamic_choices( $field, $form_id, $form_data = array() ) { if ( empty( $field['dynamic_choices'] ) ) { return false; } $choices = array(); if ( 'post_type' === $field['dynamic_choices'] ) { if ( empty( $field['dynamic_post_type'] ) ) { return false; } $posts = wpforms_get_hierarchical_object( apply_filters( 'wpforms_dynamic_choice_post_type_args', array( 'post_type' => $field['dynamic_post_type'], 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', ), $field, $form_id ), true ); foreach ( $posts as $post ) { $choices[] = array( 'value' => $post->ID, 'label' => wpforms_get_post_title( $post ), 'depth' => isset( $post->depth ) ? absint( $post->depth ) : 1, ); } } elseif ( 'taxonomy' === $field['dynamic_choices'] ) { if ( empty( $field['dynamic_taxonomy'] ) ) { return false; } $terms = wpforms_get_hierarchical_object( apply_filters( 'wpforms_dynamic_choice_taxonomy_args', array( 'taxonomy' => $field['dynamic_taxonomy'], 'hide_empty' => false, ), $field, $form_data ), true ); foreach ( $terms as $term ) { $choices[] = array( 'value' => $term->term_id, 'label' => wpforms_get_term_name( $term ), 'depth' => isset( $term->depth ) ? absint( $term->depth ) : 1, ); } } return $choices; } /** * Insert an array into another array before/after a certain key. * * @link https://gist.github.com/scribu/588429 * * @since 1.3.9 * * @param array $array The initial array. * @param array $pairs The array to insert. * @param string $key The certain key. * @param string $position Where to insert the array - before or after the key. * * @return array */ function wpforms_array_insert( $array, $pairs, $key, $position = 'after' ) { $key_pos = array_search( $key, array_keys( $array ), true ); if ( 'after' === $position ) { $key_pos ++; } if ( false !== $key_pos ) { $result = array_slice( $array, 0, $key_pos ); $result = array_merge( $result, $pairs ); $result = array_merge( $result, array_slice( $array, $key_pos ) ); } else { $result = array_merge( $array, $pairs ); } return $result; } /** * Recursively remove empty strings from an array. * * @since 1.3.9.1 * * @param array $data * * @return array */ function wpforms_array_remove_empty_strings( $data ) { foreach ( $data as $key => $value ) { if ( is_array( $value ) ) { $data[ $key ] = wpforms_array_remove_empty_strings( $data[ $key ] ); } if ( '' === $data[ $key ] ) { unset( $data[ $key ] ); } } return $data; } /** * Check whether plugin works in a debug mode. * * @since 1.2.3 * * @return bool */ function wpforms_debug() { $debug = false; if ( ( defined( 'WPFORMS_DEBUG' ) && true === WPFORMS_DEBUG ) && is_super_admin() ) { $debug = true; } return apply_filters( 'wpforms_debug', $debug ); } /** * Helper function to display debug data. * * @since 1.0.0 * * @param mixed $data What to dump, can be any type. * @param bool $echo Whether to print or return. Default is to print. * * @return string|void */ function wpforms_debug_data( $data, $echo = true ) { if ( ! wpforms_debug() ) { return; } if ( is_array( $data ) || is_object( $data ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r $data = print_r( $data, true ); } $output = sprintf( '
' . print_r( $message, true ) . ''; // phpcs:ignore } // Filter logs types from Tools -> Logs page. $logs_types = wpforms_setting( 'logs-types', false ); if ( $logs_types && empty( array_intersect( $logs_types, $types ) ) ) { return; } // Filter user roles from Tools -> Logs page. $current_user = function_exists( 'wp_get_current_user' ) ? wp_get_current_user() : null; $current_user_id = $current_user ? $current_user->ID : 0; $current_user_roles = $current_user ? $current_user->roles : []; $logs_user_roles = wpforms_setting( 'logs-user-roles', false ); if ( $logs_user_roles && empty( array_intersect( $logs_user_roles, $current_user_roles ) ) ) { return; } // Filter logs users from Tools -> Logs page. $logs_users = wpforms_setting( 'logs-users', false ); if ( $logs_users && ! in_array( $current_user_id, $logs_users, true ) ) { return; } $log = wpforms()->get( 'log' ); if ( ! method_exists( $log, 'add' ) ) { return; } // Create log entry. $log->add( $title, $message, $types, isset( $args['form_id'] ) ? absint( $args['form_id'] ) : 0, isset( $args['parent'] ) ? absint( $args['parent'] ) : 0, $current_user_id ); } /** * Check whether the current page is in AMP mode or not. * We need to check for specific functions, as there is no special AMP header. * * @since 1.4.1 * * @param bool $check_theme_support Whether theme support should be checked. Defaults to true. * * @return bool */ function wpforms_is_amp( $check_theme_support = true ) { $is_amp = false; if ( // AMP by Automattic; ampforwp. ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) || // Better AMP. ( function_exists( 'is_better_amp' ) && is_better_amp() ) ) { $is_amp = true; } if ( $is_amp && $check_theme_support ) { $is_amp = current_theme_supports( 'amp' ); } return apply_filters( 'wpforms_is_amp', $is_amp ); } /** * Decode special characters, both alpha- (<) and numeric-based ('). * Sanitize recursively, preserve new lines. * Handle all the possible mixed variations of < and `<` that can be processed into tags. * * @since 1.4.1 * @since 1.6.0 Sanitize recursively, preserve new lines. * * @param string $string Raw string to decode. * * @return string */ function wpforms_decode_string( $string ) { if ( ! is_string( $string ) ) { return $string; } /* * Sanitization should be done first, so tags are stripped and < is converted to < etc. * This iteration may do nothing when the string already comes with < and > only. */ $string = wpforms_sanitize_text_deeply( $string, true ); // Now we need to convert the string without tags: < back to < (same for quotes). $string = wp_kses_decode_entities( html_entity_decode( $string, ENT_QUOTES ) ); // And now we need to sanitize AGAIN, to avoid unwanted tags that appeared after decoding. return wpforms_sanitize_text_deeply( $string, true ); } /** * Get a suffix for assets, `.min` if debug is disabled. * * @since 1.4.1 * * @return string */ function wpforms_get_min_suffix() { return wpforms_debug() ? '' : '.min'; } /** * Get the required label text, with a filter. * * @since 1.4.4 * * @return string */ function wpforms_get_required_label() { return apply_filters( 'wpforms_required_label', esc_html__( 'This field is required.', 'wpforms-lite' ) ); } /** * Get the required field label HTML, with a filter. * * @since 1.4.8 * * @return string */ function wpforms_get_field_required_label() { $label_html = apply_filters_deprecated( 'wpforms_field_required_label', array( ' *' ), '1.4.8 of the WPForms plugin', 'wpforms_get_field_required_label' ); return apply_filters( 'wpforms_get_field_required_label', $label_html ); } /** * Get the default capability to manage everything for WPForms. * * @since 1.4.4 * * @return string */ function wpforms_get_capability_manage_options() { return apply_filters( 'wpforms_manage_cap', 'manage_options' ); } /** * Check WPForms permissions for currently logged in user. * Both short (e.g. 'view_own_forms') or long (e.g. 'wpforms_view_own_forms') capability name can be used. * Only WPForms capabilities get processed. * * @since 1.4.4 * * @param array|string $caps Capability name(s). * @param int $id ID of the specific object to check against if capability is a "meta" cap. "Meta" * capabilities, e.g. 'edit_post', 'edit_user', etc., are capabilities used by * map_meta_cap() to map to other "primitive" capabilities, e.g. 'edit_posts', * edit_others_posts', etc. Accessed via func_get_args() and passed to * WP_User::has_cap(), then map_meta_cap(). * * @return bool */ function wpforms_current_user_can( $caps = [], $id = 0 ) { $access = wpforms()->get( 'access' ); if ( ! method_exists( $access, 'current_user_can' ) ) { return false; } $user_can = $access->current_user_can( $caps , $id ); return apply_filters( 'wpforms_current_user_can', $user_can, $caps, $id ); } /** * Return date and time formatted as expected. * * @since 1.6.3 * * @param string|int $date Date to format. * @param string $format Optional. Format for the date and time. * @param bool $gmt_offset Optional. GTM offset. * * @return string */ function wpforms_datetime_format( $date, $format = '', $gmt_offset = false ) { if ( '' === $format ) { $format = sprintf( '%s %s', get_option( 'date_format' ), get_option( 'time_format' ) ); } if ( is_string( $date ) ) { $date = strtotime( $date ); } if ( $gmt_offset ) { $date += (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); } return date_i18n( $format, $date ); } /** * Return date formatted as expected. * * @since 1.6.3 * * @param string|int $date Date to format. * @param string $format Optional. Format for the date. * @param bool $gmt_offset Optional. GTM offset. * * @return string */ function wpforms_date_format( $date, $format = '', $gmt_offset = false ) { if ( '' === $format ) { $format = get_option( 'date_format' ); } return wpforms_datetime_format( $date, $format, $gmt_offset ); } /** * Get the certain date of a specified day in a specified format. * * @since 1.4.4 * @since 1.6.3 Added $use_gmt_offset parameter. * * @param string $period Supported values: start, end. * @param string $timestamp Default is the current timestamp, if left empty. * @param string $format Default is a MySQL format. * @param bool $use_gmt_offset Use GTM offset. * * @return string */ function wpforms_get_day_period_date( $period, $timestamp = '', $format = 'Y-m-d H:i:s', $use_gmt_offset = false ) { $date = ''; if ( empty( $timestamp ) ) { $timestamp = time(); } $offset_sec = $use_gmt_offset ? get_option( 'gmt_offset' ) * 3600 : 0; switch ( $period ) { case 'start_of_day': $date = gmdate( $format, strtotime( 'today', $timestamp ) - $offset_sec ); break; case 'end_of_day': $date = gmdate( $format, strtotime( 'tomorrow', $timestamp ) - 1 - $offset_sec ); break; } return $date; } /** * Return available date formats. * * @since 1.7.5 * * @return array */ function wpforms_date_formats() { /** * Filters available date formats. * * @since 1.3.0 * * @param array $date_formats Default date formats. * Item key is JS date character - see https://flatpickr.js.org/formatting/ * Item value is in PHP format - see http://php.net/manual/en/function.date.php. */ return (array) apply_filters( 'wpforms_datetime_date_formats', [ 'm/d/Y' => 'm/d/Y', 'd/m/Y' => 'd/m/Y', 'F j, Y' => 'F j, Y', ] ); } /** * Return available time formats. * * @since 1.7.7 * * @return array */ function wpforms_time_formats() { /** * Filters available time formats. * * @since 1.5.9 * * @param array $time_formats Default time formats. * Item key is in PHP format which it used in jquery.timepicker as well, * see http://php.net/manual/en/function.date.php. */ return (array) apply_filters( 'wpforms_datetime_time_formats', [ 'g:i A' => '12 H', 'H:i' => '24 H', ] ); } /** * Get an array of all possible provider addons. * * @since 1.5.5 * * @return array */ function wpforms_get_providers_all() { return [ [ 'name' => 'ActiveCampaign', 'slug' => 'activecampaign', 'img' => 'addon-icon-activecampaign.png', 'plugin' => 'wpforms-activecampaign/wpforms-activecampaign.php', 'plugin_slug' => 'wpforms-activecampaign', 'license' => 'elite', ], [ 'name' => 'AWeber', 'slug' => 'aweber', 'img' => 'addon-icon-aweber.png', 'plugin' => 'wpforms-aweber/wpforms-aweber.php', 'plugin_slug' => 'wpforms-aweber', 'license' => 'pro', ], [ 'name' => 'Campaign Monitor', 'slug' => 'campaign-monitor', 'img' => 'addon-icon-campaign-monitor.png', 'plugin' => 'wpforms-campaign-monitor/wpforms-campaign-monitor.php', 'plugin_slug' => 'wpforms-campaign-monitor', 'license' => 'pro', ], [ 'name' => 'Drip', 'slug' => 'drip', 'img' => 'addon-icon-drip.png', 'plugin' => 'wpforms-drip/wpforms-drip.php', 'plugin_slug' => 'wpforms-drip', 'license' => 'pro', ], [ 'name' => 'GetResponse', 'slug' => 'getresponse', 'img' => 'addon-icon-getresponse.png', 'plugin' => 'wpforms-getresponse/wpforms-getresponse.php', 'plugin_slug' => 'wpforms-getresponse', 'license' => 'pro', ], [ 'name' => 'Mailchimp', 'slug' => 'mailchimp', 'img' => 'addon-icon-mailchimp.png', 'plugin' => 'wpforms-mailchimp/wpforms-mailchimp.php', 'plugin_slug' => 'wpforms-mailchimp', 'license' => 'pro', ], [ 'name' => 'Salesforce', 'slug' => 'salesforce', 'img' => 'addon-icon-salesforce.png', 'plugin' => 'wpforms-salesforce/wpforms-salesforce.php', 'plugin_slug' => 'wpforms-salesforce', 'license' => 'elite', ], [ 'name' => 'Sendinblue', 'slug' => 'sendinblue', 'img' => 'addon-icon-sendinblue.png', 'plugin' => 'wpforms-sendinblue/wpforms-sendinblue.php', 'plugin_slug' => 'wpforms-sendinblue', 'license' => 'pro', ], [ 'name' => 'Zapier', 'slug' => 'zapier', 'img' => 'addon-icon-zapier.png', 'plugin' => 'wpforms-zapier/wpforms-zapier.php', 'plugin_slug' => 'wpforms-zapier', 'license' => 'pro', ], [ 'name' => 'HubSpot', 'slug' => 'hubspot', 'img' => 'addon-icon-hubspot.png', 'plugin' => 'wpforms-hubspot/wpforms-hubspot.php', 'plugin_slug' => 'wpforms-hubspot', 'license' => 'pro', ], ]; } /** * Get an array of all the active provider addons. * * @since 1.4.7 * * @return array */ function wpforms_get_providers_available() { return (array) apply_filters( 'wpforms_providers_available', array() ); } /** * Get options for all providers. * * @since 1.4.7 * * @param string $provider Define a single provider to get options for this one only. * * @return array */ function wpforms_get_providers_options( $provider = '' ) { $options = get_option( 'wpforms_providers', [] ); $provider = sanitize_key( $provider ); $data = $options; if ( ! empty( $provider ) && isset( $options[ $provider ] ) ) { $data = $options[ $provider ]; } return (array) apply_filters( 'wpforms_get_providers_options', $data, $provider ); } /** * Update options for all providers. * * @since 1.4.7 * * @param string $provider Provider slug. * @param array|false $options If false is passed - provider will be removed. Otherwise saved. * @param string $key Optional key to identify which connection to update. If empty - generate a new one. */ function wpforms_update_providers_options( $provider, $options, $key = '' ) { $providers = wpforms_get_providers_options(); $id = ! empty( $key ) ? $key : uniqid(); $provider = sanitize_key( $provider ); if ( $options ) { $providers[ $provider ][ $id ] = (array) $options; } else { unset( $providers[ $provider ] ); } update_option( 'wpforms_providers', $providers ); } /** * Helper function to determine if loading on WPForms related admin page. * * Here we determine if the current administration page is owned/created by * WPForms. This is done in compliance with WordPress best practices for * development, so that we only load required WPForms CSS and JS files on pages * we create. As a result we do not load our assets admin wide, where they might * conflict with other plugins needlessly, also leading to a better, faster user * experience for our users. * * @since 1.3.9 * * @param string $slug Slug identifier for a specific WPForms admin page. * @param string $view Slug identifier for a specific WPForms admin page view ("subpage"). * * @return bool */ function wpforms_is_admin_page( $slug = '', $view = '' ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended // Check against basic requirements. if ( ! is_admin() || empty( $_REQUEST['page'] ) || strpos( $_REQUEST['page'], 'wpforms' ) === false ) { return false; } // Check against page slug identifier. if ( ( ! empty( $slug ) && 'wpforms-' . $slug !== $_REQUEST['page'] ) || ( empty( $slug ) && 'wpforms-builder' === $_REQUEST['page'] ) ) { return false; } // Check against sub-level page view. if ( ! empty( $view ) && ( empty( $_REQUEST['view'] ) || $view !== $_REQUEST['view'] ) ) { return false; } // phpcs:enable return true; } /** * Get the ISO 639-2 Language Code from user/site locale. * * @see http://www.loc.gov/standards/iso639-2/php/code_list.php * * @since 1.5.0 * * @return string */ function wpforms_get_language_code() { $default_lang = 'en'; $locale = get_user_locale(); if ( ! empty( $locale ) ) { $lang = explode( '_', $locale ); if ( ! empty( $lang ) && is_array( $lang ) ) { $default_lang = strtolower( $lang[0] ); } } return $default_lang; } /** * Determine if we should show the "Show Values" toggle for checkbox, radio, or * select fields in form builder. Legacy. * * @since 1.5.0 * * @return bool */ function wpforms_show_fields_options_setting() { return apply_filters( 'wpforms_fields_show_options_setting', false ); } /** * Check if a string is empty. * * @since 1.5.0 * * @param string $string String to test. * * @return bool */ function wpforms_is_empty_string( $string ) { return is_string( $string ) && '' === $string; } /** * 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( array( 'wpforms_form_preview' => absint( $form_id ), ), home_url() ); if ( $new_window ) { $url = add_query_arg( array( 'new_window' => 1, ), $url ); } return $url; } /** * Include a template - alias to \WPForms\Helpers\Template::get_html. * Use 'require' if $args are passed or 'load_template' if not. * * @since 1.5.6 * * @param string $template_name Template name. * @param array $args Arguments. * @param bool $extract Extract arguments. * * @throws \RuntimeException If extract() tries to modify the scope. * * @return string Compiled HTML. */ function wpforms_render( $template_name, $args = array(), $extract = false ) { return \WPForms\Helpers\Templates::get_html( $template_name, $args, $extract ); } /** * Chain monad, useful for chaining certain array or string related functions. * * @since 1.5.6 * * @param mixed $value Any data. * * @return \WPForms\Helpers\Chain */ function wpforms_chain( $value ) { return \WPForms\Helpers\Chain::of( $value ); } /** * Get the current installation license type (always lowercase). * * @since 1.5.6 * * @return string|false */ function wpforms_get_license_type() { $type = wpforms_setting( 'type', '', 'wpforms_license' ); if ( empty( $type ) || ! wpforms()->is_pro() ) { return false; } return strtolower( $type ); } /** * Get the current installation license key. * * @since 1.6.2.3 * * @return string */ function wpforms_get_license_key() { // Check for license key. $key = wpforms_setting( 'key', '', 'wpforms_license' ); // Allow wp-config constant to pass key. if ( empty( $key ) && defined( 'WPFORMS_LICENSE_KEY' ) ) { $key = WPFORMS_LICENSE_KEY; } return $key; } /** * Get when WPForms was first installed. * * @since 1.6.0 * * @param string $type Specific install type to check for. * * @return int|false Unix timestamp. False on failure. */ function wpforms_get_activated_timestamp( $type = '' ) { $activated = (array) get_option( 'wpforms_activated', [] ); if ( empty( $activated ) ) { return false; } // When a passed install type is empty, then get it from a DB. // If it is installed/activated first, it is saved first. $type = empty( $type ) ? (string) array_keys( $activated )[0] : $type; if ( ! empty( $activated[ $type ] ) ) { return absint( $activated[ $type ] ); } // Fallback. $types = array_diff( [ 'lite', 'pro' ], [ $type ] ); foreach ( $types as $_type ) { if ( ! empty( $activated[ $_type ] ) ) { return absint( $activated[ $_type ] ); } } return false; } /** * Retrieve a timestamp when WPForms was upgraded. * * @since 1.7.5 * * @param string $version Specific plugin version to check for. * * @return int|false Unix timestamp or migration status. False on failure. * Available migration statuses: * -2 if migration is failed; * -1 if migration is started (in progress); * 0 if migration is completed, but no luck to set a timestamp. */ function wpforms_get_upgraded_timestamp( $version ) { $option_name = wpforms()->is_pro() ? 'wpforms_versions' : 'wpforms_versions_lite'; $upgrades = (array) get_option( $option_name, [] ); if ( ! isset( $upgrades[ $version ] ) ) { return false; } return (int) $upgrades[ $version ]; } /** * Detect if AJAX frontend form submit is being processed. * * @since 1.5.8.2 * @since 1.6.5 Added filterable frontend ajax actions list as a fallback to missing referer cases. * @since 1.6.7.1 Removed a requirement for an AJAX action to be a WPForms action if referer is not missing. * * @return bool */ function wpforms_is_frontend_ajax() { if ( ! wp_doing_ajax() ) { return false; } // Additional check to make sure the request targets admin-ajax.php. if ( isset( $_SERVER['SCRIPT_FILENAME'] ) && basename( sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ) !== 'admin-ajax.php' ) { return false; } $ref = wp_get_raw_referer(); if ( ! $ref ) { // Try to detect a frontend AJAX call indirectly by comparing the current action // with a known frontend actions list in case there's no HTTP referer. $frontend_actions = [ 'wpforms_submit', 'wpforms_file_upload_speed_test', 'wpforms_upload_chunk_init', 'wpforms_upload_chunk', 'wpforms_file_chunks_uploaded', 'wpforms_remove_file', 'wpforms_restricted_email', 'wpforms_form_locker_unique_answer', 'wpforms_form_abandonment', ]; $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended // This filter may be running as early as "plugins_loaded" hook. Please mind the hooks order when using it. $frontend_actions = (array) apply_filters( 'wpforms_is_frontend_ajax_frontend_actions', $frontend_actions ); return in_array( $action, $frontend_actions, true ); } $path = wp_parse_url( $ref, PHP_URL_PATH ); $admin_path = wp_parse_url( admin_url(), PHP_URL_PATH ); // It's a frontend AJAX call if HTTP referer doesn't contain an admin path. return strpos( $path, $admin_path ) === false; } /** * Dequeue enqueues by URI list. * Parts of URI (e.g. filename) is also supported. * * @since 1.6.1 * * @param array|string $uris List of URIs or individual URI to dequeue. * @param \WP_Scripts|\WP_Styles $enqueues Enqueues list to dequeue from. */ function wpforms_dequeue_by_uri( $uris, $enqueues ) { if ( empty( $enqueues->queue ) ) { return; } foreach ( $enqueues->queue as $handle ) { if ( empty( $enqueues->registered[ $handle ]->src ) ) { continue; } $src = wp_make_link_relative( $enqueues->registered[ $handle ]->src ); // Support full URLs. $src = site_url( $src ); foreach ( (array) $uris as $uri ) { if ( strpos( $src, $uri ) !== false ) { wp_dequeue_script( $handle ); break; } } } } /** * Dequeue scripts by URI list. * Parts of URI (e.g. filename) is also supported. * * @since 1.6.1 * * @param array|string $uris List of URIs or individual URI to dequeue. */ function wpforms_dequeue_scripts_by_uri( $uris ) { wpforms_dequeue_by_uri( $uris, wp_scripts() ); } /** * Dequeue styles by URI list. * Parts of URI (e.g. filename) is also supported. * * @since 1.6.1 * * @param array|string $uris List of URIs or individual URI to dequeue. */ function wpforms_dequeue_styles_by_uri( $uris ) { wpforms_dequeue_by_uri( $uris, wp_styles() ); } /** * Count words in the string. * * @since 1.6.2 * * @param string $string String value. * * @return integer Words count. */ function wpforms_count_words( $string ) { if ( ! is_string( $string ) ) { return 0; } $patterns = [ '/([A-Z]+),([A-Z]+)/i', '/([0-9]+),([A-Z]+)/i', '/([A-Z]+),([0-9]+)/i', ]; foreach ( $patterns as $pattern ) { $string = preg_replace_callback( $pattern, function( $matches ) { return $matches[1] . ', ' . $matches[2]; }, $string ); } $words = preg_split( '/[\s]+/', $string ); return is_array( $words ) ? count( $words ) : 0; } /** * Get WPForms upload root path (e.g. /wp-content/uploads/wpforms). * * As of 1.7.0, you can pass in your own value that matches the output of wp_upload_dir() * in order to use this function inside of a filter without infinite looping. * * @since 1.6.1 * * @return array WPForms upload root path (no trailing slash). */ function wpforms_upload_dir() { $upload_dir = wp_upload_dir(); if ( ! empty( $upload_dir['error'] ) ) { return [ 'error' => $upload_dir['error'] ]; } $basedir = wp_is_stream( $upload_dir['basedir'] ) ? $upload_dir['basedir'] : realpath( $upload_dir['basedir'] ); $wpforms_upload_root = trailingslashit( $basedir ) . 'wpforms'; /** * Allow developers to change a directory where cache and uploaded files will be stored. * * @since 1.5.2 * * @param string $wpforms_upload_root WPForms upload root directory. */ $custom_uploads_root = apply_filters( 'wpforms_upload_root', $wpforms_upload_root ); if ( is_dir( $custom_uploads_root ) && wp_is_writable( $custom_uploads_root ) ) { $wpforms_upload_root = wp_is_stream( $custom_uploads_root ) ? $custom_uploads_root : realpath( $custom_uploads_root ); } return [ 'path' => $wpforms_upload_root, 'url' => trailingslashit( $upload_dir['baseurl'] ) . 'wpforms', 'error' => false, ]; } /** * Create index.html file in the specified directory if it doesn't exist. * * @since 1.6.1 * * @param string $path Path to the directory. * * @return int|false Number of bytes that were written to the file, or false on failure. */ function wpforms_create_index_html_file( $path ) { if ( ! is_dir( $path ) || is_link( $path ) ) { return false; } $index_file = wp_normalize_path( trailingslashit( $path ) . 'index.html' ); // Do nothing if index.html exists in the directory. if ( file_exists( $index_file ) ) { return false; } // Create empty index.html. return file_put_contents( $index_file, '' ); // phpcs:ignore WordPress.WP.AlternativeFunctions } /** * Create .htaccess file in the WPForms upload directory. * * @since 1.6.1 * * @return bool True when the .htaccess file exists, false on failure. */ function wpforms_create_upload_dir_htaccess_file() { if ( ! apply_filters( 'wpforms_create_upload_dir_htaccess_file', true ) ) { return false; } $upload_dir = wpforms_upload_dir(); if ( ! empty( $upload_dir['error'] ) ) { return false; } $htaccess_file = wp_normalize_path( trailingslashit( $upload_dir['path'] ) . '.htaccess' ); $cache_key = 'wpforms_htaccess_file'; if ( is_file( $htaccess_file ) ) { $cached_stat = get_transient( $cache_key ); $stat = array_intersect_key( stat( $htaccess_file ), [ 'size' => 0, 'mtime' => 0, 'ctime' => 0, ] ); if ( $cached_stat === $stat ) { return true; } @unlink( $htaccess_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } if ( ! function_exists( 'insert_with_markers' ) ) { require_once ABSPATH . 'wp-admin/includes/misc.php'; } $contents = apply_filters( 'wpforms_create_upload_dir_htaccess_file_content', '# Disable PHP and Python scripts parsing.