first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,785 @@
<?php
/**
* Global admin related items and functionality.
*
* @since 1.3.9
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Admin\Blocks\Links;
use WPForms\Admin\Notice;
/**
* Load styles for all WPForms-related admin screens.
*
* @since 1.3.9
*/
function wpforms_admin_styles() {
if ( ! wpforms_is_admin_page() ) {
return;
}
$min = wpforms_get_min_suffix();
// jQuery.Confirm Reloaded.
wp_enqueue_style(
'jquery-confirm',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.css',
[],
'1.0.0'
);
// Minicolors (color picker).
wp_enqueue_style(
'minicolors',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.minicolors/jquery.minicolors.min.css',
[],
'2.3.6'
);
// FontAwesome.
wp_enqueue_style(
'wpforms-font-awesome',
WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css',
null,
'7.0.1'
);
// FontAwesome v4 compatibility shims.
wp_enqueue_style(
'wpforms-font-awesome-v4-shim',
WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/v4-shims.min.css',
null,
'4.7.0'
);
// Main admin styles.
wp_enqueue_style(
'wpforms-admin',
WPFORMS_PLUGIN_URL . "assets/css/admin{$min}.css",
[],
WPFORMS_VERSION
);
wp_enqueue_style(
'wpforms-multiselect-checkboxes',
WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.css',
[],
'1.0.0'
);
// Remove TinyMCE editor styles from third-party themes and plugins.
remove_editor_styles();
// WordPress 5.7 color set.
if ( version_compare( get_bloginfo( 'version' ), '5.7', '>=' ) ) {
wp_enqueue_style(
'wpforms-admin-wp5.7-color',
WPFORMS_PLUGIN_URL . "assets/css/admin-wp5.7-colors{$min}.css",
[ 'wpforms-admin' ],
WPFORMS_VERSION
);
}
}
add_action( 'admin_enqueue_scripts', 'wpforms_admin_styles', 5 );
/**
* Load scripts for all WPForms-related admin screens.
*
* @since 1.3.9
*
* @noinspection HtmlUnknownTarget
*/
function wpforms_admin_scripts() {
if ( ! wpforms_is_admin_page() ) {
return;
}
$min = wpforms_get_min_suffix();
if ( wpforms_is_admin_page( 'settings', 'email' ) ) {
wp_enqueue_media();
}
// jQuery.Confirm Reloaded.
wp_enqueue_script(
'jquery-confirm',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.js',
[ 'jquery' ],
'1.0.0',
false
);
// Minicolors (color picker).
wp_enqueue_script(
'minicolors',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.minicolors/jquery.minicolors.min.js',
[ 'jquery' ],
'2.3.6',
false
);
// Choices.js.
wp_enqueue_script(
'choicesjs',
WPFORMS_PLUGIN_URL . 'assets/lib/choices.min.js',
[],
'10.2.0',
false
);
// jQuery Conditionals.
wp_enqueue_script(
'conditions',
WPFORMS_PLUGIN_URL . 'assets/lib/conditions.min.js',
[ 'jquery' ],
'1.1.0',
false
);
wp_enqueue_script(
'wpforms-generic-utils',
WPFORMS_PLUGIN_URL . "assets/js/share/utils{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
// Load admin utils JS.
wp_enqueue_script(
'wpforms-admin-utils',
WPFORMS_PLUGIN_URL . "assets/js/admin/share/admin-utils{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
// Main admin script.
wp_enqueue_script(
'wpforms-admin',
WPFORMS_PLUGIN_URL . "assets/js/admin/admin{$min}.js",
[ 'jquery', 'underscore', 'wp-util' ],
WPFORMS_VERSION,
false
);
wp_enqueue_script(
'wpforms-multiselect-checkboxes',
WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.js',
[],
WPFORMS_VERSION,
true
);
$default_choicesjs_loading_text = esc_html__( 'Loading...', 'wpforms-lite' );
$default_choicesjs_no_results_text = esc_html__( 'No results found', 'wpforms-lite' );
$default_choicesjs_no_choices_text = esc_html__( 'No choices to choose from', 'wpforms-lite' );
$image_extensions = wpforms_chain( get_allowed_mime_types() )
->map(
static function ( $mime ) {
return strpos( $mime, 'image/' ) === 0 ? $mime : '';
}
)
->array_filter()
->array_values()
->value();
$strings = [
'addon_activate' => esc_html__( 'Activate', 'wpforms-lite' ),
'addon_activated' => esc_html__( 'Activated', 'wpforms-lite' ),
'addon_active' => esc_html__( 'Active', 'wpforms-lite' ),
'addon_deactivate' => esc_html__( 'Deactivate', 'wpforms-lite' ),
'addon_inactive' => esc_html__( 'Inactive', 'wpforms-lite' ),
'addon_install' => esc_html__( 'Install Addon', 'wpforms-lite' ),
'addon_error' => sprintf(
wp_kses( /* translators: %1$s - addon download URL, %2$s - link to manual installation guide, %3$s - link to contact support. */
__( 'Could not install the addon. Please <a href="%1$s" target="_blank" rel="noopener noreferrer">download it from wpforms.com</a> and <a href="%2$s" target="_blank" rel="noopener noreferrer">install it manually</a>, or <a href="%3$s" target="_blank" rel="noopener noreferrer">contact support</a> for assistance.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
'target' => true,
'rel' => true,
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/account/licenses/', 'Licenses', 'Addons Error' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-manually-install-addons-in-wpforms/', 'Addons Doc', 'Addons Error' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/contact/', 'Contact', 'Addons Error' ) )
),
'plugin_error' => esc_html__( 'Could not install the plugin automatically. Please download and install it manually.', 'wpforms-lite' ),
'addon_search' => esc_html__( 'Searching Addons', 'wpforms-lite' ),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'cancel' => esc_html__( 'Cancel', 'wpforms-lite' ),
'continue' => esc_html__( 'Continue', 'wpforms-lite' ),
'close' => esc_html__( 'Close', 'wpforms-lite' ),
'close_refresh' => esc_html__( 'Close and Refresh', 'wpforms-lite' ),
'column_selector_title' => esc_html__( 'Change columns to display', 'wpforms-lite' ),
'column_selector_no_fields' => esc_html__( 'Sorry, there are no form fields that match your criteria.', 'wpforms-lite' ),
'column_selector_no_meta' => esc_html__( 'Sorry, there is no entry meta that match your criteria.', 'wpforms-lite' ),
'entry_delete_confirm' => esc_html__( 'Are you sure you want to delete this entry? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'entry_delete_all_confirm' => esc_html__( 'Are you sure you want to delete ALL entries? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'entry_delete_n_confirm' => sprintf( /* translators: %s - entry count. */
esc_html__( 'Are you sure you want to delete %s entries? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'{entry_count}'
),
'entry_trash_confirm' => esc_html__( 'Are you sure you want to trash this entry? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'entry_trash_all_confirm' => esc_html__( 'Are you sure you want to trash ALL entries? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'entry_trash_n_confirm' => sprintf( /* translators: %s - entry count. */
esc_html__( 'Are you sure you want to trash %s entries? This will also remove all associated files, notes, and logs.', 'wpforms-lite' ),
'{entry_count}'
),
'entry_empty_fields_hide' => esc_html__( 'Hide Empty Fields', 'wpforms-lite' ),
'entry_empty_fields_show' => esc_html__( 'Show Empty Fields', 'wpforms-lite' ),
'entry_note_delete_confirm' => esc_html__( 'Are you sure you want to delete this note?', 'wpforms-lite' ),
'entry_unstar' => esc_html__( 'Unstar entry', 'wpforms-lite' ),
'entry_star' => esc_html__( 'Star entry', 'wpforms-lite' ),
'entry_read' => esc_html__( 'Mark entry read', 'wpforms-lite' ),
'entry_unread' => esc_html__( 'Mark entry unread', 'wpforms-lite' ),
'form_delete_confirm' => esc_html__( 'Are you sure you want to delete this form and all its entries?', 'wpforms-lite' ),
'template_delete_confirm' => esc_html__( 'Are you sure you want to delete this template and all its entries?', 'wpforms-lite' ),
'form_delete_n_confirm' => esc_html__( 'Are you sure you want to delete the selected forms and all their entries?', 'wpforms-lite' ),
'form_delete_all_confirm' => esc_html__( 'Are you sure you want to delete ALL the forms in the trash and all their entries?', 'wpforms-lite' ),
'form_duplicate_confirm' => esc_html__( 'Are you sure you want to duplicate this form?', 'wpforms-lite' ),
'template_duplicate_confirm' => esc_html__( 'Are you sure you want to duplicate this template?', 'wpforms-lite' ),
'heads_up' => esc_html__( 'Heads up!', 'wpforms-lite' ),
'importer_forms_required' => esc_html__( 'Please select at least one form to import.', 'wpforms-lite' ),
'isPro' => wpforms()->is_pro(),
'nonce' => wp_create_nonce( 'wpforms-admin' ),
'almost_done' => esc_html__( 'Almost Done', 'wpforms-lite' ),
'thanks_for_interest' => esc_html__( 'Thanks for your interest in WPForms Pro!', 'wpforms-lite' ),
'oops' => esc_html__( 'Oops!', 'wpforms-lite' ),
'uh_oh' => esc_html__( 'Uh oh!', 'wpforms-lite' ),
'ok' => esc_html__( 'OK', 'wpforms-lite' ),
'plugin_install_activate_btn' => esc_html__( 'Install and Activate', 'wpforms-lite' ),
'plugin_install_activate_confirm' => esc_html__( 'needs to be installed and activated to import its forms. Would you like us to install and activate it for you?', 'wpforms-lite' ),
'plugin_activate_btn' => esc_html__( 'Activate', 'wpforms-lite' ),
'plugin_activate_confirm' => esc_html__( 'needs to be activated to import its forms. Would you like us to activate it for you?', 'wpforms-lite' ),
'provider_delete_confirm' => esc_html__( 'Are you sure you want to disconnect this account? Any form connections you have set up will be lost.', 'wpforms-lite' ),
'provider_delete_error' => esc_html__( 'Could not disconnect this account.', 'wpforms-lite' ),
'provider_auth_error' => esc_html__( 'Could not authenticate with the provider.', 'wpforms-lite' ),
'connecting' => esc_html__( 'Connecting...', 'wpforms-lite' ),
'save_refresh' => esc_html__( 'Save and Refresh', 'wpforms-lite' ),
'save_changes' => esc_html__( 'Save Changes', 'wpforms-lite' ),
'server_error' => esc_html__( 'Unfortunately there was a server connection error.', 'wpforms-lite' ),
'unknown_error' => esc_html__( 'Unknown error.', 'wpforms-lite' ),
'settings_form_style_base' => sprintf(
wp_kses( /* translators: %s - WPForms.com docs page URL. */
__( 'You\'ve selected <strong>Base Styling Only</strong>, which may result in styling issues. <a href="%s" target="_blank" rel="noopener noreferrer">Please check out our tutorial</a> for common issues and recommendations.', 'wpforms-lite' ),
[
'strong' => [],
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-choose-an-include-form-styling-setting/', 'settings-license-modal', 'Base Styling Only' ) )
),
'settings_form_style_none' => sprintf(
wp_kses( /* translators: %s - WPForms.com docs page URL. */
__( 'You\'ve selected <strong>No Styling</strong>, which will likely result in significant styling issues and is recommended only for developers. <a href="%s" target="_blank" rel="noopener noreferrer">Please check out our tutorial</a> for more details and recommendations.', 'wpforms-lite' ),
[
'strong' => [],
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-choose-an-include-form-styling-setting/', 'settings-license-modal', 'No Styling' ) )
),
'testing' => esc_html__( 'Testing', 'wpforms-lite' ),
'recreating' => esc_html__( 'Recreating', 'wpforms-lite' ),
'upgrade_completed' => esc_html__( 'Upgrade was successfully completed!', 'wpforms-lite' ),
'upload_image_title' => esc_html__( 'Upload or Choose Your Image', 'wpforms-lite' ),
'upload_image_button' => esc_html__( 'Use Image', 'wpforms-lite' ),
'upload_image_extensions' => $image_extensions,
'upload_image_extensions_error' => esc_html__( 'You tried uploading a file type that is not allowed. Please try again.', 'wpforms-lite' ),
'upgrade_modal' => wpforms_get_upgrade_modal_text(),
'choicesjs_loading' => $default_choicesjs_loading_text,
'choicesjs_no_results' => $default_choicesjs_no_results_text,
'choicesjs_no_choices' => $default_choicesjs_no_choices_text,
'debug' => wpforms_debug(),
'edit_license' => esc_html__( 'To edit the License Key, please first click the Remove Key button. Please note that removing this key will remove access to updates, addons, and support.', 'wpforms-lite' ),
'something_went_wrong' => esc_html__( 'Something went wrong', 'wpforms-lite' ),
'success' => esc_html__( 'Success', 'wpforms-lite' ),
'loading' => esc_html__( 'Loading...', 'wpforms-lite' ),
'use_default_template' => esc_html__( 'Use Default Template', 'wpforms-lite' ),
'error_select_template' => esc_html__( 'Something went wrong while applying the form template. Please try again. If the error persists, contact our support team.', 'wpforms-lite' ),
'try_again' => sprintf(
wp_kses( /* translators: %s - link to WPForms.com docs page. */
__( 'Something went wrong. Please try again, and if the problem persists, <a href="%1$s" target="_blank" rel="noopener noreferrer">contact our support team</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/contact/', 'error-modal', 'contact our support team' ) )
),
];
/**
* Allow theme/plugin developers to adjust main strings on backend/admin part.
*
* @since 1.3.9
*
* @param array $strings Main admin localized strings.
*/
$strings = (array) apply_filters( 'wpforms_admin_strings', $strings );
/**
* Allow theme/plugin developers to adjust Choices.js settings on backend/admin part.
*
* @see https://github.com/Choices-js/Choices#setup For configuration options.
*
* @since 1.7.3
*
* @param array $choicesjs_config Choicesjs configuration.
*/
$choicesjs_config = (array) apply_filters(
'wpforms_admin_scripts_choicesjs_config',
[
'searchEnabled' => false,
// Forces the search to look for exact matches anywhere in the string.
'fuseOptions' => [
'threshold' => 0.1,
'distance' => 1000,
],
'loadingText' => ! empty( $strings['choicesjs_loading'] ) ? $strings['choicesjs_loading'] : $default_choicesjs_loading_text,
'noResultsText' => ! empty( $strings['choicesjs_no_results'] ) ? $strings['choicesjs_no_results'] : $default_choicesjs_no_results_text,
'noChoicesText' => ! empty( $strings['choicesjs_no_choices'] ) ? $strings['choicesjs_no_choices'] : $default_choicesjs_no_choices_text,
]
);
wp_localize_script(
'wpforms-admin',
'wpforms_admin_choicesjs_config',
$choicesjs_config
);
wp_localize_script(
'wpforms-admin',
'wpforms_admin',
$strings
);
}
add_action( 'admin_enqueue_scripts', 'wpforms_admin_scripts' );
/**
* Add body class to WPForms admin pages for easy reference.
*
* @since 1.3.9
*
* @param string $classes CSS classes, space separated.
*
* @return string
*/
function wpforms_admin_body_class( $classes ) {
if ( ! wpforms_is_admin_page() ) {
return $classes;
}
return "$classes wpforms-admin-page";
}
add_filter( 'admin_body_class', 'wpforms_admin_body_class' );
/**
* Output the WPForms admin header.
*
* @since 1.3.9
*/
function wpforms_admin_header() {
// Bail if we're not on a WPForms screen or page (also exclude form builder).
if ( ! wpforms_is_admin_page() ) {
return;
}
/**
* Prevent admin header outputting if needed.
*
* @since 1.5.7
*
* @param bool $is_admin_header_visible True if admin page header should be outputted.
*/
if ( ! apply_filters( 'wpforms_admin_header', true ) ) {
return;
}
// Omit header from the Welcome activation screen.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
if ( sanitize_key( $_REQUEST['page'] ) === 'wpforms-getting-started' ) {
return;
}
$current_screen = get_current_screen();
$show_screen_options = $current_screen && $current_screen->show_screen_options();
/**
* Fire before the admin header is outputted.
*
* @since 1.5.7
*/
do_action( 'wpforms_admin_header_before' );
?>
<div id="wpforms-header-temp"></div>
<div id="wpforms-header" class="wpforms-header <?php echo esc_attr( $show_screen_options ? 'wpforms-header-show-screen-options' : '' ); ?>">
<img class="wpforms-header-logo" src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/logo.png' ); ?>" alt="WPForms Logo">
<?php
$current_page = sanitize_text_field( wp_unslash( $_REQUEST['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$current_page = str_replace( 'wpforms-', '', $current_page );
Links::render(
[
'docs' => [
'medium' => 'header_links',
'content' => $current_page . '_docs',
],
'support' => [
'medium' => 'header_links',
'content' => $current_page . '_support',
],
]
);
?>
</div>
<?php
/**
* Fire after the admin header is outputted.
*
* @since 1.5.7
*/
do_action( 'wpforms_admin_header_after' );
}
add_action( 'in_admin_header', 'wpforms_admin_header', 100 );
/**
* Remove non-WPForms notices from WPForms pages.
*
* @since 1.3.9
* @since 1.6.9 Added callback for removing on `admin_footer` hook.
*/
function wpforms_admin_hide_unrelated_notices() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
if ( ! wpforms_is_admin_page() ) {
return;
}
global $wp_filter;
// Define rules to remove callbacks.
$rules = [
'user_admin_notices' => [], // remove all callbacks.
'admin_notices' => [],
'all_admin_notices' => [],
'admin_footer' => [
'render_delayed_admin_notices', // remove this particular callback.
],
];
// Extra deny callbacks (will be removed for each hook tag defined in $rules).
$common_deny_callbacks = [
'wpformsdb_admin_notice', // 'Database for WPForms' plugin.
];
$notice_types = array_keys( $rules );
foreach ( $notice_types as $notice_type ) {
if ( empty( $wp_filter[ $notice_type ]->callbacks ) || ! is_array( $wp_filter[ $notice_type ]->callbacks ) ) {
continue;
}
$remove_all_filters = empty( $rules[ $notice_type ] );
foreach ( $wp_filter[ $notice_type ]->callbacks as $priority => $hooks ) {
foreach ( $hooks as $name => $arr ) {
if ( is_object( $arr['function'] ) && is_callable( $arr['function'] ) ) {
if ( $remove_all_filters ) {
unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] );
}
continue;
}
$class = ! empty( $arr['function'][0] ) && is_object( $arr['function'][0] ) ? strtolower( get_class( $arr['function'][0] ) ) : '';
// Remove all callbacks except WPForms notices.
if ( $remove_all_filters && strpos( $class, 'wpforms' ) === false ) {
unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] );
continue;
}
$cb = is_array( $arr['function'] ) ? $arr['function'][1] : $arr['function'];
// Remove a specific callback.
if ( ! $remove_all_filters ) {
if ( in_array( $cb, $rules[ $notice_type ], true ) ) {
unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] );
}
continue;
}
// Remove non-WPForms callbacks from `$common_deny_callbacks` denylist.
if ( in_array( $cb, $common_deny_callbacks, true ) ) {
unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] );
}
}
}
}
}
add_action( 'admin_print_scripts', 'wpforms_admin_hide_unrelated_notices' );
/**
* Upgrade a link used within the various admin pages.
*
* Previously was only included as a method in wpforms-lite.php, but made
* available globally in 1.3.9.
*
* @since 1.3.9
*
* @param string $medium URL parameter: utm_medium.
* @param string $content URL parameter: utm_content.
*
* @return string
*/
function wpforms_admin_upgrade_link( $medium = 'link', $content = '' ) {
$url = 'https://wpforms.com/lite-upgrade/';
if ( wpforms()->is_pro() ) {
$license_key = wpforms_get_license_key();
$url = add_query_arg(
'license_key',
sanitize_text_field( $license_key ),
'https://wpforms.com/pricing/'
);
}
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$upgrade = wpforms_utm_link( $url, apply_filters( 'wpforms_upgrade_link_medium', $medium ), $content );
/**
* Modify upgrade link.
*
* @since 1.5.1
*
* @param string $upgrade Upgrade links.
*/
return apply_filters( 'wpforms_upgrade_link', $upgrade );
}
/**
* Check the current PHP version and display a notice if on unsupported PHP.
*
* @since 1.4.0.1
* @since 1.5.0 Raising this awareness of old PHP version message from 5.2 to 5.3.
* @since 1.7.9 Raising this awareness of old PHP version message to 7.1.
* @since 1.8.4 Raising this awareness of old PHP version message to 7.3.
*
* @noinspection HtmlUnknownTarget
*/
function wpforms_check_php_version() {
// Display for PHP below 7.4.
if ( PHP_VERSION_ID >= 70400 ) {
return;
}
// Display for admins only.
if ( ! is_super_admin() ) {
return;
}
// Display on Dashboard page only.
if ( isset( $GLOBALS['pagenow'] ) && $GLOBALS['pagenow'] !== 'index.php' ) {
return;
}
// Display the notice, finally.
Notice::error(
'<p>' .
sprintf(
wp_kses( /* translators: %1$s - WPForms plugin name; %2$s - WPForms.com URL to a related doc. */
__( 'Your site is running an outdated version of PHP that is no longer supported and may cause issues with %1$s. <a href="%2$s" target="_blank" rel="noopener noreferrer">Read more</a> for additional information.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
'<strong>WPForms</strong>',
'https://wpforms.com/docs/supported-php-version/'
) .
'<br><br><em>' .
wp_kses(
__( '<strong>Please Note:</strong> Support for PHP 7.3 and below will be discontinued soon. After this, if no further action is taken, WPForms functionality will be disabled.', 'wpforms-lite' ),
[
'strong' => [],
'em' => [],
]
) .
'</em></p>'
);
}
add_action( 'admin_init', 'wpforms_check_php_version' );
/**
* Get an upgrade modal text.
*
* @since 1.4.4
*
* @param string $type Either "pro" or "elite". Default is "pro".
*
* @return string
* @noinspection HtmlUnknownTarget
*/
function wpforms_get_upgrade_modal_text( $type = 'pro' ) {
switch ( $type ) {
case 'elite':
$level = 'WPForms Elite';
break;
case 'pro':
default:
$level = 'WPForms Pro';
}
if ( wpforms()->is_pro() ) {
return '<p>' .
sprintf(
wp_kses( /* translators: %s - WPForms.com contact page URL. */
__( 'Thank you for considering upgrading. If you have any questions, please <a href="%s" target="_blank" rel="noopener noreferrer">let us know</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url(
wpforms_utm_link(
'https://wpforms.com/contact/',
'Upgrade Follow Up Modal',
'Contact Support'
)
)
) .
'</p>' .
'<p>' .
wp_kses(
__( 'After upgrading, your license key will remain the same.<br>You may need to do a quick refresh to unlock your new addons. In your WordPress admin, go to <strong>WPForms &raquo; Settings</strong>. If you don\'t see your updated plan, click <em>refresh</em>.', 'wpforms-lite' ),
[
'strong' => [],
'br' => [],
'em' => [],
]
) .
'</p>' .
'<p>' .
sprintf(
wp_kses( /* translators: %s - WPForms.com upgrade from Lite to paid docs page URL. */
__( 'Check out <a href="%s" target="_blank" rel="noopener noreferrer">our documentation</a> for step-by-step instructions.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/upgrade-wpforms-license/', 'Upgrade License Doc', 'Upgrade Now' ) )
) .
'</p>';
}
return '<p>' .
sprintf(
wp_kses( /* translators: %s - WPForms.com contact page URL. */
__( 'If you have any questions or issues just <a href="%s" target="_blank" rel="noopener noreferrer">let us know</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url(
wpforms_utm_link(
'https://wpforms.com/contact/',
'Upgrade Intention Alert',
'Upgrade Intention Alert'
)
)
) .
'</p>' .
'<p>' .
sprintf(
wp_kses( /* translators: %s - license level, WPForms Pro or WPForms Elite. */
__( 'After purchasing a license, just <strong>enter your license key on the WPForms Settings page</strong>. This will let your site automatically upgrade to %s! (Don\'t worry, all your forms and settings will be preserved.)', 'wpforms-lite' ),
[
'strong' => [],
'br' => [],
]
),
$level
) .
'</p>' .
'<p>' .
sprintf(
wp_kses( /* translators: %s - WPForms.com upgrade from Lite to paid docs page URL. */
__( 'Check out <a href="%s" target="_blank" rel="noopener noreferrer">our documentation</a> for step-by-step instructions.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url(
wpforms_utm_link(
'https://wpforms.com/docs/upgrade-wpforms-lite-paid-license/',
'Upgrade Intention Alert',
'Upgrade Documentation'
)
)
) .
'</p>';
}
/**
* Hide the wp-admin area "Version x.x" in footer on WPForms pages.
*
* @since 1.5.7
*
* @param string $text Default "Version x.x" or "Get Version x.x" text.
*
* @return string
*/
function wpforms_admin_hide_wp_version( $text ) {
// Reset text if we're not on a WPForms screen or page.
if ( wpforms_is_admin_page() ) {
return 'WPForms ' . WPFORMS_VERSION;
}
return $text;
}
add_filter( 'update_footer', 'wpforms_admin_hide_wp_version', PHP_INT_MAX );

View File

@@ -0,0 +1,951 @@
<?php
/**
* Ajax actions used in by admin.
*
* @since 1.0.0
*/
use WPForms\Helpers\DB;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Save a form.
*
* @since 1.0.0
*/
function wpforms_save_form() {
// Run a security check.
if ( ! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ) {
wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'wpforms-lite' ) );
}
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) );
}
// Check for form data.
if ( empty( $_POST['data'] ) ) {
wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms-lite' ) );
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$form_post = json_decode( wp_unslash( $_POST['data'] ), false );
$data = wpforms_prepare_form_data( $form_post );
$data = wpforms_sanitize_form_data( $data );
// Process fields data.
$data['fields'] = wpforms()->obj( 'builder_save_form' )->process_fields( $data['fields'], $data );
// Get form tags.
$form_tags = isset( $data['settings']['form_tags_json'] ) ? json_decode( wp_unslash( $data['settings']['form_tags_json'] ), true ) : [];
// Clear unnecessary data.
unset( $data['settings']['form_tags_json'] );
// Store tags labels in the form settings.
$data['settings']['form_tags'] = wp_list_pluck( $form_tags, 'label' );
// Update form data.
$form_id = (int) wpforms()->obj( 'form' )->update( $data['id'], $data, [ 'context' => 'save_form' ] );
/**
* Fires after updating form data.
*
* @since 1.4.0
*
* @param int $form_id Form ID.
* @param array $data Form data.
*/
do_action( 'wpforms_builder_save_form', $form_id, $data );
if ( ! $form_id ) {
wp_send_json_error( esc_html__( 'Something went wrong while saving the form.', 'wpforms-lite' ) );
}
// Update form tags.
wp_set_post_terms(
$form_id,
wpforms()->obj( 'forms_tags_ajax' )->get_processed_tags( $form_tags ),
WPForms_Form_Handler::TAGS_TAXONOMY
);
$response_data = [
'form_name' => esc_html( $data['settings']['form_title'] ),
'form_desc' => $data['settings']['form_desc'],
'redirect' => admin_url( 'admin.php?page=wpforms-overview' ),
];
/**
* Allows filtering ajax response data after the form was saved.
*
* @since 1.5.1
*
* @param array $response_data The data to be sent in the response.
* @param int $form_id Form ID.
* @param array $data Form data.
*/
$response_data = apply_filters(
'wpforms_builder_save_form_response_data',
$response_data,
$form_id,
$data
);
wp_send_json_success( $response_data );
}
add_action( 'wp_ajax_wpforms_save_form', 'wpforms_save_form' );
/**
* Prepare form data.
*
* @since 1.9.4
*
* @param object $form_post Form data received from $_POST.
*
* @return array
*/
function wpforms_prepare_form_data( $form_post ): array {
$data = [
'fields' => [],
];
if ( ! $form_post ) {
return $data;
}
$new_post_data_accum = [];
foreach ( $form_post as $post_input_data ) {
// For input names that are arrays (e.g. `menu-item-db-id[3][4][5]`),
// derive the array path keys via regex and set the value in $_POST.
preg_match( '#([^\[]*)(\[(.+)])?#', $post_input_data->name, $matches );
$array_bits = [ $matches[1] ];
if ( isset( $matches[3] ) ) {
/**
* This array_merge is not slow because it is new for each loop iteration.
*
* @noinspection SlowArrayOperationsInLoopInspection
*/
$array_bits = array_merge( $array_bits, explode( '][', $matches[3] ) );
}
$new_post_data = [];
// Build the new array value from leaf to trunk.
for ( $i = count( $array_bits ) - 1; $i >= 0; $i-- ) {
if ( $i === count( $array_bits ) - 1 ) {
$new_post_data[ $array_bits[ $i ] ] = wp_slash( $post_input_data->value );
} else {
$new_post_data = [
$array_bits[ $i ] => $new_post_data,
];
}
}
$new_post_data_accum[] = $new_post_data;
}
return array_replace_recursive( $data, ...$new_post_data_accum );
}
/**
* Create a new form.
*
* @since 1.0.0
*/
function wpforms_new_form() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Prevent the second form creating if a user has no license set.
// Redirect will lead to the warning page.
if ( wpforms()->is_pro() && empty( wpforms_get_license_type() ) && wp_count_posts( 'wpforms' )->publish >= 1 ) {
wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=wpforms-builder&view=setup' ) ] );
}
if ( empty( $_POST['title'] ) ) {
wp_send_json_error(
[
'error_type' => 'missing_form_title',
'message' => esc_html__( 'No Form Name Provided', 'wpforms-lite' ),
]
);
}
$form_title = sanitize_text_field( wp_unslash( $_POST['title'] ) );
$form_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
$category = empty( $_POST['category'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['category'] ) );
$subcategory = empty( $_POST['subcategory'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['subcategory'] ) );
if ( ! wpforms()->obj( 'builder_templates' )->is_valid_template( $form_template ) ) {
wp_send_json_error(
[
'error_type' => 'invalid_template',
'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'wpforms-lite' ),
]
);
}
$title_query = new WP_Query(
[
'post_type' => 'wpforms',
'title' => $form_title,
'posts_per_page' => 1,
'fields' => 'ids',
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'no_found_rows' => true,
]
);
$title_exists = $title_query->post_count > 0;
$form_id = wpforms()->obj( 'form' )->add(
$form_title,
[],
[
'template' => $form_template,
'category' => $category,
'subcategory' => $subcategory,
]
);
if ( $title_exists ) {
// Skip creating a revision for this action.
remove_action( 'post_updated', 'wp_save_post_revision' );
wp_update_post(
[
'ID' => $form_id,
'post_title' => $form_title . ' (ID #' . $form_id . ')',
]
);
// Restore the initial revisions state.
add_action( 'post_updated', 'wp_save_post_revision' );
}
if ( ! $form_id ) {
wp_send_json_error(
[
'error_type' => 'cant_create_form',
'message' => esc_html__( 'Error Creating Form', 'wpforms-lite' ),
]
);
}
if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
wp_send_json_success(
[
'id' => $form_id,
'redirect' => add_query_arg(
[
'view' => 'fields',
'form_id' => $form_id,
'newform' => '1',
],
admin_url( 'admin.php?page=wpforms-builder' )
),
]
);
}
if ( wpforms_current_user_can( 'view_forms' ) ) {
wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=wpforms-overview' ) ] );
}
wp_send_json_success( [ 'redirect' => admin_url() ] );
}
add_action( 'wp_ajax_wpforms_new_form', 'wpforms_new_form' );
/**
* Update form template.
*
* @since 1.0.0
*/
function wpforms_update_form_template() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error(
[
'error_type' => 'permissions_denied',
'message' => esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ),
]
);
}
// Check for form ID.
if ( empty( $_POST['form_id'] ) ) {
wp_send_json_error(
[
'error_type' => 'invalid_form_id',
'message' => esc_html__( 'No Form ID Provided', 'wpforms-lite' ),
]
);
}
// Set initial variables.
$form_id = absint( $_POST['form_id'] );
$form_template = empty( $_POST['template'] ) ? 'blank' : sanitize_text_field( wp_unslash( $_POST['template'] ) );
$category = empty( $_POST['category'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['category'] ) );
$subcategory = empty( $_POST['subcategory'] ) ? 'all' : sanitize_text_field( wp_unslash( $_POST['subcategory'] ) );
// Check for valid template.
if ( ! wpforms()->obj( 'builder_templates' )->is_valid_template( $form_template ) ) {
wp_send_json_error(
[
'error_type' => 'invalid_template',
'message' => esc_html__( 'The template you selected is currently not available, but you can try again later. If you continue to have trouble, please reach out to support.', 'wpforms-lite' ),
]
);
}
// Get current form data.
$data = wpforms()->obj( 'form' )->get(
$form_id,
[
'content_only' => true,
]
);
// Get the cached data from the form template JSON.
$template_data = wpforms()->obj( 'builder_templates' )->get_template( $form_template );
// If the template title is set, use it. Otherwise, clear the form title.
$template_title = ! empty( $template_data['name'] ) ? $template_data['name'] : '';
// If the form title is set, use it. Otherwise, use the template title.
$form_title = ! empty( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : $template_title;
// Check if the current form title is equal to the previous template name.
// If so, set the form title equal to the new template name.
$prev_template_slug = $data['meta']['template'] ?? '';
$prev_template = wpforms()->obj( 'builder_templates' )->get_template( $prev_template_slug );
$form_title = isset( $prev_template['name'] ) && $prev_template['name'] === $form_title ? $template_title : $form_title;
// If these template titles are empty, use the form title.
$form_pages_title = $template_title ? $template_title : $form_title;
$form_conversational_title = ! empty( $template_data['data']['settings']['conversational_forms_title'] ) ? $template_data['data']['settings']['conversational_forms_title'] : $form_title;
// If these template slugs are empty, use the form title.
$form_conversational_slug = ! empty( $template_data['data']['settings']['conversational_forms_page_slug'] ) ? $template_data['data']['settings']['conversational_forms_page_slug'] : $form_title;
$form_pages_slug = ! empty( $template_data['data']['settings']['form_pages_page_slug'] ) ? $template_data['data']['settings']['form_pages_page_slug'] : $form_title;
// Loop over notifications.
$notifications = $template_data['data']['settings']['notifications'] ?? [];
foreach ( $notifications as $key => $notification ) {
// If the subject is empty, set it to an empty string.
$notification_subject = ! empty( $notification['subject'] ) ? sanitize_text_field( $notification['subject'] ) : '';
$data['settings']['notifications'][ $key ]['subject'] = $notification_subject;
}
// Loop over confirmations.
$confirmations = $template_data['data']['settings']['confirmations'] ?? [];
foreach ( $confirmations as $key => $confirmation ) {
// If the message is empty, set it to an empty string.
$confirmation_message = ! empty( $confirmation['message'] ) ? wp_kses_post( $confirmation['message'] ) : '';
$data['settings']['confirmations'][ $key ]['message'] = $confirmation_message;
}
// Set updated form titles.
$data['settings']['form_title'] = sanitize_text_field( $form_title );
$data['settings']['form_pages_title'] = sanitize_text_field( $form_pages_title );
$data['settings']['conversational_forms_title'] = sanitize_text_field( $form_conversational_title );
// Set updated form slugs.
$data['settings']['form_pages_page_slug'] = sanitize_title( $form_pages_slug );
$data['settings']['conversational_forms_page_slug'] = sanitize_title( $form_conversational_slug );
// Try to update the form.
$updated = (bool) wpforms()->obj( 'form' )->update(
$form_id,
$data,
[
'template' => $form_template,
'category' => $category,
'subcategory' => $subcategory,
]
);
// If the form was updated, return the form ID and redirect to the form builder.
if ( $updated ) {
wp_send_json_success(
[
'id' => $form_id,
'redirect' => add_query_arg(
[
'view' => 'fields',
'form_id' => $form_id,
],
admin_url( 'admin.php?page=wpforms-builder' )
),
]
);
}
// Otherwise, return an error.
wp_send_json_error(
[
'error_type' => 'cant_update',
'message' => esc_html__( 'Error Updating Template', 'wpforms-lite' ),
]
);
}
add_action( 'wp_ajax_wpforms_update_form_template', 'wpforms_update_form_template' );
/**
* Form Builder update next field ID.
*
* @since 1.2.9
*/
function wpforms_builder_increase_next_field_id() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error();
}
// Check for required items.
if ( empty( $_POST['form_id'] ) ) {
wp_send_json_error();
}
$args = [];
// In the case of duplicating the Layout field that contains a bunch of fields,
// we need to set the next `field_id` to the desired value which is passed via POST argument.
if ( ! empty( $_POST['field_id'] ) ) {
$args['field_id'] = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
}
wpforms()->obj( 'form' )->next_field_id( absint( $_POST['form_id'] ), $args );
wp_send_json_success();
}
add_action( 'wp_ajax_wpforms_builder_increase_next_field_id', 'wpforms_builder_increase_next_field_id' );
/**
* Form Builder Dynamic Choices option toggle.
*
* This can be triggered with select/radio/checkbox fields.
*
* @since 1.2.8
*/
function wpforms_builder_dynamic_choices() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error();
}
// Check for valid/required items.
if ( ! isset( $_POST['field_id'] ) || empty( $_POST['type'] ) || ! in_array( $_POST['type'], [ 'post_type', 'taxonomy' ], true ) ) {
wp_send_json_error();
}
$type = sanitize_key( $_POST['type'] );
$id = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
// Fetch the option row HTML to be returned to the builder.
$field = new WPForms_Field_Select( false );
$field_args = [
'id' => $id,
'dynamic_choices' => $type,
];
$option_row = $field->field_option( 'dynamic_choices_source', $field_args, [], false );
wp_send_json_success(
[
'markup' => $option_row,
]
);
}
add_action( 'wp_ajax_wpforms_builder_dynamic_choices', 'wpforms_builder_dynamic_choices' );
/**
* Form Builder Dynamic Choices Source option toggles.
*
* This can be triggered with select/radio/checkbox fields.
*
* @since 1.2.8
*/
function wpforms_builder_dynamic_source() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_forms' ) ) {
wp_send_json_error();
}
// Check for required items.
if ( ! isset( $_POST['field_id'] ) || empty( $_POST['form_id'] ) || empty( $_POST['type'] ) || empty( $_POST['source'] ) ) {
wp_send_json_error();
}
$type = sanitize_key( $_POST['type'] );
$source = sanitize_key( $_POST['source'] );
$id = sanitize_text_field( wp_unslash( $_POST['field_id'] ) );
$form_id = absint( $_POST['form_id'] );
$items = [];
$total = 0;
$source_name = '';
$type_name = '';
if ( $type === 'post_type' ) {
$type_name = esc_html__( 'post type', 'wpforms-lite' );
$args = [
'post_type' => $source,
'posts_per_page' => 20,
'orderby' => 'title',
'order' => 'ASC',
];
/**
* Filters the arguments used to query the post type for dynamic choices.
*
* @since 1.4.1
*
* @param array $args Arguments used to query the post's type for dynamic choices.
* @param array $field Field.
* @param int $form_id Form ID.
*/
$args = (array) apply_filters( 'wpforms_dynamic_choice_post_type_args', $args, [ 'id' => $id ], $form_id );
$posts = wpforms_get_hierarchical_object( $args, true );
$total = wp_count_posts( $source );
$total = $total->publish;
$pt = get_post_type_object( $source );
if ( $pt !== null ) {
$source_name = $pt->labels->name;
}
foreach ( $posts as $post ) {
$items[] = esc_html( wpforms_get_post_title( $post ) );
}
} elseif ( $type === 'taxonomy' ) {
$type_name = esc_html__( 'taxonomy', 'wpforms-lite' );
$args = [
'taxonomy' => $source,
'hide_empty' => false,
'number' => 20,
];
/**
* Filters the arguments used to query the taxonomy for dynamic choices.
*
* @since 1.4.1
*
* @param array $args Arguments used to query the post's type for dynamic choices.
* @param array $field Field.
* @param int $form_id Form ID.
*/
$args = apply_filters( 'wpforms_dynamic_choice_taxonomy_args', $args, [ 'id' => $id ], $form_id );
$terms = wpforms_get_hierarchical_object( $args, true );
$total = wp_count_terms( $source );
$tax = get_taxonomy( $source );
$source_name = $tax->labels->name;
foreach ( $terms as $term ) {
$items[] = esc_html( wpforms_get_term_name( $term ) );
}
}
if ( empty( $items ) ) {
$items = [];
}
wp_send_json_success(
[
'items' => $items,
'source' => $source,
'source_name' => $source_name,
'total' => $total,
'type' => $type,
'type_name' => $type_name,
]
);
}
add_action( 'wp_ajax_wpforms_builder_dynamic_source', 'wpforms_builder_dynamic_source' );
/**
* Perform a test connection to verify that the current web host can successfully
* make outbound SSL connections.
*
* @since 1.4.5
*/
function wpforms_verify_ssl() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can() ) {
wp_send_json_error(
[
'msg' => esc_html__( 'You do not have permission to perform this operation.', 'wpforms-lite' ),
]
);
}
$response = wp_remote_post( 'https://wpforms.com/connection-test.php' );
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
wp_send_json_success(
[
'msg' => esc_html__( 'Success! Your server can make SSL connections.', 'wpforms-lite' ),
]
);
}
wp_send_json_error(
[
'msg' => esc_html__( 'There was an error and the connection failed. Please contact your web host with the technical details below.', 'wpforms-lite' ),
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
'debug' => '<pre>' . print_r( map_deep( $response, 'wp_strip_all_tags' ), true ) . '</pre>',
]
);
}
add_action( 'wp_ajax_wpforms_verify_ssl', 'wpforms_verify_ssl' );
/**
* Recreate custom database tables.
*
* @since 1.9.0
*/
function wpforms_recreate_tables() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can() ) {
wp_send_json_error(
[
'msg' => esc_html__( 'You do not have permission to perform this operation.', 'wpforms-lite' ),
]
);
}
DB::create_custom_tables( true );
if ( DB::custom_tables_exist() ) {
wp_send_json_success(
[
'msg' => esc_html__( 'WPForms custom database tables are recreated.', 'wpforms-lite' ),
]
);
}
wp_send_json_error(
[
'msg' => esc_html__( 'Error recreating WPForms custom database tables.', 'wpforms-lite' ),
]
);
}
add_action( 'wp_ajax_wpforms_recreate_tables', 'wpforms_recreate_tables' );
/**
* Deactivate addon.
*
* @since 1.0.0
* @since 1.6.2.3 Updated the permissions checking.
*/
function wpforms_deactivate_addon() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! current_user_can( 'deactivate_plugins' ) ) {
wp_send_json_error( esc_html__( 'Plugin deactivation is disabled for you on this site.', 'wpforms-lite' ) );
}
$type = empty( $_POST['type'] ) ? 'addon' : sanitize_key( $_POST['type'] );
if ( isset( $_POST['plugin'] ) ) {
$plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ) );
deactivate_plugins( $plugin );
/**
* Fire after the plugin deactivating via the WPForms installer.
*
* @since 1.6.3
*
* @param string $plugin Plugin deactivated.
*/
do_action( 'wpforms_plugin_deactivated', $plugin );
if ( $type === 'plugin' ) {
wp_send_json_success( esc_html__( 'Plugin deactivated.', 'wpforms-lite' ) );
} else {
wp_send_json_success( esc_html__( 'Addon deactivated.', 'wpforms-lite' ) );
}
}
wp_send_json_error( esc_html__( 'Could not deactivate the addon. Please deactivate from the Plugins page.', 'wpforms-lite' ) );
}
add_action( 'wp_ajax_wpforms_deactivate_addon', 'wpforms_deactivate_addon' );
/**
* Activate addon.
*
* @since 1.0.0
* @since 1.6.2.3 Updated the permissions checking.
*/
function wpforms_activate_addon() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! current_user_can( 'activate_plugins' ) ) {
wp_send_json_error( esc_html__( 'Plugin activation is disabled for you on this site.', 'wpforms-lite' ) );
}
$success_messages = [
'plugin' => __( 'Plugin activated.', 'wpforms-lite' ),
'addon' => __( 'Addon activated.', 'wpforms-lite' ),
];
$error_messages = [
'plugin' => __( 'Could not activate the plugin. Please activate it on the Plugins page.', 'wpforms-lite' ),
'addon' => __( 'Could not activate the addon. Please activate it on the Plugins page.', 'wpforms-lite' ),
];
$type = ! empty( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : 'addon';
$success_message = $success_messages[ $type ];
$error_message = $error_messages[ $type ];
if ( isset( $_POST['plugin'] ) ) {
$plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ) );
$activate = wpforms_activate_plugin( $plugin );
/**
* Fire after the plugin activating via the WPForms installer.
*
* @since 1.6.3.1
*
* @param string $plugin Path to the plugin file relative to the plugins' directory.
*/
do_action( 'wpforms_plugin_activated', $plugin );
if ( $activate === null ) {
wp_send_json_success( wp_kses_post( $success_message ) );
}
$error_message = $activate->get_error_message();
}
wp_send_json_error( wp_kses_post( $error_message ) );
}
add_action( 'wp_ajax_wpforms_activate_addon', 'wpforms_activate_addon' );
/**
* Install addon.
*
* @since 1.0.0
* @since 1.6.2.3 Updated the permissions checking.
*
* @noinspection HtmlUnknownTarget
*/
function wpforms_install_addon() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
$generic_error = esc_html__( 'There was an error while performing your request.', 'wpforms-lite' );
$type = ! empty( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : 'addon';
// Check if new installations are allowed.
if ( ! wpforms_can_install( $type ) ) {
wp_send_json_error( $generic_error );
}
$error = $type === 'plugin'
? esc_html__( 'Could not install the plugin. Please download and install it manually.', 'wpforms-lite' )
: sprintf(
wp_kses( /* translators: %1$s - addon download URL, %2$s - link to a manual installation guide, %3$s - link to contact support. */
__( 'Could not install the addon. Please <a href="%1$s" target="_blank" rel="noopener noreferrer">download it from wpforms.com</a> and <a href="%2$s" target="_blank" rel="noopener noreferrer">install it manually</a>, or <a href="%3$s" target="_blank" rel="noopener noreferrer">contact support</a> for assistance.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
'target' => true,
'rel' => true,
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/account/licenses/', 'Licenses', 'Addons Error' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-manually-install-addons-in-wpforms/', 'Addons Doc', 'Addons Error' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/contact/', 'Contact', 'Addons Error' ) )
);
$plugin_url = ! empty( $_POST['plugin'] ) ? esc_url_raw( wp_unslash( $_POST['plugin'] ) ) : '';
if ( empty( $plugin_url ) ) {
wp_send_json_error( $error );
}
$args_str = ! empty( $_POST['args'] ) ? sanitize_text_field( wp_unslash( $_POST['args'] ) ) : '';
$args = json_decode( $args_str, true ) ?? [];
// Set the current screen to avoid undefined notices.
set_current_screen( 'wpforms_page_wpforms-settings' );
// Prepare variables.
$url = esc_url_raw(
add_query_arg(
[
'page' => 'wpforms-addons',
],
admin_url( 'admin.php' )
)
);
ob_start();
$creds = request_filesystem_credentials( $url, '', false, false );
// Hide the filesystem credentials form.
ob_end_clean();
// Check for file system permissions.
if ( $creds === false ) {
wp_send_json_error( $error );
}
if ( ! WP_Filesystem( $creds ) ) {
wp_send_json_error( $error );
}
/*
* We do not need any extra credentials if we have gotten this far, so let's install the plugin.
*/
// Do not allow WordPress to search/download translations, as this will break JS output.
remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 );
// Create the plugin upgrader with our custom skin.
$installer = new WPForms\Helpers\PluginSilentUpgrader( new WP_Ajax_Upgrader_Skin() );
// Error check.
if ( ! method_exists( $installer, 'install' ) ) {
wp_send_json_error( $error );
}
$installer->install( $plugin_url, $args );
// Flush the cache and return the newly installed plugin basename.
wp_cache_flush();
$plugin_basename = $installer->plugin_info();
if ( empty( $plugin_basename ) ) {
wp_send_json_error( $error );
}
$result = [
'msg' => $generic_error,
'is_activated' => false,
'basename' => $plugin_basename,
];
// Check for permissions.
if ( ! current_user_can( 'activate_plugins' ) ) {
$result['msg'] = $type === 'plugin' ? esc_html__( 'Plugin installed.', 'wpforms-lite' ) : esc_html__( 'Addon installed.', 'wpforms-lite' );
wp_send_json_success( $result );
}
// Activate the plugin silently.
$activated = activate_plugin( $plugin_basename );
if ( ! is_wp_error( $activated ) ) {
/**
* Fire after the plugin activating via the WPForms installer.
*
* @since 1.7.0
*
* @param string $plugin_basename Path to the plugin file relative to the plugins' directory.
*/
do_action( 'wpforms_plugin_activated', $plugin_basename );
$result['is_activated'] = true;
$result['msg'] = $type === 'plugin' ? esc_html__( 'Plugin installed & activated.', 'wpforms-lite' ) : esc_html__( 'Addon installed & activated.', 'wpforms-lite' );
wp_send_json_success( $result );
}
// Fallback error just in case.
wp_send_json_error( $result );
}
add_action( 'wp_ajax_wpforms_install_addon', 'wpforms_install_addon' );
/**
* Search pages for dropdown.
*
* @since 1.7.9
*/
function wpforms_ajax_search_pages_for_dropdown() {
// Run a security check.
if ( ! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ) {
wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'wpforms-lite' ) );
}
if ( ! array_key_exists( 'search', $_GET ) ) {
wp_send_json_error( esc_html__( 'Incorrect usage of this operation.', 'wpforms-lite' ) );
}
$search = sanitize_text_field( wp_unslash( $_GET['search'] ) );
$result_pages = [];
$previous_page_label = esc_html__( 'Back to Previous Page (Referrer) ', 'wpforms-lite' );
if ( stripos( strtolower( $previous_page_label ), $search ) !== false ) {
$result_pages[] = [
'value' => 'previous_page',
'label' => $previous_page_label,
];
}
$result_pages += wpforms_search_pages_for_dropdown( $search );
if ( empty( $result_pages ) ) {
wp_send_json_success( [] );
}
wp_send_json_success( $result_pages );
}
add_action( 'wp_ajax_wpforms_ajax_search_pages_for_dropdown', 'wpforms_ajax_search_pages_for_dropdown' );

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,942 @@
<?php
use WPForms\Admin\Education\Helpers;
/**
* Output fields to be used on panels (settings etc.).
*
* @since 1.0.0
*
* @param string $option Field type.
* @param string $panel Panel name.
* @param string $field Field name.
* @param array $form_data Form data.
* @param string $label Label.
* @param array $args Arguments.
* @param bool $do_echo Output the result.
*
* @return string|null
* @noinspection HtmlWrongAttributeValue
* @noinspection HtmlUnknownAttribute
*/
function wpforms_panel_field( $option, $panel, $field, $form_data, $label, $args = [], $do_echo = true ): ?string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded
// Required params.
if ( empty( $option ) || empty( $panel ) || empty( $field ) ) {
return '';
}
// Setup basic vars.
$panel = esc_attr( $panel );
$field = esc_attr( $field );
$panel_id = sanitize_html_class( $panel );
$parent = ! empty( $args['parent'] ) ? esc_attr( $args['parent'] ) : '';
$subsection = ! empty( $args['subsection'] ) ? esc_attr( $args['subsection'] ) : '';
$index = isset( $args['index'] ) ? esc_attr( $args['index'] ) : '';
$index = is_numeric( $index ) ? absint( $index ) : $index;
$label = ! empty( $label ) ? wp_kses( $label, [ 'span' => [ 'class' => [] ] ] ) : '';
$class = ! empty( $args['class'] ) ? wpforms_sanitize_classes( $args['class'] ) : '';
$input_class = ! empty( $args['input_class'] ) ? wpforms_sanitize_classes( $args['input_class'] ) : '';
$default = $args['default'] ?? '';
$placeholder = ! empty( $args['placeholder'] ) ? esc_attr( $args['placeholder'] ) : '';
$data_attr = '';
$output = '';
$smarttags_toggle = '';
$input_id = sprintf( 'wpforms-panel-field-%s-%s', sanitize_html_class( $panel_id ), sanitize_html_class( $field ) );
if ( ! empty( $args['input_id'] ) ) {
$input_id = esc_attr( $args['input_id'] );
}
// Sanitize the subsection only if it doesn't contain a connection ID tag.
if ( strpos( $subsection, '%connection_id%' ) === false ) {
$subsection = sanitize_html_class( $subsection );
}
// Check for smart tags.
if ( ! empty( $args['smarttags'] ) ) {
$type = ! empty( $args['smarttags']['type'] ) ? esc_attr( $args['smarttags']['type'] ) : 'fields';
$fields = ! empty( $args['smarttags']['fields'] ) ? esc_attr( $args['smarttags']['fields'] ) : '';
$is_repeater_allowed = ! empty( $args['smarttags']['allow-repeated-fields'] ) ? esc_attr( $args['smarttags']['allow-repeated-fields'] ) : '';
$allowed_smarttags = ! empty( $args['smarttags']['allowed'] ) ? esc_attr( $args['smarttags']['allowed'] ) : '';
$location = ! empty( $args['location'] ) ? esc_attr( $args['location'] ) : '';
$args['data'] = [
'location' => $location,
'type' => $type,
'fields' => $fields,
'allow-repeated-fields' => $is_repeater_allowed,
'allowed-smarttags' => $allowed_smarttags,
];
// BC for old addons that use the old smart tags system.
$smarttags_toggle = sprintf(
'<a href="#" class="toggle-smart-tag-display toggle-unfoldable-cont" data-location="%5$s" data-type="%1$s" data-fields="%2$s" data-allow-repeated-fields="%3$s" data-allowed-smarttags="%6$s">
<i class="fa fa-tags"></i><span>%4$s</span>
</a>',
esc_attr( $type ),
esc_attr( $fields ),
esc_attr( $is_repeater_allowed ),
esc_html__( 'Show Smart Tags', 'wpforms-lite' ),
esc_attr( $location ),
esc_attr( $allowed_smarttags )
);
}
if ( ! empty( $args['pro_badge'] ) ) {
$label .= Helpers::get_badge( 'Pro', 'sm', 'inline', 'silver' );
}
// Check if we should store values in a parent array.
if ( ! empty( $parent ) ) {
if ( $subsection && ! wpforms_is_empty_string( $index ) ) {
$field_name = sprintf( '%s[%s][%s][%s][%s]', $parent, $panel, $subsection, $index, $field );
$value = $form_data[ $parent ][ $panel ][ $subsection ][ $index ][ $field ] ?? $default;
$input_id = sprintf( 'wpforms-panel-field-%s-%s-%s-%s', sanitize_html_class( $panel_id ), $subsection, sanitize_html_class( $index ), sanitize_html_class( $field ) );
} elseif ( ! empty( $subsection ) ) {
$field_name = sprintf( '%s[%s][%s][%s]', $parent, $panel, $subsection, $field );
$value = $form_data[ $parent ][ $panel ][ $subsection ][ $field ] ?? $default;
$input_id = sprintf( 'wpforms-panel-field-%s-%s-%s', sanitize_html_class( $panel_id ), $subsection, sanitize_html_class( $field ) );
} else {
$field_name = sprintf( '%s[%s][%s]', $parent, $panel, $field );
$value = $form_data[ $parent ][ $panel ][ $field ] ?? $default;
}
} else {
$field_name = sprintf( '%s[%s]', $panel, $field );
$value = $form_data[ $panel ][ $field ] ?? $default;
}
if ( isset( $args['field_name'] ) ) {
$field_name = $args['field_name'];
}
if ( isset( $args['value'] ) ) {
$value = $args['value'];
}
// Check for data attributes.
if ( ! empty( $args['data'] ) ) {
foreach ( $args['data'] as $key => $val ) {
if ( is_array( $val ) ) {
$val = wp_json_encode( $val );
}
$data_attr .= ' data-' . $key . '=\'' . $val . '\'';
}
}
// Check for readonly inputs.
if ( ! empty( $args['readonly'] ) ) {
$data_attr .= 'readonly';
}
// Determine what field type to output.
switch ( $option ) {
// Text input.
case 'text':
// Handle min and max attributes for number fields.
if ( ! empty( $args['type'] ) && $args['type'] === 'number' ) {
if ( isset( $args['min'] ) && is_int( $args['min'] ) ) {
$data_attr .= sprintf( ' min="%1$d" oninput="validity.valid||(value=\'%1$d\');" ', esc_attr( $args['min'] ) );
}
if ( isset( $args['max'] ) && is_int( $args['max'] ) ) {
$data_attr .= sprintf( ' max="%1$d" oninput="validity.valid||(value=\'%1$d\');" ', esc_attr( $args['max'] ) );
}
}
$output = sprintf(
'<input type="%s" id="%s" name="%s" value="%s" placeholder="%s" class="%s" %s>',
! empty( $args['type'] ) ? esc_attr( $args['type'] ) : 'text',
$input_id,
$field_name,
esc_attr( $value ),
$placeholder,
$input_class,
$data_attr
);
break;
// Image uploader.
case 'image_upload':
$output = wpforms_panel_field_image_upload_control(
$option,
$args,
$panel,
$parent,
$field,
$form_data,
$field_name,
$input_id
);
break;
// Textarea.
case 'textarea':
$output = sprintf(
'<textarea id="%s" name="%s" rows="%d" placeholder="%s" class="%s" %s>%s</textarea>',
$input_id,
$field_name,
! empty( $args['rows'] ) ? (int) $args['rows'] : '3',
$placeholder,
$input_class,
$data_attr,
esc_textarea( $value )
);
break;
// TinyMCE.
case 'tinymce':
$id = str_replace( '-', '_', $input_id );
$args['tinymce']['textarea_name'] = $field_name;
$args['tinymce']['teeny'] = true;
$args['tinymce'] = wp_parse_args(
$args['tinymce'],
[
'media_buttons' => false,
'teeny' => true,
]
);
ob_start();
wp_editor( $value, $id, $args['tinymce'] );
$output = ob_get_clean();
break;
// Checkbox.
case 'checkbox':
$output = sprintf(
'<input type="checkbox" id="%s" name="%s" value="1" class="%s" %s %s>',
$input_id,
$field_name,
$input_class,
checked( '1', $value, false ),
$data_attr
);
$output .= sprintf(
'<label for="%s" class="inline">%s',
$input_id,
$label
);
if ( ! empty( $args['before_tooltip'] ) ) {
$output .= $args['before_tooltip'];
}
if ( ! empty( $args['tooltip'] ) ) {
$output .= sprintf( '<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>', esc_attr( $args['tooltip'] ) );
}
$output .= '</label>';
break;
// Toggle.
case 'toggle':
$toggle_args = $args;
$toggle_args['input-class'] = $input_class;
$output = wpforms_panel_field_toggle_control( $toggle_args, $input_id, $field_name, $label, $value, $data_attr );
break;
// Radio.
case 'radio':
$options = $args['options'];
$radio_counter = 1;
foreach ( $options as $key => $item ) {
if ( empty( $item['label'] ) ) {
continue;
}
$item_value = ! empty( $item['value'] ) ? $item['value'] : $key;
$output .= '<span class="row">';
if ( ! empty( $item['pre_label'] ) ) {
$output .= '<label>' . $item['pre_label'];
}
$output .= sprintf(
'<input type="radio" id="%s-%d" name="%s" value="%s" class="%s" %s %s>',
$input_id,
$radio_counter,
$field_name,
$item_value,
$input_class,
checked( $item_value, $value, false ),
$data_attr
);
if ( empty( $item['pre_label'] ) ) {
$output .= sprintf(
'<label for="%s-%d" class="inline">%s',
$input_id,
$radio_counter,
$item['label']
);
} else {
$output .= '<span class="wpforms-panel-field-radio-label">' . $item['label'] . '</span>';
}
if ( ! empty( $item['tooltip'] ) ) {
$output .= sprintf( '<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>', esc_attr( $item['tooltip'] ) );
}
$output .= '</label></span>';
++$radio_counter;
}
if ( ! empty( $output ) ) {
$output = '<div class="wpforms-panel-field-radio-container">' . $output . '</div>';
}
break;
// Select.
case 'select':
if ( empty( $args['options'] ) && empty( $args['field_map'] ) && empty( $args['multiple'] ) ) {
return '';
}
if ( ! empty( $args['field_map'] ) ) {
$options = [];
$available_fields = wpforms_get_form_fields( $form_data, $args['field_map'] );
if ( ! empty( $available_fields ) ) {
foreach ( $available_fields as $id => $available_field ) {
$options[ $id ] = ! empty( $available_field['label'] )
? esc_attr( $available_field['label'] )
: sprintf( /* translators: %d - field ID. */
esc_html__( 'Field #%d', 'wpforms-lite' ),
absint( $id )
);
}
}
$input_class .= ' wpforms-field-map-select';
$data_attr .= ' data-field-map-allowed="' . implode( ' ', $args['field_map'] ) . '"';
if ( ! empty( $placeholder ) ) {
$data_attr .= ' data-field-map-placeholder="' . esc_attr( $placeholder ) . '"';
}
} else {
$options = $args['options'];
}
if ( array_key_exists( 'choicesjs', $args ) && is_array( $args['choicesjs'] ) ) {
$input_class .= ' choicesjs-select';
$data_attr .= ! empty( $args['choicesjs']['use_ajax'] ) ? ' data-choicesjs-use-ajax=1' : '';
$data_attr .= ! empty( $args['choicesjs']['callback_fn'] ) ? ' data-choicesjs-callback-fn="' . esc_attr( $args['choicesjs']['callback_fn'] ) . '"' : '';
}
if ( ! empty( $args['multiple'] ) ) {
$data_attr .= ' multiple';
}
$output = sprintf(
'<select id="%s" name="%s" class="%s" %s>',
$input_id,
$field_name,
esc_attr( $input_class ),
$data_attr
);
if ( ! empty( $placeholder ) ) {
$output .= '<option value="">' . $placeholder . '</option>';
}
// This argument is used to disable some options, it takes an array of option values.
// For instance, if you want to disable options with value '1' and '2', you should pass array( '1', '2' ).
$disabled_options = ! empty( $args['disabled_options'] ) ? (array) $args['disabled_options'] : [];
foreach ( $options as $key => $item ) {
// If the option is disabled, we add the disabled attribute.
$disabled = in_array( $key, $disabled_options, true ) ? 'disabled' : '';
// Disabled options cannot be selected, so we bail early.
if ( ! empty( $disabled ) ) {
$output .= sprintf(
'<option value="%s" %s>%s</option>',
esc_attr( $key ),
$disabled,
$item
);
continue;
}
if ( is_array( $value ) ) {
$selected = in_array( $key, $value, true ) ? 'selected' : '';
} else {
$selected = selected( $key, $value, false );
}
$output .= sprintf(
'<option value="%s" %s>%s</option>',
esc_attr( $key ),
$selected,
$item
);
}
$output .= '</select>';
break;
case 'color':
$class .= ' wpforms-panel-field-colorpicker';
$input_class .= ' wpforms-color-picker';
$fallback_value = $args['data']['fallback-color'] ?? $value;
$output = sprintf(
'<input type="text" id="%s" name="%s" value="%s" data-fallback-color="%s" class="%s" %s>',
$input_id,
$field_name,
esc_attr( $value ),
esc_attr( $fallback_value ),
wpforms_sanitize_classes( $input_class ),
$data_attr
);
break;
/**
* Number input.
*
* @since 1.9.8
*/
case 'number':
if ( isset( $args['min'] ) ) {
$data_attr .= sprintf( ' min="%1$d" oninput="validity.valid||(value=\'%1$d\');" ', esc_attr( $args['min'] ) );
}
if ( isset( $args['step'] ) ) {
$data_attr .= sprintf( ' step="%1$d" oninput="validity.valid||(value=\'%1$d\');" ', esc_attr( $args['step'] ) );
}
$output = '<div class="wpforms-panel-field-number-wrapper">';
$output .= sprintf(
'<input type="number" id="%s" name="%s" value="%s" placeholder="%s" class="%s" %s>',
$input_id,
$field_name,
esc_attr( $value ),
$placeholder,
$input_class,
$data_attr
);
if ( ! empty( $args['show_unit'] ) ) {
$output .= '<span class="wpforms-panel-field-number-unit">' . $args['show_unit'] . '</span>';
}
$output .= '</div>';
break;
}
// Put the pieces together.
$field_open = sprintf(
'<div id="%s-wrap" class="wpforms-panel-field %s %s">',
$input_id,
$class,
'wpforms-panel-field-' . sanitize_html_class( $option )
);
$field_open .= ! empty( $args['before'] ) ? $args['before'] : '';
if ( $option !== 'toggle' && $option !== 'checkbox' && ! empty( $label ) ) {
$field_label = sprintf(
'<label for="%s">%s',
$input_id,
$label
);
if ( ! empty( $args['tooltip'] ) ) {
$field_label .= sprintf( '<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>', esc_attr( $args['tooltip'] ) );
}
if ( ! empty( $args['after_tooltip'] ) ) {
$field_label .= $args['after_tooltip'];
}
// BC for old addons that use the old smart tags system.
if ( $smarttags_toggle && empty( $args['tinymce'] ) && strpos( $input_class, 'wpforms-smart-tags-enabled' ) === false ) {
$field_label .= $smarttags_toggle;
}
$field_label .= '</label>';
if ( ! empty( $args['after_label'] ) ) {
$field_label .= $args['after_label'];
}
} else {
$field_label = '';
}
$field_close = '';
$field_close .= ! empty( $args['after'] ) ? $args['after'] : '';
$field_close .= '</div>';
$output = $field_open . $field_label . $output . $field_close;
// Wash our hands.
if ( $do_echo ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $output;
return null;
}
return $output;
}
/**
* Create toggle control.
*
* It's like a regular checkbox but with a modern visual appearance.
*
* @since 1.6.8
*
* @param array $args Arguments array.
*
* @type bool $status If `true`, control will display the current status next to the toggle.
* @type string $status_on Status `On` text. By default, `On`.
* @type string $status_off Status `Off` text. By default, `Off`.
* @type bool $label_hide If `true `, then the label will not display.
* @type string $tooltip Tooltip text.
* @type string $input_class CSS class for the hidden `<input type=checkbox>`.
* @type string $control_class CSS class for the wrapper `<span>`.
*
* @param string $input_id Input ID.
* @param string $field_name Field name.
* @param string $label Label text. Can contain HTML to display additional badges.
* @param mixed $value Value.
* @param string $data_attr Attributes.
*
* @return string
* @noinspection HtmlUnknownAttribute
*/
function wpforms_panel_field_toggle_control( $args, $input_id, $field_name, $label, $value, $data_attr ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$checked = checked( true, (bool) $value, false );
$status = '';
if ( ! empty( $args['status'] ) ) {
$status_on = ! empty( $args['status-on'] ) ? $args['status-on'] : esc_html__( 'On', 'wpforms-lite' );
$status_off = ! empty( $args['status-off'] ) ? $args['status-off'] : esc_html__( 'Off', 'wpforms-lite' );
$status = sprintf(
'<label
for="%s"
class="wpforms-toggle-control-status"
data-on="%s"
data-off="%s">
%s
</label>',
esc_attr( $input_id ),
esc_attr( $status_on ),
esc_attr( $status_off ),
esc_html( $value ? $status_on : $status_off )
);
}
$label_html = empty( $args['label-hide'] ) && ! empty( $label ) ?
sprintf(
'<label for="%s" class="wpforms-toggle-control-label">%s</label>',
esc_attr( $input_id ),
$label
) : '';
$label_html .= isset( $args['tooltip'] ) ?
sprintf(
'<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>',
esc_attr( $args['tooltip'] )
) : '';
$label_left = ! empty( $args['label-left'] ) ? $label_html . $status : '';
$label_right = empty( $args['label-left'] ) ? $status . $label_html : '';
$title = isset( $args['title'] ) ? ' title="' . esc_attr( $args['title'] ) . '"' : '';
$control_class = ! empty( $args['control-class'] ) ? $args['control-class'] : '';
$input_class = ! empty( $args['input-class'] ) ? $args['input-class'] : '';
return sprintf(
'<span class="wpforms-toggle-control %8$s" %9$s>
%1$s
<input type="checkbox" id="%2$s" name="%3$s" class="%7$s" value="1" %4$s %5$s %10$s>
<label class="wpforms-toggle-control-icon" for="%2$s"></label>
%6$s
</span>',
$label_left,
esc_attr( $input_id ),
esc_attr( $field_name ),
$checked,
$data_attr,
$label_right,
wpforms_sanitize_classes( $input_class ),
wpforms_sanitize_classes( $control_class ),
$title,
! empty( $args['disabled'] ) ? 'disabled' : ''
);
}
/**
* Get a settings block state, whether it's opened or closed.
*
* @since 1.4.8
*
* @param int $form_id Form ID.
* @param int $block_id Block ID.
* @param string $block_type Block type.
*
* @return string
*/
function wpforms_builder_settings_block_get_state( $form_id, $block_id, $block_type ): string {
$form_id = absint( $form_id );
$block_id = absint( $block_id );
$block_type = sanitize_key( $block_type );
$state = 'opened';
$all_states = get_user_meta( get_current_user_id(), 'wpforms_builder_settings_collapsable_block_states', true );
if ( empty( $all_states ) ) {
return $state;
}
if (
is_array( $all_states ) &&
! empty( $all_states[ $form_id ][ $block_type ][ $block_id ] ) &&
$all_states[ $form_id ][ $block_type ][ $block_id ] === 'closed'
) {
$state = 'closed';
}
// Backward compatibility for notifications.
if ( $block_type === 'notification' && $state !== 'closed' ) {
$notification_states = get_user_meta( get_current_user_id(), 'wpforms_builder_notification_states', true );
}
if (
! empty( $notification_states[ $form_id ][ $block_id ] ) &&
$notification_states[ $form_id ][ $block_id ] === 'closed'
) {
$state = 'closed';
}
if ( $block_type === 'notification' ) {
// Backward compatibility for notifications.
/**
* Filters notification get state.
*
* @since 1.4.8
*
* @param string $state Notification get state.
* @param int $form_id Form ID.
* @param int $block_id Block ID.
*
* @return string
*/
return (string) apply_filters( 'wpforms_builder_notification_get_state', $state, $form_id, $block_id ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
}
/**
* Filters settings block state.
*
* @since 1.4.8
*
* @param string $state Settings block state.
* @param int $form_id Form ID.
* @param int $block_id Block ID.
* @param string $block_type Block type.
*
* @return string
*/
return apply_filters( 'wpforms_builder_settings_block_get_state', $state, $form_id, $block_id, $block_type );
}
/**
* Get the list of allowed tags, used in a pair with the wp_kses () function.
* This allows removing of all potentially harmful HTML tags and attributes.
*
* @since 1.5.9
*
* @return array Allowed Tags.
*/
function wpforms_builder_preview_get_allowed_tags(): array {
static $allowed_tags;
if ( ! empty( $allowed_tags ) ) {
return $allowed_tags;
}
$atts = [ 'align', 'class', 'type', 'id', 'for', 'style', 'src', 'rel', 'href', 'target', 'value', 'width', 'height' ];
$tags = [ 'label', 'iframe', 'style', 'button', 'strong', 'small', 'table', 'span', 'abbr', 'code', 'pre', 'div', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'li', 'em', 'hr', 'br', 'th', 'tr', 'td', 'p', 'a', 'b', 'i' ];
$allowed_atts = array_fill_keys( $atts, [] );
$allowed_tags = array_fill_keys( $tags, $allowed_atts );
return $allowed_tags;
}
/**
* Output builder panel fields group wrapper.
*
* @since 1.6.6
*
* @param string $inner Inner HTML to wrap.
* @param array $args Array of arguments.
* @param bool $do_echo Flag to display.
*
* @return string|null
* @noinspection HtmlUnknownAttribute
*/
function wpforms_panel_fields_group( $inner, $args = [], $do_echo = true ): ?string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$group = ! empty( $args['group'] ) ? $args['group'] : '';
$unfoldable = ! empty( $args['unfoldable'] );
$default = ( ! empty( $args['default'] ) && $args['default'] === 'opened' ) ? ' opened' : '';
$opened = ! empty( $_COOKIE[ 'wpforms_fields_group_' . $group ] ) && $_COOKIE[ 'wpforms_fields_group_' . $group ] === 'true' ? ' opened' : $default;
$class = ! empty( $args['class'] ) ? wpforms_sanitize_classes( $args['class'] ) : '';
$output = sprintf(
'<div class="wpforms-panel-fields-group %1$s%2$s" %3$s>',
$class,
$unfoldable ? ' unfoldable' . $opened : '',
$unfoldable ? ' data-group="' . $group . '"' : ''
);
if ( ! empty( $args['borders'] ) && in_array( 'top', $args['borders'], true ) ) {
$output .= '<div class="wpforms-panel-fields-group-border-top"></div>';
}
if ( ! empty( $args['title'] ) ) {
$chevron = $unfoldable ? '<i class="fa fa-chevron-circle-right"></i>' : '';
$output .= '<div class="wpforms-panel-fields-group-title">' . esc_html( $args['title'] ) . $chevron . '</div>';
}
if ( ! empty( $args['description'] ) ) {
$output .= '<div class="wpforms-panel-fields-group-description">' . wp_kses_post( $args['description'] ) . '</div>';
}
$output .= sprintf(
'<div class="wpforms-panel-fields-group-inner" %s>%s</div>',
empty( $opened ) && $unfoldable ? ' style="display: none;"' : '',
$inner
);
if ( ! empty( $args['borders'] ) && in_array( 'bottom', $args['borders'], true ) ) {
$output .= '<div class="wpforms-panel-fields-group-border-bottom"></div>';
}
$output .= '</div>';
if ( ! $do_echo ) {
return $output;
}
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return null;
}
/**
* Get the pages for the "Show Page" dropdown selection in Confirmations Settings in Builder.
*
* @since 1.7.9
*
* @param array $form_data Form data.
* @param int $confirmation_id Confirmation ID.
*
* @return array
*/
function wpforms_builder_form_settings_confirmation_get_pages( $form_data, $confirmation_id ): array {
$pre_selected_page_id = empty( $form_data['settings']['confirmations'][ $confirmation_id ]['page'] )
? 0
: absint( $form_data['settings']['confirmations'][ $confirmation_id ]['page'] );
$pages = [ 'previous_page' => esc_html__( 'Back to Previous Page (Referrer) ', 'wpforms-lite' ) ];
$pages += wp_list_pluck( wpforms_search_posts(), 'post_title', 'ID' );
if ( empty( $pre_selected_page_id ) || isset( $pages[ $pre_selected_page_id ] ) ) {
return $pages;
}
// If the pre-selected page isn't in `$pages`, we manually fetch it include it in `$pages`.
$pre_selected_page = get_post( $pre_selected_page_id );
if ( empty( $pre_selected_page ) ) {
return $pages;
}
$pages[ $pre_selected_page->ID ] = wpforms_get_post_title( $pre_selected_page );
return $pages;
}
/**
* Generates an image upload control for WPForms builder panels.
*
* @since 1.8.0
*
* @param string $option Field type.
* @param array $args Arguments for the control:
* - default_id - Default image ID if no value is set.
* - default_size - Default image size ('full', 'large', 'medium', 'small').
* - default_position - Default image position ('left', 'center', 'right').
* - default_url - Default image URL if no value is set.
* @param string $panel Panel name.
* @param string $parent_name Parent field name.
* @param string $field Field name.
* @param array $form Form data.
* @param string $field_name Field name attribute.
* @param string $input_id Input ID attribute.
*
* @return string HTML markup for the image upload control.
*/
function wpforms_panel_field_image_upload_control( // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
string $option,
array $args,
string $panel,
string $parent_name,
string $field,
array $form,
string $field_name,
string $input_id
): string {
// Handle subsection, which is the primary use case.
$subsection = ! empty( $args['subsection'] ) ? $args['subsection'] : '';
// Set default values from args if they exist.
$image_id = ! empty( $args['default_id'] ) ? $args['default_id'] : 0;
$image_size = ! empty( $args['default_size'] ) ? $args['default_size'] : 'medium';
$image_position = ! empty( $args['default_position'] ) ? $args['default_position'] : 'left';
$image_url = ! empty( $args['default_url'] ) ? $args['default_url'] : '';
$hidden_fields = ! empty( $args['hidden_fields'] ) ? $args['hidden_fields'] : [];
$key_id = $field . '_id';
$key_size = $field . '_size';
$key_position = $field . '_position';
$key_url = $field . '_url';
// Get stored values if they exist.
if (
isset( $form[ $parent_name ][ $panel ][ $subsection ][ $key_url ] )
) {
$image_id = absint( $form[ $parent_name ][ $panel ][ $subsection ][ $key_id ] ?? $image_id );
$image_size = $form[ $parent_name ][ $panel ][ $subsection ][ $key_size ] ?? $image_size;
$image_position = $form[ $parent_name ][ $panel ][ $subsection ][ $key_position ] ?? $image_position;
$image_url = $form[ $parent_name ][ $panel ][ $subsection ][ $key_url ];
}
// Check if we have an image.
$has_image = ! empty( $image_id ) || ! empty( $image_url );
if ( ! empty( $image_id ) && empty( $image_url ) ) {
$image_attributes = wp_get_attachment_image_src( $image_id, 'full' );
if ( $image_attributes ) {
$image_url = $image_attributes[0];
} else {
// The image doesn't exist or is invalid.
$has_image = false;
$image_id = 0;
}
}
// Determine button visibility classes.
$upload_button_class = $has_image ? 'wpforms-image-upload-button wpforms-hidden' : 'wpforms-image-upload-button';
$remove_button_class = $has_image ? 'wpforms-image-remove-button' : 'wpforms-image-remove-button wpforms-hidden';
// Set preview image source.
$preview_src = $has_image && $image_url ? $image_url : '';
// Define standard sizes.
$sizes = [
'full' => esc_html__( 'Full', 'wpforms-lite' ),
'large' => esc_html__( 'Large', 'wpforms-lite' ),
'medium' => esc_html__( 'Medium', 'wpforms-lite' ),
'small' => esc_html__( 'Small', 'wpforms-lite' ),
];
// Define standard positions.
$positions = [
'left' => esc_html__( 'Left', 'wpforms-lite' ),
'center' => esc_html__( 'Center', 'wpforms-lite' ),
'right' => esc_html__( 'Right', 'wpforms-lite' ),
];
// Prepare the field name prefix. Remove the square bracket at the end if present.
$field_name_prefix = preg_replace( '/]$/', '', $field_name );
// Start output buffering to capture HTML.
ob_start();
?>
<div
class="wpforms-setting-field wpforms-setting-field-image-upload <?php echo sanitize_html_class( $option ); ?>"
id="wpforms-setting-field-<?php echo esc_attr( $input_id ); ?>">
<div class="wpforms-setting-content">
<div class="wpforms-image-upload-control" id="<?php echo esc_attr( $input_id ); ?>-control">
<div class="wpforms-image-preview" aria-live="polite">
<img
src="<?php echo esc_url( $preview_src ); ?>"
alt="<?php echo $has_image ? esc_attr__( 'Preview of selected image', 'wpforms-lite' ) : esc_attr__( 'No image selected', 'wpforms-lite' ); ?>">
</div>
<div class="wpforms-image-controls">
<?php if ( ! in_array( 'size', $hidden_fields, true ) ) : ?>
<div class="wpforms-image-control-group">
<label for="<?php echo esc_attr( $input_id ); ?>_size"><?php echo esc_html__( 'Size', 'wpforms-lite' ); ?></label>
<select id="<?php echo esc_attr( $input_id ); ?>_size" name="<?php echo esc_attr( $field_name_prefix . '_size]' ); ?>">
<?php foreach ( $sizes as $value => $label ) : ?>
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $image_size, $value ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<?php if ( ! in_array( 'position', $hidden_fields, true ) ) : ?>
<div class="wpforms-image-control-group">
<label for="<?php echo esc_attr( $input_id ); ?>_position"><?php echo esc_html__( 'Position', 'wpforms-lite' ); ?></label>
<select id="<?php echo esc_attr( $input_id ); ?>_position" name="<?php echo esc_attr( $field_name_prefix . '_position]' ); ?>">
<?php foreach ( $positions as $value => $label ) : ?>
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $image_position, $value ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="wpforms-image-buttons">
<button type="button"
class="<?php echo esc_attr( $upload_button_class ); ?> wpforms-btn wpforms-btn-sm wpforms-btn-light-grey"
aria-label="<?php esc_attr_e( 'Upload an image', 'wpforms-lite' ); ?>">
<?php echo esc_html__( 'Upload Image', 'wpforms-lite' ); ?>
</button>
<button type="button"
class="<?php echo esc_attr( $remove_button_class ); ?> wpforms-btn wpforms-btn-sm wpforms-btn-light-grey"
aria-label="<?php esc_attr_e( 'Remove the selected image', 'wpforms-lite' ); ?>">
<?php echo esc_html__( 'Remove Image', 'wpforms-lite' ); ?>
</button>
</div>
</div>
<input type="hidden"
class="wpforms-image-upload-id"
id="<?php echo esc_attr( $input_id ); ?>_id"
name="<?php echo esc_attr( $field_name_prefix . '_id]' ); ?>"
value="<?php echo esc_attr( $image_id ); ?>">
<input type="hidden"
class="wpforms-image-upload-url"
id="<?php echo esc_attr( $input_id ); ?>_url"
name="<?php echo esc_attr( $field_name_prefix . '_url]' ); ?>"
value="<?php echo esc_attr( $image_url ); ?>">
</div>
</div>
</div>
<?php
// Return the captured HTML.
return ob_get_clean();
}

View File

@@ -0,0 +1,462 @@
<?php
/**
* Base panel class.
*
* @since 1.0.0
*/
abstract class WPForms_Builder_Panel {
/**
* Full name of the panel.
*
* @since 1.0.0
*
* @var string
*/
public $name;
/**
* Slug.
*
* @since 1.0.0
*
* @var string
*/
public $slug;
/**
* Font Awesome Icon used for the editor button, eg "fa-list".
*
* @since 1.0.0
*
* @var mixed
*/
public $icon = false;
/**
* Priority order the field button should show inside the "Add Fields" tab.
*
* @since 1.0.0
*
* @var int
*/
public $order = 50;
/**
* If panel contains a sidebar element or is full width.
*
* @since 1.0.0
*
* @var bool
*/
public $sidebar = false;
/**
* Determine whether the panel content will be loaded on demand.
*
* @since 1.8.6
*
* @var bool
*/
public $on_demand = false;
/**
* Contain form object if we have one.
*
* @since 1.0.0
*
* @var object
*/
public $form;
/**
* Contain array of the form data (post_content).
*
* @since 1.0.0
*
* @var array
*/
public $form_data;
/**
* Class instance.
*
* @since 1.7.7
*
* @var static
*/
private static $instance;
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
// Load form if found.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$form_id = isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;
$this->form = wpforms()->obj( 'form' )->get( $form_id );
$this->form_data = $this->form ? wpforms_decode( $this->form->post_content ) : false;
// Get current revision, if available.
$revision = wpforms()->obj( 'revisions' )->get_revision();
// If we're viewing a valid revision, replace the form data so the Form Builder shows correct state.
if ( $revision && isset( $revision->post_content ) ) {
$this->form_data = wpforms_decode( $revision->post_content );
}
// Bootstrap.
$this->init();
// Save instance.
self::$instance = $this;
// Primary panel button.
add_action( 'wpforms_builder_panel_buttons', [ $this, 'button' ], $this->order, 2 );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$is_active = $this->slug === sanitize_key( $_GET['view'] ?? 'setup' );
if ( $this->on_demand && ! $is_active ) {
// Load panel loader enqueues.
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues_loader' ] );
}
// Load payments panel enqueues.
add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues_payments' ] );
// Load panel specific enqueues.
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ], 15 );
if ( $is_active || ! $this->on_demand ) {
// Output.
add_action( 'wpforms_builder_panels', [ $this, 'panel_output' ], $this->order, 2 );
}
}
/**
* Get class instance.
*
* @since 1.7.7
*
* @return static
*/
public static function instance() {
if ( self::$instance === null || ! self::$instance instanceof static ) {
self::$instance = new static();
}
return self::$instance;
}
/**
* All systems go. Used by children.
*
* @since 1.0.0
*/
public function init() {
}
/**
* Enqueue assets for the builder. Used by children.
*
* @since 1.0.0
*/
public function enqueues() {
}
/**
* Enqueue panel loader assets.
*
* @since 1.8.6
*/
public function enqueues_loader() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-panel-loader',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/panel-loader{$min}.js",
[ 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
}
/**
* Enqueue assets for the payments panel.
*
* @since 1.9.5
*/
public function enqueues_payments() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-payments-utils',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/payments-utils{$min}.js",
[ 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
$strings = [
'payments_plan_placeholder' => esc_html__( 'Plan Name', 'wpforms-lite' ),
'payments_disabled_recurring' => esc_html__( 'You can only use one payment type at a time. If you\'d like to enable Recurring Payments, please disable One-Time Payments.', 'wpforms-lite' ),
'payments_disabled_one_time' => esc_html__( 'You can only use one payment type at a time. If you\'d like to enable One-Time Payments, please disable Recurring Payments.', 'wpforms-lite' ),
];
wp_localize_script(
'wpforms-builder-payments-utils',
'wpforms_builder_payments_utils',
$strings
);
}
/**
* Primary panel button in the left panel navigation.
*
* @since 1.0.0
*
* @param mixed $form
* @param string $view
*/
public function button( $form, $view ) {
$active = $view === $this->slug ? 'active' : '';
?>
<button class="wpforms-panel-<?php echo esc_attr( $this->slug ); ?>-button <?php echo esc_attr( $active ); ?>" data-panel="<?php echo esc_attr( $this->slug ); ?>">
<i class="fa <?php echo esc_attr( $this->icon ); ?>"></i>
<span><?php echo esc_html( $this->name ); ?></span>
</button>
<?php
}
/**
* Output the contents of the panel.
*
* @since 1.0.0
*
* @param object $form Current form object.
* @param string $view Active Form Builder view (panel).
*/
public function panel_output( $form, $view ) {
$wrap = $this->sidebar ? 'wpforms-panel-sidebar-content' : 'wpforms-panel-full-content';
$classes = [ 'wpforms-panel' ];
// Determine whether the form data is corrupted and a dedicated alert message needs to be shown,
// keep the revisions panel to be able to restore the form.
$is_form_corrupted = is_array( $this->form_data ) && empty( $this->form_data ) && $this->slug !== 'revisions';
if ( in_array( $this->slug, [ 'fields', 'revisions' ], true ) ) {
$classes[] = 'wpforms-panel-fields';
}
if ( $view === $this->slug ) {
$classes[] = 'active';
}
if ( $is_form_corrupted ) {
$classes[] = 'wpforms-panel-corrupted-data';
}
printf( '<div class="%s" id="wpforms-panel-%s">', wpforms_sanitize_classes( $classes, true ), esc_attr( $this->slug ) );
printf( '<div class="%s">', esc_attr( $wrap ) );
if ( $this->sidebar === true && ! $is_form_corrupted ) {
if ( $this->slug === 'fields' ) {
echo '<div class="wpforms-panel-sidebar-toggle"><div class="wpforms-panel-sidebar-toggle-vertical-line"></div><div class="wpforms-panel-sidebar-toggle-icon"><i class="fa fa-angle-left"></i></div></div>';
}
echo '<div class="wpforms-panel-sidebar">';
do_action( 'wpforms_builder_before_panel_sidebar', $this->form, $this->slug );
$this->panel_sidebar();
do_action( 'wpforms_builder_after_panel_sidebar', $this->form, $this->slug );
echo '</div>';
/**
* Allow adding custom content after the panel sidebar in the Form Builder.
*
* @since 1.9.7
*
* @param object $form Current form object.
* @param string $slug Current panel slug.
*/
do_action( 'wpforms_builder_panel_sidebar_after', $this->form, $this->slug );
}
echo '<div class="wpforms-panel-content-wrap">';
echo '<div class="wpforms-panel-content">';
if ( $is_form_corrupted ) {
$this->form_corrupted_message();
} else {
/**
* Allow adding custom content before the panel content in the Form Builder.
*
* @since 1.0.0
*
* @param object $form Current form object.
* @param string $slug Current panel slug.
*/
do_action( 'wpforms_builder_before_panel_content', $this->form, $this->slug ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->panel_content();
/**
* Allow adding custom content after the panel content in the Form Builder.
*
* @since 1.0.0
*
* @param object $form Current form object.
* @param string $slug Current panel slug.
*/
do_action( 'wpforms_builder_after_panel_content', $this->form, $this->slug ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
echo '</div>';
echo '</div>';
echo '</div>';
echo '</div>';
}
/**
* Output the panel's sidebar if we have one.
*
* @since 1.0.0
*/
public function panel_sidebar() {
}
/**
* Output panel sidebar sections.
*
* @since 1.0.0
*
* @param string $name Sidebar section name.
* @param string $slug Sidebar section slug.
* @param string $icon Sidebar section icon.
*/
public function panel_sidebar_section( $name, $slug, $icon = '' ) {
$default_classes = [
'wpforms-panel-sidebar-section',
'wpforms-panel-sidebar-section-' . $slug,
];
if ( $slug === 'default' ) {
$default_classes[] = 'default';
}
if ( ! empty( $icon ) ) {
$default_classes[] = 'icon';
}
/**
* Allow adding custom CSS classes to a sidebar section in the Form Builder.
*
* @since 1.7.7.2
*
* @param array $classes Sidebar section classes.
* @param string $name Sidebar section name.
* @param string $slug Sidebar section slug.
* @param string $icon Sidebar section icon.
*/
$classes = (array) apply_filters( 'wpforms_builder_panel_sidebar_section_classes', [], $name, $slug, $icon );
$classes = array_merge( $default_classes, $classes );
echo '<a href="#" class="' . wpforms_sanitize_classes( $classes, true ) . '" data-section="' . esc_attr( $slug ) . '">';
if ( ! empty( $icon ) ) {
echo '<img src="' . esc_url( $icon ) . '">';
}
echo esc_html( $name );
echo '<i class="fa fa-angle-right wpforms-toggle-arrow"></i>';
echo '</a>';
}
/**
* Output the panel's primary content.
*
* @since 1.0.0
*/
public function panel_content() {
}
/**
* Error message for a corrupted form.
*
* @since 1.9.7
*/
private function form_corrupted_message(): void {
?>
<div class="wpforms-builder-preview-corrupted-data-content">
<div class="wpforms-builder-corrupted-data-title">
<h2>
<?php esc_html_e( 'Corrupted Form Data', 'wpforms-lite' ); ?>
</h2>
</div>
<div>
<p>
<?php
printf(
wp_kses(
__( 'A critical error has occurred, preventing your form from loading. This issue may arise from incorrect code in a third-party theme or plugin, or from invalid characters in your form. You can attempt to restore a previous version of your form using <a href="#" class="wpforms-panel-content-revisions-link">Form Revisions</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
],
]
)
);
?>
</p>
<br>
<p>
<?php
printf(
wp_kses( /* translators: %s - WPForms contact support link. */
__( 'If the issue persists, <a href="%s" target="_blank" rel="noopener noreferrer">please contact support</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/account/support/', 'Corrupted Form Data' ) )
);
?>
</p>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,821 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
use WPForms\Forms\Fields\Traits\MultiFieldMenu as MultiFieldMenuTrait;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Fields management panel.
*
* @since 1.0.0
*/
class WPForms_Builder_Panel_Fields extends WPForms_Builder_Panel {
use MultiFieldMenuTrait;
/**
* All systems go.
*
* @since 1.0.0
*
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Fields', 'wpforms-lite' );
$this->slug = 'fields';
$this->icon = 'fa-list-alt';
$this->order = 10;
$this->sidebar = true;
$this->hooks();
}
/**
* Add hooks.
*
* @since 1.9.8
*
* @return void
*/
private function hooks(): void {
if ( ! $this->form ) {
return;
}
add_action( 'wpforms_builder_fields', [ $this, 'search' ], 5 );
add_action( 'wpforms_builder_fields', [ $this, 'fields' ] );
add_action( 'wpforms_builder_fields_options', [ $this, 'fields_options' ] );
add_action( 'wpforms_builder_preview', [ $this, 'preview' ] );
add_filter( 'wpforms_builder_strings', [ $this, 'multi_select_strings' ] );
// Template for form builder previews.
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'field_preview_templates' ] );
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'choices_limit_message_template' ] );
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'choices_empty_message_template' ] );
}
/**
* Enqueue assets for the Fields panel.
*
* @since 1.0.0
* @since 1.6.8 All the builder stylesheets enqueues moved to the `\WPForms_Builder::enqueues()`.
*
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-search-fields',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/search-fields{$min}.js",
[ 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
}
/**
* Add Multi-Select Builder strings.
*
* @since 1.9.9
*
* @param array $strings Form Builder strings.
*
* @return array Form Builder strings.
*/
public function multi_select_strings( array $strings ): array {
$strings['multi_select'] = [
'repeater_tooltip' => esc_html__( "The Repeater field can't be selected with other fields.", 'wpforms-lite' ),
'layout_tooltip' => esc_html__( "The Layout field can't be selected with other fields.", 'wpforms-lite' ),
'general_tooltip' => esc_html__( 'This field cant be selected alongside Layout or Repeater fields.', 'wpforms-lite' ),
/* translators: %s - Cmd or Ctrl key. */
'copy_toast_single' => esc_html__( 'Field copied. Use %1$s + V to paste.', 'wpforms-lite' ),
/* translators: %d - number of fields, %s - Cmd or Ctrl key. */
'copy_toast_multiple' => esc_html__( '%1$d fields copied. Use %2$s + V to paste.', 'wpforms-lite' ),
];
return $strings;
}
/**
* Output the Field panel sidebar.
*
* @since 1.0.0
*
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function panel_sidebar() {
// Sidebar contents are not valid unless we have a form.
if ( ! $this->form ) {
return;
}
?>
<ul class="wpforms-tabs wpforms-clear">
<li class="wpforms-tab" id="add-fields">
<a href="#" class="active">
<i class="fa fa-list-alt"></i><?php esc_html_e( 'Add Fields', 'wpforms-lite' ); ?>
</a>
</li>
<li class="wpforms-tab" id="field-options">
<a href="#">
<i class="fa fa-sliders"></i><?php esc_html_e( 'Field Options', 'wpforms-lite' ); ?>
</a>
</li>
</ul>
<div id="wpforms-add-fields-tab" class="wpforms-add-fields wpforms-tab-content">
<?php
/**
* Fires to add fields.
*
* @since 1.0.0
*
* @param array $form Form.
*/
do_action( 'wpforms_builder_fields', $this->form ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
?>
</div>
<div id="wpforms-field-options" class="wpforms-field-options wpforms-tab-content">
<?php
/**
* Fires to add field options.
*
* @since 1.0.0
*
* @param array $form Form.
*/
do_action( 'wpforms_builder_fields_options', $this->form ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
?>
</div>
<?php
}
/**
* Output the Field panel primary content.
*
* @since 1.0.0
*
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function panel_content() {
// Check if there is a form created.
if ( ! $this->form ) {
echo '<div class="wpforms-alert wpforms-alert-info">';
echo wp_kses(
__( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">set up your form</a> before you can manage the fields.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
'data-panel' => [],
],
]
);
echo '</div>';
return;
}
?>
<div class="wpforms-preview-wrap">
<div class="wpforms-preview">
<div class="wpforms-title-desc">
<div class="wpforms-title-desc-inner">
<h2 class="wpforms-form-name">
<?php echo esc_html( $this->form_data['settings']['form_title'] ?? $this->form->post_title ); ?>
</h2>
<span class="wpforms-form-desc">
<?php
echo wp_kses(
$this->form_data['settings']['form_desc'] ?? $this->form->post_excerpt,
wpforms_builder_preview_get_allowed_tags()
);
?>
</span>
</div>
</div>
<?php
/**
* Fires after the fields panel (form preview) title.
*
* @since 1.9.4
*
* @param array $form_data Form data.
*/
do_action( 'wpforms_builder_panel_fields_panel_content_title_after', $this->form_data );
?>
<div class="wpforms-no-fields-holder wpforms-hidden">
<?php $this->no_fields_options(); ?>
<?php $this->no_fields_preview(); ?>
</div>
<div class="wpforms-field-wrap">
<?php
/**
* Fires to output the form preview.
*
* @since 1.0.0
*
* @param array $form Form.
*/
do_action( 'wpforms_builder_preview', $this->form ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
?>
</div>
<?php
$captcha_settings = wpforms_get_captcha_settings();
$extra_class = 'is-' . $captcha_settings['provider'];
?>
<div class="wpforms-field-recaptcha <?php echo sanitize_html_class( $extra_class ); ?>">
<div class="wpforms-field-recaptcha-wrap">
<div class="wpforms-field-recaptcha-wrap-l">
<svg class="wpforms-field-hcaptcha-icon" fill="none" viewBox="0 0 83 90"><path opacity=".5" d="M60.012 69.998H50.01V80h10.002V69.998z" fill="#0074BF"/><path opacity=".7" d="M50.01 69.998H40.008V80H50.01V69.998zM40.008 69.998H30.006V80h10.002V69.998z" fill="#0074BF"/><path opacity=".5" d="M30.006 69.998H20.004V80h10.002V69.998z" fill="#0074BF"/><path opacity=".7" d="M70.014 60.013H60.014v10.002h10.002V60.012z" fill="#0082BF"/><path opacity=".8" d="M60.012 60.013H50.01v10.002h10.002V60.012z" fill="#0082BF"/><path d="M50.01 60.013H40.008v10.002H50.01V60.012zM40.008 60.013H30.006v10.002h10.002V60.012z" fill="#0082BF"/><path opacity=".8" d="M30.006 60.013H20.004v10.002h10.002V60.012z" fill="#0082BF"/><path opacity=".7" d="M20.004 60.013H10.002v10.002h10.002V60.012z" fill="#0082BF"/><path opacity=".5" d="M80 50.01H69.998v10.002H80V50.01z" fill="#008FBF"/><path opacity=".8" d="M70.014 50.01H60.014v10.002h10.002V50.01z" fill="#008FBF"/><path d="M60.012 50.01H50.01v10.002h10.002V50.01zM50.01 50.01H40.008v10.002H50.01V50.01zM40.008 50.01H30.006v10.002h10.002V50.01zM30.006 50.01H20.004v10.002h10.002V50.01z" fill="#008FBF"/><path opacity=".8" d="M20.004 50.01H10.002v10.002h10.002V50.01z" fill="#008FBF"/><path opacity=".5" d="M10.002 50.01H0v10.002h10.002V50.01z" fill="#008FBF"/><path opacity=".7" d="M80 40.008H69.998V50.01H80V40.008z" fill="#009DBF"/><path d="M70.014 40.008H60.014V50.01h10.002V40.008zM60.012 40.008H50.01V50.01h10.002V40.008zM50.01 40.008H40.008V50.01H50.01V40.008zM40.008 40.008H30.006V50.01h10.002V40.008zM30.006 40.008H20.004V50.01h10.002V40.008zM20.004 40.008H10.002V50.01h10.002V40.008z" fill="#009DBF"/><path opacity=".7" d="M10.002 40.008H0V50.01h10.002V40.008z" fill="#009DBF"/><path opacity=".7" d="M80 30.006H69.998v10.002H80V30.006z" fill="#00ABBF"/><path d="M70.014 30.006H60.014v10.002h10.002V30.006zM60.012 30.006H50.01v10.002h10.002V30.006zM50.01 30.006H40.008v10.002H50.01V30.006zM40.008 30.006H30.006v10.002h10.002V30.006zM30.006 30.006H20.004v10.002h10.002V30.006zM20.004 30.006H10.002v10.002h10.002V30.006z" fill="#00ABBF"/><path opacity=".7" d="M10.002 30.006H0v10.002h10.002V30.006z" fill="#00ABBF"/><path opacity=".5" d="M80 20.004H69.998v10.002H80V20.004z" fill="#00B9BF"/><path opacity=".8" d="M70.014 20.004H60.014v10.002h10.002V20.004z" fill="#00B9BF"/><path d="M60.012 20.004H50.01v10.002h10.002V20.004zM50.01 20.004H40.008v10.002H50.01V20.004zM40.008 20.004H30.006v10.002h10.002V20.004zM30.006 20.004H20.004v10.002h10.002V20.004z" fill="#00B9BF"/><path opacity=".8" d="M20.004 20.004H10.002v10.002h10.002V20.004z" fill="#00B9BF"/><path opacity=".5" d="M10.002 20.004H0v10.002h10.002V20.004z" fill="#00B9BF"/><path opacity=".7" d="M70.014 10.002H60.014v10.002h10.002V10.002z" fill="#00C6BF"/><path opacity=".8" d="M60.012 10.002H50.01v10.002h10.002V10.002z" fill="#00C6BF"/><path d="M50.01 10.002H40.008v10.002H50.01V10.002zM40.008 10.002H30.006v10.002h10.002V10.002z" fill="#00C6BF"/><path opacity=".8" d="M30.006 10.002H20.004v10.002h10.002V10.002z" fill="#00C6BF"/><path opacity=".7" d="M20.004 10.002H10.002v10.002h10.002V10.002z" fill="#00C6BF"/><path opacity=".5" d="M60.012 0H50.01v10.002h10.002V0z" fill="#00D4BF"/><path opacity=".7" d="M50.01 0H40.008v10.002H50.01V0zM40.008 0H30.006v10.002h10.002V0z" fill="#00D4BF"/><path opacity=".5" d="M30.006 0H20.004v10.002h10.002V0z" fill="#00D4BF"/><path d="M26.34 36.84l2.787-6.237c1.012-1.592.88-3.55-.232-4.66a3.6 3.6 0 00-.481-.399 3.053 3.053 0 00-2.571-.298 4.246 4.246 0 00-2.322 1.791s-3.816 8.907-5.242 12.905c-1.426 3.998-.863 11.346 4.611 16.836 5.806 5.806 14.215 7.132 19.573 3.102.232-.116.431-.25.63-.415l16.521-13.8c.797-.664 1.99-2.024.93-3.583-1.046-1.526-3.003-.481-3.816.033l-9.504 6.917a.421.421 0 01-.597-.05s0-.017-.017-.017c-.249-.298-.282-1.078.1-1.393l14.58-12.374c1.26-1.128 1.426-2.787.414-3.915-.995-1.11-2.57-1.078-3.848.067l-13.12 10.267a.578.578 0 01-.813-.083c0-.016-.017-.016-.017-.033-.265-.298-.365-.78-.066-1.078l14.862-14.414c1.178-1.095 1.244-2.936.15-4.097a2.824 2.824 0 00-2.024-.863 2.905 2.905 0 00-2.09.83L39.544 36.144c-.365.364-1.078 0-1.161-.432a.474.474 0 01.132-.431l11.628-13.237a2.86 2.86 0 00.15-4.047 2.86 2.86 0 00-4.048-.15c-.05.05-.1.084-.133.133L28.447 37.47c-.63.63-1.56.664-2.007.299a.657.657 0 01-.1-.929z" fill="#fff"/></svg>
<svg class="wpforms-field-recaptcha-icon" viewBox="0 0 28 27.918"><path d="M28 13.943l-.016-.607V2l-3.133 3.134a13.983 13.983 0 00-21.964.394l5.134 5.183a6.766 6.766 0 012.083-2.329A6.171 6.171 0 0114.025 7.1a1.778 1.778 0 01.492.066 6.719 6.719 0 015.17 3.119l-3.625 3.641 11.941.016" fill="#1c3aa9"/><path d="M13.943 0l-.607.016H2.018l3.133 3.133a13.969 13.969 0 00.377 21.964l5.183-5.134A6.766 6.766 0 018.382 17.9 6.171 6.171 0 017.1 13.975a1.778 1.778 0 01.066-.492 6.719 6.719 0 013.117-5.167l3.641 3.641L13.943 0" fill="#4285f4"/><path d="M0 13.975l.016.607v11.334l3.133-3.133a13.983 13.983 0 0021.964-.394l-5.134-5.183a6.766 6.766 0 01-2.079 2.33 6.171 6.171 0 01-3.92 1.279 1.778 1.778 0 01-.492-.066 6.719 6.719 0 01-5.167-3.117l3.641-3.641c-4.626 0-9.825.016-11.958-.016" fill="#ababab"/></svg>
<svg class="wpforms-field-turnstile-icon" fill="none" viewBox="0 0 106 106"> <g clip-path="url(#a)"> <path fill="#F4801F" d="m72.375 76.265.541-1.877c.643-2.231.405-4.29-.678-5.808-1.011-1.397-2.66-2.216-4.68-2.312l-38.213-.486a.743.743 0 0 1-.683-1.012 1.012 1.012 0 0 1 .885-.678l38.583-.506c4.554-.207 9.532-3.92 11.267-8.454l2.196-5.748a1.354 1.354 0 0 0 .061-.779 25.13 25.13 0 0 0-48.312-2.6 11.307 11.307 0 0 0-17.708 11.849A16.054 16.054 0 0 0 .172 76.28a.744.744 0 0 0 .734.643H71.48a.927.927 0 0 0 .895-.658Z"/> <path fill="#F9AB41" d="M85.11 49.82c-.338 0-.692.01-1.063.03a.444.444 0 0 0-.162.035.59.59 0 0 0-.384.405l-1.518 5.191c-.648 2.231-.41 4.29.678 5.808a5.895 5.895 0 0 0 4.675 2.313l8.15.505a.728.728 0 0 1 .577.314.759.759 0 0 1 .086.693 1.012 1.012 0 0 1-.885.678l-8.465.506c-4.599.213-9.552 3.921-11.287 8.45l-.612 1.598a.455.455 0 0 0 .4.617h29.157a.782.782 0 0 0 .779-.592 20.92 20.92 0 0 0-10.822-24.36 20.916 20.916 0 0 0-9.294-2.191h-.01Z"/> </g> <defs> <clipPath id="a"> <path fill="#fff" d="M0 0h106v106H0z"/> </clipPath> </defs> </svg>
</div>
<div class="wpforms-field-recaptcha-wrap-r">
<p class="wpforms-field-hcaptcha-title">hCaptcha</p>
<p class="wpforms-field-recaptcha-title">reCAPTCHA</p>
<p class="wpforms-field-turnstile-title">Turnstile</p>
<p class="wpforms-field-recaptcha-desc">
<span class="wpforms-field-recaptcha-desc-txt"><?php esc_html_e( 'Enabled', 'wpforms-lite' ); ?></span><svg class="wpforms-field-recaptcha-desc-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 256c0-37.7-23.7-69.9-57.1-82.4 14.7-32.4 8.8-71.9-17.9-98.6-26.7-26.7-66.2-32.6-98.6-17.9C325.9 23.7 293.7 0 256 0s-69.9 23.7-82.4 57.1c-32.4-14.7-72-8.8-98.6 17.9-26.7 26.7-32.6 66.2-17.9 98.6C23.7 186.1 0 218.3 0 256s23.7 69.9 57.1 82.4c-14.7 32.4-8.8 72 17.9 98.6 26.6 26.6 66.1 32.7 98.6 17.9 12.5 33.3 44.7 57.1 82.4 57.1s69.9-23.7 82.4-57.1c32.6 14.8 72 8.7 98.6-17.9 26.7-26.7 32.6-66.2 17.9-98.6 33.4-12.5 57.1-44.7 57.1-82.4zm-144.8-44.25L236.16 341.74c-4.31 4.28-11.28 4.25-15.55-.06l-75.72-76.33c-4.28-4.31-4.25-11.28.06-15.56l26.03-25.82c4.31-4.28 11.28-4.25 15.56.06l42.15 42.49 97.2-96.42c4.31-4.28 11.28-4.25 15.55.06l25.82 26.03c4.28 4.32 4.26 11.29-.06 15.56z"></path></svg>
</p>
</div>
</div>
</div>
<?php
$submit = ! empty( $this->form_data['settings']['submit_text'] ) ? $this->form_data['settings']['submit_text'] : esc_html__( 'Submit', 'wpforms-lite' );
$submit_style = empty( $this->form_data['fields'] ) ? 'display: none;' : '';
printf( '<p class="wpforms-field-submit" style="%1$s"><input type="submit" value="%2$s" class="wpforms-field-submit-button"></p>', esc_attr( $submit_style ), esc_attr( $submit ) );
/** This action is documented in includes/class-frontend.php. */
do_action( 'wpforms_display_submit_after', $this->form_data, 'submit' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
?>
<?php wpforms_debug_data( $this->form_data ); ?>
</div>
</div>
<?php
}
/**
* Builder field buttons.
*
* @since 1.0.0
*/
public function fields(): void {
$fields = wpforms_get_builder_fields();
// Output the buttons.
foreach ( $fields as $id => $group ) {
usort( $group['fields'], [ $this, 'field_order' ] );
echo '<div class="wpforms-add-fields-group">';
echo '<a href="#" class="wpforms-add-fields-heading" data-group="' . esc_attr( $id ) . '">';
echo '<span>' . esc_html( $group['group_name'] ) . '</span>';
echo '<i class="fa fa-angle-down"></i>';
echo '</a>';
echo '<div class="wpforms-add-fields-buttons">';
foreach ( $group['fields'] as $field ) {
/**
* Attributes of the form field button on the Add Fields tab in the Form Builder.
*
* @since 1.5.1
*
* @param array $attributes Field attributes.
* @param array $field Field data.
* @param array $form_data Form data.
*/
$atts = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_builder_field_button_attributes',
[
'id' => 'wpforms-add-fields-' . $field['type'],
'class' => [ 'wpforms-add-fields-button' ],
'data' => [
'field-type' => $field['type'],
],
'atts' => [],
],
$field,
$this->form_data
);
if ( ! empty( $field['keywords'] ) ) {
$atts['data']['field-keywords'] = $field['keywords'];
}
if ( ! empty( $field['class'] ) ) {
$atts['class'][] = $field['class'];
}
echo '<button ' . wpforms_html_attributes( $atts['id'], $atts['class'], $atts['data'], $atts['atts'] ) . '>';
if ( $field['icon'] ) {
echo '<i class="fa ' . esc_attr( $field['icon'] ) . '"></i> ';
}
echo esc_html( $field['name'] );
echo '</button>';
}
echo '</div>';
echo '</div>';
}
}
/**
* Editor Field Options.
*
* @since 1.0.0
*/
public function fields_options(): void {
// Check to make sure the form actually has fields created already.
if ( empty( $this->form_data['fields'] ) ) {
$this->no_fields_options();
return;
}
$fields = $this->form_data['fields'];
foreach ( $fields as $field ) {
/**
* Filters the class attribute of the field option container in the Form Builder.
*
* @since 1.3.0
*
* @param string $class Field option class.
* @param array $field Field data.
*/
$class = apply_filters( 'wpforms_builder_field_option_class', '', $field ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
printf( '<div class="wpforms-field-option wpforms-field-option-%s %s" id="wpforms-field-option-%d" data-field-id="%d">', sanitize_html_class( $field['type'] ), wpforms_sanitize_classes( $class ), (int) $field['id'], (int) $field['id'] );
printf( '<input type="hidden" name="fields[%d][id]" value="%d" class="wpforms-field-option-hidden-id">', (int) $field['id'], (int) $field['id'] );
printf( '<input type="hidden" name="fields[%d][type]" value="%s" class="wpforms-field-option-hidden-type">', (int) $field['id'], esc_attr( $field['type'] ) );
/**
* Fires after the field option container in the Form Builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
do_action( "wpforms_builder_fields_options_{$field['type']}", $field ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
echo '</div>';
}
}
/**
* Editor preview (right pane).
*
* @since 1.0.0
*/
public function preview(): void {
// Check to make sure the form actually has fields created already.
if ( empty( $this->form_data['fields'] ) ) {
$this->no_fields_preview();
return;
}
/**
* Filters the fields which must be displayed on the base level on the preview panel in the Form Builder.
*
* @since 1.7.7
*
* @param array $fields Form fields data.
*/
$fields = (array) apply_filters( 'wpforms_builder_panel_fields_preview_fields', $this->form_data['fields'] );
foreach ( $fields as $field ) {
$this->preview_single_field(
$field,
[]
);
}
}
/**
* Preview a single field.
*
* @since 1.7.7
*
* @param array $field Field data.
* @param array $args Additional arguments.
*
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function preview_single_field( $field, $args ): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$class = ! empty( $field['size'] ) ? 'size-' . esc_attr( $field['size'] ) : '';
$class .= ! empty( $field['label_hide'] ) ? ' label_hide' : '';
$class .= ! empty( $field['read_only'] ) ? ' readonly' : '';
$class .= isset( $field['label'] ) && empty( $field['label'] ) && ! in_array( $field['type'], [ 'html', 'content' ], true ) ? ' label_empty' : '';
$class .= ! empty( $field['sublabel_hide'] ) ? ' sublabel_hide' : '';
$class .= ! empty( $field['required'] ) ? ' required' : '';
$class .= isset( $field['meta']['delete'] ) && $field['meta']['delete'] === false ? ' no-delete' : '';
$class .= isset( $field['meta']['duplicate'] ) && $field['meta']['duplicate'] === false ? ' no-duplicate' : '';
if ( ! empty( $field['input_columns'] ) ) {
$class .= $field['input_columns'] === '2' ? ' wpforms-list-2-columns' : '';
$class .= $field['input_columns'] === '3' ? ' wpforms-list-3-columns' : '';
$class .= $field['input_columns'] === 'inline' ? ' wpforms-list-inline' : '';
}
/**
* Filters class attribute of the field preview container in the Form Builder.
*
* @since 1.4.0
*
* @param string $css Field preview class.
* @param array $field Field data.
*/
$class = apply_filters( 'wpforms_field_preview_class', $class, $field ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
if ( ! has_action( "wpforms_display_field_{$field['type']}" ) ) {
$this->unavailable_fields_preview( $field );
return;
}
printf(
'<div class="wpforms-field wpforms-field-%1$s %2$s" id="wpforms-field-%3$s" data-field-id="%3$s" data-field-type="%1$s">',
esc_attr( $field['type'] ),
esc_attr( $class ),
wpforms_validate_field_id( $field['id'] )
);
/**
* Filters display the field duplicate button flag.
*
* @since 1.5.6.2
*
* @param bool $display_duplicate_button Display field duplicate button flag.
* @param array $field Field data.
* @param array $form_data Form data.
*/
if ( apply_filters( 'wpforms_field_preview_display_duplicate_button', true, $field, $this->form_data ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
printf(
'<a href="#" class="wpforms-field-duplicate" title="%s"><i class="fa fa-files-o" aria-hidden="true"></i></a>',
esc_attr__( 'Duplicate Field', 'wpforms-lite' )
);
}
printf(
'<a href="#" class="wpforms-field-delete" title="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>',
esc_attr__( 'Delete Field', 'wpforms-lite' )
);
// Multi-field actions menu.
echo $this->get_multi_field_menu_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( empty( $_COOKIE['wpforms_field_helper_hide'] ) ) {
printf(
'<div class="wpforms-field-helper">
<span class="wpforms-field-helper-edit">%s</span>
<span class="wpforms-field-helper-drag">%s</span>
<span class="wpforms-field-helper-hide" title="%s">
<i class="fa fa-times-circle" aria-hidden="true"></i>
</span>
</div>',
esc_html__( 'Click to Edit', 'wpforms-lite' ),
esc_html__( 'Drag to Reorder', 'wpforms-lite' ),
esc_attr__( 'Hide Helper', 'wpforms-lite' )
);
}
/**
* Fires after the field preview output in the Form Builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
do_action( "wpforms_builder_fields_previews_{$field['type']}", $field ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
echo '</div>';
}
/**
* Generate HTML for hidden inputs from given data.
*
* @since 1.6.7
*
* @param array $data Field array data.
* @param string $name Input name prefix.
*/
private function generate_hidden_inputs( array $data = [], string $name = '' ): void {
foreach ( $data as $key => $value ) {
if ( $key === 'id' ) {
continue;
}
$key = ! empty( $data['id'] ) ? sprintf( '[%s][%s]', $data['id'], $key ) : sprintf( '[%s]', $key );
if ( ! empty( $name ) ) {
$key = trim( $name ) . $key;
}
if ( is_array( $value ) ) {
$this->generate_hidden_inputs( $value, $key );
} else {
printf( "<input type='hidden' name='%s' value='%s' />", esc_attr( $key ), esc_attr( $value ) );
}
}
}
/**
* Unavailable builder field display.
*
* @since 1.6.7
*
* @param array $field Field array data.
*
* @noinspection HtmlUnknownTarget
*/
public function unavailable_fields_preview( array $field ): void {
// Using ucwords() for certain fields may generate incorrect words.
switch ( $field['type'] ) {
case 'url':
$field_type = 'URL';
break;
case 'html':
$field_type = 'HTML';
break;
case 'gdpr-checkbox':
$field_type = 'GDPR Checkbox';
break;
default:
$field_type = ucwords( preg_replace( '/[_-]/', ' ', $field['type'] ) );
}
$warning_message = sprintf( /* translators: %s - unavailable field name. */
esc_html__( 'Unfortunately, the %s field is not available and will be ignored on the front end.', 'wpforms-lite' ),
'<b>' . $field_type . '</b>'
);
$field_id = $field['id'] ?? 0;
printf(
'<div class="wpforms-alert wpforms-alert-warning wpforms-alert-dismissible wpforms-alert-field-not-available" data-field-id="%s" data-field-type="unavailable">',
wpforms_validate_field_id( $field['id'] )
);
printf(
'<div class="wpforms-alert-message">
<p>%1$s</p>
</div>
<div class="wpforms-alert-buttons">
<a href="%2$s" target="_blank" rel="noopener noreferrer" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey">%3$s</a>
<button type="button" class="wpforms-dismiss-button" title="%4$s" data-field-id="%5$s" />
</div>',
$warning_message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'https://wpforms.com/docs/how-to-import-and-export-wpforms/#field-missing',
esc_html__( 'Learn More', 'wpforms-lite' ),
esc_attr__( 'Dismiss this message. The field will be deleted as well.', 'wpforms-lite' ),
wpforms_validate_field_id( $field_id )
);
// Save unavailable fields data in hidden inputs.
$this->generate_hidden_inputs( $field, 'fields' );
echo '</div>';
}
/**
* No fields options markup.
*
* @since 1.6.0
*/
public function no_fields_options(): void {
printf(
'<p class="no-fields wpforms-alert wpforms-alert-warning">%s</p>',
esc_html__( 'You don\'t have any fields yet.', 'wpforms-lite' )
);
}
/**
* No fields preview placeholder markup.
*
* @since 1.6.0
*/
public function no_fields_preview(): void {
printf(
'<div class="no-fields-preview">
<h4>%1$s</h4>
<p>%2$s</p>
</div>',
esc_html__( 'You don\'t have any fields yet. Add some!', 'wpforms-lite' ),
esc_html__( 'Take your pick from our wide variety of fields and start building out your form!', 'wpforms-lite' )
);
}
/**
* Sort Add Field buttons by order provided.
*
* @since 1.0.0
*
* @param array $a First item.
* @param array $b Second item.
*
* @return int
*/
public function field_order( array $a, array $b ): int {
return $a['order'] - $b['order'];
}
/**
* Template for form builder preview.
*
* @since 1.4.5
*/
public function field_preview_templates(): void {
// Checkbox, Radio, and Payment Multiple/Checkbox field choices.
?>
<script type="text/html" id="tmpl-wpforms-field-preview-checkbox-radio-payment-multiple">
<# if ( data.settings.choices_images ) { #>
<ul class="primary-input wpforms-image-choices wpforms-image-choices-{{ data.settings.choices_images_style }}">
<# _.each( data.order, function( choiceID, key ) { #>
<li class="wpforms-image-choices-item<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' wpforms-selected' ); } #>">
<label>
<span class="wpforms-image-choices-image">
<# if ( ! _.isEmpty( data.settings.choices[choiceID].image ) ) { #>
<img src="{{ data.settings.choices[choiceID].image }}" alt="{{ data.settings.choices[choiceID].label }}" title="{{ data.settings.choices[choiceID].label }}">
<# } else { #>
<img src="{{ wpforms_builder.image_placeholder }}" alt="{{ data.settings.choices[choiceID].label }}" title="{{ data.settings.choices[choiceID].label }}">
<# } #>
</span>
<# if ( 'none' === data.settings.choices_images_style ) { #>
<br>
<input type="{{ data.type }}" readonly<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' checked' ); } #>>
<# } else { #>
<input class="wpforms-screen-reader-element" type="{{ data.type }}" readonly<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' checked' ); } #>>
<# } #>
<span class="wpforms-image-choices-label">
{{{ WPFormsBuilder.fieldChoiceLabel( data, choiceID ) }}}
</span>
</label>
</li>
<# }) #>
</ul>
<# } else if ( data.settings.choices_icons ) { #>
<ul class='primary-input wpforms-icon-choices wpforms-icon-choices-{{ data.settings.choices_icons_style }} wpforms-icon-choices-{{ data.settings.choices_icons_size }}' style="--wpforms-icon-choices-color: {{ data.settings.choices_icons_color }};">
<# _.each( data.order, function( choiceID, key ) { #>
<li class="wpforms-icon-choices-item<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' wpforms-selected' ); } #>">
<label>
<span class="wpforms-icon-choices-icon">
<i class="ic-fa-{{ data.settings.choices[choiceID].icon_style }} ic-fa-{{ data.settings.choices[choiceID].icon }}"></i>
<span class="wpforms-icon-choices-icon-bg"></span>
</span>
<# if ( 'none' === data.settings.choices_icons_style ) { #>
<input type='{{ data.type }}' readonly<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' checked' ); } #>>
<# } else { #>
<input class='wpforms-screen-reader-element' type='{{ data.type }}' readonly<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' checked' ); } #>>
<# } #>
<span class='wpforms-icon-choices-label'>
{{{ WPFormsBuilder.fieldChoiceLabel( data, choiceID ) }}}
</span>
</label>
</li>
<# }) #>
</ul>
<# } else { #>
<ul class="primary-input">
<# _.each( data.order, function( choiceID, key ) { #>
<li>
<input type="{{ data.type }}" readonly<# if ( 1 === data.settings.choices[choiceID].default ) { print( ' checked' ); } #>>
{{{ WPFormsBuilder.fieldChoiceLabel( data, choiceID ) }}}
</li>
<# }) #>
</ul>
<# } #>
</script>
<?php
}
/**
* Template for form builder preview.
*
* @since 1.6.9
*/
public function choices_limit_message_template(): void {
?>
<script type="text/html" id="tmpl-wpforms-choices-limit-message">
<div class="wpforms-alert-dynamic wpforms-alert wpforms-alert-warning">
<?php
printf(
wp_kses( /* translators: %s - total number of choices. */
__( 'Showing the first 20 choices.<br> All %s choices will be displayed when viewing the form.', 'wpforms-lite' ),
[
'br' => [],
]
),
'{{ data.total }}'
);
?>
</div>
</script>
<?php
}
/**
* Template for an empty choices message.
*
* @since 1.8.2
*
* @return void
*/
public function choices_empty_message_template(): void {
?>
<script type="text/html" id="tmpl-wpforms-empty-choice-message">
<div class="wpforms-notice-dynamic-empty wpforms-alert wpforms-alert-warning">
{{ data.message }}
</div>
</script>
<?php
}
/**
* Builder fields search.
*
* @since 1.8.3
*/
public function search(): void {
?>
<div class="wpforms-search-fields-wrapper">
<div class="wpforms-search-fields-input-wrapper">
<label for="wpforms-search-fields-input" class="wpforms-screen-reader-element"><?php esc_html_e( 'Search fields:', 'wpforms-lite' ); ?></label>
<input type="search" id="wpforms-search-fields-input" placeholder="<?php echo esc_attr__( 'Search fields...', 'wpforms-lite' ); ?>" autocomplete="off">
<i class="fa fa-times wpforms-search-fields-input-close" aria-hidden="true"></i>
</div>
<div class="wpforms-search-fields-list">
<div class="wpforms-add-fields-group">
<div class="wpforms-add-fields-buttons"></div>
</div>
</div>
<div class="wpforms-search-fields-no-results">
<p>
<?php esc_html_e( "Sorry, we didn't find any fields that match your criteria.", 'wpforms-lite' ); ?>
</p>
</div>
</div>
<?php
}
}
new WPForms_Builder_Panel_Fields();

View File

@@ -0,0 +1,112 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Payments panel.
*
* @since 1.0.0
*/
class WPForms_Builder_Panel_Payments extends WPForms_Builder_Panel {
/**
* All systems go.
*
* @since 1.0.0
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Payments', 'wpforms-lite' );
$this->slug = 'payments';
$this->icon = 'fa-usd';
$this->order = 10;
$this->sidebar = true;
}
/**
* Output the Payments panel sidebar.
*
* @since 1.0.0
*/
public function panel_sidebar() {
// Sidebar contents are not valid unless we have a form.
if ( ! $this->form ) {
return;
}
$this->panel_sidebar_section( esc_html__( 'Default', 'wpforms-lite' ), 'default' );
do_action( 'wpforms_payments_panel_sidebar', $this->form );
}
/**
* Output the Payments panel primary content.
*
* @since 1.0.0
*/
public function panel_content() {
// An array of all the active provider addons.
$payments_active = apply_filters( 'wpforms_payments_available', [] );
if ( ! $this->form ) {
// Check if there is a form created. When no form has been created
// yet let the user know we need a form to setup a payment.
echo '<div class="wpforms-alert wpforms-alert-info">';
echo wp_kses(
__( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">setup your form</a> before you can manage these settings.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
'data-panel' => [],
],
]
);
echo '</div>';
return;
}
if ( empty( $payments_active ) ) {
// Check for active payment addons. When no payment addons are
// activated let the user know they need to install/activate an
// addon to setup a payment.
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-default">
<div class="illustration illustration-payments"></div>
<h5>' . esc_html__( 'Install Your Payment Integration', 'wpforms-lite' ) . '</h5>
<p>' . sprintf(
wp_kses(
/* translators: %s - addons page URL. */
__( 'It seems you do not have any payment addons activated. You can head over to the <a href="%s">Addons page</a> to install and activate the addon for your payment service.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=wpforms-addons' ) )
) .
'</p>
</div>';
} else {
// Everything is good - display default instructions.
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-default">
<div class="illustration illustration-payments"></div>
<h5>' . esc_html__( 'Install Your Payment Integration', 'wpforms-lite' ) . '</h5>
<p>' . esc_html__( 'It seems you don\'t have any payment addons activated. Click one of the available addons and start accepting payments today!', 'wpforms-lite' ) . '</p>
</div>';
}
do_action( 'wpforms_payments_panel_content', $this->form );
}
}
new WPForms_Builder_Panel_Payments();

View File

@@ -0,0 +1,154 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Providers panel.
*
* @since 1.0.0
*/
class WPForms_Builder_Panel_Providers extends WPForms_Builder_Panel {
/**
* All systems go.
*
* @since 1.0.0
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Marketing', 'wpforms-lite' );
$this->slug = 'providers';
$this->icon = 'fa-bullhorn';
$this->order = 10;
$this->sidebar = true;
}
/**
* Enqueue assets for the Providers panel.
*
* @since 1.0.0
* @since 1.6.8 All the builder stylesheets enqueues moved to the `\WPForms_Builder::enqueues()`.
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-providers',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/admin-builder-providers{$min}.js",
[ 'jquery', 'wpforms-builder' ],
WPFORMS_VERSION,
false
);
wp_localize_script(
'wpforms-builder-providers',
'wpforms_builder_providers',
[
'url' => esc_url( remove_query_arg( [ 'newform', 'section' ], add_query_arg( [ 'view' => 'providers' ] ) ) ),
'confirm_save' => esc_html__( 'We need to save your progress to continue to the Marketing panel. Is that OK?', 'wpforms-lite' ),
'confirm_connection' => esc_html__( 'Are you sure you want to delete this connection?', 'wpforms-lite' ),
/* translators: %s - connection type. */
'prompt_connection' => esc_html( sprintf( __( 'Enter a %s nickname', 'wpforms-lite' ), '%type%' ) ),
'prompt_placeholder' => esc_html__( 'Eg: Newsletter Optin', 'wpforms-lite' ),
'error_name' => esc_html__( 'You must provide a connection nickname.', 'wpforms-lite' ),
'required_field' => esc_html__( 'Field required', 'wpforms-lite' ),
'custom_fields_placeholder' => esc_html__( '--- Select Form Field ---', 'wpforms-lite' ),
]
);
}
/**
* Output the Provider panel sidebar.
*
* @since 1.0.0
*/
public function panel_sidebar() {
// Sidebar contents are not valid unless we have a form.
if ( ! $this->form ) {
return;
}
$this->panel_sidebar_section( esc_html__( 'Default', 'wpforms-lite' ), 'default' );
do_action( 'wpforms_providers_panel_sidebar', $this->form );
}
/**
* Output the Provider panel primary content.
*
* @since 1.0.0
*
* @noinspection HtmlUnknownTarget
*/
public function panel_content() {
if ( ! $this->form ) {
// Check if there is a form created.
// When no form has been created yet let the user know, we need a form to set up a provider.
echo '<div class="wpforms-alert wpforms-alert-info">';
echo wp_kses(
__( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">set up your form</a> before you can manage these settings.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
'data-panel' => [],
],
]
);
echo '</div>';
return;
}
// An array of all the active provider addons.
$providers_active = wpforms_get_providers_available();
if ( empty( $providers_active ) ) {
// Check for active provider addons.
// When no provider addons are activated,
// let the user know they need to install/activate an addon to set up a provider.
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-info">';
echo '<h5>' . esc_html__( 'Install Your Marketing Integration', 'wpforms-lite' ) . '</h5>';
echo '<p>' .
sprintf(
wp_kses(
/* translators: %s - plugin admin area Addons page. */
__( 'It seems you do not have any marketing addons activated. You can head over to the <a href="%s">Addons page</a> to install and activate the addon for your provider.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=wpforms-addons' ) )
) .
'</p>';
echo '</div>';
} else {
// Everything is good - display default instructions.
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-default">
<div class="illustration illustration-marketing"></div>
<h5>' . esc_html__( 'Select Your Marketing Integration', 'wpforms-lite' ) . '</h5>
<p>' . esc_html__( /** @lang text */ 'Select your email marketing service provider or CRM from the options on the left. If you don\'t see your email marketing service listed, then let us know and we\'ll do our best to get it added as fast as possible.', 'wpforms-lite' ) . '</p>
</div>';
}
do_action( 'wpforms_providers_panel_content', $this->form );
}
}
new WPForms_Builder_Panel_Providers();

View File

@@ -0,0 +1,205 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Revisions management panel.
*
* @since 1.7.3
*/
class WPForms_Builder_Panel_Revisions extends WPForms_Builder_Panel {
/**
* Panel title.
*
* @since 1.8.8
*
* @var string
*/
private $title;
/**
* All systems go.
*
* @since 1.7.3
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Revisions', 'wpforms-lite' );
$this->slug = 'revisions';
$this->icon = 'fa-history';
$this->order = 10;
$this->sidebar = true;
$this->title = $this->form && $this->form->post_type === 'wpforms-template' ?
__( 'Form Template Revisions', 'wpforms-lite' ) :
__( 'Form Revisions', 'wpforms-lite' );
$this->hooks();
}
/**
* Hook into WordPress lifecycle.
*
* @since 1.7.3
*/
private function hooks() {
// Add a notice above all panels if revision is loaded.
add_action( 'wpforms_builder_panels', [ $this, 'panel_notice' ], 100, 2 );
}
/**
* Primary panel button in the left panel navigation.
*
* @since 1.7.3
*
* @param mixed $form The form object.
* @param string $view Current view/panel.
*/
public function button( $form, $view ) {
$classes = 'wpforms-panel-revisions-button';
if ( $view === $this->slug ) {
$classes .= ' active';
}
$badge = '';
if ( $this->form && ! wp_revisions_enabled( $this->form ) && ! wpforms()->obj( 'revisions' )->panel_viewed() ) {
$badge = '
<span class="badge-exclamation">
<svg width="4" height="10" fill="none">
<path fill="#fff" fill-rule="evenodd" d="M3.5 8.1c0-.8-.7-1.5-1.5-1.5S.5 7.3.5 8.1 1.2 9.6 2 9.6 3.5 8.9 3.5 8ZM1 .9c-.3 0-.5.2-.4.4l.2 4.4c0 .2.2.3.4.3h1.6c.2 0 .3-.1.4-.3l.2-4.4c0-.2-.2-.4-.4-.4H1Z" clip-rule="evenodd"/>
</svg>
</span>';
}
printf(
'<div class="wpforms-panel-revisions-button-spacer"></div>
<button class="%1$s" data-panel="%2$s" title="%6$s">
%3$s
<i class="fa %4$s"></i>
<span class="screen-reader-text">%5$s</span>
</button>',
esc_attr( $classes ),
esc_attr( $this->slug ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$badge,
esc_attr( $this->icon ),
esc_html( $this->name ),
esc_html( $this->title )
);
}
/**
* Output the Settings panel sidebar.
*
* @since 1.7.3
*/
public function panel_sidebar() {
// Sidebar contents are not valid unless we have a form.
if ( ! $this->form ) {
return;
}
printf(
'<div class="wpforms-revisions-header">
<h3>%s</h3>
<p>%s</p>
</div>',
esc_html( $this->title ),
esc_html__( 'Select a revision to roll back to that version. All changes, including settings, will be reverted.', 'wpforms-lite' )
);
// Render a list of form revisions, including current version. All data is safe, escaped in the template.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms()->obj( 'revisions' )->render_revisions_list();
$revisions_to_keep = wp_revisions_to_keep( $this->form );
if ( $revisions_to_keep === 0 ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render( 'builder/revisions/notice-disabled' );
}
if ( $revisions_to_keep > 0 ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'builder/revisions/notice-limited',
[
'revisions_to_keep' => $revisions_to_keep,
],
true
);
}
}
/**
* Output revision notice above the panels.
*
* @since 1.7.3
*
* @return void
*/
public function panel_notice() {
$revision = wpforms()->obj( 'revisions' )->get_revision();
if ( ! $revision ) {
return;
}
$restore_link = sprintf(
'<a href="%1$s">%2$s</a>',
esc_url(
wp_nonce_url(
wpforms()->obj( 'revisions' )->get_url(
[
'revision_id' => $revision->ID,
'action' => 'restore_revision',
]
),
'restore_revision',
'wpforms_nonce'
)
),
__( 'Restore this revision', 'wpforms-lite' )
);
$back_link = sprintf(
'<a href="%1$s">%2$s</a>',
esc_url( wpforms()->obj( 'revisions' )->get_url() ),
__( 'go back to the current version', 'wpforms-lite' )
);
$message = sprintf( /* translators: %1$s - revision date, %2$s - revision time, %3$s - "Restore this revision" link, %4$s - "go back to the current version" link. */
__( 'Youre currently viewing a form revision from %1$s at %2$s. %3$s or %4$s.', 'wpforms-lite' ),
wpforms()->obj( 'revisions' )->get_formatted_datetime( $revision->post_modified_gmt ),
wpforms()->obj( 'revisions' )->get_formatted_datetime( $revision->post_modified_gmt, 'time' ),
$restore_link,
$back_link
);
printf(
'<div class="wpforms-revision-notice">
<p><i class="fa fa-history"></i>%s</p>
</div>',
wp_kses(
$message,
[
'a' => [
'href' => [],
],
]
)
);
}
}
new WPForms_Builder_Panel_Revisions();

View File

@@ -0,0 +1,385 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Admin\Forms\Tags;
/**
* Settings management panel.
*
* @since 1.0.0
*/
class WPForms_Builder_Panel_Settings extends WPForms_Builder_Panel {
/**
* All systems go.
*
* @since 1.0.0
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Settings', 'wpforms-lite' );
$this->slug = 'settings';
$this->icon = 'fa-sliders';
$this->order = 10;
$this->sidebar = true;
/**
* Filters the form data for the form builder.
*
* @since 1.9.0
*
* @param array $form_data Form data.
*/
$this->form_data = apply_filters( 'wpforms_builder_panel_settings_init_form_data', $this->form_data );
}
/**
* Output the Settings panel sidebar.
*
* @since 1.0.0
*/
public function panel_sidebar() {
// Sidebar contents are not valid unless we have a form.
if ( ! $this->form ) {
return;
}
$sections = [
'general' => esc_html__( 'General', 'wpforms-lite' ),
'anti_spam' => esc_html__( 'Spam Protection and Security', 'wpforms-lite' ),
'confirmation' => esc_html__( 'Confirmations', 'wpforms-lite' ),
'notifications' => esc_html__( 'Notifications', 'wpforms-lite' ),
'themes' => esc_html__( 'Themes', 'wpforms-lite' ),
];
/**
* Filters builder settings sections.
*
* @since 1.1.9
*
* @param array $sections Sections.
* @param array $form_data Form data.
*
* @return array
*/
$sections = (array) apply_filters( 'wpforms_builder_settings_sections', $sections, $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
foreach ( $sections as $slug => $section ) {
$this->panel_sidebar_section( $section, $slug );
}
}
/**
* Enqueue assets.
*
* @since 1.7.5
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-settings',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/settings{$min}.js",
[ 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-builder-settings',
'wpforms_builder_settings',
[
'choicesjs_config' => $this->get_choicesjs_config(),
'all_tags_choices' => Tags::get_all_tags_choices(),
]
);
}
/**
* Get Choices.js configuration.
*
* @since 1.7.5
*
* @return array
*/
private function get_choicesjs_config(): array {
$config = Tags::get_choicesjs_config();
$config['noResultsText'] = esc_html__( 'Press Enter or "," key to add new tag', 'wpforms-lite' );
return $config;
}
/**
* Output the Settings panel primary content.
*
* @since 1.0.0
*/
public function panel_content() {
// Check if there is a form created.
if ( ! $this->form ) {
echo '<div class="wpforms-alert wpforms-alert-info">';
echo wp_kses(
__( 'You need to <a href="#" class="wpforms-panel-switch" data-panel="setup">setup your form</a> before you can manage the settings.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
'data-panel' => [],
],
]
);
echo '</div>';
return;
}
/*
* General.
*/
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-general">';
echo '<div class="wpforms-panel-content-section-title">';
esc_html_e( 'General', 'wpforms-lite' );
echo '</div>';
wpforms_panel_field(
'text',
'settings',
'form_title',
$this->form_data,
esc_html__( 'Form Name', 'wpforms-lite' ),
[
'default' => $this->form->post_title,
]
);
wpforms_panel_field(
'textarea',
'settings',
'form_desc',
$this->form_data,
esc_html__( 'Form Description', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Enter descriptive text or instructions to help your users understand the requirements of your form.', 'wpforms-lite' ),
'input_class' => 'wpforms-smart-tags-enabled',
'data' => [
'type' => 'all',
'fields' => '',
],
]
);
if ( $this->form->post_type === 'wpforms-template' ) {
wpforms_panel_field(
'textarea',
'settings',
'template_description',
$this->form_data,
esc_html__( 'Template Description', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Describe the use case for your template. Only displayed internally.', 'wpforms-lite' ),
]
);
}
$this->general_setting_tags();
wpforms_panel_field(
'text',
'settings',
'submit_text',
$this->form_data,
esc_html__( 'Submit Button Text', 'wpforms-lite' ),
[
'default' => esc_html__( 'Submit', 'wpforms-lite' ),
]
);
wpforms_panel_field(
'text',
'settings',
'submit_text_processing',
$this->form_data,
esc_html__( 'Submit Button Processing Text', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Enter the submit button text you would like the button display while the form submit is processing.', 'wpforms-lite' ),
]
);
$this->general_setting_advanced();
echo '</div>';
/*
* Notifications.
*/
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-notifications" data-panel="notifications">';
/**
* Output notifications.
*
* @since 1.6.7.3
*
* @param WPForms_Builder_Panel_Settings $settings Current settings.
*/
do_action( 'wpforms_form_settings_notifications', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
echo '</div>';
/*
* Confirmations.
*/
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-confirmation" data-panel="confirmations">';
/**
* Output confirmations.
*
* @since 1.6.7.3
*
* @param WPForms_Builder_Panel_Settings $settings Current settings.
*/
do_action( 'wpforms_form_settings_confirmations', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
echo '</div>';
/**
* Output custom panels.
*
* @since 1.6.7.3
*
* @param WPForms_Builder_Panel_Settings $settings Current settings.
*/
do_action( 'wpforms_form_settings_panel_content', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Output the Tags setting.
*
* @since 1.7.5
*/
private function general_setting_tags() {
$form_tags = [];
if ( ! empty( $this->form_data['settings']['form_tags'] ) ) {
$form_tags = get_terms(
[
'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY,
'name' => $this->form_data['settings']['form_tags'],
'hide_empty' => false,
]
);
$form_tags = is_wp_error( $form_tags ) ? [] : (array) $form_tags;
}
$tags_value = wp_list_pluck( $form_tags, 'term_id' );
$tags_options = wp_list_pluck( $form_tags, 'name', 'term_id' );
wpforms_panel_field(
'select',
'settings',
'form_tags',
$this->form_data,
esc_html__( 'Tags', 'wpforms-lite' ),
[
'options' => $tags_options,
'value' => $tags_value,
'multiple' => true,
'tooltip' => esc_html__( 'Mark form with the tags. To create a new tag, simply type it and press Enter.', 'wpforms-lite' ),
]
);
}
/**
* Output the *CAPTCHA settings.
*
* @since 1.6.8
*
* @noinspection HtmlUnknownTarget
*/
private function general_setting_advanced() {
ob_start();
wpforms_panel_field(
'text',
'settings',
'form_class',
$this->form_data,
esc_html__( 'Form CSS Class', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Enter CSS class names for the form wrapper. Multiple class names should be separated with spaces.', 'wpforms-lite' ),
]
);
wpforms_panel_field(
'text',
'settings',
'submit_class',
$this->form_data,
esc_html__( 'Submit Button CSS Class', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Enter CSS class names for the form submit button. Multiple names should be separated with spaces.', 'wpforms-lite' ),
]
);
wpforms_panel_field(
'toggle',
'settings',
'dynamic_population',
$this->form_data,
esc_html__( 'Enable Prefill by URL', 'wpforms-lite' ),
[
'tooltip' => sprintf(
'<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>',
wpforms_utm_link( 'https://wpforms.com/developers/how-to-enable-dynamic-field-population/', 'Builder Settings', 'Prefill by URL Tooltip' ),
esc_html__( 'How to use Prefill by URL', 'wpforms-lite' )
),
]
);
wpforms_panel_field(
'toggle',
'settings',
'ajax_submit',
$this->form_data,
esc_html__( 'Enable AJAX form submission', 'wpforms-lite' ),
[
'tooltip' => esc_html__( 'Enables form submission without page reload.', 'wpforms-lite' ),
]
);
/**
* Fires after general settings.
*
* @since 1.0.2
*
* @param WPForms_Builder_Panel_Settings $settings Current settings.
*/
do_action( 'wpforms_form_settings_general', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
// Wrap advanced settings to the unfoldable group.
wpforms_panel_fields_group(
ob_get_clean(),
[
'borders' => [ 'top' ],
'unfoldable' => true,
'group' => 'settings_advanced',
'title' => esc_html__( 'Advanced', 'wpforms-lite' ),
]
);
}
}
new WPForms_Builder_Panel_Settings();

View File

@@ -0,0 +1,132 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Admin\Traits\FormTemplates;
use WPForms\Integrations\AI\Helpers as AIHelpers;
/**
* Setup panel.
*
* @since 1.0.0
* @since 1.6.8 Form Builder Refresh.
*/
class WPForms_Builder_Panel_Setup extends WPForms_Builder_Panel {
use FormTemplates;
/**
* All systems go.
*
* @since 1.0.0
*/
public function init() {
// Define panel information.
$this->name = esc_html__( 'Setup', 'wpforms-lite' );
$this->slug = 'setup';
$this->icon = 'fa-cog';
$this->order = 5;
$this->on_demand = true;
$this->addons_obj = wpforms()->obj( 'addons' );
}
/**
* Enqueue assets for the Setup panel.
*
* @since 1.0.0
* @since 1.6.8 All the builder stylesheets enqueues moved to the `\WPForms_Builder::enqueues()`.
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-setup',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/setup{$min}.js",
[ 'wpforms-builder', 'listjs' ],
WPFORMS_VERSION,
true
);
}
/**
* Output the Settings panel primary content.
*
* @since 1.0.0
*
* @noinspection HtmlUnknownTarget
*/
public function panel_content() {
?>
<div id="wpforms-setup-form-name">
<label for="wpforms-setup-name"><?php esc_html_e( 'Name Your Form', 'wpforms-lite' ); ?></label>
<input type="text" id="wpforms-setup-name" placeholder="<?php esc_attr_e( 'Enter your form name here&hellip;', 'wpforms-lite' ); ?>">
</div>
<div class="wpforms-setup-title">
<?php esc_html_e( 'Select a Template', 'wpforms-lite' ); ?>
<span class="wpforms-setup-title-after"></span>
</div>
<p class="wpforms-setup-desc secondary-text">
<?php
printf(
wp_kses( /* translators: %1$s - create a template doc link, %2$s - Contact us page link. */
__( 'To speed up the process, you can select from one of our pre-made templates, start with a <a href="#" class="wpforms-trigger-blank">blank form</a> or <a href="%1$s" target="_blank" rel="noopener noreferrer">create your own</a>.', 'wpforms-lite' ),
[
'strong' => [],
'a' => [
'href' => [],
'class' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-create-a-custom-form-template/', 'builder-templates', 'Create Your Own Template Documentation' ) )
);
if ( AIHelpers::is_disabled() ) {
echo ' ';
printf(
wp_kses( /* translators: %1$s - create a template doc link, %2$s - Contact us page link. */
__( 'Have a suggestion for a new template? <a href="%1$s" target="_blank" rel="noopener noreferrer">Wed love to hear it</a>!', 'wpforms-lite' ),
[
'strong' => [],
'a' => [
'href' => [],
'class' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/form-template-suggestion/', 'builder-templates', 'Suggest a Template' ) )
);
}
?>
</p>
<?php
$this->output_templates_content();
/**
* Fires after WPForms builder setup panel.
*
* @since 1.0.6
*/
do_action( 'wpforms_setup_panel_after' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
new WPForms_Builder_Panel_Setup();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Functionality related to the admin TinyMCE editor.
*
* @since 1.0.0
*/
class WPForms_Admin_Editor {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
add_action( 'media_buttons', [ $this, 'media_button' ], 15 );
}
/**
* Allow easy shortcode insertion via a custom media button.
*
* @since 1.0.0
*
* @param string $editor_id Editor Id.
*/
public function media_button( $editor_id ) {
if ( ! \wpforms_current_user_can( 'view_forms' ) ) {
return;
}
// Provide the ability to conditionally disable the button, so it can be
// disabled for custom fields or front-end use such as bbPress. We default
// to only showing within the post editor page.
if ( ! apply_filters( 'wpforms_display_media_button', $this->is_post_editor_page(), $editor_id ) ) {
return;
}
// Setup the icon - currently using a dashicon.
$icon = '<span class="wp-media-buttons-icon wpforms-menu-icon" style="font-size:16px;margin-top:-2px;"><svg width="18" height="18" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M643 911v128h-252v-128h252zm0-255v127h-252v-127h252zm758 511v128h-341v-128h341zm0-256v128h-672v-128h672zm0-255v127h-672v-127h672zm135 860v-1240q0-8-6-14t-14-6h-32l-378 256-210-171-210 171-378-256h-32q-8 0-14 6t-6 14v1240q0 8 6 14t14 6h1240q8 0 14-6t6-14zm-855-1110l185-150h-406zm430 0l221-150h-406zm553-130v1240q0 62-43 105t-105 43h-1240q-62 0-105-43t-43-105v-1240q0-62 43-105t105-43h1240q62 0 105 43t43 105z" fill="#82878c"/></svg></span>';
printf(
'<button type="button" class="button wpforms-insert-form-button" data-editor="%s">%s %s</button>',
esc_attr( $editor_id ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$icon,
esc_html__( 'Add Form', 'wpforms-lite' )
);
$min = wpforms_get_min_suffix();
// If we have made it this far then load the JS.
wp_enqueue_script(
'wpforms-editor',
WPFORMS_PLUGIN_URL . "assets/js/admin/admin-editor{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
add_action( 'admin_footer', [ $this, 'shortcode_modal' ] );
}
/**
* Check if we are on the post editor admin page.
*
* @since 1.6.2
*
* @returns boolean True if it is post editor admin page.
*/
public function is_post_editor_page() {
if ( ! is_admin() ) {
return false;
}
// get_current_screen() is loaded after 'admin_init' hook and may not exist yet.
if ( ! function_exists( 'get_current_screen' ) ) {
return false;
}
$screen = get_current_screen();
return $screen !== null && $screen->parent_base === 'edit';
}
/**
* Modal window for inserting the form shortcode into TinyMCE.
*
* Thickbox is old and busted so we don't use that. Creating a custom view in
* Backbone would make me pull my hair out. So instead we offer a small clean
* modal that is based off of the WordPress insert link modal.
*
* @since 1.0.0
*/
public function shortcode_modal() {
?>
<div id="wpforms-modal-backdrop" style="display: none"></div>
<div id="wpforms-modal-wrap" style="display: none">
<form id="wpforms-modal" tabindex="-1">
<div id="wpforms-modal-title">
<?php esc_html_e( 'Insert Form', 'wpforms-lite' ); ?>
<button type="button" id="wpforms-modal-close"><span class="screen-reader-text"><?php esc_html_e( 'Close', 'wpforms-lite' ); ?></span></button>
</div>
<div id="wpforms-modal-inner">
<div id="wpforms-modal-options">
<?php
echo '<p id="wpforms-modal-notice">';
printf(
wp_kses( /* translators: %s - WPForms documentation URL. */
__( 'Heads up! Don\'t forget to test your form. <a href="%s" target="_blank" rel="noopener noreferrer">Check out our complete guide</a>!', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'rel' => [],
'target' => [],
],
]
),
'https://wpforms.com/docs/how-to-properly-test-your-wordpress-forms-before-launching-checklist/'
);
echo '</p>';
$args = apply_filters( 'wpforms_modal_select', [] );
$forms = wpforms()->obj( 'form' )->get( '', $args );
if ( ! empty( $forms ) ) {
printf( '<p><label for="wpforms-modal-select-form">%s</label></p>', esc_html__( 'Select a form below to insert', 'wpforms-lite' ) );
echo '<select id="wpforms-modal-select-form">';
foreach ( $forms as $form ) {
printf( '<option value="%d">%s</option>', esc_html( $form->ID ), esc_html( $form->post_title ) );
}
echo '</select><br>';
printf( '<p class="wpforms-modal-inline"><input type="checkbox" id="wpforms-modal-checkbox-title"><label for="wpforms-modal-checkbox-title">%s</label></p>', esc_html__( 'Show form name', 'wpforms-lite' ) );
printf( '<p class="wpforms-modal-inline"><input type="checkbox" id="wpforms-modal-checkbox-description"><label for="wpforms-modal-checkbox-description">%s</label></p>', esc_html__( 'Show form description', 'wpforms-lite' ) );
} else {
echo '<p>';
printf(
wp_kses(
/* translators: %s - WPForms Builder page. */
__( 'Whoops, you haven\'t created a form yet. Want to <a href="%s">give it a go</a>?', 'wpforms-lite' ),
[
'a' => [
'href' => [],
],
]
),
esc_url( admin_url( 'admin.php?page=wpforms-builder' ) )
);
echo '</p>';
}
?>
</div>
</div>
<div class="submitbox">
<div id="wpforms-modal-cancel">
<a class="submitdelete deletion" href="#"><?php esc_html_e( 'Cancel', 'wpforms-lite' ); ?></a>
</div>
<?php if ( ! empty( $forms ) ) : ?>
<div id="wpforms-modal-update">
<button class="button button-primary" id="wpforms-modal-submit"><?php esc_html_e( 'Add Form', 'wpforms-lite' ); ?></button>
</div>
<?php endif; ?>
</div>
</form>
</div>
<style type="text/css">
.wpforms-insert-form-button svg path {
fill: #0071a1;
}
.wpforms-insert-form-button:hover svg path {
fill: #016087;
}
#wpforms-modal-wrap {
display: none;
background-color: #fff;
-webkit-box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
width: 500px;
height: 285px;
overflow: hidden;
margin-left: -250px;
margin-top: -125px;
position: fixed;
top: 50%;
left: 50%;
z-index: 100205;
-webkit-transition: height 0.2s, margin-top 0.2s;
transition: height 0.2s, margin-top 0.2s;
}
#wpforms-modal-backdrop {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
min-height: 360px;
background: #000;
opacity: 0.7;
filter: alpha(opacity=70);
z-index: 100200;
}
#wpforms-modal {
position: relative;
height: 100%;
}
#wpforms-modal-title {
background: #fcfcfc;
border-bottom: 1px solid #dfdfdf;
height: 36px;
font-size: 18px;
font-weight: 600;
line-height: 36px;
padding: 0 36px 0 16px;
top: 0;
right: 0;
left: 0;
}
#wpforms-modal-close {
color: #666;
padding: 0;
position: absolute;
top: 0;
right: 0;
width: 36px;
height: 36px;
text-align: center;
background: none;
border: none;
cursor: pointer;
}
#wpforms-modal-close:before {
font: normal 20px/36px 'dashicons';
vertical-align: top;
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
width: 36px;
height: 36px;
content: '\f158';
}
#wpforms-modal-close:hover,
#wpforms-modal-close:focus {
color: #2ea2cc;
}
#wpforms-modal-close:focus {
outline: none;
-webkit-box-shadow: 0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, .8);
}
#wpforms-modal-inner {
padding: 0 16px 50px;
}
#wpforms-modal-search-toggle:after {
display: inline-block;
font: normal 20px/1 'dashicons';
vertical-align: top;
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
content: '\f140';
}
#wpforms-modal-notice {
background-color: #d9edf7;
border: 1px solid #bce8f1;
color: #31708f;
padding: 10px;
}
#wpforms-modal #wpforms-modal-options {
padding: 8px 0 12px;
}
#wpforms-modal #wpforms-modal-options .wpforms-modal-inline {
display: inline-block;
margin: 0;
padding: 0 20px 0 0;
}
#wpforms-modal-select-form {
margin-bottom: 1em;
max-width: 100%;
}
#wpforms-modal .submitbox {
padding: 8px 16px;
background: #fcfcfc;
border-top: 1px solid #dfdfdf;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
#wpforms-modal-cancel {
line-height: 25px;
float: left;
}
#wpforms-modal-update {
line-height: 23px;
float: right;
}
#wpforms-modal-submit {
float: right;
margin-bottom: 0;
}
@media screen and ( max-width: 782px ) {
#wpforms-modal-wrap {
height: 280px;
margin-top: -140px;
}
#wpforms-modal-inner {
padding: 0 16px 60px;
}
#wpforms-modal-cancel {
line-height: 32px;
}
}
@media screen and ( max-width: 520px ) {
#wpforms-modal-wrap {
width: auto;
margin-left: 0;
left: 10px;
right: 10px;
max-width: 500px;
}
}
@media screen and ( max-height: 520px ) {
#wpforms-modal-wrap {
-webkit-transition: none;
transition: none;
}
}
@media screen and ( max-height: 290px ) {
#wpforms-modal-wrap {
height: auto;
margin-top: 0;
top: 10px;
bottom: 10px;
}
#wpforms-modal-inner {
overflow: auto;
height: -webkit-calc(100% - 92px);
height: calc(100% - 92px);
padding-bottom: 2px;
}
}
</style>
<?php
}
}
new WPForms_Admin_Editor();

View File

@@ -0,0 +1,623 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/**
* Register menu elements and do other global tasks.
*
* @since 1.0.0
*/
class WPForms_Admin_Menu {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.9.7.3
*
* @return void
*/
private function hooks(): void {
// Let's make some menus.
add_action( 'admin_menu', [ $this, 'register_menus' ], 9 );
add_action( 'admin_head', [ $this, 'hide_wpforms_submenu_items' ] );
add_action( 'admin_head', [ $this, 'adjust_pro_menu_item' ] );
add_action( 'admin_head', [ $this, 'admin_menu_styles' ], 11 );
// Plugins page settings link.
add_filter( 'plugin_action_links_' . plugin_basename( WPFORMS_PLUGIN_DIR . 'wpforms.php' ), [ $this, 'settings_link' ], 10, 4 );
add_action( 'activated_plugin', [ $this, 'activated_rotation_plugin' ], 10, 2 );
}
/**
* Register our menus.
*
* @since 1.0.0
*/
public function register_menus(): void {
$manage_cap = wpforms_get_capability_manage_options();
$access = wpforms()->obj( 'access' );
if ( ! $access || ! method_exists( $access, 'get_menu_cap' ) ) {
return;
}
// Default Forms top level menu item.
add_menu_page(
esc_html__( 'WPForms', 'wpforms-lite' ),
esc_html__( 'WPForms', 'wpforms-lite' ),
$access->get_menu_cap( 'view_forms' ),
'wpforms-overview',
[ $this, 'admin_page' ],
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
'data:image/svg+xml;base64,' . base64_encode( '<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#9ea3a8" d="M643 911v128h-252v-128h252zm0-255v127h-252v-127h252zm758 511v128h-341v-128h341zm0-256v128h-672v-128h672zm0-255v127h-672v-127h672zm135 860v-1240q0-8-6-14t-14-6h-32l-378 256-210-171-210 171-378-256h-32q-8 0-14 6t-6 14v1240q0 8 6 14t14 6h1240q8 0 14-6t6-14zm-855-1110l185-150h-406zm430 0l221-150h-406zm553-130v1240q0 62-43 105t-105 43h-1240q-62 0-105-43t-43-105v-1240q0-62 43-105t105-43h1240q62 0 105 43t43 105z"/></svg>' ),
/**
* Filters WPForms menu position.
*
* @since 1.6.0.2
*
* @param string|int|float $position Menu position.
*/
apply_filters( 'wpforms_menu_position', '58.9' ) // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
);
// All Forms sub menu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms', 'wpforms-lite' ),
esc_html__( 'All Forms', 'wpforms-lite' ),
$access->get_menu_cap( 'view_forms' ),
'wpforms-overview',
[ $this, 'admin_page' ]
);
// Add New submenu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms Builder', 'wpforms-lite' ),
esc_html__( 'Add New Form', 'wpforms-lite' ),
$access->get_menu_cap( [ 'create_forms', 'edit_forms' ] ),
'wpforms-builder',
[ $this, 'admin_page' ]
);
// Entries sub menu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'Form Entries', 'wpforms-lite' ),
esc_html__( 'Entries', 'wpforms-lite' ),
$access->get_menu_cap( 'view_entries' ),
'wpforms-entries',
[ $this, 'admin_page' ]
);
// Payments sub menu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'Payments', 'wpforms-lite' ),
esc_html__( 'Payments', 'wpforms-lite' ) . $this->get_new_badge_html(),
$manage_cap,
WPForms\Admin\Payments\Payments::SLUG,
[ $this, 'admin_page' ]
);
do_action_deprecated( // phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
'wpform_admin_menu',
[ $this ],
'1.5.5 of the WPForms plugin',
'wpforms_admin_menu'
);
/**
* Fires after constructing the WPForms admin menu.
*
* @since 1.5.4.2
*
* @param WPForms_Admin_Menu $instance WPForms Admin Menu instance.
*/
do_action( 'wpforms_admin_menu', $this );
// Templates sub menu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms Templates', 'wpforms-lite' ),
esc_html__( 'Form Templates', 'wpforms-lite' ),
$access->get_menu_cap( 'edit_forms' ),
'wpforms-templates',
[ $this, 'admin_page' ]
);
// Settings submenu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms Settings', 'wpforms-lite' ),
esc_html__( 'Settings', 'wpforms-lite' ),
$manage_cap,
'wpforms-settings',
[ $this, 'admin_page' ]
);
// Tools sub menu item.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms Tools', 'wpforms-lite' ),
esc_html__( 'Tools', 'wpforms-lite' ),
$access->get_menu_cap( [ 'create_forms', 'view_forms', 'view_entries' ] ),
'wpforms-tools',
[ $this, 'admin_page' ]
);
// Hidden placeholder paged used for misc content.
add_submenu_page(
'wpforms-settings',
esc_html__( 'WPForms', 'wpforms-lite' ),
esc_html__( 'Info', 'wpforms-lite' ),
$access->get_menu_cap( 'any' ),
'wpforms-page',
[ $this, 'admin_page' ]
);
// Addons submenu page.
add_submenu_page(
'wpforms-overview',
esc_html__( 'WPForms Addons', 'wpforms-lite' ),
'<span style="color:#f18500">' . esc_html__( 'Addons', 'wpforms-lite' ) . '</span>',
$access->get_menu_cap( 'edit_forms' ),
'wpforms-addons',
[ $this, 'admin_page' ]
);
// Rotating submenu.
$rotation = $this->get_rotating_submenu();
if ( $rotation ) {
add_submenu_page(
'wpforms-overview',
$rotation['page_title'],
$rotation['menu_title'],
$manage_cap,
$rotation['menu_slug'],
[ $this, 'admin_page' ]
);
}
// SMTP submenu page.
add_submenu_page(
'wpforms-overview',
esc_html__( 'SMTP', 'wpforms-lite' ),
esc_html__( 'SMTP', 'wpforms-lite' ),
$manage_cap,
WPForms\Admin\Pages\SMTP::SLUG,
[ $this, 'admin_page' ]
);
// About submenu page.
add_submenu_page(
'wpforms-overview',
esc_html__( 'About WPForms', 'wpforms-lite' ),
esc_html__( 'About Us', 'wpforms-lite' ),
$access->get_menu_cap( 'any' ),
WPForms_About::SLUG,
[ $this, 'admin_page' ]
);
// Community submenu page.
add_submenu_page(
'wpforms-overview',
esc_html__( 'Community', 'wpforms-lite' ),
esc_html__( 'Community', 'wpforms-lite' ),
$manage_cap,
WPForms\Admin\Pages\Community::SLUG,
[ $this, 'admin_page' ]
);
if ( ! wpforms()->is_pro() ) {
add_submenu_page(
'wpforms-overview',
esc_html__( 'Upgrade to Pro', 'wpforms-lite' ),
esc_html__( 'Upgrade to Pro', 'wpforms-lite' ),
$manage_cap,
wpforms_admin_upgrade_link( 'admin-menu' )
);
}
}
/**
* Hide the "Add New" admin menu item if a user can't create forms.
*
* @since 1.5.8
*/
public function hide_wpforms_submenu_items(): void {
if ( wpforms_current_user_can( 'create_forms' ) ) {
return;
}
global $submenu;
if ( ! isset( $submenu['wpforms-overview'] ) ) {
return;
}
foreach ( $submenu['wpforms-overview'] as $key => $item ) {
if ( isset( $item[2] ) && $item[2] === 'wpforms-builder' ) {
unset( $submenu['wpforms-overview'][ $key ] );
break;
}
}
$this->hide_wpforms_menu_item();
}
/**
* Hide the "WPForms" admin menu if it has no submenu items.
*
* @since 1.5.8
*/
public function hide_wpforms_menu_item(): void {
global $submenu, $menu;
if ( ! empty( $submenu['wpforms-overview'] ) ) {
return;
}
unset( $submenu['wpforms-overview'] );
foreach ( $menu as $key => $item ) {
if ( isset( $item[2] ) && $item[2] === 'wpforms-overview' ) {
unset( $menu[ $key ] );
break;
}
}
}
/**
* Make changes to the PRO menu item.
*
* @since 1.8.1
*/
public function adjust_pro_menu_item(): void {
global $submenu;
// Bail if a plugin menu is not registered.
if ( ! isset( $submenu['wpforms-overview'] ) ) {
return;
}
$upgrade_link_position = key(
array_filter(
$submenu['wpforms-overview'],
static function ( $item ) {
return strpos( urldecode( $item[2] ), 'wpforms.com/lite-upgrade' ) !== false;
}
)
);
// Bail if "Upgrade to Pro" menu item is not registered.
if ( $upgrade_link_position === null ) {
return;
}
// Add the PRO badge to the menu item.
// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
if ( isset( $submenu['wpforms-overview'][ $upgrade_link_position ][4] ) ) {
$submenu['wpforms-overview'][ $upgrade_link_position ][4] .= ' wpforms-sidebar-upgrade-pro';
} else {
$submenu['wpforms-overview'][ $upgrade_link_position ][] = 'wpforms-sidebar-upgrade-pro';
}
$current_screen = get_current_screen();
$upgrade_utm_content = $current_screen === null ? 'Upgrade to Pro' : 'Upgrade to Pro - ' . $current_screen->base;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$upgrade_utm_content = empty( $_GET['view'] ) ? $upgrade_utm_content : $upgrade_utm_content . ': ' . sanitize_key( $_GET['view'] );
// Add utm_content to the menu item.
$submenu['wpforms-overview'][ $upgrade_link_position ][2] = esc_url(
add_query_arg(
'utm_content',
$upgrade_utm_content,
$submenu['wpforms-overview'][ $upgrade_link_position ][2]
)
);
// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
}
/**
* Wrapper for the hook to render our custom settings pages.
*
* @since 1.0.0
*/
public function admin_page(): void {
/**
* Fires to show the WPForms admin page.
*
* @since 1.0.0
*/
do_action( 'wpforms_admin_page' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Add a settings link to the Plugins page.
*
* @since 1.3.9
*
* @param array|mixed $links Plugin row links.
* @param string $plugin_file Path to the plugin file relative to the plugins' directory.
* @param array $plugin_data An array of plugin data. See `get_plugin_data()`.
* @param string $context The plugin context.
*
* @return array $links
* @noinspection PhpUnusedParameterInspection
* @noinspection HtmlUnknownTarget
* @noinspection PhpMissingParamTypeInspection
*/
public function settings_link( $links, $plugin_file, $plugin_data, $context ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
$custom['wpforms-pro'] = sprintf(
'<a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer"
style="color: #00a32a; font-weight: 700;"
onmouseover="this.style.color=\'#008a20\';"
onmouseout="this.style.color=\'#00a32a\';"
>%3$s</a>',
esc_url(
wpforms_admin_upgrade_link(
'all-plugins',
'Get WPForms Pro'
)
),
esc_attr__( 'Upgrade to WPForms Pro', 'wpforms-lite' ),
esc_html__( 'Get WPForms Pro', 'wpforms-lite' )
);
$custom['wpforms-settings'] = sprintf(
'<a href="%s" aria-label="%s">%s</a>',
esc_url(
add_query_arg(
[ 'page' => 'wpforms-settings' ],
admin_url( 'admin.php' )
)
),
esc_attr__( 'Go to WPForms Settings page', 'wpforms-lite' ),
esc_html__( 'Settings', 'wpforms-lite' )
);
$custom['wpforms-docs'] = sprintf(
'<a href="%1$s" aria-label="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>',
esc_url(
add_query_arg(
[
'utm_content' => 'Documentation',
'utm_campaign' => 'liteplugin',
'utm_medium' => 'all-plugins',
'utm_source' => 'WordPress',
],
'https://wpforms.com/docs/'
)
),
esc_attr__( 'Read the documentation', 'wpforms-lite' ),
esc_html__( 'Docs', 'wpforms-lite' )
);
return array_merge( $custom, (array) $links );
}
/**
* Determine which submenu item to show (rotation).
*
* Current behavior:
* - Show item until the plugin has been activated for 7 or more days.
* - Once 7+ days have passed since activation - show next item.
* - Once the last item has been active for more than 7 days, always display the first item (WP Consent page).
*
* @since 1.9.8.6
*
* @return array|null { menu_title, page_title, menu_slug } or null to show none.
*/
private function get_rotating_submenu(): ?array {
$items = $this->get_rotation_items();
$now = time();
$defaults = [
'label' => '',
'menu_slug' => '',
'slug' => '',
'plugin_file' => '',
];
// Find the first item that should be displayed.
foreach ( $items as $item ) {
$item = wp_parse_args( $item, $defaults );
$label = (string) $item['label'];
$menu_slug = (string) $item['menu_slug'];
$plugin_slug = (string) $item['slug'];
if ( empty( $label ) || empty( $menu_slug ) ) {
continue; // Skip misconfigured items.
}
$timestamp = $this->get_promo_plugin_activation_timestamp( $plugin_slug );
// Show if a plugin has never activated or within 7 days of activation.
$within = $timestamp === 0 || ( $now - $timestamp ) < 7 * DAY_IN_SECONDS;
if ( $within ) {
return [
'menu_title' => $label,
'page_title' => $label,
'menu_slug' => $menu_slug,
];
}
}
// If all items are considered "complete", return the first one (cycle back).
$first = $items[0];
$label = $first['label'];
$menu_slug = $first['menu_slug'];
if ( ! empty( $label ) && ! empty( $menu_slug ) ) {
return [
'menu_title' => $label,
'page_title' => $label,
'menu_slug' => $menu_slug,
];
}
return null;
}
/**
* List of rotating plugins files.
*
* @since 1.9.8.6
*
* @return array
*/
private function get_rotation_plugins(): array {
return [
'wpconsent-cookies-banner-privacy-suite/wpconsent.php' => 'wpconsent',
'wpconsent-premium/wpconsent-premium.php' => 'wpconsent',
'sugar-calendar-lite/sugar-calendar-lite.php' => 'sugar-calendar',
'sugar-calendar/sugar-calendar.php' => 'sugar-calendar',
'duplicator/duplicator.php' => 'duplicator',
'duplicator-pro/duplicator-pro.php' => 'duplicator',
'uncanny-automator/uncanny-automator.php' => 'uncanny-automator',
'uncanny-automator-pro/uncanny-automator-pro.php' => 'uncanny-automator',
];
}
/**
* Record the activation time of a rotation plugin.
*
* @since 1.9.8.6
*
* @param string $plugin Path to the plugin file relative to the plugins' directory.
* @param bool $network_wide Whether the plugin is being activated network wide.
*/
public function activated_rotation_plugin( string $plugin, bool $network_wide ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
$rotation_plugins = $this->get_rotation_plugins();
$plugin_key = $rotation_plugins[ $plugin ] ?? '';
if ( empty( $plugin_key ) ) {
return;
}
$activated_plugins = (array) get_option( 'wpforms_rotation_activated_plugins', [] );
// Skip if already recorded.
if ( isset( $activated_plugins[ $plugin_key ] ) ) {
return;
}
$activated_plugins[ $plugin_key ] = time();
update_option( 'wpforms_rotation_activated_plugins', $activated_plugins );
}
/**
* Editable list of rotating submenu items.
*
* @since 1.9.8.6
*
* @return array
*/
private function get_rotation_items(): array {
return [
[
'label' => esc_html__( 'Privacy Compliance', 'wpforms-lite' ),
'menu_slug' => WPForms\Admin\Pages\PrivacyCompliance::SLUG,
'slug' => 'wpconsent',
'plugin_file' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php',
],
[
'label' => esc_html__( 'Events', 'wpforms-lite' ),
'menu_slug' => WPForms\Admin\Pages\SugarCalendar::SLUG,
'slug' => 'sugar-calendar',
'plugin_file' => 'sugar-calendar-lite/sugar-calendar-lite.php',
],
[
'label' => esc_html__( 'Backups', 'wpforms-lite' ),
'menu_slug' => WPForms\Admin\Pages\Duplicator::SLUG,
'slug' => 'duplicator',
'plugin_file' => 'duplicator/duplicator.php',
],
[
'label' => esc_html__( 'Automation', 'wpforms-lite' ),
'menu_slug' => WPForms\Admin\Pages\UncannyAutomator::SLUG,
'slug' => 'uncanny-automator',
'plugin_file' => 'uncanny-automator/uncanny-automator.php',
],
];
}
/**
* Get the HTML for the "NEW!" badge.
*
* @since 1.7.8
*
* @return string
*/
private function get_new_badge_html(): string {
return '<span class="wpforms-menu-new">&nbsp;NEW!</span>';
}
/**
* Output inline styles for the admin menu.
*
* @since 1.7.8
*/
public function admin_menu_styles(): void {
$styles = '#adminmenu .wpforms-menu-new { display: inline-block; color: #f18500; vertical-align: super; font-size: 9px; font-weight: 600; padding-inline-start: 2px; }';
if ( ! wpforms()->is_pro() ) {
$styles .= 'a.wpforms-sidebar-upgrade-pro { background-color: #00a32a !important; color: #fff !important; font-weight: 600 !important; }';
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
printf( '<style>%s</style>', $styles );
}
/**
* Get a timestamp.
*
* @since 1.9.8.6
*
* @param string $slug Slug of the plugin.
*
* @return int
*/
private function get_promo_plugin_activation_timestamp( string $slug ): int {
$activated_plugins = (array) get_option( 'wpforms_rotation_activated_plugins', [] );
return isset( $activated_plugins[ $slug ] ) ? (int) $activated_plugins[ $slug ] : 0;
}
}
new WPForms_Admin_Menu();

View File

@@ -0,0 +1,139 @@
<?php
/**
* Admin notices, on the fly.
*
* @example
* WPForms_Admin_Notice::success( 'All is good!' );
*
* @example
* WPForms_Admin_Notice::warning( 'Do something please.' );
*
* @since 1.3.9
* @deprecated 1.7.2
*/
class WPForms_Admin_Notice {
/**
* Single instance holder.
*
* @since 1.3.9
* @var mixed
*/
private static $_instance = null;
/**
* Added notices.
*
* @since 1.3.9
* @var array
*/
public $notices = [];
/**
* Get the instance.
*
* @since 1.3.9
* @return WPForms_Admin_Notice
*/
public static function getInstance() {
if ( self::$_instance === null ) {
self::$_instance = new WPForms_Admin_Notice();
}
return self::$_instance;
}
/**
* Hook when called.
*
* @since 1.3.9
*/
public function __construct() {
_deprecated_function( __METHOD__, '1.7.2 of the WPForms plugin' );
add_action( 'admin_notices', [ &$this, 'display' ] );
}
/**
* Display the notices.
*
* @since 1.3.9
*/
public function display() {
// At least one WPForms capability is necessary to see admin notices.
if ( ! wpforms_current_user_can( 'any' ) ) {
return;
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo implode( ' ', $this->notices );
}
/**
* Add notice to instance property.
*
* @since 1.3.9
*
* @param string $message Message to display.
* @param string $type Type of the notice (default: '').
*/
public static function add( $message, $type = '' ) {
_deprecated_function( __METHOD__, '1.7.2 of the WPForms plugin' );
$instance = self::getInstance();
$id = 'wpforms-notice-' . ( count( $instance->notices ) + 1 );
$type = ! empty( $type ) ? 'notice-' . $type : '';
$notice = sprintf( '<div class="notice wpforms-notice %s" id="%s">%s</div>', $type, $id, wpautop( $message ) );
$instance->notices[] = $notice;
}
/**
* Add Info notice.
*
* @since 1.3.9
*
* @param string $message Message to display.
*/
public static function info( $message ) {
self::add( $message, 'info' );
}
/**
* Add Error notice.
*
* @since 1.3.9
*
* @param string $message Message to display.
*/
public static function error( $message ) {
self::add( $message, 'error' );
}
/**
* Add Success notice.
*
* @since 1.3.9
*
* @param string $message Message to display.
*/
public static function success( $message ) {
self::add( $message, 'success' );
}
/**
* Add Warning notice.
*
* @since 1.3.9
*
* @param string $message Message to display.
*/
public static function warning( $message ) {
self::add( $message, 'warning' );
}
}

View File

@@ -0,0 +1,346 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Admin\Notice;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Ask for some love.
*
* @since 1.3.2
*/
class WPForms_Review {
/**
* List of page slugs.
*
* Some 3rd-party addons may use page slugs that start with `wpforms-` (e.g., WPForms Views),
* so we should define exact pages we want the footer to be displayed on instead
* of targeting any page that looks like a WPForms page.
*
* @since 1.9.8.6
*
* @var array
*/
private const PAGES = [
'wpforms-about',
'wpforms-addons',
'wpforms-community',
'wpforms-entries',
'wpforms-overview',
'wpforms-payments',
'wpforms-settings',
'wpforms-smtp',
'wpforms-templates',
'wpforms-tools',
'wpforms-wpconsent',
'wpforms-sugar-calendar',
'wpforms-duplicator',
'wpforms-uncanny-automator',
];
/**
* Primary class constructor.
*
* @since 1.3.2
*/
public function __construct() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.9.4
*
* @return void
*/
private function hooks(): void {
// Admin notice requesting review.
add_action( 'admin_init', [ $this, 'review_request' ] );
// Admin footer text.
add_filter( 'admin_footer_text', [ $this, 'admin_footer' ], 1, 2 );
add_action( 'in_admin_footer', [ $this, 'promote_wpforms' ] );
}
/**
* Add admin notices as needed for reviews.
*
* @since 1.3.2
*/
public function review_request() {
if (
// Only consider showing the review request to admin users.
! is_super_admin() ||
// If the user has opted out of product announcement notifications, don't display the review request.
wpforms_setting( 'hide-announcements' ) ||
// Do not show the review request on Addons page.
wpforms_is_admin_page( 'addons' )
) {
return;
}
// Verify that we can do a check for reviews.
$notices = (array) get_option( 'wpforms_admin_notices', [] );
$time = time();
$load = false;
if ( empty( $notices['review_request'] ) ) {
$notices['review_request'] = [
'time' => $time,
'dismissed' => false,
];
update_option( 'wpforms_admin_notices', $notices );
return;
}
// Check if it has been dismissed or not.
if (
isset( $notices['review_request']['dismissed'], $notices['review_request']['time'] ) &&
! $notices['review_request']['dismissed'] &&
( ( $notices['review_request']['time'] + DAY_IN_SECONDS ) <= $time )
) {
$load = true;
}
// If we cannot load, return early.
if ( ! $load ) {
return;
}
// The Logic is slightly different depending on what's at our disposal.
if ( class_exists( 'WPForms_Entry_Handler', false ) && wpforms()->is_pro() ) {
$this->review();
} else {
$this->review_lite();
}
}
/**
* Maybe show review request.
*
* @since 1.3.9
*/
public function review() {
// Fetch total entries.
$entry_handler = wpforms()->obj( 'entry' );
$entries = $entry_handler ? $entry_handler->get_entries( [ 'number' => 50 ], true ) : 0;
// Only show review request if the site has collected at least 50 entries.
if ( empty( $entries ) || $entries < 50 ) {
return;
}
ob_start();
// We have a candidate! Output a review message.
$this->review_content();
Notice::info(
ob_get_clean(),
[
'dismiss' => Notice::DISMISS_GLOBAL,
'slug' => 'review_request',
'autop' => false,
'class' => 'wpforms-review-notice',
]
);
}
/**
* Maybe show Lite review request.
*
* @since 1.3.9
*/
public function review_lite() {
// Do not show the review request on Entries pages.
if ( wpforms_is_admin_page( 'entries' ) ) {
return;
}
// Fetch when plugin was initially installed.
$activated = (array) get_option( 'wpforms_activated', [] );
if ( ! empty( $activated['lite'] ) ) {
// Only continue if the plugin has been installed for at least 14 days.
if ( ( $activated['lite'] + ( DAY_IN_SECONDS * 14 ) ) > time() ) {
return;
}
} else {
$activated['lite'] = time();
update_option( 'wpforms_activated', $activated );
return;
}
// Only proceed with displaying if the user created at least one form.
$form_count = wp_count_posts( 'wpforms' );
if ( empty( $form_count->publish ) ) {
return;
}
// Check if the Constant Contact notice is displaying.
$cc = get_option( 'wpforms_constant_contact', false );
// If it's displaying don't ask for review until they configure CC or
// dismiss the notice.
if ( $cc ) {
return;
}
ob_start();
// We have a candidate! Output a review message.
$this->review_content();
Notice::info(
ob_get_clean(),
[
'dismiss' => Notice::DISMISS_GLOBAL,
'slug' => 'review_lite_request',
'autop' => false,
'class' => 'wpforms-review-notice',
]
);
}
/**
* Output the review content.
*
* @since 1.8.7.2
*/
private function review_content(): void {
?>
<p><?php esc_html_e( 'Hey, there! It looks like you enjoy creating forms with WPForms. Would you do us a favor and take a few seconds to give us a 5-star review? Wed love to hear from you.', 'wpforms-lite' ); ?></p>
<p>
<a
href="<?php echo wpforms_wp_org_review_link(); ?>" class="wpforms-notice-dismiss wpforms-review-out"
target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Ok, you deserve it', 'wpforms-lite' ); ?>
</a>
<br>
<a href="#" class="wpforms-notice-dismiss" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Nope, maybe later', 'wpforms-lite' ); ?>
</a>
<br>
<a href="#" class="wpforms-notice-dismiss" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'I already did', 'wpforms-lite' ); ?>
</a>
</p>
<?php
}
/**
* When a user is on a WPForms related admin page, display footer text
* that graciously asks them to rate us.
*
* @since 1.3.2
*
* @param string|mixed $text Footer text.
*
* @return string
* @noinspection HtmlUnknownTarget
*/
public function admin_footer( $text ): string {
global $current_screen;
$text = (string) $text;
if ( ! empty( $current_screen->id ) && strpos( $current_screen->id, 'wpforms' ) !== false ) {
$url = wpforms_wp_org_review_link();
$text = sprintf(
wp_kses( /* translators: $1$s - WPForms plugin name, $2$s - WP.org review link, $3$s - WP.org review link. */
__( 'Please rate %1$s <a href="%2$s" target="_blank" rel="noopener noreferrer">&#9733;&#9733;&#9733;&#9733;&#9733;</a> on <a href="%3$s" target="_blank" rel="noopener">WordPress.org</a> to help us spread the word.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
'<strong>WPForms</strong>',
$url,
$url
);
}
return $text;
}
/**
* Pre-footer promotion block, displayed on all WPForms admin pages except Form Builder.
*
* @since 1.8.0
*/
public function promote_wpforms() {
// phpcs:ignore WordPress.Security.NonceVerification
$current_page = isset( $_REQUEST['page'] ) ? sanitize_key( $_REQUEST['page'] ) : '';
if ( ! in_array( $current_page, self::PAGES, true ) ) {
return;
}
$links = [
[
'url' => wpforms()->is_pro() ?
wpforms_utm_link(
'https://wpforms.com/account/support/',
'Plugin Footer',
'Contact Support'
) : 'https://wordpress.org/support/plugin/wpforms-lite/',
'text' => __( 'Support', 'wpforms-lite' ),
'target' => '_blank',
],
[
'url' => wpforms_utm_link(
'https://wpforms.com/docs/',
'Plugin Footer',
'Plugin Documentation'
),
'text' => __( 'Docs', 'wpforms-lite' ),
'target' => '_blank',
],
[
'url' => 'https://www.facebook.com/groups/wpformsvip/',
'text' => __( 'VIP Circle', 'wpforms-lite' ),
'target' => '_blank',
],
[
'url' => admin_url( 'admin.php?page=wpforms-about' ),
'text' => __( 'Free Plugins', 'wpforms-lite' ),
],
];
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'admin/promotion',
[
'title' => __( 'Made with ♥ by the WPForms Team', 'wpforms-lite' ),
'links' => $links,
],
true
);
}
}
new WPForms_Review();

View File

@@ -0,0 +1,820 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Admin\Notice;
use WPForms\Migrations\Migrations as LiteMigration;
use WPForms\Pro\Migrations\Migrations;
use WPForms\Admin\Settings\Payments;
/**
* Settings class.
*
* @since 1.0.0
*/
class WPForms_Settings {
/**
* The current active tab.
*
* @since 1.3.9
*
* @var string
*/
public $view;
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.5.4
*/
private function hooks() {
// Maybe load settings page.
add_action( 'admin_init', [ $this, 'init' ] );
}
/**
* Determine if the user is viewing the settings page, if so, party on.
*
* @since 1.0.0
*/
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Only load if we are actually on the settings page.
if ( ! wpforms_is_admin_page( 'settings' ) ) {
return;
}
// Include API callbacks and functions.
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/settings-api.php';
// Show downgraded notice.
$this->maybe_display_downgraded_notice();
// Watch for triggered save.
$this->save_settings();
// Determine the current active settings tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'general';
$this->modify_url();
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
// Monitor custom tables.
$this->monitor_custom_tables();
// Hook for addons.
do_action( 'wpforms_settings_init', $this );
}
/**
* Remove `wpforms-integration` query arg from URL.
* The `wpforms-integration` query arg is used to highlight a specific provider on the Integrations page.
*
* @since 1.8.5.4
*/
private function modify_url() {
if ( $this->view !== 'integrations' ) {
return;
}
$_SERVER['REQUEST_URI'] = remove_query_arg( 'wpforms-integration' );
}
/**
* Display admin notice about using a downgraded version of WPForms.
*
* @since 1.8.5.4
*/
private function maybe_display_downgraded_notice() {
if ( ! $this->is_downgraded_version() ) {
return;
}
$notice = sprintf(
wp_kses( /* translators: %1$s - WPForms.com doc page URL; %2$s - button text. */
__(
'It looks like you\'ve downgraded to an older version of WPForms. We recommend always using the latest version as some features may not function as expected in older versions. <a href="%1$s" target="_blank" rel="noopener">%2$s</a>',
'wpforms-lite'
),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/why-you-should-always-use-the-latest-version-of-wpforms/', 'Settings', 'Downgrade notice' ) ),
esc_html__( 'Learn More', 'wpforms-lite' )
);
Notice::warning(
$notice,
[
'dismiss' => Notice::DISMISS_GLOBAL,
'slug' => 'wpforms_is_downgraded',
]
);
}
/**
* Check if plugin was downgraded.
*
* @since 1.8.5.4
*
* @return bool
*/
private function is_downgraded_version(): bool {
// Get all installed versions.
$installed_versions = wpforms()->is_pro() ?
(array) get_option( Migrations::MIGRATED_OPTION_NAME, [] ) :
(array) get_option( LiteMigration::MIGRATED_OPTION_NAME, [] );
// Get the most recent installed version.
$db_latest = array_keys( $installed_versions )[ count( $installed_versions ) - 1 ];
// Check if downgrade happened.
return version_compare( $db_latest, WPFORMS_VERSION, '>' );
}
/**
* Sanitize and save settings.
*
* @since 1.3.9
*/
public function save_settings() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded
// Check nonce and other various security checks.
if ( ! isset( $_POST['wpforms-settings-submit'] ) || empty( $_POST['nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wpforms-settings-nonce' ) ) {
return;
}
if ( ! wpforms_current_user_can() ) {
return;
}
if ( empty( $_POST['view'] ) ) {
return;
}
$current_view = sanitize_key( $_POST['view'] );
// Get registered fields and current settings.
$fields = $this->get_registered_settings( $current_view );
$settings = get_option( 'wpforms_settings', [] );
$original_settings = $settings;
// Views excluded from saving list.
$exclude_views = apply_filters( 'wpforms_settings_exclude_view', [], $fields, $settings );
if ( is_array( $exclude_views ) && in_array( $current_view, $exclude_views, true ) ) {
// Run a custom save processing for excluded views.
do_action( 'wpforms_settings_custom_process', $current_view, $fields, $settings );
return;
}
if ( empty( $fields ) || ! is_array( $fields ) ) {
return;
}
// Sanitize and prep each field.
foreach ( $fields as $id => $field ) {
// Certain field types are not valid for saving and are skipped.
$exclude = apply_filters( 'wpforms_settings_exclude_type', [ 'content', 'license', 'providers' ] );
if ( empty( $field['type'] ) || in_array( $field['type'], $exclude, true ) ) {
continue;
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$value = isset( $_POST[ $id ] ) ? wp_unslash( $_POST[ $id ] ) : false;
$value_prev = isset( $settings[ $id ] ) ? $settings[ $id ] : false;
// Trim all string values.
if ( is_string( $value ) ) {
$value = trim( $value );
}
// Custom filter can be provided for sanitizing, otherwise use defaults.
if ( ! empty( $field['filter'] ) && is_callable( $field['filter'] ) ) {
$value = call_user_func( $field['filter'], $value, $id, $field, $value_prev );
} else {
switch ( $field['type'] ) {
case 'checkbox':
case 'toggle':
$value = (bool) $value;
break;
case 'image':
$value = esc_url_raw( $value );
break;
case 'color':
$value = wpforms_sanitize_hex_color( $value );
break;
case 'color_scheme':
$value = array_map( 'wpforms_sanitize_hex_color', $value );
break;
case 'number':
$value = (float) $value;
break;
case 'radio':
case 'select':
$value = $this->validate_field_with_options( $field, $value, $value_prev );
break;
case 'text':
default:
$value = sanitize_text_field( $value );
break;
}
}
// Add to settings.
$settings[ $id ] = $value;
}
// Save settings.
wpforms_update_settings( $settings );
Notice::success( esc_html__( 'Settings were successfully saved.', 'wpforms-lite' ) );
if ( isset( $original_settings['currency'], $settings['currency'] ) && $original_settings['currency'] !== $settings['currency'] ) {
Notice::warning( esc_html__( "You've changed your currency. Please double-check the product prices in your forms and verify that they're correct.", 'wpforms-lite' ) );
}
}
/**
* Enqueue assets for the settings page.
*
* @since 1.0.0
*/
public function enqueues() {
do_action( 'wpforms_settings_enqueue' );
}
/**
* Return registered settings tabs.
*
* @since 1.3.9
*
* @return array
*/
public function get_tabs() {
$tabs = [
'general' => [
'name' => esc_html__( 'General', 'wpforms-lite' ),
'form' => true,
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
'validation' => [
'name' => esc_html__( 'Validation', 'wpforms-lite' ),
'form' => true,
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
'integrations' => [
'name' => esc_html__( 'Integrations', 'wpforms-lite' ),
'form' => false,
'submit' => false,
],
'geolocation' => [
'name' => esc_html__( 'Geolocation', 'wpforms-lite' ),
'form' => false,
'submit' => false,
],
'misc' => [
'name' => esc_html__( 'Misc', 'wpforms-lite' ),
'form' => true,
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
];
return apply_filters( 'wpforms_settings_tabs', $tabs );
}
/**
* Output tab navigation area.
*
* @since 1.3.9
*/
public function tabs() {
$tabs = $this->get_tabs();
echo '<ul class="wpforms-admin-tabs">';
foreach ( $tabs as $id => $tab ) {
$active = $id === $this->view ? 'active' : '';
$link = add_query_arg( 'view', $id, admin_url( 'admin.php?page=wpforms-settings' ) );
echo '<li><a href="' . esc_url_raw( $link ) . '" class="' . esc_attr( $active ) . '">' . esc_html( $tab['name'] ) . '</a></li>';
}
echo '</ul>';
}
/**
* Return all the default registered settings fields.
*
* @since 1.3.9
*
* @param string $view The current view (tab) on Settings page.
*
* @return array
*/
public function get_registered_settings( $view = '' ) {
$defaults = [
// General Settings tab.
'general' => [
'license-heading' => [
'id' => 'license-heading',
'content' => '<h4>' . esc_html__( 'License', 'wpforms-lite' ) . '</h4><p>' . esc_html__( 'Your license key provides access to updates and addons.', 'wpforms-lite' ) . '</p>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading' ],
],
'license-key' => [
'id' => 'license-key',
'name' => esc_html__( 'License Key', 'wpforms-lite' ),
'type' => 'license',
],
'general-heading' => [
'id' => 'general-heading',
'content' => '<h4>' . esc_html__( 'General', 'wpforms-lite' ) . '</h4>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'disable-css' => [
'id' => 'disable-css',
'name' => esc_html__( 'Include Form Styling', 'wpforms-lite' ),
'desc' => sprintf(
wp_kses( /* translators: %s - WPForms.com form styling setting URL. */
__( 'Determines which CSS files to load and use for the site. "Base and Form Theme Styling" is recommended, unless you are experienced with CSS or instructed by support to change settings. <a href="%s" target="_blank" rel="noopener noreferrer" class="wpforms-learn-more">Learn More</a>', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
'class' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-choose-an-include-form-styling-setting/', 'settings-license', 'Form Styling Documentation' ) )
),
'type' => 'select',
'choicesjs' => true,
'default' => 1,
'options' => [
1 => esc_html__( 'Base and form theme styling', 'wpforms-lite' ),
2 => esc_html__( 'Base styling only', 'wpforms-lite' ),
3 => esc_html__( 'No styling', 'wpforms-lite' ),
],
],
'global-assets' => [
'id' => 'global-assets',
'name' => esc_html__( 'Load Assets Globally', 'wpforms-lite' ),
'desc' => esc_html__( 'Load WPForms assets site-wide. Only check if your site is having compatibility issues or instructed to by support.', 'wpforms-lite' ),
'type' => 'toggle',
'status' => true,
],
'gdpr-heading' => [
'id' => 'GDPR',
'content' => '<h4>' . esc_html__( 'GDPR', 'wpforms-lite' ) . '</h4>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'gdpr' => [
'id' => 'gdpr',
'name' => esc_html__( 'GDPR Enhancements', 'wpforms-lite' ),
'desc' => sprintf(
wp_kses( /* translators: %s - WPForms.com GDPR documentation URL. */
__( 'Enable GDPR related features and enhancements. <a href="%s" target="_blank" rel="noopener noreferrer" class="wpforms-learn-more">Learn More</a>', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
'class' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-create-gdpr-compliant-forms/', 'settings-license', 'GDPR Documentation' ) )
),
'type' => 'toggle',
'status' => true,
],
],
// Validation messages settings tab.
'validation' => [
'validation-heading' => [
'id' => 'validation-heading',
'content' => sprintf( /* translators: %s - WPForms.com smart tags documentation URL. */
esc_html__( '%1$s These messages are displayed to the users as they fill out a form in real-time. Messages can include plain text and/or %2$sSmart Tags%3$s.', 'wpforms-lite' ),
'<h4>' . esc_html__( 'Validation Messages', 'wpforms-lite' )
. '</h4><p>',
'<a href="' . esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-use-smart-tags-in-wpforms/#smart-tags', 'Settings - Validation', 'Smart Tag Documentation' ) ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
),
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading' ],
],
'validation-required' => [
'id' => 'validation-required',
'name' => esc_html__( 'Required', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'This field is required.', 'wpforms-lite' ),
],
'validation-email' => [
'id' => 'validation-email',
'name' => esc_html__( 'Email', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Please enter a valid email address.', 'wpforms-lite' ),
],
'validation-email-suggestion' => [
'id' => 'validation-email-suggestion',
'name' => esc_html__( 'Email Suggestion', 'wpforms-lite' ),
'type' => 'text',
'default' => sprintf( /* translators: %s - suggested email address. */
esc_html__( 'Did you mean %s?', 'wpforms-lite' ),
'{suggestion}'
),
],
'validation-email-restricted' => [
'id' => 'validation-email-restricted',
'name' => esc_html__( 'Email Restricted', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'This email address is not allowed.', 'wpforms-lite' ),
],
'validation-number' => [
'id' => 'validation-number',
'name' => esc_html__( 'Number', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Please enter a valid number.', 'wpforms-lite' ),
],
'validation-number-positive' => [
'id' => 'validation-number-positive',
'name' => esc_html__( 'Number Positive', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Please enter a valid positive number.', 'wpforms-lite' ),
],
'validation-minimum-price' => [
'id' => 'validation-minimum-price',
'name' => esc_html__( 'Minimum Price', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Amount entered is less than the required minimum.', 'wpforms-lite' ),
],
'validation-confirm' => [
'id' => 'validation-confirm',
'name' => esc_html__( 'Confirm Value', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Field values do not match.', 'wpforms-lite' ),
],
'validation-inputmask-incomplete' => [
'id' => 'validation-inputmask-incomplete',
'name' => esc_html__( 'Input Mask Incomplete', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Please fill out the field in required format.', 'wpforms-lite' ),
],
'validation-check-limit' => [
'id' => 'validation-check-limit',
'name' => esc_html__( 'Checkbox Selection Limit', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'You have exceeded the number of allowed selections: {#}.', 'wpforms-lite' ),
],
'validation-character-limit' => [
'id' => 'validation-character-limit',
'name' => esc_html__( 'Character Limit', 'wpforms-lite' ),
'type' => 'text',
'default' => sprintf( /* translators: %1$s - characters limit, %2$s - number of characters left. */
esc_html__( 'Limit is %1$s characters. Characters remaining: %2$s.', 'wpforms-lite' ),
'{limit}',
'{remaining}'
),
],
'validation-word-limit' => [
'id' => 'validation-word-limit',
'name' => esc_html__( 'Word Limit', 'wpforms-lite' ),
'type' => 'text',
'default' => sprintf( /* translators: %1$s - words limit, %2$s - number of words left. */
esc_html__( 'Limit is %1$s words. Words remaining: %2$s.', 'wpforms-lite' ),
'{limit}',
'{remaining}'
),
],
'validation-requiredpayment' => [
'id' => 'validation-requiredpayment',
'name' => esc_html__( 'Payment Required', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Payment is required.', 'wpforms-lite' ),
],
'validation-creditcard' => [
'id' => 'validation-creditcard',
'name' => esc_html__( 'Credit Card', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Please enter a valid credit card number.', 'wpforms-lite' ),
],
'validation-min' => [
'id' => 'validation-min',
'name' => esc_html__( 'Minimum Value', 'wpforms-lite' ),
'type' => 'text',
'default' => sprintf( /* translators: %s - value to compare with. */
esc_html__( 'Please enter a value greater than or equal to %s.', 'wpforms-lite' ),
'{value}'
),
],
'validation-max' => [
'id' => 'validation-max',
'name' => esc_html__( 'Maximum Value', 'wpforms-lite' ),
'type' => 'text',
'default' => sprintf( /* translators: %s - value to compare with. */
esc_html__( 'Please enter a value less than or equal to %s.', 'wpforms-lite' ),
'{value}'
),
],
],
// Provider integrations settings tab.
'integrations' => [
'integrations-heading' => [
'id' => 'integrations-heading',
'content' => '<h4>' . esc_html__( 'Integrations', 'wpforms-lite' ) . '</h4><p>' . esc_html__( 'Manage integrations with popular providers such as Constant Contact, Mailchimp, Zapier, and more.', 'wpforms-lite' ) . '</p>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading' ],
],
'integrations-providers' => [
'id' => 'integrations-providers',
'content' => '<h4>' . esc_html__( 'Integrations', 'wpforms-lite' ) . '</h4><p>' . esc_html__( 'Manage integrations with popular providers such as Constant Contact, Mailchimp, Zapier, and more.', 'wpforms-lite' ) . '</p>',
'type' => 'providers',
'wrap' => 'none',
],
],
// Misc. settings tab.
'misc' => [
'misc-heading' => [
'id' => 'misc-heading',
'content' => '<h4>' . esc_html__( 'Miscellaneous', 'wpforms-lite' ) . '</h4>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'delete-spam-entries' => [
'id' => 'delete-spam-entries',
'name' => esc_html__( 'Delete Spam Entries', 'wpforms-lite' ),
'desc' => esc_html__( 'Choose the frequency spam entries are automatically deleted.', 'wpforms-lite' ),
'type' => 'select',
'default' => 90,
'is_hidden' => ! $this->show_spam_entries_setting(),
'options' => [
7 => esc_html__( '7 Days', 'wpforms-lite' ),
15 => esc_html__( '15 Days', 'wpforms-lite' ),
30 => esc_html__( '30 Days', 'wpforms-lite' ),
90 => esc_html__( '90 Days', 'wpforms-lite' ),
],
],
'hide-announcements' => [
'id' => 'hide-announcements',
'name' => esc_html__( 'Hide Announcements', 'wpforms-lite' ),
'desc' => esc_html__( 'Hide plugin announcements and update details.', 'wpforms-lite' ),
'type' => 'toggle',
'status' => true,
],
'hide-admin-bar' => [
'id' => 'hide-admin-bar',
'name' => esc_html__( 'Hide Admin Bar Menu', 'wpforms-lite' ),
'desc' => esc_html__( 'Hide the WPForms admin bar menu.', 'wpforms-lite' ),
'type' => 'toggle',
'status' => true,
],
'uninstall-data' => [
'id' => 'uninstall-data',
'name' => esc_html__( 'Uninstall WPForms', 'wpforms-lite' ),
'desc' => $this->get_uninstall_desc(),
'type' => 'toggle',
'status' => true,
],
],
];
$defaults = apply_filters( 'wpforms_settings_defaults', $defaults );
// Take care of invalid views.
if ( ! empty( $view ) && ! array_key_exists( $view, $defaults ) ) {
$this->view = key( $defaults );
return reset( $defaults );
}
return empty( $view ) ? $defaults : $defaults[ $view ];
}
/**
* Get uninstall description.
*
* @since 1.8.4
*
* @return string
*/
private function get_uninstall_desc() {
$desc = esc_html__( 'Remove ALL WPForms data upon plugin deletion.', 'wpforms-lite' );
$warning = esc_html__( 'All forms and settings will be unrecoverable.', 'wpforms-lite' );
if ( wpforms()->is_pro() ) {
$desc = esc_html__( 'Remove ALL WPForms data upon plugin deletion.', 'wpforms-lite' );
$warning = esc_html__( 'All forms, entries, and uploaded files will be unrecoverable.', 'wpforms-lite' );
}
return sprintf( '%s <span class="wpforms-settings-warning">%s</span>', $desc, $warning );
}
/**
* Return array containing markup for all the appropriate settings fields.
*
* @since 1.3.9
*
* @param string $view View slug.
*
* @return array
*/
public function get_settings_fields( $view = '' ) {
$fields = [];
$settings = $this->get_registered_settings( $view );
foreach ( $settings as $id => $args ) {
$fields[ $id ] = wpforms_settings_output_field( $args );
}
return apply_filters( 'wpforms_settings_fields', $fields, $view );
}
/**
* Build the output for the plugin settings page.
*
* @since 1.0.0
*/
public function output() {
$tabs = $this->get_tabs();
$fields = $this->get_settings_fields( $this->view );
?>
<div id="wpforms-settings" class="wrap wpforms-admin-wrap">
<?php $this->tabs(); ?>
<h1 class="wpforms-h1-placeholder"></h1>
<?php
if ( wpforms()->is_pro() && class_exists( 'WPForms_License', false ) ) {
wpforms()->obj( 'license' )->notices( true );
}
?>
<div class="wpforms-admin-content wpforms-admin-settings wpforms-admin-content-<?php echo esc_attr( $this->view ); ?> wpforms-admin-settings-<?php echo esc_attr( $this->view ); ?>">
<?php
// Some tabs rely on AJAX and do not contain a form, such as Integrations.
if ( ! empty( $tabs[ $this->view ]['form'] ) ) :
?>
<form class="wpforms-admin-settings-form" method="post">
<input type="hidden" name="action" value="update-settings">
<input type="hidden" name="view" value="<?php echo esc_attr( $this->view ); ?>">
<input type="hidden" name="nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpforms-settings-nonce' ) ); ?>">
<?php endif; ?>
<?php do_action( 'wpforms_admin_settings_before', $this->view, $fields ); ?>
<?php
foreach ( $fields as $field ) {
echo $field; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<?php if ( ! empty( $tabs[ $this->view ]['submit'] ) ) : ?>
<p class="submit">
<button type="submit" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" name="wpforms-settings-submit">
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $tabs[ $this->view ]['submit'];
?>
</button>
</p>
<?php endif; ?>
<?php do_action( 'wpforms_admin_settings_after', $this->view, $fields ); ?>
<?php if ( ! empty( $tabs[ $this->view ]['form'] ) ) : ?>
</form>
<?php endif; ?>
</div>
</div>
<?php
}
/**
* Monitor that all custom tables exist and recreate if missing.
* This logic works on Settings > General page only.
*
* @since 1.6.2
*/
public function monitor_custom_tables() {
// Proceed on Settings plugin admin area page only.
if ( $this->view !== 'general' ) {
return;
}
/*
* Tasks Meta table.
*/
$meta = new \WPForms\Tasks\Meta();
if ( $meta->table_exists() ) {
return;
}
$meta->create_table();
}
/**
* Validate radio and select fields.
*
* @since 1.7.5.5
*
* @param array $field Field.
* @param mixed $value Value.
* @param mixed $value_prev Previous value.
*
* @return mixed
*/
private function validate_field_with_options( $field, $value, $value_prev ) {
$value = sanitize_text_field( $value );
if ( isset( $field['options'] ) && array_key_exists( $value, $field['options'] ) ) {
return $value;
}
return isset( $field['default'] ) ? $field['default'] : $value_prev;
}
/**
* Check if spam entries setting should be shown.
*
* Show setting only if WPFORMS_DELETE_SPAM_ENTRIES is not defined, and the plugin is Pro.
*
* @since 1.9.1
*
* @return bool
*/
private function show_spam_entries_setting(): bool {
return ! defined( 'WPFORMS_DELETE_SPAM_ENTRIES' ) && wpforms()->is_pro();
}
}
new WPForms_Settings();

View File

@@ -0,0 +1,394 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/** @noinspection PhpIllegalPsrClassPathInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Migrations\Base as MigrationsBase;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Welcome page class.
*
* This page is shown when the plugin is activated.
*
* @since 1.0.0
*/
class WPForms_Welcome {
/**
* Hidden welcome page slug.
*
* @since 1.5.6
*/
private const SLUG = 'wpforms-getting-started';
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
add_action( 'plugins_loaded', [ $this, 'hooks' ] );
}
/**
* Register all WP hooks.
*
* @since 1.5.6
*/
public function hooks(): void {
// If the user is in admin ajax or doing cron, return.
if ( wp_doing_ajax() || wp_doing_cron() ) {
return;
}
// If the user cannot manage_options, return.
if ( ! wpforms_current_user_can() ) {
return;
}
add_action( 'admin_menu', [ $this, 'register' ] );
add_action( 'admin_head', [ $this, 'hide_menu' ] );
add_action( 'admin_init', [ $this, 'redirect' ], 9999 );
}
/**
* Register the pages to be used for the Welcome screen (and tabs).
*
* These pages will be removed from the Dashboard menu, so they will be not shown.
* Sneaky, sneaky.
*
* @since 1.0.0
*/
public function register(): void {
// Getting started - shows after installation.
add_dashboard_page(
esc_html__( 'Welcome to WPForms', 'wpforms-lite' ),
esc_html__( 'Welcome to WPForms', 'wpforms-lite' ),
/**
* Filter the capability to add the Welcome page.
*
* @since 1.5.6
*
* @param string $capability The capability to manage everything for WPForms.
*/
apply_filters( 'wpforms_welcome_cap', wpforms_get_capability_manage_options() ),
self::SLUG,
[ $this, 'output' ]
);
}
/**
* Removed the dashboard pages from the admin menu.
*
* This means the pages are still available to us but hidden.
*
* @since 1.0.0
*/
public function hide_menu(): void {
remove_submenu_page( 'index.php', self::SLUG );
}
/**
* Welcome screen redirect.
*
* This function checks if a new installation or update has just occurred.
* If so, then we redirect the user to the appropriate page.
*
* @since 1.0.0
*/
public function redirect(): void {
// Check if we should consider redirection.
if ( ! get_transient( 'wpforms_activation_redirect' ) ) {
return;
}
// If we are redirecting, clear the transient so it only happens once.
delete_transient( 'wpforms_activation_redirect' );
// Check an option to disable welcome redirect.
if ( get_option( 'wpforms_activation_redirect', false ) ) {
return;
}
// Only do this for single site installs.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['activate-multi'] ) || is_network_admin() ) {
return;
}
global $pagenow;
/**
* When installing the plugin using the WPForms Gutenberg block, we should not redirect to the Getting Started page.
* It causes the WPForms block rendering error.
*/
if ( in_array( $pagenow, [ 'edit.php', 'post.php', 'post-new.php', 'site-editor.php' ], true ) ) {
return;
}
/**
* We should not redirect from the Form Builder.
*/
if ( wpforms_is_admin_page( 'builder' ) ) {
return;
}
// Check if this is an update or first install.
$upgrade = get_option( MigrationsBase::PREVIOUS_CORE_VERSION_OPTION_NAME );
if ( ! $upgrade ) {
// Initial install.
wp_safe_redirect( admin_url( 'index.php?page=' . self::SLUG ) );
exit;
}
}
/**
* Getting Started screen. Shows after the first installation.
*
* @since 1.0.0
*/
public function output(): void {
$class = wpforms()->is_pro() ? 'pro' : 'lite';
?>
<div id="wpforms-welcome" class="<?php echo sanitize_html_class( $class ); ?>">
<div class="container">
<div class="intro">
<div class="sullie">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/sullie.png' ); ?>" alt="<?php esc_attr_e( 'Sullie the WPForms mascot', 'wpforms-lite' ); ?>">
</div>
<div class="block">
<h1><?php esc_html_e( 'Welcome to WPForms', 'wpforms-lite' ); ?></h1>
<h6><?php esc_html_e( 'Thank you for choosing WPForms - the most powerful drag & drop WordPress form builder in the market.', 'wpforms-lite' ); ?></h6>
</div>
<a href="#" class="play-video" title="<?php esc_attr_e( 'Watch how to create your first form', 'wpforms-lite' ); ?>">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-video.png' ); ?>" alt="<?php esc_attr_e( 'Watch how to create your first form', 'wpforms-lite' ); ?>" class="video-thumbnail">
</a>
<div class="block">
<h6><?php esc_html_e( 'WPForms makes it easy to create forms in WordPress. You can watch the video tutorial or read our guide on how to create your first form.', 'wpforms-lite' ); ?></h6>
<div class="button-wrap wpforms-clear">
<div class="left">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-builder' ) ); ?>" class="wpforms-btn wpforms-btn-block wpforms-btn-lg wpforms-btn-orange">
<?php esc_html_e( 'Create Your First Form', 'wpforms-lite' ); ?>
</a>
</div>
<div class="right">
<a href="<?php echo esc_url( wpforms_utm_link( 'https://wpforms.com/docs/creating-first-form/', 'welcome-page', 'Read the Full Guide' ) ); ?>"
class="wpforms-btn wpforms-btn-block wpforms-btn-lg wpforms-btn-grey" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Read the Full Guide', 'wpforms-lite' ); ?>
</a>
</div>
</div>
</div>
</div><!-- /.intro -->
<?php
/**
* Fires after Welcome Intro.
*
* @since 1.5.6
*/
do_action( 'wpforms_welcome_intro_after' );
?>
<div class="features">
<div class="block">
<h1><?php esc_html_e( 'WPForms Features &amp; Addons', 'wpforms-lite' ); ?></h1>
<h6><?php esc_html_e( 'WPForms is both easy to use and extremely powerful. We have tons of helpful features that allow us to give you everything you need from a form builder.', 'wpforms-lite' ); ?></h6>
<div class="feature-list wpforms-clear">
<div class="feature-block first">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-1.png' ); ?>">
<h5><?php esc_html_e( 'Drag &amp; Drop Form Builder', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Easily create an amazing form in just a few minutes without writing any code.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block last">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-2.png' ); ?>">
<h5><?php esc_html_e( 'Form Templates', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Start with pre-built form templates to save even more time.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block first">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-3.png' ); ?>">
<h5><?php esc_html_e( 'Responsive Mobile Friendly', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'WPForms is 100% responsive meaning it works on mobile, tablets & desktop.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block last">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-4.png' ); ?>">
<h5><?php esc_html_e( 'Smart Conditional Logic', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Easily create high performance forms with our smart conditional logic.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block first">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-5.png' ); ?>">
<h5><?php esc_html_e( 'Instant Notifications', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Respond to leads quickly with our instant form notification feature for your team.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block last">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-6.png' ); ?>">
<h5><?php esc_html_e( 'Entry Management', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'View all your leads in one place to streamline your workflow.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block first">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-7.png' ); ?>">
<h5><?php esc_html_e( 'Payments Made Easy', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Easily collect payments, donations, and online orders without hiring a developer.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block last">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-8.png' ); ?>">
<h5><?php esc_html_e( 'Marketing &amp; Subscriptions', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Create subscription forms and connect with your email marketing service.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block first">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-9.png' ); ?>">
<h5><?php esc_html_e( 'Easy to Embed', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Easily embed your forms in blog posts, pages, sidebar widgets, footer, etc.', 'wpforms-lite' ); ?></p>
</div>
<div class="feature-block last">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-feature-icon-10.png' ); ?>">
<h5><?php esc_html_e( 'Spam Protection', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'Our smart captcha and spam protection automatically prevents spam submissions.', 'wpforms-lite' ); ?></p>
</div>
</div>
<div class="button-wrap">
<a href="<?php echo esc_url( wpforms_utm_link( 'https://wpforms.com/features/', 'welcome-page', 'See All Features' ) ); ?>"
class="wpforms-btn wpforms-btn-lg wpforms-btn-grey" rel="noopener noreferrer" target="_blank">
<?php esc_html_e( 'See All Features', 'wpforms-lite' ); ?>
</a>
</div>
</div>
</div><!-- /.features -->
<div class="upgrade-cta upgrade">
<div class="block wpforms-clear">
<div class="left">
<h2><?php esc_html_e( 'Upgrade to PRO', 'wpforms-lite' ); ?></h2>
<ul>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Advanced Fields', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Conditional Logic', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Payment Forms', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Surveys & Polls', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Signatures', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Form Abandonment', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Entry Management', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'File Uploads', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Geolocation', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Conversational Forms', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'User Registration', 'wpforms-lite' ); ?></li>
<li><span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Marketing Integrations', 'wpforms-lite' ); ?></li>
</ul>
</div>
<div class="right">
<h2><span>PRO</span></h2>
<div class="price">
<span class="amount">199</span><br>
<span class="term"><?php esc_html_e( 'per year', 'wpforms-lite' ); ?></span>
</div>
<a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'welcome', 'Upgrade Now CTA Section' ) ); ?>" rel="noopener noreferrer" target="_blank"
class="wpforms-btn wpforms-btn-block wpforms-btn-lg wpforms-btn-orange wpforms-upgrade-modal">
<?php esc_html_e( 'Upgrade Now', 'wpforms-lite' ); ?>
</a>
</div>
</div>
</div>
<div class="testimonials upgrade">
<div class="block">
<h1><?php esc_html_e( 'Testimonials', 'wpforms-lite' ); ?></h1>
<div class="testimonial-block wpforms-clear">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-testimonial-bill.jpg' ); ?>">
<p><?php esc_html_e( 'WPForms is by far the easiest form plugin to use. My clients love it its one of the few plugins they can use without any training. As a developer I appreciate how fast, modern, clean and extensible it is.', 'wpforms-lite' ); ?>
<p>
<p><strong>Bill Erickson</strong>, Erickson Web Consulting</p>
</div>
<div class="testimonial-block wpforms-clear">
<img src="<?php echo esc_url( WPFORMS_PLUGIN_URL . 'assets/images/welcome-testimonial-david.jpg' ); ?>">
<p><?php esc_html_e( 'As a business owner, time is my most valuable asset. WPForms allow me to create smart online forms with just a few clicks. With their pre-built form templates and the drag & drop builder, I can create a new form that works in less than 2 minutes without writing a single line of code. Well worth the investment.', 'wpforms-lite' ); ?>
<p>
<p><strong>David Henzel</strong>, MaxCDN</p>
</div>
</div>
</div><!-- /.testimonials -->
<div class="footer">
<div class="block wpforms-clear">
<div class="button-wrap wpforms-clear">
<div class="left">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-builder' ) ); ?>"
class="wpforms-btn wpforms-btn-block wpforms-btn-lg wpforms-btn-orange">
<?php esc_html_e( 'Create Your First Form', 'wpforms-lite' ); ?>
</a>
</div>
<div class="right">
<a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'welcome', 'Upgrade to WPForms Pro' ) ); ?>" target="_blank" rel="noopener noreferrer"
class="wpforms-btn wpforms-btn-block wpforms-btn-lg wpforms-btn-trans-green wpforms-upgrade-modal">
<span class="underline">
<?php esc_html_e( 'Upgrade to WPForms Pro', 'wpforms-lite' ); ?> <span class="dashicons dashicons-arrow-right"></span>
</span>
</a>
</div>
</div>
</div>
</div><!-- /.footer -->
</div><!-- /.container -->
</div><!-- /#wpforms-welcome -->
<?php
}
}
new WPForms_Welcome();

View File

@@ -0,0 +1,734 @@
<?php
/**
* Settings API.
*
* @since 1.3.7
*/
use WPForms\Admin\Education\Helpers as EducationHelpers;
/**
* Settings output wrapper.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_output_field( array $args ): string {
// Define default callback for this field type.
$callback = ! empty( $args['type'] ) && function_exists( 'wpforms_settings_' . $args['type'] . '_callback' ) ? 'wpforms_settings_' . $args['type'] . '_callback' : 'wpforms_settings_missing_callback';
// Allow custom callback to be provided via arg.
if ( ! empty( $args['callback'] ) && function_exists( $args['callback'] ) ) {
$callback = $args['callback'];
}
// Store returned markup from callback.
$field = $callback( $args );
// Allow arg to bypass standard field wrap for custom display.
if ( ! empty( $args['wrap'] ) ) {
return $field;
}
// Default class names.
$class = [
'wpforms-setting-row',
"wpforms-setting-row-{$args['type']}",
'wpforms-clear',
];
// Row attributes.
$wrapper_attributes = wpforms_html_attributes(
'wpforms-setting-row-' . wpforms_sanitize_key( $args['id'] ),
! empty( $args['class'] ) ? array_merge( $class, (array) $args['class'] ) : $class,
! empty( $args['data_attributes'] ) && is_array( $args['data_attributes'] ) ? $args['data_attributes'] : [],
! empty( $args['is_hidden'] ) ? [ 'style' => 'display:none;' ] : []
);
// Build standard field markup and return.
$output = "<div {$wrapper_attributes}>";
if ( ! empty( $args['name'] ) && empty( $args['no_label'] ) ) {
$output .= '<span class="wpforms-setting-label">';
$output .= '<label for="wpforms-setting-' . wpforms_sanitize_key( $args['id'] ) . '">' . esc_html( $args['name'] );
// Add education badge, if needed.
// The badge should be added after the label text, but before the label closing tag.
if ( ! empty( $args['education_badge'] ) ) {
$output .= wp_kses( $args['education_badge'], [ 'span' => [ 'class' => [] ] ] );
}
$output .= '</label>';
$output .= '</span>';
}
$output .= '<span class="wpforms-setting-field">';
$output .= $field;
if ( ! empty( $args['desc_after'] ) ) {
$output .= '<div class="wpforms-clear">' . $args['desc_after'] . '</div>';
}
$output .= '</span>';
$output .= '</div>';
return $output;
}
/**
* Missing Callback.
*
* If a function is missing for settings, callbacks alert the user.
*
* @since 1.3.9
*
* @param array $args Arguments passed by the setting.
*
* @return string
*/
function wpforms_settings_missing_callback( array $args ): string {
return sprintf(
/* translators: %s - ID of a setting. */
esc_html__( 'The callback function used for the %s setting is missing.', 'wpforms-lite' ),
'<strong>' . wpforms_sanitize_key( $args['id'] ) . '</strong>'
);
}
/**
* Settings content field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_content_callback( array $args ): string {
return ! empty( $args['content'] ) ? $args['content'] : '';
}
/**
* Settings license field callback.
*
* @since 1.3.9
*
* @param array $args Settings arguments.
*
* @return string
* @noinspection HtmlUnknownTarget
* @noinspection PhpUnusedParameterInspection
*/
function wpforms_settings_license_callback( array $args ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$output = '<p>' . esc_html__( 'You\'re using WPForms Lite - no license needed. Enjoy!', 'wpforms-lite' ) . ' 🙂</p>';
$output .=
'<p>' .
sprintf(
wp_kses( /* translators: %s - WPForms.com upgrade URL. */
__( 'To unlock more features consider <strong><a href="%s" target="_blank" rel="noopener noreferrer" class="wpforms-upgrade-modal">upgrading to PRO</a></strong>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'class' => [],
'target' => [],
'rel' => [],
],
'strong' => [],
]
),
esc_url( wpforms_admin_upgrade_link( 'settings-license', 'Upgrade to WPForms Pro text Link' ) )
) .
'</p>';
$output .=
'<p class="discount-note">' .
wp_kses(
__( 'As a valued WPForms Lite user you receive <strong>50% off</strong>, automatically applied at checkout!', 'wpforms-lite' ),
[
'strong' => [],
]
) .
'</p>';
$output .= '<hr><p>' . esc_html__( 'Already purchased? Simply enter your license key below to enable WPForms PRO!', 'wpforms-lite' ) . '</p>';
$output .= '<p>';
$output .= '<input type="password" spellcheck="false" id="wpforms-settings-upgrade-license-key" placeholder="' . esc_attr__( 'Paste license key here', 'wpforms-lite' ) . '" value="">';
$output .= '<button class="wpforms-btn wpforms-btn-md wpforms-btn-blue" id="wpforms-settings-connect-btn">' . esc_html__( 'Verify Key', 'wpforms-lite' ) . '</button>';
$output .= '</p>';
/**
* Filter license settings HTML output.
*
* @since 1.7.9
*
* @param string $output HTML markup to be rendered in place of license settings.
*/
return (string) apply_filters( 'wpforms_settings_license_output', $output );
}
/**
* Settings text input field callback.
*
* @since 1.3.9
* @since 1.10.0 Adds the ability to make text input readonly.
*
* @param array $args Settings arguments.
*
* @return string
*/
function wpforms_settings_text_callback( array $args ): string {
if ( ! in_array( $args['type'], [ 'text', 'password' ], true ) ) {
$args['type'] = 'text';
}
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$value = wpforms_setting( $args['id'], $default );
$id = wpforms_sanitize_key( $args['id'] );
$readonly = ! empty( $args['readonly'] ) ? ' readonly' : '';
$output = '<input type="' . esc_attr( $args['type'] ) . '" id="wpforms-setting-' . $id . '" name="' . $id . '" value="' . esc_attr( $value ) . ' " ' . $readonly . ' />';
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings password input field callback.
*
* @since 1.8.4
*
* @param array $args Setting field arguments.
*
* @return string
*/
function wpforms_settings_password_callback( array $args ): string {
return wpforms_settings_text_callback( $args );
}
/**
* Settings number input field callback.
*
* @since 1.5.3
*
* @param array $args Setting field arguments.
*
* @return string
* @noinspection HtmlUnknownAttribute
*/
function wpforms_settings_number_callback( array $args ): string {
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$id = 'wpforms-setting-' . wpforms_sanitize_key( $args['id'] );
$attr = [
'value' => wpforms_setting( $args['id'], $default ),
'name' => wpforms_sanitize_key( $args['id'] ),
];
$data = ! empty( $args['data'] ) ? $args['data'] : [];
if ( ! empty( $args['attr'] ) ) {
$attr = array_merge( $attr, $args['attr'] );
}
$output = sprintf(
'<input type="number" %s>',
wpforms_html_attributes( $id, [], $data, $attr )
);
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings select field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_select_callback( array $args ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$value = wpforms_setting( $args['id'], $default );
$id = wpforms_sanitize_key( $args['id'] );
$select_name = $id;
$class = ! empty( $args['choicesjs'] ) ? 'choicesjs-select' : '';
$choices = ! empty( $args['choicesjs'] );
$data = isset( $args['data'] ) ? (array) $args['data'] : [];
$attr = isset( $args['attr'] ) ? (array) $args['attr'] : [];
if ( $choices && ! empty( $args['search'] ) ) {
$data['search'] = 'true';
}
if ( ! empty( $args['placeholder'] ) ) {
$data['placeholder'] = $args['placeholder'];
}
$size_attr = '';
if ( $choices && ! empty( $args['multiple'] ) ) {
$attr[] = 'multiple';
$select_name = $id . '[]';
$size_attr = ' size="1"';
}
foreach ( $data as $name => $val ) {
$data[ $name ] = 'data-' . sanitize_html_class( $name ) . '="' . esc_attr( $val ) . '"';
}
$data = implode( ' ', $data );
$attr = implode( ' ', array_map( 'sanitize_html_class', $attr ) );
$output = $choices ? '<span class="choicesjs-select-wrap">' : '';
$output .= '<select id="wpforms-setting-' . $id . '" name="' . $select_name . '" class="' . $class . '"' . $data . $attr . $size_attr . '>';
foreach ( $args['options'] as $option => $name ) {
if ( empty( $args['selected'] ) ) {
$selected = selected( $value, $option, false );
} else {
$selected = is_array( $args['selected'] ) && in_array( $option, $args['selected'], true ) ? 'selected' : '';
}
$output .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( $name ) . '</option>';
}
$output .= '</select>';
$output .= $choices ? '</span>' : '';
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings checkbox field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_checkbox_callback( array $args ): string {
$value = wpforms_setting( $args['id'] );
$id = wpforms_sanitize_key( $args['id'] );
$checked = ! empty( $value ) ? checked( 1, $value, false ) : '';
$disabled = ! empty( $args['disabled'] ) ? ' disabled' : '';
$output = '<input type="checkbox" id="wpforms-setting-' . $id . '" name="' . $id . '" ' . $checked . $disabled . '>';
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
if ( ! empty( $args['disabled_desc'] ) ) {
$output .= '<p class="disabled-desc">' . wp_kses_post( $args['disabled_desc'] ) . '</p>';
}
return $output;
}
/**
* Settings radio field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_radio_callback( array $args ): string {
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$value = wpforms_setting( $args['id'], $default );
$id = wpforms_sanitize_key( $args['id'] );
$output = '';
$x = 1;
foreach ( $args['options'] as $option => $name ) {
$checked = checked( $value, $option, false );
$output .= '<span class="wpforms-settings-field-radio-wrapper">';
$output .= '<input type="radio" id="wpforms-setting-' . $id . '[' . $x . ']" name="' . $id . '" value="' . esc_attr( $option ) . '" ' . $checked . '>';
$output .= '<label for="wpforms-setting-' . $id . '[' . $x . ']" class="option-' . sanitize_html_class( $option ) . '">';
$output .= esc_html( $name );
$output .= '</label>';
$output .= '</span>';
++$x;
}
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Email template endpoint field callback.
*
* @since 1.8.5
*
* @param array $args Field arguments.
*
* @return string
*/
function wpforms_settings_email_template_callback( array $args ): string {
$id = wpforms_sanitize_key( $args['id'] );
$is_pro = wpforms()->is_pro();
$output = '';
$x = 1;
$education_args = [
'name' => esc_html__( 'Email Templates', 'wpforms-lite' ),
'plural' => '1',
'action' => 'upgrade',
];
foreach ( $args['options'] as $option => $attrs ) {
$checked = checked( $args['value'], $option, false );
$has_education = ! $is_pro && isset( $attrs['is_pro'] ) && $attrs['is_pro'];
$class = [ 'wpforms-settings-field-radio-wrapper', 'wpforms-card-image' ];
$data = [];
// Add class and data attributes for education modal, if needed.
if ( $has_education ) {
$class[] = 'education-modal'; // This class is used for JS.
$data = $education_args; // This data is used for JS.
}
$output .= '<span ' . wpforms_html_attributes( '', $class, $data ) . '>';
$output .= '<input type="radio" id="wpforms-setting-' . $id . '[' . $x . ']" name="' . $id . '" value="' . esc_attr( $option ) . '" ' . $checked . '>';
$output .= '<label for="wpforms-setting-' . $id . '[' . $x . ']" class="option-' . sanitize_html_class( $option ) . '">';
$output .= esc_html( $attrs['name'] );
// Add class and data attributes for education modal, if needed.
if ( $has_education ) {
$output .= EducationHelpers::get_badge( 'Pro' );
}
$output .= '<span class="wpforms-card-image-overlay">';
$output .= '<span class="wpforms-btn-choose wpforms-btn wpforms-btn-md wpforms-btn-orange">';
$output .= esc_html__( 'Choose', 'wpforms-lite' ) . '</span>';
// Only add the preview action button if provided.
if ( ! empty( $attrs['preview'] ) ) {
$output .= '<a href="' . esc_url( $attrs['preview'] ) . '" class="wpforms-btn-preview wpforms-btn wpforms-btn-md wpforms-btn-light-grey" target="_blank">';
$output .= esc_html__( 'Preview', 'wpforms-lite' );
$output .= '</a>';
}
$output .= '</span>';
$output .= '</label>';
$output .= '</span>';
++$x;
}
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings toggle field callback.
*
* @since 1.7.4
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_toggle_callback( array $args ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$value = ! empty( $args['value'] ) ? $args['value'] : wpforms_setting( $args['id'] );
$id = wpforms_sanitize_key( $args['id'] );
$class = ! empty( $args['control-class'] ) ? $args['control-class'] : '';
$class .= ! empty( $args['is-important'] ) ? ' wpforms-important' : '';
$input_attr = ! empty( $args['input-attr'] ) ? $args['input-attr'] : '';
$default_args = [
'control-class' => $class,
];
$args = wp_parse_args( $args, $default_args );
$output = wpforms_panel_field_toggle_control(
$args,
'wpforms-setting-' . $id,
$id,
! empty( $args['label'] ) ? $args['label'] : '',
$value,
$input_attr
);
$desc_on = ! empty( $args['desc'] ) ? $args['desc'] : '';
$desc_on = ! empty( $args['desc-on'] ) ? $args['desc-on'] : $desc_on;
$desc_off = ! empty( $args['desc-off'] ) ? $args['desc-off'] : '';
$output .= sprintf(
'<p class="desc desc-on wpforms-toggle-desc%1$s">%2$s</p>',
empty( $value ) && ! empty( $desc_off ) ? ' wpforms-hidden' : '',
wp_kses_post( $desc_on )
);
if ( ! empty( $desc_off ) ) {
$output .= sprintf(
'<p class="desc desc-off wpforms-toggle-desc%1$s">%2$s</p>',
empty( $value ) ? '' : ' wpforms-hidden',
wp_kses_post( $desc_off )
);
}
if ( ! empty( $args['disabled_desc'] ) ) {
$output .= '<p class="disabled-desc">' . wp_kses_post( $args['disabled_desc'] ) . '</p>';
}
return $output;
}
/**
* Settings image uploads field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_image_callback( array $args ): string {
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$value = wpforms_setting( $args['id'], $default );
$id = wpforms_sanitize_key( $args['id'] );
$output = '';
if ( ! empty( $value ) ) {
$output .= '<img src="' . esc_url_raw( $value ) . '">';
}
$output .= '<input type="text" id="wpforms-setting-' . $id . '" name="' . $id . '" value="' . esc_url_raw( $value ) . '">';
// Show the remove button if specified.
if ( isset( $args['show_remove'] ) && $args['show_remove'] ) {
$output .= '<button class="wpforms-btn wpforms-btn-md wpforms-setting-remove-image">' . esc_html__( 'Remove Image', 'wpforms-lite' ) . '</button>';
}
$output .= '<button class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey wpforms-setting-upload-image">' . esc_html__( 'Upload Image', 'wpforms-lite' ) . '</button>';
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings color picker field callback.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
*/
function wpforms_settings_color_callback( array $args ): string {
$default = isset( $args['default'] ) ? esc_html( $args['default'] ) : '';
$value = wpforms_setting( $args['id'], $default );
$id = wpforms_sanitize_key( $args['id'] );
$data = isset( $args['data'] ) ? (array) $args['data'] : [];
foreach ( $data as $name => $val ) {
$data[ $name ] = 'data-' . sanitize_html_class( $name ) . '="' . esc_attr( $val ) . '"';
}
$data = implode( ' ', $data );
$output = '<input type="text" id="wpforms-setting-' . $id . '" class="wpforms-color-picker" name="' . $id . '" value="' . esc_attr( $value ) . '" ' . $data . '>';
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Color scheme endpoint fieldset callback.
* This function will output a fieldset with color picker inputs.
*
* @since 1.8.5
*
* @param array $args Field arguments.
*
* @return string
*/
function wpforms_settings_color_scheme_callback( array $args ): string {
$id = wpforms_sanitize_key( $args['id'] );
$value = wpforms_setting( $args['id'], [] );
$output = '';
foreach ( $args['colors'] as $color => $attrs ) {
$data = isset( $attrs['data'] ) ? (array) $attrs['data'] : [];
$default_value = isset( $data['fallback-color'] ) ? wpforms_sanitize_hex_color( $data['fallback-color'] ) : '';
$field_id = "{$id}-{$color}";
$field_value = isset( $value[ $color ] ) ? wpforms_sanitize_hex_color( $value[ $color ] ) : $default_value;
$input_attributes = wpforms_html_attributes(
"wpforms-setting-{$field_id}",
[ 'wpforms-color-picker' ],
$data,
[
'type' => 'text',
'name' => "{$id}[{$color}]",
'value' => esc_attr( $field_value ),
]
);
$output .= "<input {$input_attributes}>";
$output .= '<label for="wpforms-setting-' . $field_id . '">';
$output .= esc_html( $attrs['name'] );
$output .= '</label>';
}
if ( ! empty( $args['desc'] ) ) {
$output .= '<p class="desc">' . wp_kses_post( $args['desc'] ) . '</p>';
}
return $output;
}
/**
* Settings providers field callback - this is for the Integrations tab.
*
* @since 1.3.9
*
* @param array $args Arguments.
*
* @return string
* @noinspection PhpUnusedParameterInspection
*/
function wpforms_settings_providers_callback( array $args ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$active = wpforms_get_providers_available();
$providers = wpforms_get_providers_options();
$output = '<div id="wpforms-settings-providers">';
ob_start();
/**
* Output settings providers.
*
* @since 1.3.9.1
*
* @param array $active Active providers.
* @param array $providers Providers options.
*/
do_action( 'wpforms_settings_providers', $active, $providers );
$output .= ob_get_clean();
$output .= '</div>';
return $output;
}
/**
* Webhooks' endpoint field callback.
*
* @since 1.8.4
*
* @param array $args Field arguments.
*
* @return string
*/
function wpforms_settings_webhook_endpoint_callback( array $args ): string {
if ( empty( $args['url'] ) ) {
return ''; // Early return if no URL is provided.
}
$provider = $args['provider'] ?? 'stripe';
$input_id = "wpforms-{$provider}-webhook-endpoint-url";
$copy_btn = '<a class="button button-secondary wpforms-copy-to-clipboard" data-clipboard-target="#' . esc_attr( $input_id ) . '" href="#" aria-label="' . esc_attr__( 'Copy webhook URL', 'wpforms-lite' ) . '"><span class="dashicons dashicons-admin-page"></span></a>';
$input_field = '<input type="text" disabled id="' . esc_attr( $input_id ) . '" value="' . esc_url( $args['url'] ) . '" />';
$output = sprintf(
'<div class="%1$s">%2$s %3$s</div>',
esc_attr( "wpforms-{$provider}-webhook-endpoint-url" ),
$input_field,
$copy_btn
);
if ( ! empty( $args['desc'] ) ) {
$output .= sprintf( '<p class="desc">%s</p>', wp_kses_post( $args['desc'] ) );
}
return $output;
}
/**
* Settings field columns callback.
*
* @since 1.5.8
*
* @param array $args Arguments passed by the setting.
*
* @return string
*/
function wpforms_settings_columns_callback( array $args ): string {
if ( empty( $args['columns'] ) || ! is_array( $args['columns'] ) ) {
return '';
}
$output = '<div class="wpforms-setting-columns">';
foreach ( $args['columns'] as $column ) {
// Define default callback for this field type.
$callback = ! empty( $column['type'] ) ? 'wpforms_settings_' . $column['type'] . '_callback' : '';
// Allow custom callback to be provided via arg.
if ( ! empty( $column['callback'] ) ) {
$callback = $column['callback'];
}
$output .= '<div class="wpforms-setting-column">';
if ( ! empty( $column['name'] ) ) {
$output .= '<label><b>' . wp_kses_post( $column['name'] ) . '</b></label>';
}
if ( function_exists( $callback ) ) {
$output .= $callback( $column );
}
$output .= '</div>';
}
$output .= '</div>';
return $output;
}

View File

@@ -0,0 +1,949 @@
<?php
// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/** @noinspection PhpIllegalPsrClassPathInspection */
// phpcs:disable Generic.Commenting.DocComment.MissingShort
use WPForms\Helpers\DB;
/**
* DB class.
*
* This handy class originated from Pippin's Easy Digital Downloads.
* See https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/class-edd-db.php
*
* Subclasses should define $table_name, $version, and $primary_key in __construct() method.
*
* @since 1.1.6
*/
abstract class WPForms_DB {
/**
* Maximum length of index key.
*
* Indexes have a maximum size of 767 bytes. Historically, we haven't needed to be concerned about that.
* As of WP 4.2, however, WP moved to utf8mb4, which uses 4 bytes per character. This means that an index, which
* used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
*
* @since 1.8.2
*/
const MAX_INDEX_LENGTH = 191;
/**
* The dedicated cache key to store the All Keys array.
*
* @since 1.9.0
*/
const ALL_KEYS = '_all_keys';
/**
* Database table name.
*
* @since 1.1.6
*
* @var string
*/
public $table_name;
/**
* Database version.
*
* @since 1.1.6
*
* @var string
*/
public $version;
/**
* Primary key (unique field) for the database table.
*
* @since 1.1.6
*
* @var string
*/
public $primary_key;
/**
* Database type identifier.
*
* @since 1.5.1
*
* @var string
*/
public $type;
/**
* Cache group.
*
* @since 1.9.0
*
* @var string
*/
private $cache_group;
/**
* Cache disabled.
*
* @since 1.9.0
*
* @var bool
*/
private $cache_disabled;
/**
* WPForms_DB constructor.
*
* @since 1.9.0
*/
public function __construct() {
$this->cache_group = static::class . '_cache';
$this->cache_disabled = defined( 'WPFORMS_DISABLE_DB_CACHE' ) && WPFORMS_DISABLE_DB_CACHE;
$this->hooks();
}
/**
* Query filter.
*
* @since 1.9.0
*
* @return void
*/
private function hooks() {
add_filter( 'query', [ $this, 'query_filter' ] );
}
/**
* Retrieve the list of columns for the database table.
* Subclasses should define an array of columns here.
*
* @since 1.1.6
*
* @return array List of columns.
*/
public function get_columns() {
return [];
}
/**
* Retrieve column defaults.
* Subclasses can define default for any/all columns defined in the get_columns() method.
*
* @since 1.1.6
*
* @return array All defined column defaults.
*/
public function get_column_defaults() {
return [];
}
/**
* Filter the query.
*
* @since 1.9.0
*
* @param string|mixed $query Query.
*
* @return string
*/
public function query_filter( $query ): string {
$query = (string) $query;
if ( strpos( $query, $this->table_name ) === false ) {
// Not a query for our table, bail out.
return $query;
}
if ( ! $this->is_select( $query ) ) {
// Flush cache on non-SELECT queries.
$this->cache_flush_group();
}
return $query;
}
/**
* Retrieve a row from the database based on a given row ID.
*
* @since 1.1.6
*
* @param int $row_id Row ID.
*
* @return null|object
*/
public function get( $row_id ) {
global $wpdb;
$key = md5( __METHOD__ . $row_id );
$row = $this->cache_get( $key, $found );
if ( $found ) {
return $row;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
(int) $row_id
)
);
$this->cache_set( $key, $row );
return $row;
}
/**
* Retrieve a row based on column and row ID.
*
* @since 1.1.6
*
* @param string $column Column name.
* @param int|string $value Column value.
*
* @return object|null Database query result, object or null on failure.
*/
public function get_by( $column, $value ) {
global $wpdb;
if (
empty( $value ) ||
! array_key_exists( $column, $this->get_columns() )
) {
return null;
}
$key = md5( __METHOD__ . $column . $value );
$row = $this->cache_get( $key, $found );
if ( $found ) {
return $row;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$value
)
);
$this->cache_set( $key, $row );
return $row;
}
/**
* Retrieve a value based on column name and row ID.
*
* @since 1.1.6
*
* @param string $column Column name.
* @param int|string $row_id Row ID.
*
* @return string|null Database query result (as string), or null on failure.
* @noinspection PhpUnused
*/
public function get_column( $column, $row_id ) {
global $wpdb;
if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) {
return null;
}
$key = md5( __METHOD__ . $column . $row_id );
$var = $this->cache_get( $key, $found );
if ( $found ) {
return $var;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$var = $wpdb->get_var(
$wpdb->prepare(
"SELECT $column FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
(int) $row_id
)
);
$this->cache_set( $key, $var );
return $var;
}
/**
* Retrieve one column value based on another given column and matching value.
*
* @since 1.1.6
*
* @param string $column Column name.
* @param string $column_where Column to match against in the WHERE clause.
* @param string $column_value Value to match to the column in the WHERE clause.
*
* @return string|null Database query result (as string), or null on failure.
* @noinspection PhpUnused
*/
public function get_column_by( $column, $column_where, $column_value ) {
global $wpdb;
if (
empty( $column ) ||
empty( $column_where ) ||
empty( $column_value ) ||
! array_key_exists( $column_where, $this->get_columns() ) ||
! array_key_exists( $column, $this->get_columns() )
) {
return null;
}
$key = md5( __METHOD__ . $column . $column_where . $column_value );
$var = $this->cache_get( $key, $found );
if ( $found ) {
return $var;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$var = $wpdb->get_var(
$wpdb->prepare(
"SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$column_value
)
);
$this->cache_set( $key, $var );
return $var;
}
/**
* Clone of $wpdb->query() with caching.
*
* @since 1.9.0
*
* @param string $query Database query.
*
* @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
* affected/selected for all other queries. Boolean false on error.
*
* @noinspection PhpMissingParamTypeInspection
*/
public function query( $query ) {
global $wpdb;
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->query( $query );
}
$key = md5( __METHOD__ . $query );
$results = $this->cache_get( $key, $found );
if ( $found ) {
return $results;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->query( $query );
$this->cache_set( $key, $results );
return $results;
}
/**
* Clone of $wpdb->get_results() with caching.
*
* @since 1.9.0
*
* @param string|null $query SQL query.
* @param string $output Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
*
* @return array|object|null Database query results.
* @noinspection PhpMissingParamTypeInspection
*/
public function get_results( $query = null, $output = OBJECT ) {
global $wpdb;
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( $query, $output );
}
$key = md5( __METHOD__ . $query . $output );
$results = $this->cache_get( $key, $found );
if ( $found ) {
return $results;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results( $query, $output );
$this->cache_set( $key, $results );
return $results;
}
/**
* Clone of $wpdb->get_col() with caching.
*
* @since 1.9.4
*
* @param string|null $query SQL query.
* @param int $x Column to return. Indexed from 0.
*
* @return array Database query results.
* @noinspection PhpMissingParamTypeInspection
*/
public function get_col( $query = null, $x = 0 ) {
global $wpdb;
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_col( $query );
}
$key = md5( __METHOD__ . $query . $x );
$col = $this->cache_get( $key, $found );
if ( $found ) {
return $col;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$col = $wpdb->get_col( $query, $x );
$this->cache_set( $key, $col );
return $col;
}
/**
* Clone of $wpdb->get_row() with caching.
*
* @since 1.9.0
*
* @param string|null $query SQL query.
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
* correspond to an stdClass object, an associative array, or a numeric array,
* respectively. Default OBJECT.
* @param int $y Optional. Row to return. Indexed from 0. Default 0.
*
* @return array|int|object|stdClass|null Database query result in format specified by $output or null on failure.
* @noinspection PhpMissingParamTypeInspection
*/
public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
global $wpdb;
if ( ! $query ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_row( $query, $output, $y );
}
$key = md5( __METHOD__ . $query . $output . $y );
$row = $this->cache_get( $key, $found );
if ( $found ) {
return $row;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$row = $wpdb->get_row( $query, $output, $y );
$this->cache_set( $key, $row );
return $row;
}
/**
* Clone of $wpdb->get_var() with caching.
*
* @since 1.9.0
*
* @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
* @param int $x Optional. Column of value to return. Indexed from 0. Default 0.
* @param int $y Optional. Row of value to return. Indexed from 0. Default 0.
*
* @return string|null Database query result (as string), or null on failure.
*
* @noinspection PhpMissingParamTypeInspection
*/
public function get_var( $query = null, $x = 0, $y = 0 ) {
global $wpdb;
if ( ! $query ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_var( $query, $x, $y );
}
$key = md5( __METHOD__ . $query . $x . $y );
$var = $this->cache_get( $key, $found );
if ( $found ) {
return $var;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$var = $wpdb->get_var( $query, $x, $y );
$this->cache_set( $key, $var );
return $var;
}
/**
* Insert a new record into the database.
*
* @since 1.1.6
*
* @param array $data Column data.
* @param string $type Optional. Data type context.
*
* @return int ID for the newly inserted record. Zero otherwise.
*/
public function add( $data, $type = '' ) {
global $wpdb;
// Set default values.
$data = wp_parse_args( $data, $this->get_column_defaults() );
do_action( 'wpforms_pre_insert_' . $type, $data );
// Initialise column format array.
$column_formats = $this->get_columns();
// Force fields to lower a case.
$data = array_change_key_case( $data );
// Whitelist columns.
$data = array_intersect_key( $data, $column_formats );
// Reorder $column_formats to match the order of columns given in $data.
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->insert( $this->table_name, $data, $column_formats );
do_action( 'wpforms_post_insert_' . $type, $wpdb->insert_id, $data );
return $wpdb->insert_id;
}
/**
* Insert a new record into the database. This runs the add() method.
*
* @see add()
*
* @since 1.1.6
*
* @param array $data Column data.
*
* @return int ID for the newly inserted record.
*/
public function insert( $data ) {
return $this->add( $data );
}
/**
* Update an existing record in the database.
*
* @since 1.1.6
*
* @param int|string $row_id Row ID for the record being updated.
* @param array $data Optional. Array of columns and associated data to update. Default empty array.
* @param string $where Optional. Column to match against in the WHERE clause. If empty, $primary_key
* will be used. Default empty.
* @param string $type Optional. Data type context, e.g. 'affiliate', 'creative', etc. Default empty.
*
* @return bool False if the record could not be updated, true otherwise.
*/
public function update( $row_id, $data = [], $where = '', $type = '' ) {
global $wpdb;
// Row ID must be a positive integer.
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
return false;
}
if ( empty( $where ) ) {
$where = $this->primary_key;
}
/**
* Fires before updating a record in the database.
*
* @since 1.5.9
* @since 1.9.2 Added $row_id parameter.
*
* @param array $data Array of columns and associated data to update.
* @param int $row_id Row ID for the record being updated.
*/
do_action( "wpforms_pre_update_{$type}", $data, $row_id );
// Initialise column format array.
$column_formats = $this->get_columns();
// Force fields to the lower case.
$data = array_change_key_case( $data );
// Whitelist columns.
$data = array_intersect_key( $data, $column_formats );
// Reorder $column_formats to match the order of columns given in $data.
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( $wpdb->update( $this->table_name, $data, [ $where => $row_id ], $column_formats ) === false ) {
return false;
}
/**
* Fires after a record has been updated in the database.
*
* @since 1.1.6
* @since 1.9.2 Added $row_id parameter.
*
* @param array $data Array of columns and associated data that were updated.
* @param int $row_id Row ID for the record that was updated.
*/
do_action( "wpforms_post_update_{$type}", $data, $row_id );
return true;
}
/**
* Delete a record from the database.
*
* @since 1.1.6
*
* @param int|string $row_id Row ID.
*
* @return bool False if the record could not be deleted, true otherwise.
*/
public function delete( $row_id = 0 ): bool {
global $wpdb;
// Row ID must be a positive integer.
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
return false;
}
/**
* Fires before a record is deleted from the database.
*
* @since 1.5.9
*
* @param int $row_id Row ID.
*/
do_action( 'wpforms_pre_delete', $row_id );
/**
* Fires before a record is deleted from the database by type.
*
* @since 1.5.9
* @since 1.8.6 Added `$primary_key` parameter.
*
* @param int $row_id Column value.
* @param string $primary_key Column name.
*/
do_action( 'wpforms_pre_delete_' . $this->type, $row_id, $this->primary_key );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM $this->table_name WHERE $this->primary_key = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$row_id
)
);
if ( $result === false ) {
return false;
}
do_action( 'wpforms_post_delete', $row_id );
do_action( 'wpforms_post_delete_' . $this->type, $row_id );
return true;
}
/**
* Delete a record from the database by column.
*
* @since 1.1.6
*
* @param string $column Column name.
* @param int|string $column_value Column value.
*
* @return bool False if the record could not be deleted, true otherwise.
*/
public function delete_by( $column, $column_value ) {
global $wpdb;
if (
empty( $column ) ||
empty( $column_value ) ||
! array_key_exists( $column, $this->get_columns() )
) {
return false;
}
// This action is documented in includes/class-db.php method delete().
do_action( 'wpforms_pre_delete', $column_value );
// This action is documented in includes/class-db.php method delete().
do_action( 'wpforms_pre_delete_' . $this->type, $column_value, $column );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM $this->table_name WHERE $column = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$column_value
)
);
if ( $result === false ) {
return false;
}
do_action( 'wpforms_post_delete', $column_value );
do_action( 'wpforms_post_delete_' . $this->type, $column_value );
return true;
}
/**
* Delete record(s) from the database using WHERE IN syntax.
*
* @since 1.6.4
*
* @param string $column Column name.
* @param mixed $column_values Column values.
*
* @return int|bool Number of deleted records, false otherwise.
*/
public function delete_where_in( $column, $column_values ) {
global $wpdb;
if ( empty( $column ) || empty( $column_values ) ) {
return false;
}
if ( ! array_key_exists( $column, $this->get_columns() ) ) {
return false;
}
$values = (array) $column_values;
foreach ( $values as $key => $value ) {
// Check if a string contains an integer and sanitize accordingly.
if ( (string) (int) $value === $value ) {
$values[ $key ] = (int) $value;
$placeholders[ $key ] = '%d';
} else {
$values[ $key ] = sanitize_text_field( $value );
$placeholders[ $key ] = '%s';
}
}
$placeholders = isset( $placeholders ) ? implode( ',', $placeholders ) : '';
$sql = "DELETE FROM $this->table_name WHERE $column IN ( $placeholders )";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->query( $wpdb->prepare( $sql, $values ) );
}
/**
* Check if the given table exists.
*
* @since 1.1.6
* @since 1.5.9 Default value is now the current child class table name.
*
* @param string $table The table name. Defaults to the child class table name.
*
* @return bool If the table name exists.
*/
public function table_exists( string $table = '' ): bool {
$table = ! empty( $table ) ? sanitize_text_field( $table ) : $this->table_name;
return DB::table_exists( $table );
}
/**
* Build WHERE for a query.
*
* @since 1.7.2.2
*
* @param array $args Optional args.
* @param array $keys Allowed arg items.
* @param string|string[] $formats Formats of arg items.
*
* @return string
*/
protected function build_where( $args, $keys = [], $formats = [] ) {
$formats = array_pad( $formats, count( $keys ), '%d' );
$where = '';
foreach ( $keys as $index => $key ) {
// Value `$args[ $key ]` can be a natural number and a numeric string.
// We should skip empty string values, but continue working with '0'.
if ( empty( $args[ $key ] ) && $args[ $key ] !== '0' ) {
continue;
}
$ids = wpforms_wpdb_prepare_in( $args[ $key ], $formats[ $index ] );
$where .= empty( $where ) ? 'WHERE' : 'AND';
$where .= " `{$key}` IN ( {$ids} ) ";
}
return $where;
}
/**
* WP Cache Get wrapper.
*
* @since 1.9.0
*
* @param int|string $key Cache key.
* @param bool|null $found Whether the key was found in the cache.
*
* @return false|mixed
* @noinspection PhpMissingParamTypeInspection
*/
private function cache_get( $key, &$found ) {
if ( $this->cache_disabled ) {
$found = false;
return false;
}
$all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
$all_keys = $found ? (array) $all_keys : [];
if ( ! in_array( $key, $all_keys, true ) ) {
$found = false;
return false;
}
$data = wp_cache_get( $key, $this->cache_group, false, $found );
return $found ? $data : false;
}
/**
* WP Cache Set wrapper.
*
* @since 1.9.0
*
* @param string $key Cache key.
* @param mixed $data Cache data.
*
* @return bool
* @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
*/
private function cache_set( string $key, $data ): bool {
if ( $this->cache_disabled ) {
return false;
}
$all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
$all_keys = $found ? array_unique( array_merge( (array) $all_keys, [ $key ] ) ) : [ $key ];
return (
wp_cache_set( $key, $data, $this->cache_group ) &&
wp_cache_set( self::ALL_KEYS, $all_keys, $this->cache_group )
);
}
/**
* Flush the cache group.
*
* @since 1.9.0
*
* @return bool
* @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
*/
private function cache_flush_group(): bool {
if ( $this->cache_disabled ) {
return false;
}
$all_keys = wp_cache_get( self::ALL_KEYS, $this->cache_group, false, $found );
if ( ! $found ) {
return true;
}
$result = wp_cache_delete( self::ALL_KEYS, $this->cache_group );
foreach ( (array) $all_keys as $key ) {
$result = wp_cache_delete( $key, $this->cache_group ) && $result;
}
return $result;
}
/**
* Check if the query is a SELECT query.
*
* @since 1.9.0
*
* @param string|null $query SQL query.
*
* @return bool
* @noinspection PhpMissingParamTypeInspection
*/
private function is_select( $query ): bool {
return stripos( trim( (string) $query ), 'SELECT' ) === 0;
}
/**
* Get an instance of the current class.
* Used to reload the class while going through the blogs of multisite.
*
* @see WPForms_Install::maybe_create_tables()
*
* @since 1.8.9
*/
public static function get_instance(): WPForms_DB {
return new static();
}
}

View File

@@ -0,0 +1,98 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Load the field types.
*
* @since 1.0.0
*/
class WPForms_Fields {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
$this->init();
}
/**
* Initialize hooks.
*
* @since 1.2.8
* @since 1.8.2 Moved base class loading to \WPForms\WPForms::includes.
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.7
*/
private function hooks() {
// Load default fields on WP init.
add_action( 'init', [ $this, 'load' ] );
}
/**
* Load default field types.
*
* @since 1.0.0
* @since 1.9.4 Removed Pro fields from the list. They loaded in the main Loader class.
*/
public function load() {
$fields = [
'text',
'textarea',
'select',
'radio',
'checkbox',
'email',
'name',
'number',
'number-slider',
'internal-information',
];
// Include GDPR Checkbox field if GDPR enhancements are enabled.
if ( wpforms_setting( 'gdpr' ) ) {
$fields[] = 'gdpr-checkbox';
}
/**
* Filters array of fields to be loaded.
*
* @since 1.0.0
*
* @param array $fields Field types.
*/
$fields = (array) apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_load_fields',
$fields
);
foreach ( $fields as $field ) {
$file = WPFORMS_PLUGIN_DIR . 'includes/fields/class-' . $field . '.php';
if ( file_exists( $file ) ) {
require_once $file;
}
}
// We have to put it here due to tests for restricted emails.
new WPForms_Field_Email();
}
}
new WPForms_Fields();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Helpers\DB;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handle plugin installation upon activation.
*
* @since 1.0.0
*/
class WPForms_Install {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
// When activated, trigger install method.
register_activation_hook( WPFORMS_PLUGIN_FILE, [ $this, 'install' ] );
register_deactivation_hook( WPFORMS_PLUGIN_FILE, [ $this, 'deactivate' ] );
$this->hooks();
}
/**
* Hooks.
*
* @since 1.9.0
*
* @return void
*/
private function hooks() {
// Watch for new multisite blogs.
add_action( 'wp_initialize_site', [ $this, 'new_multisite_blog' ], 10, 2 );
// Watch for delayed admin install.
add_action( 'admin_init', [ $this, 'admin' ] );
}
/**
* Perform certain actions on plugin activation.
*
* @since 1.0.0
*
* @param bool $network_wide Whether to enable the plugin for all sites in the network
* or just the current site. Multisite only. Default is false.
*
* @noinspection DisconnectedForeachInstructionInspection
*/
public function install( $network_wide = false ) {
// Check if we are on multisite and network activating.
if ( $network_wide && is_multisite() ) {
// Multisite - go through each subsite and run the installer.
$sites = get_sites(
[
'fields' => 'ids',
'number' => 0,
]
);
foreach ( $sites as $blog_id ) {
switch_to_blog( $blog_id );
$this->run();
restore_current_blog();
}
} else {
// Normal single site.
$this->run();
}
set_transient( 'wpforms_just_activated', wpforms()->is_pro() ? 'pro' : 'lite', 60 );
// Abort, so we only set the transient for single site installs.
if ( isset( $_GET['activate-multi'] ) || is_network_admin() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// Add transient to trigger redirect to the Welcome screen.
set_transient( 'wpforms_activation_redirect', true, 30 );
}
/**
* Run manual installation.
*
* @since 1.5.4.2
*
* @param bool $silent Silent install, disables welcome page.
*/
public function manual( $silent = false ) {
$this->install( is_plugin_active_for_network( plugin_basename( WPFORMS_PLUGIN_FILE ) ) );
if ( $silent ) {
delete_transient( 'wpforms_activation_redirect' );
}
}
/**
* Perform certain actions on plugin deactivation.
*
* @since 1.5.9
*/
public function deactivate() {
// Unschedule all ActionScheduler actions by group.
wpforms()->obj( 'tasks' )->cancel_all();
// Remove plugin cron jobs.
wp_clear_scheduled_hook( 'wpforms_email_summaries_cron' );
// Check if the event is scheduled before attempting to clear it.
// This event is only registered for the Lite edition of the plugin.
// It's advisable to verify if the CRON event is scheduled using `wp_next_scheduled`.
// This precaution ensures that you are not attempting to clear a scheduled
// hook that may not exist, which could result in unexpected behavior.
if ( wp_next_scheduled( 'wpforms_weekly_entries_count_cron' ) ) {
wp_clear_scheduled_hook( 'wpforms_weekly_entries_count_cron' );
}
}
/**
* Watch for delayed install procedure from WPForms admin.
*
* @since 1.5.4.2
*/
public function admin() {
if ( ! is_admin() ) {
return;
}
$install = get_option( 'wpforms_install', false );
if ( empty( $install ) ) {
return;
}
$this->manual( true );
delete_option( 'wpforms_install' );
}
/**
* Run the actual installer.
*
* @since 1.5.4.2
*/
protected function run() {
// Create custom database tables.
$this->maybe_create_tables();
// Hook for Pro users.
/**
* Fires before WPForms plugin installation is performed.
*
* @since 1.3.0
*/
do_action( 'wpforms_install' );
/*
* Set the current version to be referenced in future updates.
*/
// Used by Pro migrations.
update_option( 'wpforms_version', WPFORMS_VERSION );
// Used by Lite migrations.
update_option( 'wpforms_version_lite', WPFORMS_VERSION );
// Store the date when the initial activation was performed.
$type = class_exists( 'WPForms_Lite', false ) ? 'lite' : 'pro';
$activated = (array) get_option( 'wpforms_activated', [] );
if ( empty( $activated[ $type ] ) ) {
$activated[ $type ] = time();
update_option( 'wpforms_activated', $activated );
}
}
/**
* When a new site is created in multisite, see if we are network activated,
* and if so run the installer.
*
* @since 1.3.0
* @since 1.8.4 Added $new_site and $args parameters and removed $blog_id, $user_id, $domain, $path, $site_id,
* and $meta parameters.
*
* @param WP_Site $new_site New site object.
* @param array $args Arguments for the initialization.
*
* @noinspection PhpUnusedParameterInspection
* @noinspection PhpMissingParamTypeInspection
*/
public function new_multisite_blog( $new_site, $args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
if ( is_plugin_active_for_network( plugin_basename( WPFORMS_PLUGIN_FILE ) ) ) {
switch_to_blog( $new_site->blog_id );
$this->run();
restore_current_blog();
}
}
/**
* Create database tables if they do not exist.
* It covers new installations.
*
* @since 1.8.2
*/
private function maybe_create_tables() {
DB::create_custom_tables( true );
}
}
new WPForms_Install();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Integrations\ConstantContact\V3\ConstantContact;
/**
* Load the providers.
*
* @since 1.3.6
*/
class WPForms_Providers {
/**
* Primary class constructor.
*
* @since 1.3.6
*/
public function __construct() {
$this->init();
}
/**
* Load and init the base provider class.
*
* @since 1.3.6
*/
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Parent class template.
require_once WPFORMS_PLUGIN_DIR . 'includes/providers/class-base.php';
// Load default templates on WP init.
add_action( 'wpforms_loaded', [ $this, 'load' ] );
}
/**
* Load default marketing providers.
*
* @since 1.3.6
*/
public function load() {
$providers = [];
if ( ConstantContact::get_current_version() === 2 ) {
$providers[] = 'constant-contact';
}
/**
* Allow third-party plugins to load their own providers.
*
* @since 1.7.0
*
* @param array $providers Array of providers to load.
*/
$providers = (array) apply_filters( 'wpforms_load_providers', $providers ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
foreach ( $providers as $provider ) {
$provider = sanitize_file_name( $provider );
$path = WPFORMS_PLUGIN_DIR . 'includes/providers/class-' . $provider . '.php';
if ( file_exists( $path ) ) {
require_once $path;
}
/**
* Allow third-party plugins to load their own providers.
*
* @since 1.7.0
*/
do_action( "wpforms_load_{$provider}_provider" ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
}
new WPForms_Providers();

View File

@@ -0,0 +1,66 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Pre-configured packaged templates.
*
* @since 1.0.0
*/
class WPForms_Templates {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
$this->init();
}
/**
* Load and init the base form template class.
*
* @since 1.2.8
*/
public function init() {
// Parent class template.
require_once WPFORMS_PLUGIN_DIR . 'includes/templates/class-base.php';
// Load default templates on WP init.
add_action( 'init', [ $this, 'load' ] );
}
/**
* Load default form templates.
*
* @since 1.0.0
*/
public function load() {
$templates = apply_filters(
'wpforms_load_templates',
[
'blank',
'simple-contact-form',
]
);
foreach ( $templates as $template ) {
$template = sanitize_file_name( $template );
if ( file_exists( WPFORMS_PLUGIN_DIR . 'includes/templates/class-' . $template . '.php' ) ) {
require_once WPFORMS_PLUGIN_DIR . 'includes/templates/class-' . $template . '.php';
} elseif ( file_exists( WPFORMS_PLUGIN_DIR . 'pro/includes/templates/class-' . $template . '.php' ) && wpforms()->is_pro() ) {
require_once WPFORMS_PLUGIN_DIR . 'pro/includes/templates/class-' . $template . '.php';
}
}
}
}
new WPForms_Templates;

View File

@@ -0,0 +1,192 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WPForms widget.
*
* @since 1.0.2
*/
class WPForms_Widget extends WP_Widget {
/**
* Hold widget settings defaults, populated in constructor.
*
* @since 1.0.2
*
* @var array
*/
protected $defaults;
/**
* Constructor.
*
* @since 1.0.2
*/
public function __construct() {
// Widget defaults.
$this->defaults = [
'title' => '',
'form_id' => '',
'show_title' => false,
'show_desc' => false,
];
// Widget Slug.
$widget_slug = 'wpforms-widget';
// Widget basics.
$widget_ops = [
'classname' => $widget_slug,
'description' => esc_html_x( 'Display a form.', 'Widget', 'wpforms-lite' ),
'show_instance_in_rest' => false,
];
// Widget controls.
$control_ops = [
'id_base' => $widget_slug,
];
// Load widget.
parent::__construct( $widget_slug, esc_html_x( 'WPForms', 'Widget', 'wpforms-lite' ), $widget_ops, $control_ops );
}
/**
* Output the HTML for this widget.
*
* @since 1.0.2
*
* @param array $args An array of standard parameters for widgets in this theme.
* @param array $instance An array of settings for this widget instance.
*/
public function widget( $args, $instance ) {
// Merge with defaults.
$instance = wp_parse_args( (array) $instance, $this->defaults );
$args = wp_parse_args(
$args,
[
'before_widget' => '',
'after_widget' => '',
'before_title' => '',
'after_title' => '',
]
);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['before_widget'];
if ( ! empty( $instance['title'] ) ) {
// phpcs:ignore
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ) . $args['after_title'];
}
if ( ! empty( $instance['form_id'] ) ) {
wpforms()->obj( 'frontend' )->output( absint( $instance['form_id'] ), (bool) $instance['show_title'], (bool) $instance['show_desc'] );
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget'];
}
/**
* Deal with the settings when they are saved by the admin. Here is
* where any validation should be dealt with.
*
* @since 1.0.2
*
* @param array $new_instance An array of new settings as submitted by the admin.
* @param array $old_instance An array of the previous settings.
*
* @return array The validated and (if necessary) amended settings
*/
public function update( $new_instance, $old_instance ) {
$new_instance['title'] = wp_strip_all_tags( $new_instance['title'] );
$new_instance['form_id'] = ! empty( $new_instance['form_id'] ) ? (int) $new_instance['form_id'] : 0;
$new_instance['show_title'] = isset( $new_instance['show_title'] ) && $new_instance['show_title'] ? '1' : false;
$new_instance['show_desc'] = isset( $new_instance['show_desc'] ) && $new_instance['show_desc'] ? '1' : false;
return $new_instance;
}
/**
* Display the form for this widget on the Widgets page of the WP Admin area.
*
* @since 1.0.2
*
* @param array $instance An array of the current settings for this widget.
*/
public function form( $instance ) {
// Merge with defaults.
$instance = wp_parse_args( (array) $instance, $this->defaults );
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
<?php echo esc_html( _x( 'Title:', 'Widget', 'wpforms-lite' ) ); ?>
</label>
<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat"/>
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>">
<?php echo esc_html( _x( 'Form:', 'Widget', 'wpforms-lite' ) ); ?>
</label>
<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'form_id' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'form_id' ) ); ?>">
<?php
$forms = wpforms()->obj( 'form' )->get();
if ( ! empty( $forms ) ) {
echo '<option value="" selected disabled>' . esc_html_x( 'Select your form', 'Widget', 'wpforms-lite' ) . '</option>';
foreach ( $forms as $form ) {
echo '<option value="' . esc_attr( $form->ID ) . '" ' . selected( $instance['form_id'], $form->ID, false ) . '>' . esc_html( $form->post_title ) . '</option>';
}
} else {
echo '<option value="">' . esc_html_x( 'No forms', 'Widget', 'wpforms-lite' ) . '</option>';
}
?>
</select>
</p>
<p>
<input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'show_title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'show_title' ) ); ?>" <?php checked( '1', $instance['show_title'] ); ?>>
<label for="<?php echo esc_attr( $this->get_field_id( 'show_title' ) ); ?>">
<?php echo esc_html( _x( 'Display form name', 'Widget', 'wpforms-lite' ) ); ?>
</label>
<br>
<input type="checkbox" id="<?php echo esc_attr( $this->get_field_id( 'show_desc' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'show_desc' ) ); ?>" <?php checked( '1', $instance['show_desc'] ); ?>>
<label for="<?php echo esc_attr( $this->get_field_id( 'show_desc' ) ); ?>">
<?php echo esc_html( _x( 'Display form description', 'Widget', 'wpforms-lite' ) ); ?>
</label>
</p>
<?php
}
}
/**
* Register the WPForms widget.
*
* @since 1.0.2
*/
function wpforms_register_widgets() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Hide legacy widget. This filter is available from WP 5.8.
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
add_filter(
'widget_types_to_hide_from_legacy_widget_block',
static function ( $widget_types ) {
$widget_types[] = 'wpforms-widget';
return $widget_types;
}
);
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
register_widget( 'WPForms_Widget' );
}
add_action( 'widgets_init', 'wpforms_register_widgets' );

View File

@@ -0,0 +1,425 @@
<?php
// phpcs:ignoreFile Generic.Files.OneObjectStructurePerFile.MultipleFound
namespace WPForms {
/**
* The removed class helps prevent fatal errors for clients
* that use some of the classes we are about to remove.
* Use the class extending instead of class_alias function.
*
* @since 1.8.0
*/
class Removed {
/**
* List of removed classes in the next format:
* Fully Qualified Class Name => version.
*
* @since 1.8.0
*/
const CLASSES = [
'WPForms\Pro\Admin\Entries\DefaultScreen' => '1.8.2',
'WPForms\Pro\Integrations\AI\AI' => '1.9.4',
'WPForms\Pro\Integrations\AI\Helpers' => '1.9.4',
'WPForms_Install_Skin' => '1.9.5',
'WPForms_Install_Silent_Skin' => '1.5.6.1',
'WPForms\Helpers\PluginSilentUpgraderSkin' => '1.9.5',
];
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*/
public function __construct() {
self::trigger_error();
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $name Property name.
*/
public function __get( $name ) {
self::trigger_error( $name );
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $name Property name.
* @param mixed $value Property value.
*/
public function __set( $name, $value ) {
self::trigger_error( $name );
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $name Property name.
*/
public function __isset( $name ) {
self::trigger_error( $name );
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $name Method name.
* @param array $arguments List of arguments.
*/
public function __call( $name, $arguments ) {
self::trigger_error( $name );
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $name Method name.
* @param array $arguments List of arguments.
*/
public static function __callStatic( $name, $arguments ) {
self::trigger_error( $name );
}
/**
* Inform clients that the class is removed.
*
* @since 1.8.0
*
* @param string $element_name Property or method name.
*/
private static function trigger_error( $element_name = '' ) {
$current_class = static::class;
$removed_element = $current_class;
if ( $element_name ) {
$removed_element .= '::' . $element_name;
}
$version = ! empty( self::CLASSES[ $current_class ] ) ? self::CLASSES[ $current_class ] : WPFORMS_VERSION;
trigger_error(
sprintf(
'%1$s has been removed in %2$s of the WPForms plugin',
esc_html( $removed_element ),
esc_html( $version )
),
E_USER_WARNING
);
}
}
}
namespace WPForms\Forms {
use WPForms\Removed;
class Loader extends Removed {}
}
namespace {
/**
* Legacy `WPForms_Addons` class was refactored and moved to the new `WPForms\Pro\Admin\Pages\Addons` class.
* This alias is a safeguard to those developers who use our internal class WPForms_Addons,
* which we deleted.
*
* @since 1.6.7
*/
class_alias( wpforms()->is_pro() ? 'WPForms\Pro\Admin\Pages\Addons' : 'WPForms\Lite\Admin\Pages\Addons', 'WPForms_Addons' );
/**
* This alias is a safeguard to those developers who decided to use our internal class WPForms_Smart_Tags,
* which we deleted.
*
* @since 1.6.7
*/
class_alias( wpforms()->is_pro() ? 'WPForms\Pro\SmartTags\SmartTags' : 'WPForms\SmartTags\SmartTags', 'WPForms_Smart_Tags' );
/**
* This alias is a safeguard to those developers who decided to use our internal class \WPForms\Providers\Loader,
* which we deleted.
*
* @since 1.7.3
*/
class_alias( '\WPForms\Providers\Providers', '\WPForms\Providers\Loader' );
/**
* Legacy `\WPForms\Admin\Notifications` class was refactored and moved to the new `\WPForms\Admin\Notifications\Notifications` class.
* This alias is a safeguard to those developers who use our internal class \WPForms\Admin\Notifications,
* which we deleted.
*
* @since 1.7.5
*/
class_alias( '\WPForms\Admin\Notifications\Notifications', '\WPForms\Admin\Notifications' );
/**
* Legacy `\WPForms_Field_Payment_Checkbox` class was refactored and moved to the new `\WPForms\Forms\Fields\PaymentCheckbox\Field` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Payment_Checkbox,
* which we deleted.
*
* @since 1.8.2
*/
class_alias( '\WPForms\Forms\Fields\PaymentCheckbox\Field', '\WPForms_Field_Payment_Checkbox' );
/**
* Legacy `\WPForms_Field_Payment_Multiple` class was refactored and moved to the new `\WPForms\Forms\Fields\PaymentMultiple\Field` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Payment_Multiple,
* which we deleted.
*
* @since 1.8.2
*/
class_alias( '\WPForms\Forms\Fields\PaymentMultiple\Field', '\WPForms_Field_Payment_Multiple' );
/**
* Legacy `\WPForms_Field_Payment_Single` class was refactored and moved to the new `\WPForms\Forms\Fields\PaymentSingle\Field` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Payment_Single,
* which we deleted.
*
* @since 1.8.2
*/
class_alias( '\WPForms\Forms\Fields\PaymentSingle\Field', '\WPForms_Field_Payment_Single' );
/**
* Legacy `\WPForms_Field_Payment_Total` class was refactored and moved to the new `\WPForms\Forms\Fields\PaymentTotal\Field` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Payment_Total,
* which we deleted.
*
* @since 1.8.2
*/
class_alias( '\WPForms\Forms\Fields\PaymentTotal\Field', '\WPForms_Field_Payment_Total' );
/**
* Legacy `\WPForms_Field_Payment_Select` class was refactored and moved to the new `\WPForms\Forms\Fields\PaymentSelect\Field` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Payment_Select,
* which we deleted.
*
* @since 1.8.2
*/
class_alias( '\WPForms\Forms\Fields\PaymentSelect\Field', '\WPForms_Field_Payment_Select' );
/**
* Legacy `\WPForms\Migrations` class was refactored and moved to the new `\WPForms\Migrations\Migrations` class.
* This alias is a safeguard to those developers who use our internal class \WPForms\Migrations, which we deleted.
*
* @since 1.7.5
*/
class_alias( '\WPForms\Migrations\Migrations', '\WPForms\Migrations' );
/**
* Legacy `\WPForms_Frontend` class was refactored and moved to the new `\WPForms\Frontend\Frontend` class.
* This alias is a safeguard to those developers who use our internal class \WPForms_Frontend, which we deleted.
*
* @since 1.8.1
*/
class_alias( '\WPForms\Frontend\Frontend', '\WPForms_Frontend' );
/**
* This alias is a safeguard to those developers who use our internal class \WPForms_Overview, which we deleted.
*
* @since 1.8.6
*/
class_alias( '\WPForms\Admin\Forms\Page', '\WPForms_Overview' );
/**
* This alias is a safeguard to those developers who use our internal class \WPForms_Overview_Table, which we deleted.
*
* @since 1.8.6
*/
class_alias( '\WPForms\Admin\Forms\ListTable', '\WPForms_Overview_Table' );
/**
* This alias is a safeguard to those developers who use our internal
* class \WPForms\Emails\FetchInfoBlocksTask, which we deleted.
*
* @since 1.9.8
*/
class_alias( '\WPForms\Emails\Tasks\FetchInfoBlocksTask', '\WPForms\Emails\FetchInfoBlocksTask' );
// Pro specific aliases.
if ( wpforms()->is_pro() ) {
/**
* Legacy `\WPForms\Pro\Migrations` class was refactored and moved to the new `\WPForms\Pro\Migrations\Migrations` class.
* This alias is a safeguard to those developers who use our internal class \WPForms\Migrations, which we deleted.
*
* @since 1.7.5
*/
class_alias( '\WPForms\Pro\Migrations\Migrations', '\WPForms\Pro\Migrations' );
/**
* Legacy `\WPForms\Pro\Integrations\TranslationsPress\Translations` class was refactored and moved to the new
* `\WPForms\Pro\Integrations\Translations\Translations` class.
* This alias is a safeguard to those developers who use our internal class \WPForms\Pro\Integrations\TranslationsPress, which we deleted.
*
* @since 1.8.2.2
*/
class_alias( '\WPForms\Pro\Integrations\Translations\Translations', '\WPForms\Pro\Integrations\TranslationsPress\Translations' );
/**
* This alias is a safeguard to those developers who use our internal class \WPForms_Entries_List, which we deleted.
*
* @since 1.8.6
*/
class_alias( '\WPForms\Pro\Admin\Entries\Page', '\WPForms_Entries_List' );
/**
* This alias is a safeguard to those developers who use our internal class \WPForms_Entries_Table, which we deleted.
*
* @since 1.8.6
*/
class_alias( '\WPForms\Pro\Admin\Entries\ListTable', '\WPForms_Entries_Table' );
/**
* This alias is a safeguard to those developers who use our internal class \WPForms_Field_Layout, which we deleted.
*
* @since 1.8.9
*/
class_alias( '\WPForms\Pro\Forms\Fields\Layout\Field', '\WPForms_Field_Layout' );
/**
* These aliases are a safeguard to those developers who use our internal legacy field classes, which we deleted.
*
* @since 1.9.4
*/
class_alias( '\WPForms\Pro\Forms\Fields\Address\Field', '\WPForms_Field_Address' );
class_alias( '\WPForms\Pro\Forms\Fields\Content\Field', '\WPForms_Field_Content' );
class_alias( '\WPForms\Pro\Forms\Fields\DateTime\Field', '\WPForms_Field_Date_Time' );
class_alias( '\WPForms\Pro\Forms\Fields\Divider\Field', '\WPForms_Field_Divider' );
class_alias( '\WPForms\Pro\Forms\Fields\EntryPreview\Field', '\WPForms_Entry_Preview' );
class_alias( '\WPForms\Pro\Forms\Fields\FileUpload\Field', '\WPForms_Field_File_Upload' );
class_alias( '\WPForms\Pro\Forms\Fields\Hidden\Field', '\WPForms_Field_Hidden' );
class_alias( '\WPForms\Pro\Forms\Fields\Html\Field', '\WPForms_Field_HTML' );
class_alias( '\WPForms\Pro\Forms\Fields\Pagebreak\Field', '\WPForms_Field_Page_Break' );
class_alias( '\WPForms\Pro\Forms\Fields\Password\Field', '\WPForms_Field_Password' );
class_alias( '\WPForms\Pro\Forms\Fields\CreditCard\Field', '\WPForms_Field_CreditCard' );
class_alias( '\WPForms\Pro\Forms\Fields\Phone\Field', '\WPForms_Field_Phone' );
class_alias( '\WPForms\Pro\Forms\Fields\Rating\Field', '\WPForms_Rating_Text' );
class_alias( '\WPForms\Pro\Forms\Fields\Richtext\Field', '\WPForms_Field_Richtext' );
class_alias( '\WPForms\Pro\Forms\Fields\Url\Field', '\WPForms_Field_URL' );
/**
* This alias is a safeguard to those developers who use our internal
* class \WPForms\Pro\Integrations\AI\API\Forms, which we deleted.
*
* @since 1.9.4
*/
class_alias( '\WPForms\Integrations\AI\API\Forms', '\WPForms\Pro\Integrations\AI\API\Forms' );
/**
* This alias is a safeguard to those developers who use our internal
* class \WPForms\Pro\Integrations\AI\Admin\Ajax\Forms, which we deleted.
*
* @since 1.9.4
*/
class_alias( '\WPForms\Integrations\AI\Admin\Ajax\Forms', '\WPForms\Pro\Integrations\AI\Admin\Ajax\Forms' );
/**
* This alias is a safeguard to those developers who use our internal
* class \WPForms\Pro\Integrations\AI\Admin\Builder\Enqueues, which we deleted.
*
* @since 1.9.4
*/
class_alias( '\WPForms\Integrations\AI\Admin\Builder\Forms', '\WPForms\Pro\Integrations\AI\Admin\Builder\Enqueues' );
/**
* This alias is a safeguard to those developers who use our internal
* class \WPForms\Pro\Integrations\AI\Admin\Pages\Templates, which we deleted.
*
* @since 1.9.4
*/
class_alias( '\WPForms\Integrations\AI\Admin\Pages\Templates', '\WPForms\Pro\Integrations\AI\Admin\Pages\Templates' );
}
/**
* This adds backwards compatibility after scoping the stripe lib and using our own prefix `\WPForms\Vendor\Stripe`.
* This alias is a safeguard for the users who update core plugin to 1.8.5 but have an older version of stripe pro addon.
* Fire this right before autoloading of legacy classes so that there is no conflict with other stripe libs when aliasing.
*
* @since 1.8.5
*/
spl_autoload_register(
static function ( $class_name ) {
static $stripe_check_done = false;
static $aliases = [
'\WPForms\Vendor\Stripe\Charge' => 'Stripe\Charge',
'\WPForms\Vendor\Stripe\Customer' => 'Stripe\Customer',
'\WPForms\Vendor\Stripe\Subscription' => 'Stripe\Subscription',
'\WPForms\Vendor\Stripe\Invoice' => 'Stripe\Invoice',
'\WPForms\Vendor\Stripe\Exception\CardException' => 'Stripe\Exception\CardException',
'\WPForms\Vendor\Stripe\Source' => 'Stripe\Source',
];
if ( $stripe_check_done ) {
return;
}
// If class not for aliasing, bail.
if ( ! in_array( $class_name, $aliases, true ) ) {
return;
}
$stripe_check_done = true;
// If no Stripe Pro addon bail.
if ( ! defined( 'WPFORMS_STRIPE_VERSION' ) ) {
return;
}
// Version 3.2.0 has prefixed lib.
// Versions 2.11.0 and below already have the lib bundled, so they don't require alias.
if (
version_compare( WPFORMS_STRIPE_VERSION, '3.2.0', '>=' ) ||
version_compare( WPFORMS_STRIPE_VERSION, '2.11.0', '<=' )
) {
return;
}
// We only need to alias if we are using the legacy API version.
if ( ! \WPFormsStripe\Helpers::is_legacy_api_version() ) {
return;
}
// If a lib is already loaded by a third party plugin,
// checking the CardException class here as a niche to make sure it is the correct library.
if ( class_exists( '\Stripe\Exception\CardException', false ) ) {
return;
}
foreach ( $aliases as $prefixed => $alias ) {
class_alias( $prefixed, '\\' . $alias );
}
}
);
}
namespace WPForms\Pro\Admin\Entries {
/**
* The default Entries screen showed a chart and the form entries stats.
* Replaced with "WPForms\Pro\Admin\Entries\Overview".
*
* @since 1.5.5
* @deprecated 1.8.2
*/
class DefaultScreen extends \WPForms\Removed {}
}

View File

@@ -0,0 +1,907 @@
<?php
use WPForms\Helpers\Templates;
use WPForms\Tasks\Actions\EntryEmailsTask;
/**
* Emails.
*
* This class handles all (notification) emails sent by WPForms.
*
* Heavily influenced by the great AffiliateWP plugin by Pippin Williamson.
* https://github.com/AffiliateWP/AffiliateWP/blob/master/includes/emails/class-affwp-emails.php
*
* Note that this mailer class is no longer in active use and has been replaced with the "WPForms\Emails\Notifications" class.
* Please refer to the new mailer wrapper extension to extend or add further customizations.
*
* @deprecated 1.8.5
*
* @since 1.1.3
*/
class WPForms_WP_Emails {
/**
* Store the from address.
*
* @since 1.1.3
*
* @var string
*/
private $from_address;
/**
* Store the from name.
*
* @since 1.1.3
*
* @var string
*/
private $from_name;
/**
* Store the reply-to address.
*
* @since 1.1.3
*
* @var bool|string
*/
private $reply_to = false;
/**
* Store the reply-to name.
*
* @since 1.7.9
*
* @var bool|string
*/
private $reply_to_name = false;
/**
* Store the carbon copy addresses.
*
* @since 1.3.1
*
* @var string
*/
private $cc = false;
/**
* Store the email content type.
*
* @since 1.1.3
*
* @var string
*/
private $content_type;
/**
* Store the email headers.
*
* @since 1.1.3
*
* @var string
*/
private $headers;
/**
* Whether to send email in HTML.
*
* @since 1.1.3
*
* @var bool
*/
private $html = true;
/**
* The email template to use.
*
* @since 1.1.3
*
* @var string
*/
private $template;
/**
* Form data and settings.
*
* @since 1.1.3
*
* @var array
*/
public $form_data = [];
/**
* Fields, formatted, and sanitized.
*
* @since 1.1.3
*
* @var array
*/
public $fields = [];
/**
* Entry ID.
*
* @since 1.2.3
*
* @var int
*/
public $entry_id = '';
/**
* Notification ID that is currently being processed.
*
* @since 1.5.7
*
* @var int
*/
public $notification_id = '';
/**
* Context data to be passed to the tag.
*
* @since 1.9.9.2
*
* @var array|array[]
*/
private $context_data = [];
/**
* Get things going.
*
* @since 1.1.3
*/
public function __construct() {
if ( 'none' === $this->get_template() ) {
$this->html = false;
}
add_action( 'wpforms_email_send_before', [ $this, 'send_before' ] );
add_action( 'wpforms_email_send_after', [ $this, 'send_after' ] );
}
/**
* Set a property.
*
* @since 1.1.3
*
* @param string $key Object property key.
* @param mixed $value Object property value.
*/
public function __set( $key, $value ) {
$this->$key = $value;
}
/**
* Get the email from name.
*
* @since 1.1.3
*
* @return string The email from name
*/
public function get_from_name() {
if ( ! empty( $this->from_name ) ) {
$this->from_name = $this->process_tag( $this->from_name );
} else {
$this->from_name = get_bloginfo( 'name' );
}
return apply_filters( 'wpforms_email_from_name', wpforms_decode_string( $this->from_name ), $this );
}
/**
* Get the email from address.
*
* @since 1.1.3
*
* @return string The email from address.
*/
public function get_from_address() {
if ( ! empty( $this->from_address ) ) {
$this->from_address = $this->process_tag( $this->from_address );
} else {
$this->from_address = get_option( 'admin_email' );
}
return apply_filters( 'wpforms_email_from_address', wpforms_decode_string( $this->from_address ), $this );
}
/**
* Get the email reply-to.
*
* @since 1.1.3
*
* @return string The email reply-to address.
*/
public function get_reply_to() {
if ( ! empty( $this->reply_to ) ) {
$email = $this->reply_to;
// Optional custom format with a Reply-to Name specified: John Doe <john@doe.com>
// - starts with anything,
// - followed by space,
// - ends with <anything> (expected to be an email, validated later).
$regex = '/^(.+) (<.+>)$/';
$matches = [];
if ( preg_match( $regex, $this->reply_to, $matches ) ) {
$this->reply_to_name = wpforms_decode_string( $this->process_tag( $matches[1] ) );
$email = trim( $matches[2], '<> ' );
}
$this->reply_to = $this->process_tag( $email );
if ( ! is_email( $this->reply_to ) ) {
$this->reply_to = false;
$this->reply_to_name = false;
}
}
return apply_filters( 'wpforms_email_reply_to', wpforms_decode_string( $this->reply_to ), $this );
}
/**
* Get the email carbon copy addresses.
*
* @since 1.3.1
*
* @return string The email reply-to address.
*/
public function get_cc() {
if ( is_array( $this->cc ) ) {
$this->cc = implode( ',', $this->cc );
}
if ( ! empty( $this->cc ) ) {
$this->cc = $this->process_tag( $this->cc );
$addresses = array_map( 'trim', explode( ',', $this->cc ) );
foreach ( $addresses as $key => $address ) {
if ( ! is_email( $address ) ) {
unset( $addresses[ $key ] );
}
}
$this->cc = implode( ',', $addresses );
}
return apply_filters( 'wpforms_email_cc', wpforms_decode_string( $this->cc ), $this );
}
/**
* Get the email content type.
*
* @since 1.1.3
*
* @return string The email content type.
*/
public function get_content_type() {
if ( ! $this->content_type && $this->html ) {
$this->content_type = apply_filters( 'wpforms_email_default_content_type', 'text/html', $this );
} elseif ( ! $this->html ) {
$this->content_type = 'text/plain';
}
return apply_filters( 'wpforms_email_content_type', $this->content_type, $this );
}
/**
* Get the email headers.
*
* @since 1.1.3
*
* @return string The email headers.
*/
public function get_headers() {
if ( ! $this->headers ) {
$this->headers = "From: {$this->get_from_name()} <{$this->get_from_address()}>\r\n";
if ( $this->get_reply_to() ) {
$this->headers .= $this->reply_to_name ?
"Reply-To: {$this->reply_to_name} <{$this->get_reply_to()}>\r\n" :
"Reply-To: {$this->get_reply_to()}\r\n";
}
if ( $this->get_cc() ) {
$this->headers .= "Cc: {$this->get_cc()}\r\n";
}
$this->headers .= "Content-Type: {$this->get_content_type()}; charset=utf-8\r\n";
}
return apply_filters( 'wpforms_email_headers', $this->headers, $this );
}
/**
* Build the email.
*
* @since 1.1.3
*
* @param string $message The email message.
*
* @return string
*/
public function build_email( $message ) {
// Plain text email shortcut.
if ( false === $this->html ) {
$message = $this->process_tag( $message );
$message = str_replace( '{all_fields}', $this->wpforms_html_field_value( false ), $message );
return apply_filters( 'wpforms_email_message', wpforms_decode_string( $message ), $this );
}
/*
* Generate an HTML email.
*/
ob_start();
$this->get_template_part( 'header', $this->get_template(), true );
// Hooks into the email header.
do_action( 'wpforms_email_header', $this );
$this->get_template_part( 'body', $this->get_template(), true );
// Hooks into the email body.
do_action( 'wpforms_email_body', $this );
$this->get_template_part( 'footer', $this->get_template(), true );
// Hooks into the email footer.
do_action( 'wpforms_email_footer', $this );
$message = $this->process_tag( $message );
$message = nl2br( $message );
$body = ob_get_clean();
$message = str_replace( '{email}', $message, $body );
$message = str_replace( '{all_fields}', $this->wpforms_html_field_value( true ), $message );
$message = make_clickable( $message );
return apply_filters( 'wpforms_email_message', $message, $this );
}
/**
* Send the email.
*
* @since 1.1.3
*
* @param string $to The To address.
* @param string $subject The subject line of the email.
* @param string $message The body of the email.
* @param array $attachments Attachments to the email.
*
* @return bool
*/
public function send( $to, $subject, $message, $attachments = [] ) {
if ( ! did_action( 'init' ) && ! did_action( 'admin_init' ) ) {
_doing_it_wrong( __FUNCTION__, esc_html__( 'You cannot send emails with WPForms_WP_Emails() until init/admin_init has been reached.', 'wpforms-lite' ), null );
return false;
}
// Don't send anything if emails have been disabled.
if ( $this->is_email_disabled() ) {
return false;
}
// Don't send if email address is invalid.
if ( ! is_email( $to ) ) {
return false;
}
$this->context_data = [ 'to_email' => (array) $to ];
// Hooks before email is sent.
do_action( 'wpforms_email_send_before', $this );
// Deprecated filter for $attachments.
$attachments = apply_filters_deprecated(
'wpforms_email_attachments',
[ $attachments, $this ],
'1.5.7 of the WPForms plugin',
'wpforms_emails_send_email_data'
);
/*
* Allow to filter data on per-email basis,
* useful for localizations based on recipient email address, form settings,
* or for specific notifications - whatever available in WPForms_WP_Emails class.
*/
$data = apply_filters(
'wpforms_emails_send_email_data',
[
'to' => $to,
'subject' => $subject,
'message' => $message,
'headers' => $this->get_headers(),
'attachments' => $attachments,
],
$this
);
// Update context data, as 'to' email address could be changed by the filter above.
$this->context_data = [ 'to_email' => (array) $data['to'] ];
$entry_obj = wpforms()->obj( 'entry' );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
$send_same_process = apply_filters(
'wpforms_tasks_entry_emails_trigger_send_same_process',
false,
$this->fields,
$entry_obj ? $entry_obj->get( $this->entry_id ) : [],
$this->form_data,
$this->entry_id,
'entry'
);
if (
$send_same_process ||
! empty( $this->form_data['settings']['disable_entries'] )
) {
// Let's do this NOW.
$result = wp_mail(
$data['to'],
$this->get_prepared_subject( $data['subject'] ),
$this->build_email( $data['message'] ),
$data['headers'],
$data['attachments']
);
} else {
// Schedule the email.
$result = (bool) ( new EntryEmailsTask() )
->params(
$data['to'],
$this->get_prepared_subject( $data['subject'] ),
$this->build_email( $data['message'] ),
$data['headers'],
$data['attachments']
)
->register();
}
/**
* Hooks after the email is sent.
*
* @since 1.1.3
*
* @param WPForms_WP_Emails $this Current instance of this object.
*/
do_action( 'wpforms_email_send_after', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
return $result;
}
/**
* Add filters/actions before the email is sent.
*
* @since 1.1.3
*/
public function send_before() {
add_filter( 'wp_mail_from', [ $this, 'get_from_address' ] );
add_filter( 'wp_mail_from_name', [ $this, 'get_from_name' ] );
add_filter( 'wp_mail_content_type', [ $this, 'get_content_type' ] );
}
/**
* Remove filters/actions after the email is sent.
*
* @since 1.1.3
*/
public function send_after() {
remove_filter( 'wp_mail_from', [ $this, 'get_from_address' ] );
remove_filter( 'wp_mail_from_name', [ $this, 'get_from_name' ] );
remove_filter( 'wp_mail_content_type', [ $this, 'get_content_type' ] );
}
/**
* Convert text formatted HTML. This is primarily for turning line breaks
* into <p> and <br/> tags.
*
* @since 1.1.3
*
* @param string $message Text to convert.
*
* @return string
*/
public function text_to_html( $message ) {
if ( 'text/html' === $this->content_type || true === $this->html ) {
$message = wpautop( $message );
}
return $message;
}
/**
* Process a smart tag.
* Decodes entities and sanitized (keeping line breaks) by default.
*
* @uses wpforms_decode_string()
*
* @since 1.1.3
* @since 1.6.0 Deprecated 2 params: $sanitize, $linebreaks.
*
* @param string $content String that may contain tags.
*
* @return string|mixed
*/
public function process_tag( $content = '' ) {
return wpforms_process_smart_tags( $content, $this->form_data, $this->fields, $this->entry_id, 'email', $this->context_data );
}
/**
* Process the all fields smart tag if present.
*
* @since 1.1.3
*
* @param bool $is_html_email Toggle to use HTML or plaintext.
*
* @return string
*/
public function wpforms_html_field_value( $is_html_email = true ) { // phpcs:ignore
if ( empty( $this->fields ) ) {
return '';
}
if ( empty( $this->form_data['fields'] ) ) {
$is_html_email = false;
}
$message = '';
if ( $is_html_email ) {
/*
* HTML emails.
*/
ob_start();
// Hooks into the email field.
do_action( 'wpforms_email_field', $this );
$this->get_template_part( 'field', $this->get_template(), true );
$field_template = ob_get_clean();
// Check to see if user has added support for field type.
$other_fields = apply_filters( 'wpforms_email_display_other_fields', [], $this );
$x = 1;
foreach ( $this->form_data['fields'] as $field_id => $field ) {
$field_name = '';
$field_val = '';
// If the field exists in the form_data but not in the final
// field data, then it's a non-input based field, "other fields".
if ( empty( $this->fields[ $field_id ] ) ) {
// Check if the field type is in $other_fields, otherwise skip.
// Skip if the field is conditionally hidden.
if (
empty( $other_fields ) ||
! in_array( $field['type'], $other_fields, true ) ||
(
wpforms()->is_pro() &&
wpforms_conditional_logic_fields()->field_is_hidden( $this->form_data, $field_id )
)
) {
continue;
}
if ( $field['type'] === 'divider' ) {
$field_name = ! empty( $field['label'] ) ? str_repeat( '&mdash;', 3 ) . ' ' . $field['label'] . ' ' . str_repeat( '&mdash;', 3 ) : null;
$field_val = ! empty( $field['description'] ) ? $field['description'] : '';
} elseif ( $field['type'] === 'pagebreak' ) {
if ( ! empty( $field['position'] ) && $field['position'] === 'bottom' ) {
continue;
}
$title = ! empty( $field['title'] ) ? $field['title'] : esc_html__( 'Page Break', 'wpforms-lite' );
$field_name = str_repeat( '&mdash;', 6 ) . ' ' . $title . ' ' . str_repeat( '&mdash;', 6 );
} elseif ( $field['type'] === 'html' ) {
$field_name = ! empty( $field['name'] ) ? $field['name'] : esc_html__( 'HTML / Code Block', 'wpforms-lite' );
$field_val = $field['code'];
} elseif ( $field['type'] === 'content' ) {
$field_name = esc_html__( 'Content', 'wpforms-lite' );
$field_val = $field['content'];
}
} else {
if (
! apply_filters( 'wpforms_email_display_empty_fields', false ) &&
( ! isset( $this->fields[ $field_id ]['value'] ) || (string) $this->fields[ $field_id ]['value'] === '' )
) {
/** This filter is documented in wpforms/includes/emails/class-emails.php */
$message .= apply_filters( 'wpforms_wp_emails_html_field_value_message_html', '' , $field, $this->form_data );
continue;
}
if ( $field['type'] === 'payment-total' ) {
$field_name = isset( $this->fields[ $field_id ]['name'] ) ? $this->fields[ $field_id ]['name'] : '';
// Replace the payment total value if an order summary is enabled.
// Ideally, it could be done through the `wpforms_html_field_value` filter,
// but needed data is missed there, e.g. entry data ($this->fields).
if ( ! empty( $field['summary'] ) ) {
$field_val = $this->process_tag( '{order_summary}' );
} else {
$field_val = $this->fields[ $field_id ]['value'];
}
} else {
$field_name = isset( $this->fields[ $field_id ]['name'] ) ? $this->fields[ $field_id ]['name'] : '';
$field_val = empty( $this->fields[ $field_id ]['value'] ) && ! is_numeric( $this->fields[ $field_id ]['value'] ) ? '<em>' . esc_html__( '(empty)', 'wpforms-lite' ) . '</em>' : $this->fields[ $field_id ]['value'];
}
}
if ( empty( $field_name ) && null !== $field_name ) {
$field_name = sprintf( /* translators: %d - field ID. */
esc_html__( 'Field ID #%s', 'wpforms-lite' ),
wpforms_validate_field_id( $field['id'] )
);
}
$field_item = $field_template;
if ( 1 === $x ) {
$field_item = str_replace( 'border-top:1px solid #dddddd;', '', $field_item );
}
/**
* Filter the field name before it is added to the email message.
*
* @since 1.9.1
*
* @param string $field_name Field name.
* @param array $field Field data.
* @param array $form_data Form data and settings.
* @param string $context Context of the field name.
*/
$field_name = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_html_field_name',
$field_name,
$this->fields[ $field_id ] ?? $field,
$this->form_data,
'email-html'
);
$field_item = str_replace( '{field_name}', $field_name, $field_item );
$field_item = str_replace(
'{field_value}',
apply_filters(
'wpforms_html_field_value',
$field_val,
isset( $this->fields[ $field_id ] ) ? $this->fields[ $field_id ] : $field,
$this->form_data,
'email-html'
),
$field_item
);
/**
* Filter the field item before it is added to the email message.
*
* @since 1.9.3
*
* @param string $field_message Field message.
* @param array $field Field data.
* @param array $form_data Form data and settings.
*/
$message .= apply_filters( 'wpforms_wp_emails_html_field_value_message_html', wpautop( $field_item ), $field, $this->form_data );
$x ++;
}
} else {
/*
* Plain Text emails.
*/
foreach ( $this->fields as $field ) {
if (
! apply_filters( 'wpforms_email_display_empty_fields', false ) &&
( ! isset( $field['value'] ) || (string) $field['value'] === '' )
) {
continue;
}
$field_val = empty( $field['value'] ) && ! is_numeric( $field['value'] ) ? esc_html__( '(empty)', 'wpforms-lite' ) : $field['value'];
$field_name = $field['name'];
if ( empty( $field_name ) ) {
$field_name = sprintf( /* translators: %d - field ID. */
esc_html__( 'Field ID #%s', 'wpforms-lite' ),
wpforms_validate_field_id( $field['id'] )
);
}
$message .= '--- ' . $field_name . " ---\r\n\r\n";
$field_value = $field_val . "\r\n\r\n";
$message .= apply_filters( 'wpforms_plaintext_field_value', $field_value, $field, $this->form_data );
}
}
if ( empty( $message ) ) {
$empty_message = esc_html__( 'An empty form was submitted.', 'wpforms-lite' );
$message = $is_html_email ? wpautop( $empty_message ) : $empty_message;
}
return $message;
}
/**
* Email kill switch if needed.
*
* @since 1.1.3
*
* @return bool
*/
public function is_email_disabled() {
return (bool) apply_filters( 'wpforms_disable_all_emails', false, $this );
}
/**
* Get the enabled email template.
*
* @since 1.1.3
*
* @return string When filtering return 'none' to switch to text/plain email.
*/
public function get_template() {
if ( ! $this->template ) {
$this->template = wpforms_setting( 'email-template', 'default' );
}
return apply_filters( 'wpforms_email_template', $this->template );
}
/**
* Retrieve a template part. Taken from bbPress.
*
* @since 1.1.3
*
* @param string $slug Template file slug.
* @param string $name Optional. Default null.
* @param bool $load Maybe load.
*
* @return string
*/
public function get_template_part( $slug, $name = null, $load = true ) {
// Setup possible parts.
$templates = [];
if ( isset( $name ) ) {
$templates[] = $slug . '-' . $name . '.php';
}
$templates[] = $slug . '.php';
// Return the part that is found.
return $this->locate_template( $templates, $load, false );
}
/**
* Retrieve the name of the highest priority template file that exists.
*
* Search in the STYLESHEETPATH before TEMPLATEPATH so that themes which
* inherit from a parent theme can just overload one file. If the template is
* not found in either of those, it looks in the theme-compat folder last.
*
* Taken from bbPress.
*
* @since 1.1.3
*
* @param string|array $template_names Template file(s) to search for, in order.
* @param bool $load If true the template file will be loaded if it is found.
* @param bool $require_once Whether to require_once or require. Default true.
* Has no effect if $load is false.
*
* @return string The template filename if one is located.
*/
public function locate_template( $template_names, $load = false, $require_once = true ) {
// No file found yet.
$located = false;
// Try to find a template file.
foreach ( (array) $template_names as $template_name ) {
// Continue if template is empty.
if ( empty( $template_name ) ) {
continue;
}
// Trim off any slashes from the template name.
$template_name = ltrim( $template_name, '/' );
// Try locating this template file by looping through the template paths.
foreach ( $this->get_theme_template_paths() as $template_path ) {
$validated_path = Templates::validate_safe_path(
$template_path . $template_name,
[ 'theme', 'plugins' ]
);
if ( $validated_path ) {
$located = $validated_path;
break;
}
}
}
if ( ( true === $load ) && ! empty( $located ) ) {
load_template( $located, $require_once );
}
return $located;
}
/**
* Return a list of paths to check for template locations
*
* @since 1.1.3
*
* @return array
*/
public function get_theme_template_paths() {
$template_dir = 'wpforms-email';
$file_paths = [
1 => trailingslashit( get_stylesheet_directory() ) . $template_dir,
10 => trailingslashit( get_template_directory() ) . $template_dir,
100 => WPFORMS_PLUGIN_DIR . 'includes/emails/templates',
];
$file_paths = apply_filters( 'wpforms_email_template_paths', $file_paths );
// Sort the file paths based on priority.
ksort( $file_paths, SORT_NUMERIC );
return array_map( 'trailingslashit', $file_paths );
}
/**
* Perform email subject preparation: process tags, remove new lines, etc.
*
* @since 1.6.1
*
* @param string $subject Email subject to post-process.
*
* @return string
*/
private function get_prepared_subject( $subject ) {
$subject = $this->process_tag( $subject );
$subject = trim( str_replace( [ "\r\n", "\r", "\n" ], ' ', $subject ) );
return wpforms_decode_string( $subject );
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Email Body
*
* Heavily influenced by the great AffiliateWP plugin by Pippin Williamson.
* https://github.com/AffiliateWP/AffiliateWP/tree/master/templates/emails
*
* @since 1.1.3
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
{email}

View File

@@ -0,0 +1,328 @@
<?php
/**
* Default email template.
*
* Don't forget to run final template through
* http://templates.mailchimp.com/resources/inline-css/
*
* @since 1.1.3
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$header_image = wpforms_setting( 'email-header-image', false );
?>
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!--[if gte mso 15]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style type="text/css">
p{
margin:10px 0;
padding:0;
}
table{
border-collapse:collapse;
}
h1,h2,h3,h4,h5,h6{
display:block;
margin:0;
padding:0;
}
img,a img{
border:0;
height:auto;
outline:none;
text-decoration:none;
}
body,#bodyTable,#bodyCell{
height:100%;
margin:0;
padding:0;
width:100%;
}
#outlook a{
padding:0;
}
img{
-ms-interpolation-mode:bicubic;
}
table{
mso-table-lspace:0pt;
mso-table-rspace:0pt;
}
.ReadMsgBody{
width:100%;
}
.ExternalClass{
width:100%;
}
p,a,li,td,blockquote{
mso-line-height-rule:exactly;
}
a[href^=tel],a[href^=sms]{
color:inherit;
cursor:default;
text-decoration:none;
}
p,a,li,td,body,table,blockquote{
-ms-text-size-adjust:100%;
-webkit-text-size-adjust:100%;
}
.ExternalClass,.ExternalClass p,.ExternalClass td,.ExternalClass div,.ExternalClass span,.ExternalClass font{
line-height:100%;
}
a[x-apple-data-detectors]{
color:inherit !important;
text-decoration:none !important;
font-size:inherit !important;
font-family:inherit !important;
font-weight:inherit !important;
line-height:inherit !important;
}
#bodyCell{
padding:50px 50px;
}
.templateContainer{
max-width:600px !important;
border:0;
}
a.mcnButton{
display:block;
}
.mcnTextContent{
word-break:break-word;
}
.mcnTextContent img{
height:auto !important;
}
.mcnDividerBlock{
table-layout:fixed !important;
}
/***** Make theme edits below if needed *****/
/* Page - Background Style */
body,#bodyTable{
background-color:#e9eaec;
}
/* Page - Heading 1 */
h1{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:26px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
text-align:left;
}
/* Page - Heading 2 */
h2{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:22px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
text-align:left;
}
/* Page - Heading 3 */
h3{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:20px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
text-align:left;
}
/* Page - Heading 4 */
h4{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:18px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
text-align:left;
}
/* Header - Header Style */
#templateHeader{
border-top:0;
border-bottom:0;
padding-top:0px;
padding-bottom:20px;
}
/* Body - Body Style */
#templateBody{
background-color:#FFFFFF;
border-top:0;
border: 1px solid #c1c1c1;
padding-top:0;
padding-bottom:0px;
}
/* Body -Body Text */
#templateBody .mcnTextContent,
#templateBody .mcnTextContent p{
color:#555555;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:14px;
line-height:150%;
text-align:left;
}
/* Body - Body Link */
#templateBody .mcnTextContent a,
#templateBody .mcnTextContent p a{
color:#ff7f50;
font-weight:normal;
text-decoration:underline;
}
/* Footer - Footer Style */
#templateFooter{
background-color:#e9eaec;
border-top:0;
border-bottom:0;
padding-top:12px;
padding-bottom:12px;
}
/* Footer - Footer Text */
#templateFooter .mcnTextContent,
#templateFooter .mcnTextContent p{
color:#cccccc;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:12px;
line-height:150%;
text-align:center;
}
/* Footer - Footer Link */
#templateFooter .mcnTextContent a,
#templateFooter .mcnTextContent p a{
color:#cccccc;
font-weight:normal;
text-decoration:underline;
}
@media only screen and (min-width:768px){
.templateContainer{
width:600px !important;
}
}
@media only screen and (max-width: 480px){
body,table,td,p,a,li,blockquote{
-webkit-text-size-adjust:none !important;
}
}
@media only screen and (max-width: 480px){
body{
width:100% !important;
min-width:100% !important;
}
}
@media only screen and (max-width: 480px){
#bodyCell{
padding:20px !important;
}
}
@media only screen and (max-width: 480px){
.mcnTextContentContainer{
max-width:100% !important;
width:100% !important;
}
}
</style>
</head>
<body>
<center>
<table align="center" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="bodyTable">
<tr>
<td align="center" valign="top" id="bodyCell">
<!-- BEGIN TEMPLATE // -->
<!--[if gte mso 9]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600" style="width:600px;">
<tr>
<td align="center" valign="top" width="600" style="width:600px;">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="templateContainer">
<?php
if ( !empty( $header_image ) ) {
echo '<tr><td valign="top" align="center" id="templateHeader" style="padding-bottom:20px;text-align:center;">';
echo '<img src="' . esc_url( $header_image ) . '" alt="' . esc_attr( get_bloginfo( 'name' ) ) . '" />';
echo '</td></tr>';
}
?>
<tr>
<td valign="top" id="templateBody">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="mcnTextBlock" style="min-width:100%;">
<tbody class="mcnTextBlockOuter">
<tr>
<td valign="top" class="mcnTextBlockInner">
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%" style="min-width:100%;" class="mcnTextContentContainer">
<tbody>
<tr>
<td valign="top" class="mcnTextContent" style="padding-top:30px; padding-right: 30px; padding-bottom: 30px; padding-left: 30px;">
<!-- Content -->
<h1>Content.</h1>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td valign="top" id="templateFooter">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="mcnTextBlock" style="min-width:100%;">
<tbody class="mcnTextBlockOuter">
<tr>
<td valign="top" class="mcnTextBlockInner">
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%" style="min-width:100%;" class="mcnTextContentContainer">
<tbody>
<tr>
<td valign="top" class="mcnTextContent" style="padding-top:9px; padding-right: 18px; padding-bottom: 9px; padding-left: 18px;">
<!-- Footer content -->
Footer
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--[if gte mso 9]>
</td>
</tr>
</table>
<![endif]-->
<!-- // END TEMPLATE -->
</td>
</tr>
</table>
</center>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<?php
/**
* Email form field entry.
*
* This is used with the {all_fields} smart tag.
*
* @since 1.1.3
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-top:1px solid #dddddd; min-width: 100%;border-collapse: collapse;width:100%;"><tbody>
<tr><td style="color:#333333;padding-top: 20px;padding-bottom: 3px;"><strong>{field_name}</strong></td></tr>
<tr><td style="color:#555555;padding-top: 3px;padding-bottom: 20px;">{field_value}</td></tr>
</tbody></table>

View File

@@ -0,0 +1,67 @@
<?php
/**
* Email Footer.
*
* @since 1.1.3
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$background_color = wpforms_setting( 'email-background-color', '#e9eaec' );
?>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td valign="top" id="templateFooter" style="mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: <?php echo esc_attr( $background_color ); ?>;border-top: 0;border-bottom: 0;padding-top: 12px;padding-bottom: 12px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="mcnTextBlock" style="min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
<tbody class="mcnTextBlockOuter">
<tr>
<td valign="top" class="mcnTextBlockInner" style="mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%" style="min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;" class="mcnTextContentContainer">
<tbody>
<tr>
<td valign="top" class="mcnTextContent" style="padding-top: 9px;padding-right: 18px;padding-bottom: 9px;padding-left: 18px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;word-break: break-word;color: #aaa;font-family: Helvetica;font-size: 12px;line-height: 150%;text-align: center;">
<!-- Footer content -->
<?php
/* translators: %s - link to the site. */
$footer = sprintf( esc_html__( 'Sent from %s', 'wpforms-lite' ), '<a href="' . esc_url( home_url() ) . '" style="color:#bbbbbb;">' . wp_specialchars_decode( get_bloginfo( 'name' ) ) . '</a>' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo apply_filters( 'wpforms_email_footer_text', $footer );
?>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--[if gte mso 9]>
</td>
</tr>
</table>
<![endif]-->
<!-- // END TEMPLATE -->
</td>
</tr>
</table>
</center>
</body>
</html>

View File

@@ -0,0 +1,376 @@
<?php
/**
* Email Header
*
* @since 1.1.3
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$header_image = wpforms_setting( 'email-header-image', false );
$background_color = wpforms_setting( 'email-background-color', '#e9eaec' );
$text_direction = is_rtl() ? 'rtl' : 'ltr';
?>
<!doctype html>
<html dir="<?php echo esc_attr( $text_direction ); ?>" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!--[if gte mso 15]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo esc_html( get_bloginfo( 'name' ) ); ?></title>
<style type="text/css">
p{
margin:10px 0;
padding:0;
}
table{
border-collapse:collapse;
}
h1,h2,h3,h4,h5,h6{
display:block;
margin:0;
padding:0;
}
img,a img{
border:0;
height:auto;
outline:none;
text-decoration:none;
}
body,#bodyTable,#bodyCell{
height:100%;
margin:0;
padding:0;
width:100%;
}
#outlook a{
padding:0;
}
img{
-ms-interpolation-mode:bicubic;
}
table{
mso-table-lspace:0pt;
mso-table-rspace:0pt;
}
.ReadMsgBody{
width:100%;
}
.ExternalClass{
width:100%;
}
p,a,li,td,blockquote{
mso-line-height-rule:exactly;
}
a[href^=tel],a[href^=sms]{
color:inherit;
cursor:default;
text-decoration:none;
}
p,a,li,td,body,table,blockquote{
-ms-text-size-adjust:100%;
-webkit-text-size-adjust:100%;
}
.ExternalClass,.ExternalClass p,.ExternalClass td,.ExternalClass div,.ExternalClass span,.ExternalClass font{
line-height:100%;
}
a[x-apple-data-detectors]{
color:inherit !important;
text-decoration:none !important;
font-size:inherit !important;
font-family:inherit !important;
font-weight:inherit !important;
line-height:inherit !important;
}
#bodyCell{
padding:50px 50px;
}
.templateContainer{
max-width:600px !important;
border:0;
}
a.mcnButton{
display:block;
}
.mcnTextContent{
word-break:break-word;
}
.mcnTextContent img{
height:auto !important;
}
.mcnDividerBlock{
table-layout:fixed !important;
}
/***** Make theme edits below if needed *****/
/* Page - Background Style */
body,#bodyTable{
background-color:<?php echo esc_attr( $background_color ); ?>;
}
/* Page - Heading 1 */
h1{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:26px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
}
/* Page - Heading 2 */
h2{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:22px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
}
/* Page - Heading 3 */
h3{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:20px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
}
/* Page - Heading 4 */
h4{
color:#202020;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:18px;
font-style:normal;
font-weight:bold;
line-height:125%;
letter-spacing:normal;
}
/* Header - Header Style */
#templateHeader{
border-top:0;
border-bottom:0;
padding-top:0;
padding-bottom:20px;
text-align: center;
}
/* Body - Body Style */
#templateBody{
background-color:#FFFFFF;
border-top:0;
border: 1px solid #c1c1c1;
padding-top:0;
padding-bottom:0px;
}
/* Body -Body Text */
#templateBody .mcnTextContent,
#templateBody .mcnTextContent p{
color:#555555;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:14px;
line-height:150%;
}
/* Body - Body Link */
#templateBody .mcnTextContent a,
#templateBody .mcnTextContent p a{
color:#ff7f50;
font-weight:normal;
text-decoration:underline;
}
/* Footer - Footer Style */
#templateFooter{
background-color:<?php echo esc_attr( $background_color ); ?>;
border-top:0;
border-bottom:0;
padding-top:12px;
padding-bottom:12px;
}
/* Footer - Footer Text */
#templateFooter .mcnTextContent,
#templateFooter .mcnTextContent p{
color:#cccccc;
font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;
font-size:12px;
line-height:150%;
text-align:center;
}
/* Footer - Footer Link */
#templateFooter .mcnTextContent a,
#templateFooter .mcnTextContent p a{
color:#cccccc;
font-weight:normal;
text-decoration:underline;
}
@media only screen and (min-width:768px){
.templateContainer{
width:600px !important;
}
}
@media only screen and (max-width: 480px){
body,table,td,p,a,li,blockquote{
-webkit-text-size-adjust:none !important;
}
}
@media only screen and (max-width: 480px){
body{
width:100% !important;
min-width:100% !important;
}
}
@media only screen and (max-width: 680px){
#bodyCell{
padding:20px 20px !important;
}
}
@media only screen and (max-width: 480px){
.mcnTextContentContainer{
max-width:100% !important;
width:100% !important;
}
}
/* Rich Text compatibility - image alignment. */
.mcnTextContentContainer p::after {
content: "";
clear: both;
display: block;
}
.mcnTextContentContainer p .alignleft, .mcnTextContentContainer li .alignleft {
float: left;
margin-right: 16px;
margin-top: 8px;
margin-bottom: 8px;
}
.mcnTextContentContainer p .aligncenter, .mcnTextContentContainer li .aligncenter {
display: block;
margin-left: auto;
margin-right: auto;
}
.mcnTextContentContainer p .alignright, .mcnTextContentContainer li .alignright {
float: right;
margin-left: 16px;
margin-top: 8px;
margin-bottom: 8px;
}
/* Rich text compatibility - table */
.wpforms-iframe table,
.wpforms-iframe table th,
.wpforms-iframe table td {
border: 1px solid currentColor;
padding: 5px;
}
/* styling lists */
.mcnTextContentContainer li {
list-style-position: inside;
mso-text-indent-alt: 0;
}
/* Order Summary */
.wpforms-order-summary-container {
display: block;
border-width: 1px;
border-style: solid;
max-width: 100%;
border-color: #e2e2e2;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
box-sizing: content-box;
margin: 0;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tbody {
display: contents;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr th,
.wpforms-order-summary-container table.wpforms-order-summary-preview tr td {
text-align: center;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview .wpforms-order-summary-item-label {
text-align: start;
padding-left: 10px;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview .wpforms-order-summary-item-quantity {
width: 8ch;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview .wpforms-order-summary-item-price {
width: 6ch;
text-align: end;
padding-right: 10px;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr.wpforms-order-summary-placeholder td {
text-align: start;
padding-left: 10px;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr {
border-bottom-width: 1px;
border-bottom-style: solid;
border-color: #e2e2e2;
display: table-row;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr th,
.wpforms-order-summary-container table.wpforms-order-summary-preview tr td {
padding: 9px 0;
line-height: 20px;
background: none;
border: none;
font-weight: 400;
display: table-cell;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr.wpforms-order-summary-preview-subtotal td,
.wpforms-order-summary-container table.wpforms-order-summary-preview tr.wpforms-order-summary-preview-total td {
font-weight: 700;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr.wpforms-order-summary-preview-total {
border-bottom: none;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview caption,
.wpforms-order-summary-container table.wpforms-order-summary-preview .wpforms-order-summary-placeholder-hidden,
.wpforms-order-summary-container table.wpforms-order-summary-preview .wpforms-order-summary-item-quantity-label-short {
display: none;
}
.wpforms-order-summary-container table.wpforms-order-summary-preview tr.wpforms-order-summary-preview-coupon-total td.wpforms-order-summary-item-price {
color: #d63638;
}
</style>
</head>
<body style="height: 100%;margin: 0;padding: 0;width: 100%;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: <?php echo esc_attr( $background_color ); ?>;">
<!-- Don't forget to run final template through http://templates.mailchimp.com/resources/inline-css/ -->
<center>
<table align="center" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="bodyTable" style="border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;height: 100%;margin: 0;padding: 0;width: 100%;background-color: <?php echo esc_attr( $background_color ); ?>;">
<tr>
<td align="center" valign="top" id="bodyCell" style="mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;height: 100%;margin: 0;padding: 50px 50px;width: 100%;">
<!-- BEGIN TEMPLATE // -->
<!--[if gte mso 9]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600" style="width:600px;">
<tr>
<td align="center" valign="top" width="600" style="width:600px;">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="templateContainer" style="border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border: 0;max-width: 600px !important;">
<?php
if ( ! empty( $header_image ) ) {
echo '<tr><td valign="top" align="center" id="templateHeader" style="padding-bottom:20px;text-align:center;">';
echo '<img src="' . esc_url( $header_image ) . '" alt="' . esc_attr( get_bloginfo( 'name' ) ) . '" />';
echo '</td></tr>';
}
?>
<tr>
<td valign="top" id="templateBody" style="mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: #FFFFFF;border-top: 0;border: 1px solid #c1c1c1;padding-top: 0;padding-bottom: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="mcnTextBlock" style="min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
<tbody class="mcnTextBlockOuter">
<tr>
<td valign="top" class="mcnTextBlockInner" style="mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
<table align="left" border="0" cellpadding="0" cellspacing="0" width="100%" style="min-width: 100%;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;" class="mcnTextContentContainer">
<tbody>
<tr>
<td valign="top" style="padding-top: 30px;padding-right: 30px;padding-bottom: 30px;padding-left: 30px;" class="mcnTextContent">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,773 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Checkbox field.
*
* @since 1.0.0
*/
class WPForms_Field_Checkbox extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Checkboxes', 'wpforms-lite' );
$this->keywords = esc_html__( 'choice', 'wpforms-lite' );
$this->type = 'checkbox';
$this->icon = 'fa-check-square-o';
$this->order = 110;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks() {
// Customize HTML field values.
add_filter( 'wpforms_html_field_value', [ $this, 'field_html_value' ], 10, 4 );
add_filter( "wpforms_{$this->type}_field_html_value_images", [ $this, 'field_html_value_images' ], 10, 3 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_checkbox', [ $this, 'field_properties' ], 5, 3 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", '__return_true', PHP_INT_MAX, 2 );
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @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 ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [ ! empty( $field['random'] ) ? 'wpforms-randomize' : '' ],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
$is_choice_limit_set = ! empty( $field['choice_limit'] ) && (int) $field['choice_limit'] > 0;
if ( $is_choice_limit_set ) {
$properties['input_container']['data']['choice-limit'] = $field['choice_limit'];
}
// Set input properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$label = isset( $choice['label'] ) ? $choice['label'] : '';
// Choice labels should not be left blank, but if they are we
// provide a basic value.
$value = isset( $field['show_values'] ) ? $choice['value'] : $label;
if ( '' === $value ) {
if ( 1 === count( $choices ) ) {
$value = esc_html__( 'Checked', 'wpforms-lite' );
} else {
/* translators: %s - choice number. */
$value = sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key );
}
}
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $label,
],
'attr' => [
'name' => "wpforms[fields][{$field_id}][]",
'value' => $value,
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'icon' => isset( $choice['icon'] ) ? $choice['icon'] : '',
'icon_style' => isset( $choice['icon_style'] ) ? $choice['icon_style'] : '',
'image' => isset( $choice['image'] ) ? $choice['image'] : '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
// Rule for validator only if needed.
if ( $is_choice_limit_set ) {
$properties['inputs'][ $key ]['data']['rule-check-limit'] = 'true';
}
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Custom properties if image choices is enabled.
if ( ! $dynamic && ! empty( $field['choices_images'] ) ) {
$properties['input_container']['class'][] = 'wpforms-image-choices';
$properties['input_container']['class'][] = 'wpforms-image-choices-' . sanitize_html_class( $field['choices_images_style'] );
foreach ( $properties['inputs'] as $key => $inputs ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-image-choices-item';
if ( in_array( $field['choices_images_style'], [ 'modern', 'classic' ], true ) ) {
$properties['inputs'][ $key ]['class'][] = 'wpforms-screen-reader-element';
}
}
} elseif ( ! $dynamic && ! empty( $field['choices_icons'] ) ) {
$properties = wpforms()->obj( 'icon_choices' )->field_properties( $properties, $field );
}
// Custom properties for disclaimer format display.
if ( ! empty( $field['disclaimer_format'] ) ) {
$properties['description']['class'][] = 'wpforms-disclaimer-description';
$properties['description']['value'] = nl2br( $properties['description']['value'] );
}
// Add selected class for choices with defaults.
foreach ( $properties['inputs'] as $key => $inputs ) {
if ( ! empty( $inputs['default'] ) ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-selected';
}
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Choices Images.
$this->field_option( 'choices_images', $field );
// Hide Choices Images.
$this->field_option( 'choices_images_hide', $field );
// Choices Images Style (theme).
$this->field_option( 'choices_images_style', $field );
// Choices Icons.
$this->field_option( 'choices_icons', $field );
// Choices Icons Color.
$this->field_option( 'choices_icons_color', $field );
// Choices Icons Size.
$this->field_option( 'choices_icons_size', $field );
// Choices Icons Style.
$this->field_option( 'choices_icons_style', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Randomize order of choices.
$this->field_element(
'row',
$field,
[
'slug' => 'random',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'random',
'value' => isset( $field['random'] ) ? '1' : '0',
'desc' => esc_html__( 'Randomize Choices', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to randomize the order of the choices.', 'wpforms-lite' ),
],
false
),
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => isset( $field['show_values'] ) ? $field['show_values'] : '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
),
]
);
}
// Display format.
$this->field_option( 'input_columns', $field );
// Choice Limit.
$this->field_option( 'choice_limit', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Enable Disclaimer formatting.
$this->field_element(
'row',
$field,
[
'slug' => 'disclaimer_format',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'disclaimer_format',
'value' => isset( $field['disclaimer_format'] ) ? '1' : '0',
'desc' => esc_html__( 'Enable Disclaimer / Terms of Service Display', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to adjust the field styling to support Disclaimers and Terms of Service type agreements.', 'wpforms-lite' ),
],
false
),
]
);
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option(
'description',
$field,
[
'class' => ! empty( $field['disclaimer_format'] ) ? 'disclaimer nl2br' : false,
]
);
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
$using_image_choices = empty( $field['dynamic_choices'] ) && ! empty( $field['choices_images'] );
$using_icon_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_images'] ) && ! empty( $field['choices_icons'] );
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
$amp_state_id = '';
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$amp_state_id = str_replace( '-', '_', sanitize_key( $container['id'] ) ) . '_state';
$state = [];
foreach ( $choices as $key => $choice ) {
$state[ $choice['id'] ] = ! empty( $choice['default'] );
}
printf(
'<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
esc_attr( $amp_state_id ),
wp_json_encode( $state )
);
}
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$choice['container']['attr']['[class]'] = sprintf(
'%s + ( %s[%s] ? " wpforms-selected" : "")',
wp_json_encode( implode( ' ', $choice['container']['class'] ) ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
}
// If the field is required, has the label hidden, and has
// disclaimer mode enabled, so the required status in choice
// label.
$required = '';
if ( ! empty( $field['disclaimer_format'] ) && ! empty( $choice['required'] ) && ! empty( $field['label_hide'] ) ) {
$required = wpforms_get_field_required_label();
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
// The required constraint in HTML5 form validation does not work with checkbox groups, so omit in AMP.
$required_attr = wpforms_is_amp() && count( $choices ) > 1 ? '' : $choice['required'];
if ( $using_image_choices ) {
// Make sure the image choices are keyboard-accessible.
$choice['label']['attr']['tabindex'] = 0;
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState({ %s: { %s: ! %s[%s] } })',
wp_json_encode( $amp_state_id ),
wp_json_encode( $choice['id'] ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
$choice['label']['attr']['role'] = 'button';
}
if ( is_array( $choice['label']['class'] ) && wpforms_is_empty_string( $label ) ) {
$choice['label']['class'][] = 'wpforms-field-label-inline-empty';
}
// Image choices.
printf(
'<label %s>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] )
);
echo '<span class="wpforms-image-choices-image">';
if ( ! empty( $choice['image'] ) ) {
printf(
'<img src="%s" alt="%s"%s>',
esc_url( $choice['image'] ),
esc_attr( $label ),
! empty( $label ) ? ' title="' . esc_attr( $label ) . '"' : ''
);
}
echo '</span>';
if ( $field['choices_images_style'] === 'none' ) {
echo '<br>';
}
$choice['attr']['tabindex'] = '-1';
if ( wpforms_is_amp() ) {
$choice['attr']['[checked]'] = sprintf(
'%s[%s]',
$amp_state_id,
wp_json_encode( $choice['id'] )
);
}
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $required_attr ),
checked( '1', $choice['default'], false )
);
echo '<span class="wpforms-image-choices-label">' . wp_kses_post( $label ) . '</span>';
echo '</label>';
} elseif ( $using_icon_choices ) {
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState({ %s: { %s: ! %s[%s] } })',
wp_json_encode( $amp_state_id ),
wp_json_encode( $choice['id'] ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
$choice['label']['attr']['role'] = 'button';
}
// Icon Choices.
wpforms()->obj( 'icon_choices' )->field_display( $field, $choice, 'checkbox' );
} else {
// Normal display.
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $required_attr ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $label ),
wp_kses(
$required,
[
'span' => [
'class' => true,
],
]
)
);
}
echo '</li>';
}
echo '</ul>';
}
/**
* Validate field on form submit.
*
* @since 1.5.2
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field_id = (int) $field_id;
$field = $form_data['fields'][ $field_id ];
// Skip validation if field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
$field_submit = (array) $field_submit;
$this->validate_field_choice_limit( $field_id, $field_submit, $form_data );
// Basic required check - If field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
(
empty( $field_submit ) ||
(
count( $field_submit ) === 1 &&
( ! isset( $field_submit[0] ) || (string) $field_submit[0] === '' )
)
)
) {
$error = wpforms_get_required_label();
}
if ( ! empty( $error ) ) {
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = $error;
}
}
/**
* Format and sanitize field.
*
* @since 1.0.2
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field_submit = (array) $field_submit;
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$name = sanitize_text_field( $field['label'] );
$value_raw = wpforms_sanitize_array_combine( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( 'post_type' === $dynamic && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type.
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = $value_raw;
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$posts = [];
foreach ( $field_submit as $id ) {
$post = get_post( $id );
if ( ! is_wp_error( $post ) && ! empty( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$posts[] = esc_html( wpforms_get_post_title( $post ) );
}
}
$data['value'] = ! empty( $posts ) ? wpforms_sanitize_array_combine( $posts ) : '';
}
elseif ( 'taxonomy' === $dynamic && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy.
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = $value_raw;
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$terms = [];
foreach ( $field_submit as $id ) {
$term = get_term( $id, $field['dynamic_taxonomy'] );
if ( ! is_wp_error( $term ) && ! empty( $term ) ) {
$terms[] = esc_html( wpforms_get_term_name( $term ) );
}
}
$data['value'] = ! empty( $terms ) ? wpforms_sanitize_array_combine( $terms ) : '';
} else {
// Normal processing, dynamic population is off.
$choice_keys = [];
// If show_values is true, that means values posted are the raw values
// and not the labels. So we need to set label values. Also store
// the choice keys.
if ( ! empty( $field['show_values'] ) && (int) $field['show_values'] === 1 ) {
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $key => $choice ) {
// Check if the submitted value is the same as the choice value or if the value is empty and the key matches.
// Skip if the submitted value is empty.
if ( ( ! empty( $item ) && $item === $choice['value'] ) || ( empty( $choice['value'] ) && (int) str_replace( 'Choice ', '', $item ) === $key ) ) {
$value[] = $choice['label'];
$choice_keys[] = $key;
break;
}
}
}
$data['value'] = ! empty( $value ) ? wpforms_sanitize_array_combine( $value ) : '';
} else {
$data['value'] = $value_raw;
// Determine choices keys, this is needed for image choices.
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $key => $choice ) {
/* translators: %s - choice number. */
if ( $item === $choice['label'] || $item === sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) ) {
$choice_keys[] = $key;
break;
}
}
}
}
// Images choices are enabled, lookup and store image URLs.
if ( ! empty( $choice_keys ) && ! empty( $field['choices_images'] ) ) {
$data['images'] = [];
foreach ( $choice_keys as $key ) {
$data['images'][] = ! empty( $field['choices'][ $key ]['image'] ) ? esc_url_raw( $field['choices'][ $key ]['image'] ) : '';
}
}
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
}
new WPForms_Field_Checkbox();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,329 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* GDPR Checkbox field.
*
* @since 1.4.6
*/
class WPForms_Field_GDPR_Checkbox extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.4.6
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'GDPR Agreement', 'wpforms-lite' );
$this->type = 'gdpr-checkbox';
$this->icon = 'fa-check-square-o';
$this->order = 500;
$this->allow_read_only = false;
$this->defaults = [
1 => [
'label' => esc_html__( 'I consent to having this website store my submitted information so they can respond to my inquiry.', 'wpforms-lite' ),
'value' => '',
'image' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
// Set field to default to the required.
add_filter( 'wpforms_field_new_required', [ $this, 'field_default_required' ], 10, 2 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_gdpr-checkbox', [ $this, 'field_properties' ], 5, 3 );
}
/**
* Field should default to being required.
*
* @since 1.4.6
*
* @param bool $required Required status, true is required.
* @param array $field Field settings.
*
* @return bool
*/
public function field_default_required( $required, $field ) {
if ( $this->type === $field['type'] ) {
return true;
}
return $required;
}
/**
* Define additional field properties.
*
* @since 1.4.6
*
* @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 ) {
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = absint( $field['id'] );
$choices = ! empty( $field['choices'] ) ? $field['choices'] : [];
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
// Set input properties.
foreach ( $choices as $key => $choice ) {
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}][]",
'value' => $choice['label'],
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'image' => '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => '',
];
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
return $properties;
}
/**
* Whether the current field can be populated dynamically.
*
* @since 1.9.4
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_dynamic_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Field options panel inside the builder.
*
* @since 1.4.6
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
// Field is always required.
$this->field_element(
'text',
$field,
[
'type' => 'hidden',
'slug' => 'required',
'value' => '1',
]
);
// -------------------------------------------------------------------//
// Basic field options
// -------------------------------------------------------------------//
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option(
'choices',
$field,
[
'label' => esc_html__( 'Agreement', 'wpforms-lite' ),
]
);
// Description.
$this->field_option( 'description', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
// -------------------------------------------------------------------//
// Advanced field options
// -------------------------------------------------------------------//
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.4.6
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.4.6
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $choice ) {
$required = '';
if ( ! empty( $choice['required'] ) && ! empty( $field['label_hide'] ) ) {
$required = wpforms_get_field_required_label();
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
// Normal display.
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $choice['label']['text'] ),
wp_kses_post( $required )
);
echo '</li>';
}
echo '</ul>';
}
/**
* Format and sanitize field.
*
* @since 1.4.6
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => ! empty( $form_data['fields'][ $field_id ]['label'] ) ? sanitize_text_field( $form_data['fields'][ $field_id ]['label'] ) : '',
'value' => $form_data['fields'][ $field_id ]['choices'][1]['label'],
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
}
new WPForms_Field_GDPR_Checkbox();

View File

@@ -0,0 +1,942 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Internal information field class.
*
* @since 1.7.6
*/
class WPForms_Field_Internal_Information extends WPForms_Field {
/**
* The key used to save form checkboxes in the post meta table.
*
* @since 1.7.6
*
* @var string
*/
private const CHECKBOX_META_KEY = 'wpforms_iif_checkboxes';
/**
* Class initialization method.
*
* @since 1.7.6
*/
public function init() {
$this->name = $this->is_editable() ? esc_html__( 'Internal Information', 'wpforms-lite' ) : esc_html__( 'This field is not editable', 'wpforms-lite' );
$this->type = 'internal-information';
$this->icon = 'fa fa-sticky-note-o';
$this->order = 550;
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.7.6
*
* @noinspection PhpUnnecessaryCurlyVarSyntaxInspection
*/
private function hooks() {
add_filter( 'wpforms_entries_table_fields_disallow', [ $this, 'hide_column_in_entries_table' ], 10, 2 );
add_filter( 'wpforms_field_preview_class', [ $this, 'add_css_class_for_field_wrapper' ], 10, 2 );
add_filter( 'wpforms_field_new_class', [ $this, 'add_css_class_for_field_wrapper' ], 10, 2 );
add_filter( "wpforms_pro_admin_entries_edit_is_field_displayable_{$this->type}", '__return_false' );
add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ], 10, 2 );
add_filter( 'wpforms_frontend_form_data', [ $this, 'remove_internal_fields_on_front_end' ] );
add_filter( 'wpforms_pro_fields_entry_preview_get_ignored_fields', [ $this, 'ignore_entry_preview' ] );
add_filter( 'wpforms_process_before_form_data', [ $this, 'process_before_form_data' ], 10, 2 );
add_filter( 'wpforms_field_preview_display_duplicate_button', [ $this, 'display_duplicate_button' ], 10, 3 );
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_enqueues' ] );
add_action( 'wp_ajax_wpforms_builder_save_internal_information_checkbox', [ $this, 'save_internal_information_checkbox' ] );
}
/**
* Whether the current field can be populated dynamically.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_dynamic_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Whether the current field can be populated using a fallback.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_fallback_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Define field options to display in the left panel.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
public function field_options( $field ) {
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
$this->heading_option( $field );
$this->field_option( 'description', $field );
$this->expanded_description_option( $field );
$this->cta_label_option( $field );
$this->cta_link_option( $field );
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
$this->field_code( $field );
}
/**
* Define field preview on the right side on builder.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
public function field_preview( $field ) {
$class = wpforms_sanitize_classes( $field['class'] ?? '' );
printf(
'<div class="internal-information-wrap wpforms-clear %s">',
esc_attr( $class )
);
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render( 'fields/internal-information/icon-lightbulb' );
echo '<div class="internal-information-content">';
$this->render_preview( 'heading', $field );
$this->render_preview( 'description', $field );
$this->render_preview( 'expanded-description', $field );
$this->render_preview( 'addon', $field );
if ( $this->is_button_displayable( $field ) ) {
echo '<div class="wpforms-field-internal-information-row wpforms-field-internal-information-row-cta-button">';
echo $this->render_custom_preview( 'cta-button', $field );
echo '</div>';
}
echo '</div>';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
/**
* Checks if the button is displayable.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return bool
*/
private function is_button_displayable( $field ): bool {
return ! empty( $field['expanded-description'] ) ||
( ! empty( $field['cta-label'] ) && ! empty( $field['cta-link'] ) ) ||
$this->is_editable();
}
/**
* Stub to make the field not visible in the front-end.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
* @param array $deprecated Field attributes.
* @param array $form_data Form data.
*/
public function field_display( $field, $deprecated, $form_data ) {
}
/**
* Heading option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function heading_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'heading',
'value' => esc_html__( 'Heading', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter text for the form field heading.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'label',
'value' => ! empty( $field['label'] ) ? esc_attr( $field['label'] ) : '',
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'heading',
'content' => $output,
]
);
}
/**
* Expanded description option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function expanded_description_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'expanded-description',
'value' => esc_html__( 'Expanded Content', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter text for the form field expanded description.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'textarea',
$field,
[
'slug' => 'expanded-description',
'value' => ! empty( $field['expanded-description'] ) ? esc_html( $field['expanded-description'] ) : '',
],
false
);
$output .= sprintf(
'<p class="note">%s</p>',
esc_html__( 'Adds an expandable content area below the description.', 'wpforms-lite' )
);
$this->field_element(
'row',
$field,
[
'slug' => 'expanded-description',
'content' => $output,
]
);
}
/**
* CTA label option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function cta_label_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'cta-label',
'value' => esc_html__( 'CTA Label', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter label for the form field call to action button. The label will be ignored if the field has extended description content: in that case button will be used to expand the description content.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'cta-label',
'value' => ! empty( $field['cta-label'] ) ? esc_attr( $field['cta-label'] ) : esc_attr__( 'Learn More', 'wpforms-lite' ),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'cta-label',
'content' => $output,
]
);
}
/**
* CTA link option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function cta_link_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'cta-link',
'value' => esc_html__( 'CTA Link', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter the URL for the form field call to action button. URL will be ignored if the field has extended description content: in that case button will be used to expand the description content.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'cta-link',
'value' => ! empty( $field['cta-link'] ) ? esc_url( $field['cta-link'] ) : '',
],
false
);
$output .= sprintf(
'<p class="note">%s</p>',
esc_html__( 'CTA is hidden if Expanded Content is used.', 'wpforms-lite' )
);
$this->field_element(
'row',
$field,
[
'slug' => 'cta-link',
'content' => $output,
]
);
}
/**
* Add hidden input with code identifier.
*
* @since 1.8.9
*
* @param array $field Field data and settings.
*/
private function field_code( $field ) {
$this->field_element(
'row',
$field,
[
'slug' => 'code',
'content' => sprintf(
'<input type="hidden" name="fields[%1$s][code]" value="%2$s">',
$field['id'],
! empty( $field['code'] ) ? esc_attr( $field['code'] ) : ''
),
]
);
}
/**
* Add a CSS class to hide field settings when the field is not editable.
*
* @since 1.7.6
*
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field preview arguments.
* @param bool $do_echo Print or return the value. Print by default.
*
* @return string|null
*/
public function field_element( $option, $field, $args = [], $do_echo = true ) {
if ( ! isset( $args['class'] ) ) {
$args['class'] = '';
}
if ( ! $this->is_editable() ) {
$args['class'] .= ' wpforms-hidden ';
}
return parent::field_element( $option, $field, $args, $do_echo );
}
/**
* Render a custom option preview on the right side of the builder.
*
* @since 1.7.6
*
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field arguments.
*
* @return string
* @noinspection HtmlUnknownTarget
*/
private function render_custom_preview( $option, $field, $args = [] ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$class = ! empty( $args['class'] ) ? wpforms_sanitize_classes( $args['class'] ) : '';
$allowed_tags = $this->get_allowed_tags();
switch ( $option ) {
case 'heading':
$label = isset( $field['label'] ) && ! wpforms_is_empty_string( $field['label'] ) ? esc_html( $field['label'] ) : '';
if ( ! $label ) {
$class .= ' hidden ';
}
return sprintf(
'<label class="label-title heading %s"><span class="text">%s</span><span class="required">*</span></label>',
esc_attr( $class ),
esc_html( $label )
);
case 'description': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$description = ! empty( $field['description'] ) ? wp_kses( $field['description'], $allowed_tags ) : '';
$description = wpautop( $this->replace_checkboxes( $description, $field ) );
$description = $this->add_link_attributes( $description );
return sprintf( '<div class="description %s">%s</div>', $class, $description );
case 'expanded-description': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$description = isset( $field['expanded-description'] ) && ! wpforms_is_empty_string( $field['expanded-description'] ) ? wp_kses( $field['expanded-description'], $allowed_tags ) : '';
$description = wpautop( $this->replace_checkboxes( $description, $field ) );
$description = $this->add_link_attributes( $description );
return sprintf( '<div class="expanded-description %s">%s</div>', esc_attr( $class ), wp_kses( $description, $allowed_tags ) );
case 'cta-button': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$label = ! empty( $field['cta-label'] ) && empty( $field['expanded-description'] ) ? esc_attr( $field['cta-label'] ) : esc_attr__( 'Learn More', 'wpforms-lite' );
if ( ! empty( $field['expanded-description'] ) ) {
return sprintf(
'<div class="cta-button cta-expand-description not-expanded %s"><a href="#" target="_blank" rel="noopener noreferrer"><span class="button-label">%s</span> %s %s</a></div>',
esc_attr( $class ),
esc_html( $label ),
wpforms_render( 'fields/internal-information/icon-not-expanded' ),
wpforms_render( 'fields/internal-information/icon-expanded' )
);
}
if ( ! empty( $field['cta-link'] ) ) {
return sprintf( '<div class="cta-button cta-link-external %s"><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></div>', esc_attr( $class ), esc_url( $this->add_url_utm( $field ) ), esc_html( $label ) );
}
return sprintf( '<div class="cta-button cta-link-external %s"><a href="" target="_blank" rel="noopener noreferrer" class="hidden"><span class="button-label"></span></a></div>', esc_attr( $class ) );
case 'addon':
if ( empty( $field['addon'] ) ) {
return '';
}
return sprintf( '<input type="hidden" name="fields[%1$s][addon]" value="%2$s">', esc_attr( $field['id'] ), esc_attr( $field['addon'] ) );
}
return '';
}
/**
* Display the field button in the left panel only if the field is editable.
*
* @since 1.7.6
*
* @param array $fields All fields to display in the left panel.
*
* @return array
*/
public function field_button( $fields ) {
if ( $this->is_editable() ) {
return parent::field_button( $fields );
}
return $fields;
}
/**
* When the form is going to be displayed on the front-end, remove internal information fields.
*
* @since 1.7.6
*
* @param array $form_data Form data.
*
* @return array
*/
public function remove_internal_fields_on_front_end( $form_data ) {
if ( empty( $form_data['fields'] ) ) {
return $form_data;
}
foreach ( $form_data['fields'] as $id => $field ) {
if ( $field['type'] === $this->type ) {
unset( $form_data['fields'][ $id ] );
}
}
return $form_data;
}
/**
* Add the internal information field to the list of ignored fields for entry preview.
*
* @since 1.9.1
*
* @param array|mixed $ignored_fields Ignored fields.
*
* @return array
*/
public function ignore_entry_preview( $ignored_fields ): array {
$ignored_fields = (array) $ignored_fields;
$ignored_fields[] = $this->type;
return $ignored_fields;
}
/**
* Remove field from form data before processing the form submit.
*
* @since 1.7.6
*
* @param array $form_data Form data.
* @param array $entry Form submission raw data ($_POST).
*
* @return array
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function process_before_form_data( $form_data, $entry ) {
return $this->remove_internal_fields_on_front_end( $form_data );
}
/**
* Do not display the duplicate button.
*
* @since 1.7.6
*
* @param bool $is_visible If true, the duplicate button will be displayed.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
*
* @return bool
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function display_duplicate_button( $is_visible, $field, $form_data ) {
if ( $this->is_internal_information_field( $field ) && ! $this->is_editable() ) {
return false;
}
return $is_visible;
}
/**
* Hide the column from the entry list table.
*
* @since 1.7.6
*
* @param array|mixed $disallowed Table columns.
*
* @return array
*/
public function hide_column_in_entries_table( $disallowed ): array {
$disallowed = (array) $disallowed;
$disallowed[] = $this->type;
return $disallowed;
}
/**
* Add a CSS class for the field parent div informing about mode (editable or not).
*
* @since 1.7.6
*
* @param string $css CSS classes.
* @param array $field Field data and settings.
*
* @return string
*/
public function add_css_class_for_field_wrapper( $css, $field ) {
if ( ! $this->is_internal_information_field( $field ) ) {
return $css;
}
// If the Internal Information field is added by some add-ons, it will be hidden by default.
// Add styles to the addon assets to display the field.
// When the addon is disabled, the field is hidden.
if ( ! empty( $field['addon'] ) ) {
$css .= sprintf( ' wpforms-field-internal-information-%s-addon wpforms-hidden', $field['addon'] );
}
if ( $this->is_editable() ) {
$css .= ' internal-information-editable ';
return $css;
}
$css .= ' ui-sortable-disabled internal-information-not-editable internal-information-not-draggable ';
return str_replace( 'ui-sortable-handle', '', $css );
}
/**
* Save the checkbox state to the post meta table.
*
* @since 1.7.6
*/
public function save_internal_information_checkbox(): void {
$form_id = isset( $_POST['formId'] ) ? absint( $_POST['formId'] ) : 0;
// Run several checks: required items, security, permissions.
if (
! $form_id ||
! isset( $_POST['name'], $_POST['checked'] ) ||
! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ||
! wpforms_current_user_can( 'edit_forms', $form_id )
) {
wp_send_json_error();
}
$checked = (int) $_POST['checked'];
$name = sanitize_text_field( wp_unslash( $_POST['name'] ) );
$post_meta = get_post_meta( $form_id, self::CHECKBOX_META_KEY, true );
$post_meta = ! empty( $post_meta ) ? (array) $post_meta : [];
if ( $checked ) {
$post_meta[ $name ] = $checked;
} else {
unset( $post_meta[ $name ] );
}
update_post_meta( $form_id, self::CHECKBOX_META_KEY, $post_meta );
wp_send_json_success();
}
/**
* Localized strings for a wpforms-internal-information-field JS script.
*
* @since 1.7.6
*
* @param array $strings Localized strings.
* @param array $form The form element.
*
* @return array
* @noinspection PhpUnusedParameterInspection
*/
public function builder_strings( $strings, $form ) {
$strings['iif_redirect_url_field_error'] = esc_html__( 'You should enter a valid absolute address to the CTA Link field or leave it empty.', 'wpforms-lite' );
$strings['iif_dismiss'] = esc_html__( 'Dismiss', 'wpforms-lite' );
$strings['iif_more'] = esc_html__( 'Learn More', 'wpforms-lite' );
return $strings;
}
/**
* Enqueue wpforms-internal-information-field script.
*
* @since 1.7.6
*
* @param string $view Current view.
*
* @noinspection PhpUnusedParameterInspection, PhpUnnecessaryCurlyVarSyntaxInspection
*/
public function builder_enqueues( $view ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-md5-hash',
WPFORMS_PLUGIN_URL . 'assets/lib/md5.min.js',
[ 'wpforms-builder' ],
'2.19.0',
false
);
wp_enqueue_script(
'wpforms-internal-information-field',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/fields/internal-information{$min}.js",
[ 'wpforms-builder', 'wpforms-md5-hash' ],
WPFORMS_VERSION,
false
);
}
/**
* Checks if the user is allowed to edit the field's content.
*
* @since 1.7.6
*
* @return bool
*/
private function is_editable(): bool {
/**
* Allow changing a mode.
*
* @since 1.7.6
*
* @param bool $is_editable True if editable mode is allowed. Default: false.
*/
return (bool) apply_filters( 'wpforms_field_internal_information_is_editable', false );
}
/**
* Check if the field has type internal-information.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return bool
*/
private function is_internal_information_field( $field ): bool {
return isset( $field['type'] ) && $field['type'] === $this->type;
}
/**
* Render the result of the field_preview_option into a custom div.
*
* If the field has no value, do not echo anything.
*
* @since 1.7.6
*
* @param string $label Field label.
* @param array $field Field settings and data.
* @param array $args Field arguments.
*
* @noinspection PhpSameParameterValueInspection
*/
private function render_preview( $label, $field, $args = [] ): void {
$key = $label === 'heading' ? 'label' : $label;
if ( empty( $field[ $key ] ) && ! $this->is_editable() ) {
return;
}
$allowed_tags = $this->get_allowed_tags();
printf(
'<div class="wpforms-field-internal-information-row wpforms-field-internal-information-row-%s">%s</div>',
esc_attr( $label ),
wp_kses( $this->render_custom_preview( $label, $field, $args ), $allowed_tags )
);
}
/**
* Replace `[] some text` with checkboxes.
*
* Additionally, generates the input name by hashing the line of text where the checkbox is.
*
* @since 1.7.6
*
* @param string $description Expanded description.
* @param array $field Field data and settings.
*
* @return string
* @noinspection HtmlUnknownAttribute
*/
private function replace_checkboxes( string $description, array $field ): string {
if ( ! $this->form_id ) {
return $description;
}
$lines = explode( PHP_EOL, $description );
$replaced = [];
$post_meta = get_post_meta( $this->form_id, self::CHECKBOX_META_KEY, true );
$post_meta = ! empty( $post_meta ) ? (array) $post_meta : [];
$field_id = $field['id'] ?? 0;
$needle = '[] ';
foreach ( $lines as $line_number => $line ) {
$line = trim( $line );
if ( strpos( $line, $needle ) !== 0 ) {
$replaced[] = $line . PHP_EOL;
continue;
}
$field_name = sprintf( 'iif-%d-%s-%d', $field_id, md5( $line ), $line_number );
$checked = (int) isset( $post_meta[ $field_name ] );
$attributes = [
'name' => esc_attr( $field_name ),
'value' => 1,
];
if ( $this->is_editable() ) {
$attributes['disabled'] = 'disabled';
$attributes['title'] = esc_html__( 'This field is disabled in the editor mode.', 'wpforms-lite' );
}
$html = sprintf(
'<div class="wpforms-field-internal-information-checkbox-input"><input type="checkbox" %s %s /></div><div class="wpforms-field-internal-information-checkbox-label">',
wpforms_html_attributes(
'',
[ 'wpforms-field-internal-information-checkbox' ],
[],
$attributes
),
! $this->is_editable() ? checked( $checked, 1, false ) : ''
);
$line = substr_replace( $line, $html, 0, strlen( $needle ) );
$replaced[] = '<div class="wpforms-field-internal-information-checkbox-wrap">' . $line . '</div></div>';
}
return implode( '', $replaced );
}
/**
* Return allowed tags specific to internal information field content.
*
* @since 1.7.6
*
* @return array
*/
private function get_allowed_tags(): array {
$allowed_tags = wpforms_builder_preview_get_allowed_tags();
$allowed_tags['input'] = [
'type' => [],
'name' => [],
'value' => [],
'class' => [],
'checked' => [],
'disabled' => [],
'title' => [],
];
return $allowed_tags;
}
/**
* Adds link parameters to all links in the provided content.
*
* @since 1.8.3
*
* @param string $content The content to modify.
*
* @return string The modified content with UTM parameters added to links.
*/
private function add_link_attributes( string $content ): string {
if ( empty( $content ) || ! class_exists( 'DOMDocument' ) ) {
return $content;
}
$dom = new DOMDocument();
$form_obj = wpforms()->obj( 'form' );
$form_data = $form_obj ? $form_obj->get( $this->form_id, [ 'content_only' => true ] ) : [];
$templates_obj = wpforms()->obj( 'builder_templates' );
$template = $form_data['meta']['template'] ?? '';
$template_data = $templates_obj && $template ? $templates_obj->get_template( $template ) : [];
$template_name = $template_data['name'] ?? '';
$dom->loadHTML( htmlspecialchars_decode( htmlentities( $content ) ) );
$links = $dom->getElementsByTagName( 'a' );
foreach ( $links as $link ) {
$href = $link->getAttribute( 'href' );
$text = $link->textContent; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$modified_href = wpforms_utm_link( $href, 'Form Template Information Note', $template_name, $text );
$link->setAttribute( 'href', $modified_href );
$link->setAttribute( 'target', '_blank' );
$link->setAttribute( 'rel', 'noopener noreferrer' );
}
// Remove the wrapper elements.
$body = $dom->getElementsByTagName( 'body' )->item( 0 );
$child_nodes = $body->childNodes ?? []; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$inner_html = '';
foreach ( $child_nodes as $node ) {
$inner_html .= $dom->saveHTML( $node );
}
return $inner_html;
}
/**
* Add UTM parameters to the CTA button link.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return string
*/
private function add_url_utm( array $field ): string {
$cta_link = (string) $field['cta-link'];
if ( strpos( $cta_link, 'https://wpforms.com' ) === 0 ) {
return wpforms_utm_link( $cta_link, 'Template Documentation' );
}
return $cta_link;
}
}
new WPForms_Field_Internal_Information();

View File

@@ -0,0 +1,681 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Name text field.
*
* @since 1.0.0
*/
class WPForms_Field_Name extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Name', 'wpforms-lite' );
$this->keywords = esc_html__( 'user, first, last', 'wpforms-lite' );
$this->type = 'name';
$this->icon = 'fa-user';
$this->order = 150;
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks(): void {
// Define additional field properties.
add_filter( 'wpforms_field_properties_name', [ $this, 'field_properties' ], 5, 3 );
// Set field to default required.
add_filter( 'wpforms_field_new_required', [ $this, 'default_required' ], 10, 2 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", [ $this, 'is_field_requires_fieldset' ], PHP_INT_MAX, 2 );
}
/**
* Define additional field properties.
*
* @since 1.3.7
*
* @param array|mixed $properties Field properties.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
$properties = (array) $properties;
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
// Simple format.
if ( $format === 'simple' ) {
$properties['inputs']['primary']['attr']['placeholder'] = ! empty( $field['simple_placeholder'] )
? $field['simple_placeholder'] :
'';
$properties['inputs']['primary']['attr']['value'] = ! empty( $field['simple_default'] )
? wpforms_process_smart_tags( $field['simple_default'], $form_data, [], '', 'field-properties' )
: '';
return $properties;
}
// Expanded formats.
// Remove primary for expanded formats since we have first, middle, last.
unset( $properties['inputs']['primary'] );
// Remove reference to an input element to prevent duplication.
if ( empty( $field['sublabel_hide'] ) ) {
unset( $properties['label']['attr']['for'] );
}
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$props = [
'inputs' => [
'first' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][first]",
'value' => ! empty( $field['first_default'] )
? wpforms_process_smart_tags( $field['first_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['first_placeholder'] ) ? $field['first_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
'wpforms-first',
],
'class' => [
'wpforms-field-name-first',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'First', 'wpforms-lite' ),
],
],
'middle' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][middle]",
'value' => ! empty( $field['middle_default'] )
? wpforms_process_smart_tags( $field['middle_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['middle_placeholder'] ) ? $field['middle_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
'wpforms-one-fifth',
],
'class' => [
'wpforms-field-name-middle',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}-middle",
'required' => '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'Middle', 'wpforms-lite' ),
],
],
'last' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][last]",
'value' => ! empty( $field['last_default'] )
? wpforms_process_smart_tags( $field['last_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['last_placeholder'] ) ? $field['last_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
],
'class' => [
'wpforms-field-name-last',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}-last",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'Last', 'wpforms-lite' ),
],
],
],
];
$properties = array_merge_recursive( $properties, $props );
$has_common_error = ! empty( $properties['error']['value'] ) && is_string( $properties['error']['value'] );
// Input First: add error class if needed.
if ( ! empty( $properties['error']['value']['first'] ) || $has_common_error ) {
$properties['inputs']['first']['class'][] = 'wpforms-error';
}
// Input First: add required class if needed.
if ( ! empty( $field['required'] ) ) {
$properties['inputs']['first']['class'][] = 'wpforms-field-required';
}
// Input First: add column class.
$properties['inputs']['first']['block'][] = $format === 'first-last' ? 'wpforms-one-half' : 'wpforms-two-fifths';
// Input Middle: add error class if needed.
if ( $has_common_error ) {
$properties['inputs']['middle']['class'][] = 'wpforms-error';
}
// Input Last: add error class if needed.
if ( ! empty( $properties['error']['value']['last'] ) || $has_common_error ) {
$properties['inputs']['last']['class'][] = 'wpforms-error';
}
// Input Last: add required class if needed.
if ( ! empty( $field['required'] ) ) {
$properties['inputs']['last']['class'][] = 'wpforms-field-required';
}
// Input Last: add column class.
$properties['inputs']['last']['block'][] = $format === 'first-last' ? 'wpforms-one-half' : 'wpforms-two-fifths';
return $properties;
}
/**
* Name fields should default to being required.
*
* @since 1.0.8
*
* @param bool|mixed $required Whether the field is required.
* @param array $field Field data.
*
* @return bool
*/
public function default_required( $required, $field ): bool {
if ( $field['type'] === 'name' ) {
return true;
}
return (bool) $required;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field information.
*/
public function field_options( $field ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// Define data.
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
/*
* Basic field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Format.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'format',
'value' => esc_html__( 'Format', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Select format to use for the name form field', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'select',
$field,
[
'slug' => 'format',
'value' => $format,
'options' => [
'simple' => esc_html__( 'Simple', 'wpforms-lite' ),
'first-last' => esc_html__( 'First Last', 'wpforms-lite' ),
'first-middle-last' => esc_html__( 'First Middle Last', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'format',
'content' => $lbl . $fld,
];
$this->field_element( 'row', $field, $args );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
echo '<div class="format-selected-' . esc_attr( $format ) . ' format-selected">';
// Simple.
$simple_placeholder = ! empty( $field['simple_placeholder'] ) ? esc_attr( $field['simple_placeholder'] ) : '';
$simple_default = ! empty( $field['simple_default'] ) ? esc_attr( $field['simple_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-simple" id="wpforms-field-option-row-%d-simple" data-subfield="simple" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'simple_placeholder',
'value' => esc_html__( 'Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%d-simple_placeholder" name="fields[%d][simple_placeholder]" value="%s">', (int) $field['id'], (int) $field['id'], esc_attr( $simple_placeholder ) );
printf( '<label for="wpforms-field-option-%d-simple_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%d-simple_default" name="fields[%d][simple_default]" data-type="other" value="%s">', (int) $field['id'], (int) $field['id'], esc_attr( $simple_default ) );
printf( '<label for="wpforms-field-option-%d-simple_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// First.
$first_placeholder = ! empty( $field['first_placeholder'] ) ? esc_attr( $field['first_placeholder'] ) : '';
$first_default = ! empty( $field['first_default'] ) ? esc_attr( $field['first_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-first" id="wpforms-field-option-row-%d-first" data-subfield="first-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'first_placeholder',
'value' => esc_html__( 'First Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'First name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-first_placeholder" name="fields[%1$d][first_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $first_placeholder ) );
printf( '<label for="wpforms-field-option-%d-first_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-first_default" name="fields[%1$d][first_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $first_default ) );
printf( '<label for="wpforms-field-option-%d-first_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// Middle.
$middle_placeholder = ! empty( $field['middle_placeholder'] ) ? esc_attr( $field['middle_placeholder'] ) : '';
$middle_default = ! empty( $field['middle_default'] ) ? esc_attr( $field['middle_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-middle" id="wpforms-field-option-row-%d-middle" data-subfield="middle-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'middle_placeholder',
'value' => esc_html__( 'Middle Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Middle name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-middle_placeholder" name="fields[%1$d][middle_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $middle_placeholder ) );
printf( '<label for="wpforms-field-option-%d-middle_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-middle_default" name="fields[%1$d][middle_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $middle_default ) );
printf( '<label for="wpforms-field-option-%d-middle_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// Last.
$last_placeholder = ! empty( $field['last_placeholder'] ) ? esc_attr( $field['last_placeholder'] ) : '';
$last_default = ! empty( $field['last_default'] ) ? esc_attr( $field['last_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-last" id="wpforms-field-option-row-%d-last" data-subfield="last-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'last_placeholder',
'value' => esc_html__( 'Last Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Last name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-last_placeholder" name="fields[%1$d][last_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $last_placeholder ) );
printf( '<label for="wpforms-field-option-%d-last_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-last_default" name="fields[%1$d][last_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $last_default ) );
printf( '<label for="wpforms-field-option-%d-last_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
echo '</div>';
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide Label.
$this->field_option( 'label_hide', $field );
// Hide sublabels.
$sublabel_class = isset( $field['format'] ) && ! in_array( $field['format'], [ 'first-last', 'first-middle-last' ], true ) ? 'wpforms-hidden' : '';
$this->field_option( 'sublabel_hide', $field, [ 'class' => $sublabel_class ] );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field information.
*/
public function field_preview( $field ) {
// Define data.
$simple_placeholder = ! empty( $field['simple_placeholder'] ) ? $field['simple_placeholder'] : '';
$first_placeholder = ! empty( $field['first_placeholder'] ) ? $field['first_placeholder'] : '';
$middle_placeholder = ! empty( $field['middle_placeholder'] ) ? $field['middle_placeholder'] : '';
$last_placeholder = ! empty( $field['last_placeholder'] ) ? $field['last_placeholder'] : '';
$simple_default = ! empty( $field['simple_default'] ) ? $field['simple_default'] : '';
$first_default = ! empty( $field['first_default'] ) ? $field['first_default'] : '';
$middle_default = ! empty( $field['middle_default'] ) ? $field['middle_default'] : '';
$last_default = ! empty( $field['last_default'] ) ? $field['last_default'] : '';
$format = ! empty( $field['format'] ) ? $field['format'] : 'first-last';
// Label.
$this->field_preview_option( 'label', $field );
?>
<div class="format-selected-<?php echo sanitize_html_class( $format ); ?> format-selected wpforms-clear">
<div class="wpforms-simple">
<input type="text" placeholder="<?php echo esc_attr( $simple_placeholder ); ?>" value="<?php echo esc_attr( $simple_default ); ?>" class="primary-input" readonly>
</div>
<div class="wpforms-first-name">
<input type="text" placeholder="<?php echo esc_attr( $first_placeholder ); ?>" value="<?php echo esc_attr( $first_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'First', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-middle-name">
<input type="text" placeholder="<?php echo esc_attr( $middle_placeholder ); ?>" value="<?php echo esc_attr( $middle_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'Middle', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-last-name">
<input type="text" placeholder="<?php echo esc_attr( $last_placeholder ); ?>" value="<?php echo esc_attr( $last_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'Last', 'wpforms-lite' ); ?></label>
</div>
</div>
<?php
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field information.
* @param array $deprecated Deprecated parameter, not used anymore.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
$primary = ! empty( $field['properties']['inputs']['primary'] ) ? $field['properties']['inputs']['primary'] : '';
$first = ! empty( $field['properties']['inputs']['first'] ) ? $field['properties']['inputs']['first'] : '';
$middle = ! empty( $field['properties']['inputs']['middle'] ) ? $field['properties']['inputs']['middle'] : '';
$last = ! empty( $field['properties']['inputs']['last'] ) ? $field['properties']['inputs']['last'] : '';
// Simple format.
if ( $format === 'simple' ) {
// Primary field (Simple).
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
esc_attr( $primary['required'] )
);
// Expanded formats.
} else {
// Row wrapper.
echo '<div class="wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] ) . '">';
// First name.
echo '<div ' . wpforms_html_attributes( false, $first['block'] ) . '>';
$this->field_display_sublabel( 'first', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $first['id'], $first['class'], $first['data'], $first['attr'] ),
esc_attr( $first['required'] )
);
$this->field_display_sublabel( 'first', 'after', $field );
$this->field_display_error( 'first', $field );
echo '</div>';
// Middle name.
if ( $format === 'first-middle-last' ) {
echo '<div ' . wpforms_html_attributes( false, $middle['block'] ) . '>';
$this->field_display_sublabel( 'middle', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $middle['id'], $middle['class'], $middle['data'], $middle['attr'] ),
esc_attr( $middle['required'] )
);
$this->field_display_sublabel( 'middle', 'after', $field );
$this->field_display_error( 'middle', $field );
echo '</div>';
}
// Last name.
echo '<div ' . wpforms_html_attributes( false, $last['block'] ) . '>';
$this->field_display_sublabel( 'last', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $last['id'], $last['class'], $last['data'], $last['attr'] ),
esc_attr( $last['required'] )
);
$this->field_display_sublabel( 'last', 'after', $field );
$this->field_display_error( 'last', $field );
echo '</div>';
echo '</div>';
}
}
/**
* Validate field on submitting a form.
*
* @since 1.0.0
*
* @param int $field_id Field id.
* @param array|string $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
if ( empty( $form_data['fields'][ $field_id ]['required'] ) ) {
return;
}
// Extended validation needed for the different name fields.
$form_id = $form_data['id'];
$format = $form_data['fields'][ $field_id ]['format'];
$required = wpforms_get_required_label();
$process = wpforms()->obj( 'process' );
if ( $format === 'simple' && wpforms_is_empty_string( $field_submit ) ) {
$process->errors[ $form_id ][ $field_id ] = $required;
return;
}
if ( ! ( $format === 'first-last' || $format === 'first-middle-last' ) ) {
return;
}
$this->validate_complicated_formats( $process, $form_id, $field_id, $field_submit, $required );
}
/**
* Format and sanitize field.
*
* @since 1.0.0
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = isset( $form_data['fields'][ $field_id ]['label'] ) && ! wpforms_is_empty_string( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
$first = isset( $field_submit['first'] ) && ! wpforms_is_empty_string( $field_submit['first'] ) ? $field_submit['first'] : '';
$middle = isset( $field_submit['middle'] ) && ! wpforms_is_empty_string( $field_submit['middle'] ) ? $field_submit['middle'] : '';
$last = isset( $field_submit['last'] ) && ! wpforms_is_empty_string( $field_submit['last'] ) ? $field_submit['last'] : '';
if ( is_array( $field_submit ) ) {
$value = implode( ' ', array_filter( [ $first, $middle, $last ] ) );
} else {
$value = $field_submit;
}
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => sanitize_text_field( $value ),
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
'first' => sanitize_text_field( $first ),
'middle' => sanitize_text_field( $middle ),
'last' => sanitize_text_field( $last ),
];
}
/**
* Determine if the field requires fieldset+legend instead of the regular field label.
*
* @since 1.8.1
*
* @param bool $requires_fieldset True if it requires fieldset.
* @param array $field Field data.
*
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function is_field_requires_fieldset( $requires_fieldset, $field ) {
return isset( $field['format'] ) && $field['format'] !== 'simple';
}
/**
* Validate complicated formats.
*
* @since 1.8.2.3
*
* @param WPForms_Process $process Process class instance.
* @param int|string $form_id Form id.
* @param int|string $field_id Field id.
* @param array $field_submit Field submit.
* @param string $required Required message text.
*/
private function validate_complicated_formats( $process, $form_id, $field_id, $field_submit, $required ) {
// Prevent PHP Warning: Illegal string offset first or 'last'.
if ( isset( $process->errors[ $form_id ][ $field_id ] ) ) {
$process->errors[ $form_id ][ $field_id ] = (array) $process->errors[ $form_id ][ $field_id ];
}
if ( isset( $field_submit['first'] ) && wpforms_is_empty_string( $field_submit['first'] ) ) {
$process->errors[ $form_id ][ $field_id ]['first'] = $required;
}
if ( isset( $field_submit['last'] ) && wpforms_is_empty_string( $field_submit['last'] ) ) {
$process->errors[ $form_id ][ $field_id ]['last'] = $required;
}
}
}
new WPForms_Field_Name();

View File

@@ -0,0 +1,452 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Forms\Fields\Traits\NumberField as NumberFieldTrait;
/**
* Number Slider field.
*
* @since 1.5.7
*/
class WPForms_Field_Number_Slider extends WPForms_Field {
use NumberFieldTrait;
/**
* Default minimum value of the field.
*
* @since 1.5.7
*/
const SLIDER_MIN = 0;
/**
* Default maximum value of the field.
*
* @since 1.5.7
*/
const SLIDER_MAX = 10;
/**
* Default step value of the field.
*
* @since 1.5.7
*/
const SLIDER_STEP = 1;
/**
* Primary class constructor.
*
* @since 1.5.7
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Number Slider', 'wpforms-lite' );
$this->type = 'number-slider';
$this->icon = 'fa-sliders';
$this->order = 180;
// Customize value format for HTML emails.
add_filter( 'wpforms_html_field_value', [ $this, 'html_email_value' ], 10, 4 );
// Builder strings.
add_filter( 'wpforms_builder_strings', [ $this, 'add_builder_strings' ] );
}
/**
* Add Builder strings.
*
* @since 1.6.2.3
*
* @param array $strings Form Builder strings.
*
* @return array Form Builder strings.
*/
public function add_builder_strings( $strings ) {
$strings['error_number_slider_increment'] = esc_html__( 'Increment value should be greater than zero. Decimal fractions allowed.', 'wpforms-lite' );
return $strings;
}
/**
* Customize format for HTML email notifications.
*
* @since 1.5.7
*
* @param string $val Field value.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
* @param string $context Value display context.
*
* @return string
*/
public function html_email_value( $val, $field, $form_data = [], $context = '' ) {
if ( empty( $field['value_raw'] ) || $field['type'] !== $this->type ) {
return $val;
}
$value = isset( $field['value_raw']['value'] ) ? (float) $field['value_raw']['value'] : 0;
$min = isset( $field['value_raw']['min'] ) ? (float) $field['value_raw']['min'] : self::SLIDER_MIN;
$max = isset( $field['value_raw']['max'] ) ? (float) $field['value_raw']['max'] : self::SLIDER_MAX;
$html_value = $value;
if ( strpos( $field['value_raw']['value_display'], '{value}' ) !== false ) {
$html_value = str_replace(
'{value}',
/* translators: %1$s - Number slider selected value, %2$s - its minimum value, %3$s - its maximum value. */
sprintf( esc_html__( '%1$s (%2$s min / %3$s max)', 'wpforms-lite' ), $value, $min, $max ),
$field['value_raw']['value_display']
);
}
return $html_value;
}
/**
* Field options panel inside the builder.
*
* @since 1.5.7
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Set default values for Min, Max, Step, Default Value Options.
$field = $this->set_default_field_args( $field );
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle disabled.
$this->field_element(
'text',
$field,
[
'slug' => 'required',
'value' => '',
'type' => 'hidden',
]
);
// Min/Max.
$min_max_args = [
'class' => 'wpforms-number-slider',
'label' => esc_html__( 'Value Range', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Define the minimum and the maximum values for the slider.', 'wpforms-lite' ),
];
$min_max = $this->field_number_option_min_max( $field, $min_max_args, false );
// Default value.
$default_value_args = [
'class' => 'wpforms-number-slider-default-value',
];
$default_value = $this->field_number_option_default_value( $field, $default_value_args, false );
// Increment.
$step_args = [
'class' => 'wpforms-number-slider-step',
'tooltip' => esc_html__( 'Determines the increment between selectable values on the slider.', 'wpforms-lite' ),
];
$step = $this->field_number_option_step( $field, $step_args, false );
// Print of options markup: Minimum, Maximum, Increment, Default Value.
$this->field_element(
'row',
$field,
[
'slug' => 'number_min_max_step_dependent',
'content' => $min_max . $default_value . $step,
'class' => 'wpforms-field-number-slider-option',
],
true
);
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Value display.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'value_display',
'value' => esc_html__( 'Value Display', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Displays the currently selected value below the slider.', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'text',
$field,
[
'slug' => 'value_display',
'class' => 'wpforms-number-slider-value-display',
'value' => isset( $field['value_display'] ) ? $field['value_display'] : $this->get_default_display_value(),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'value_display',
'content' => $lbl . $fld,
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Get default display value.
*
* @since 1.7.1
*
* @return string
*/
private function get_default_display_value() {
return sprintf( /* translators: %s - value. */
esc_html__( 'Selected Value: %s', 'wpforms-lite' ),
'{value}'
);
}
/**
* Field preview inside the builder.
*
* @since 1.5.7
*
* @param array $field Field data.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
$value_display = isset( $field['value_display'] ) ? esc_attr( $field['value_display'] ) : $this->get_default_display_value();
$default_value = ! empty( $field['default_value'] ) ? (float) $field['default_value'] : 0;
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'fields/number-slider/builder-preview',
[
'min' => isset( $field['min'] ) && is_numeric( $field['min'] ) ? (float) $field['min'] : self::SLIDER_MIN,
'max' => isset( $field['max'] ) && is_numeric( $field['max'] ) ? (float) $field['max'] : self::SLIDER_MAX,
'step' => isset( $field['step'] ) && is_numeric( $field['step'] ) ? (float) $field['step'] : self::SLIDER_STEP,
'value_display' => $value_display,
'default_value' => $default_value,
'value_hint' => str_replace( '{value}', '<b>' . $default_value . '</b>', wp_kses( $value_display, wpforms_builder_preview_get_allowed_tags() ) ),
'field_id' => $field['id'],
],
true
);
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.5.7
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated field attributes. Use $field['properties'] instead.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
$value_display = isset( $field['value_display'] ) ? esc_attr( $field['value_display'] ) : esc_html__( 'Selected Value: {value}', 'wpforms-lite' );
$hint_value = ! empty( $primary['attr']['value'] ) ? (float) $primary['attr']['value'] : 0;
$hint = str_replace( '{value}', '<b>' . $hint_value . '</b>', $value_display );
// phpcs:ignore
echo wpforms_render(
'fields/number-slider/frontend',
[
'atts' => $primary['attr'],
'class' => $primary['class'],
'datas' => $primary['data'],
'id' => $primary['id'],
'max' => isset( $field['max'] ) && is_numeric( $field['max'] ) ? (float) $field['max'] : self::SLIDER_MAX,
'min' => isset( $field['min'] ) && is_numeric( $field['min'] ) ? (float) $field['min'] : self::SLIDER_MIN,
'required' => $primary['required'],
'step' => isset( $field['step'] ) && is_numeric( $field['step'] ) ? (float) $field['step'] : self::SLIDER_STEP,
'value_display' => $value_display,
'value_hint' => $hint,
],
true
);
}
/**
* Validate field on form submit.
*
* @since 1.5.7
*
* @param int $field_id Field ID.
* @param int|float|string $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$form_id = $form_data['id'];
$field_submit = (float) $this->sanitize_value( $field_submit );
// Basic required check - if field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
empty( $field_submit ) &&
(string) $field_submit !== '0'
) {
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = wpforms_get_required_label();
}
// Check if value is numeric.
if ( ! empty( $field_submit ) && ! is_numeric( $field_submit ) ) {
/**
* Filter the error message for the number field.
*
* @since 1.0.0
*
* @param string $message Error message.
*/
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = apply_filters( 'wpforms_valid_number_label', esc_html__( 'Please provide a valid value.', 'wpforms-lite' ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
/**
* Format and sanitize field.
*
* @since 1.5.7
*
* @param int $field_id Field ID.
* @param int|string|float $field_submit Submitted field value.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = ! empty( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
$value = (float) $this->sanitize_value( $field_submit );
$value_raw = [
'value' => $value,
'min' => (float) $form_data['fields'][ $field_id ]['min'],
'max' => (float) $form_data['fields'][ $field_id ]['max'],
'value_display' => wp_kses_post( $form_data['fields'][ $field_id ]['value_display'] ),
];
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => $value,
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Sanitize the value.
*
* @since 1.5.7
*
* @param string $value The number field submitted value.
*
* @return float|int|string
*/
private function sanitize_value( $value ) {
// Some browsers allow other non-digit/decimal characters to be submitted
// with the num input, which then trips the is_numeric validation below.
// To get around this we remove all chars that are not expected.
$signed_value = preg_replace( '/[^-0-9.]/', '', $value );
// If there's no number on the signed value we return zero.
// We have to do that because since PHP 8.0, the abs() function is allowed an argument with int|float type.
if ( ! is_numeric( $signed_value ) ) {
return 0;
}
$abs_value = abs( $signed_value );
$value = strpos( $signed_value, '-' ) === 0 ? '-' . $abs_value : $abs_value;
return $value;
}
/**
* Sets default field settings.
*
* @since 1.9.4
*
* @param array $field Field settings.
*
* @return array Modified array.
*/
private function set_default_field_args( $field ) {
$field['min'] = empty( $field['min'] ) ? self::SLIDER_MIN : $field['min'];
$field['max'] = empty( $field['max'] ) ? self::SLIDER_MAX : $field['max'];
$field['step'] = empty( $field['step'] ) ? self::SLIDER_STEP : $field['step'];
$field['default_value'] = empty( $field['default_value'] ) ? self::SLIDER_MIN : $field['default_value'];
return $field;
}
}
new WPForms_Field_Number_Slider();

View File

@@ -0,0 +1,278 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Forms\Fields\Traits\NumberField as NumberFieldTrait;
/**
* Number text field.
*
* @since 1.0.0
*/
class WPForms_Field_Number extends WPForms_Field {
use NumberFieldTrait;
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Numbers', 'wpforms-lite' );
$this->type = 'number';
$this->icon = 'fa-hashtag';
$this->order = 130;
$this->hooks();
$this->number_hooks();
}
/**
* Hooks.
*
* @since 1.9.4
*/
private function hooks() {
// Define additional field properties.
add_filter( 'wpforms_field_properties_number', [ $this, 'field_properties' ], 5, 3 );
}
/**
* Define additional field properties.
*
* @since 1.9.4
*
* @param array|mixed $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function field_properties( $properties, $field, $form_data ): array {
$properties = (array) $properties;
if ( is_numeric( $field['min'] ?? null ) ) {
$properties['inputs']['primary']['attr']['min'] = (float) $field['min'];
}
if ( is_numeric( $field['max'] ?? null ) ) {
$properties['inputs']['primary']['attr']['max'] = (float) $field['max'];
}
$properties['inputs']['primary']['attr']['step'] = 'any';
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Min/Max.
$this->field_number_option_min_max( $field, [ 'class' => 'wpforms-numbers' ] );
// Default value.
$this->field_option( 'default_value', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
public function field_preview( $field ) {
// Define data.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
echo '<input type="text" placeholder="' . esc_attr( $placeholder ) . '" value="' . esc_attr( $default_value ) . '" class="primary-input" readonly>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field data.
* @param array $deprecated Deprecated, not used.
* @param array $form_data Form data.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
// Primary field.
printf(
'<input type="number" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
esc_attr( $primary['required'] )
);
}
/**
* Validate field on form submit.
*
* @since 1.0.0
*
* @param int $field_id Field id.
* @param string $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$form_id = $form_data['id'];
$value = $this->sanitize_value( $field_submit );
// If field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
empty( $value ) &&
! is_numeric( $value )
) {
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = wpforms_get_required_label();
}
// Check if value is numeric.
if ( ! empty( $value ) && ! is_numeric( $value ) ) {
/**
* Filter the error message for the number field.
*
* @since 1.0.0
*
* @param string $message Error message.
*/
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = apply_filters( 'wpforms_valid_number_label', esc_html__( 'Please enter a valid number.', 'wpforms-lite' ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
/**
* Format and sanitize field.
*
* @since 1.3.5
*
* @param int $field_id Field id.
* @param string $field_submit Submitted value.
* @param array $form_data Form data.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = ! empty( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => $this->sanitize_value( $field_submit ),
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Sanitize the value.
*
* @since 1.5.7
*
* @param string $value The number field submitted value.
*
* @return float|int|string
*/
private function sanitize_value( $value ) {
if ( empty( $value ) && ! is_numeric( $value ) ) {
return '';
}
// Some browsers allow other non-digit/decimal characters to be submitted
// with the num input, which then trips the is_numeric validation below.
// To get around this we remove all chars that are not expected.
$signed_value = preg_replace( '/[^-0-9.]/', '', $value );
$abs_value = str_replace( '-', '', $signed_value );
return $signed_value < 0 ? '-' . $abs_value : $abs_value;
}
}
new WPForms_Field_Number();

View File

@@ -0,0 +1,944 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Multiple Choice field.
*
* @since 1.0.0
*/
class WPForms_Field_Radio extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Multiple Choice', 'wpforms-lite' );
$this->keywords = esc_html__( 'radio', 'wpforms-lite' );
$this->type = 'radio';
$this->icon = 'fa-dot-circle-o';
$this->order = 110;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks() {
// Customize HTML field values.
add_filter( 'wpforms_html_field_value', [ $this, 'field_html_value' ], 10, 4 );
add_filter( "wpforms_{$this->type}_field_html_value_images", [ $this, 'field_html_value_images' ], 10, 3 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_radio', [ $this, 'field_properties' ], 5, 3 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", '__return_true', PHP_INT_MAX, 2 );
// Load assets.
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_assets' ] );
// Modify an export data format for the Other option.
add_filter( 'wpforms_pro_admin_entries_export_ajax_get_entry_fields_data_field', [ $this, 'export_entry_field_data' ] );
// Allow radio fields to be included in the Keyword Filter.
add_filter( 'wpforms_pro_anti_spam_keyword_filter_get_filtered_fields', [ $this, 'add_field_to_anti_spam_keyword_filter' ] );
// Adjust entry field before saving to entry_fields DB table.
add_filter( 'wpforms_entry_save_fields', [ $this, 'save_field' ], 10, 3 );
}
/**
* Enqueue assets for the builder.
*
* @since 1.9.8.3
*/
public function builder_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-multiple-choices',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/multiple-choices{$min}.js",
[ 'jquery', 'wpforms-builder' ],
WPFORMS_VERSION,
false
);
}
/**
* Adjust builder preview container classes.
*
* Adds size-{small|medium|large} to the Radio field container in the Builder
* when the "Add Other Choice" option is enabled.
*
* @since 1.9.8.3
*
* @param string $css Existing class string.
* @param array $field Field data and settings.
*
* @return string
*/
public function preview_field_class( $css, $field ): string {
$css = parent::preview_field_class( $css, $field );
if ( $field['type'] !== $this->type ) {
return $css;
}
// Apply a size class to the field container when Other Choice is enabled.
if ( $this->has_other_choice( $field ) ) {
$size = ! empty( $field['other_size'] ) ? sanitize_html_class( $field['other_size'] ) : 'medium';
$css .= ' size-' . $size;
}
return $css;
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @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 ) {
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [ ! empty( $field['random'] ) ? 'wpforms-randomize' : '' ],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
// Set input properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$value = ! empty( $field['show_values'] ) ? $choice['value'] : $choice['label'];
/* translators: %s - choice number. */
$value = ( $value === '' ) ? sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) : $value;
// Check if this is the "Other" choice.
$is_other_choice = isset( $choice['other'] ) && (bool) $choice['other'] === true;
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}", $is_other_choice ? 'wpforms-other-choice' : '' ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
'value' => $value,
],
'class' => [],
'data' => $is_other_choice ? [ 'other-choice' => 'true' ] : [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'icon' => isset( $choice['icon'] ) ? $choice['icon'] : '',
'icon_style' => isset( $choice['icon_style'] ) ? $choice['icon_style'] : '',
'image' => isset( $choice['image'] ) ? $choice['image'] : '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Custom properties if image choices is enabled.
if ( ! $dynamic && ! empty( $field['choices_images'] ) ) {
$properties['input_container']['class'][] = 'wpforms-image-choices';
$properties['input_container']['class'][] = 'wpforms-image-choices-' . sanitize_html_class( $field['choices_images_style'] );
foreach ( $properties['inputs'] as $key => $inputs ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-image-choices-item';
if ( in_array( $field['choices_images_style'], [ 'modern', 'classic' ], true ) ) {
$properties['inputs'][ $key ]['class'][] = 'wpforms-screen-reader-element';
}
}
} elseif ( ! $dynamic && ! empty( $field['choices_icons'] ) ) {
$properties = wpforms()->obj( 'icon_choices' )->field_properties( $properties, $field );
}
// Add selected class for choices with defaults.
foreach ( $properties['inputs'] as $key => $inputs ) {
if ( ! empty( $inputs['default'] ) ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-selected';
}
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Add Other Choice.
$this->field_option( 'choices_other', $field );
// Other Field Size for "Other" input.
$this->field_option( 'other_size', $field );
// Other Placeholder for "Other" input.
$this->field_option( 'other_placeholder', $field );
// Choices Images.
$this->field_option( 'choices_images', $field );
// Hide Choices Images.
$this->field_option( 'choices_images_hide', $field );
// Choices Images Style (theme).
$this->field_option( 'choices_images_style', $field );
// Choices Icons.
$this->field_option( 'choices_icons', $field );
// Choices Icons Color.
$this->field_option( 'choices_icons_color', $field );
// Choices Icons Size.
$this->field_option( 'choices_icons_size', $field );
// Choices Icons Style.
$this->field_option( 'choices_icons_style', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Randomize order of choices.
$this->field_element(
'row',
$field,
[
'slug' => 'random',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'random',
'value' => isset( $field['random'] ) ? '1' : '0',
'desc' => esc_html__( 'Randomize Choices', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to randomize the order of the choices.', 'wpforms-lite' ),
],
false
),
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => isset( $field['show_values'] ) ? $field['show_values'] : '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
),
]
);
}
// Display format.
$this->field_option( 'input_columns', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
$using_image_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_icons'] ) && ! empty( $field['choices_images'] );
$using_icon_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_images'] ) && ! empty( $field['choices_icons'] );
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
$amp_state_id = '';
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$amp_state_id = str_replace( '-', '_', sanitize_key( $container['id'] ) ) . '_state';
$state = [
'selected' => null,
];
foreach ( $choices as $key => $choice ) {
if ( $choice['default'] ) {
$state['selected'] = $choice['attr']['value'];
break;
}
}
printf(
'<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
esc_attr( $amp_state_id ),
wp_json_encode( $state )
);
}
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$choice['container']['attr']['[class]'] = sprintf(
'%s + ( %s == %s ? " wpforms-selected" : "")',
wp_json_encode( implode( ' ', $choice['container']['class'] ) ),
$amp_state_id,
wp_json_encode( $choice['attr']['value'] )
);
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
if ( $using_image_choices ) {
// Make sure the image choices are keyboard-accessible.
$choice['label']['attr']['tabindex'] = 0;
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState(%s)',
wp_json_encode( [ $amp_state_id => $choice['attr']['value'] ] )
);
$choice['label']['attr']['role'] = 'button';
}
if ( is_array( $choice['label']['class'] ) && wpforms_is_empty_string( $label ) ) {
$choice['label']['class'][] = 'wpforms-field-label-inline-empty';
}
// Image choices.
printf(
'<label %s>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] )
);
echo '<span class="wpforms-image-choices-image">';
if ( ! empty( $choice['image'] ) ) {
printf(
'<img src="%s" alt="%s"%s>',
esc_url( $choice['image'] ),
esc_attr( $label ),
! empty( $label ) ? ' title="' . esc_attr( $label ) . '"' : ''
);
}
echo '</span>';
if ( $field['choices_images_style'] === 'none' ) {
echo '<br>';
}
$choice['attr']['tabindex'] = '-1';
if ( wpforms_is_amp() ) {
$choice['attr']['[checked]'] = sprintf(
'%s == %s',
$amp_state_id,
wp_json_encode( $choice['attr']['value'] )
);
}
printf(
'<input type="radio" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
echo '<span class="wpforms-image-choices-label">' . wp_kses_post( $choice['label']['text'] ) . '</span>';
echo '</label>';
} elseif ( $using_icon_choices ) {
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState(%s)',
wp_json_encode( [ $amp_state_id => $choice['attr']['value'] ] )
);
$choice['label']['attr']['role'] = 'button';
}
// Icon Choices.
wpforms()->obj( 'icon_choices' )->field_display( $field, $choice, 'radio' );
} else {
// Normal display.
printf(
'<input type="radio" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $label )
);
}
// Capture the text field for "Other" choice to render separately below the list.
if ( ! empty( $choice['data']['other-choice'] ) ) {
$default_value = '';
$size = ! empty( $field['other_size'] ) ? sanitize_html_class( $field['other_size'] ) : 'medium';
$size_class = 'wpforms-field-' . $size;
// Do not hide the Other input if this choice is set as default.
$hidden_class = ! empty( $choice['default'] ) ? '' : ' wpforms-hidden';
if ( isset( $field['choices'][ $key ]['value'] ) && $field['choices'][ $key ]['value'] !== '' ) {
$default_value = $field['choices'][ $key ]['value'];
}
/**
* Filters the default value of the Other choice field option.
*
* This filter allows modifying what value should be prefilled for the Other choice inputs.
*
* @since 1.9.8.3
*
* @param string $default_value Default value for the "Other" choice input.
* @param array $field Field data and settings.
* @param string $label Field label.
*/
$default_value = apply_filters( 'wpforms_field_radio_other_choice_default_value', $default_value, $field, $label );
$other_atts = [
'name' => "wpforms[fields][{$field['id']}][other]",
'value' => $default_value,
];
if ( empty( $choice['default'] ) ) {
$other_atts['disabled'] = 'disabled';
}
if ( ! empty( $field['other_placeholder'] ) ) {
$other_atts['placeholder'] = $field['other_placeholder'];
}
$other_input_html = sprintf(
'<input type="text" %s required>',
wpforms_html_attributes(
"wpforms-{$form_data['id']}-field_{$field['id']}_other",
[ 'wpforms-other-input', 'wpforms-field-required', $size_class, $hidden_class ],
[],
$other_atts
)
);
}
echo '</li>';
}
echo '</ul>';
// Render the captured "Other" input separately, under the list of options.
if ( ! empty( $other_input_html ) ) {
echo $other_input_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Validate field.
*
* @since 1.8.2
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
// Skip validation if field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
parent::validate( $field_id, $field_submit, $form_data );
}
/**
* Format and sanitize field.
*
* @since 1.0.2
* @since 1.9.8.3 Changed the expected $field_submit from string to mixed as in case with the Other option we can expect the array to arrive here.
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$name = sanitize_text_field( $field['label'] );
$value_raw = sanitize_text_field( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( 'post_type' === $dynamic && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type.
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = absint( $value_raw );
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$post = get_post( $value_raw );
if ( ! empty( $post ) && ! is_wp_error( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$data['value'] = esc_html( wpforms_get_post_title( $post ) );
}
} elseif ( 'taxonomy' === $dynamic && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy.
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = absint( $value_raw );
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$term = get_term( $value_raw, $data['dynamic_taxonomy'] );
if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
$data['value'] = esc_html( wpforms_get_term_name( $term ) );
}
} else {
// Normal processing, dynamic population is off.
$choice_key = '';
// If show_values is true, that means value posted is the raw value
// and not the label. So we need to set label value. Also store
// the choice key.
if ( ! empty( $field['show_values'] ) ) {
foreach ( $field['choices'] as $key => $choice ) {
if ( ! empty( $field_submit ) && $choice['value'] === $field_submit ) {
$data['value'] = sanitize_text_field( $choice['label'] );
$choice_key = $key;
break;
}
}
} else {
$data['value'] = $value_raw;
// Determine choice key, this is needed for image choices.
foreach ( $field['choices'] as $key => $choice ) {
/* translators: %s - choice number. */
if ( $field_submit === $choice['label'] || $value_raw === sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) ) {
$choice_key = $key;
break;
}
}
}
// Images choices are enabled, lookup and store image URL.
if ( ! empty( $choice_key ) && ! empty( $field['choices_images'] ) ) {
$data['image'] = ! empty( $field['choices'][ $choice_key ]['image'] ) ? esc_url_raw( $field['choices'][ $choice_key ]['image'] ) : '';
}
}
// For the Other option the value_raw is the option and the value is text from the input.
if ( is_array( $field_submit ) && ! empty( $field_submit['other'] ) ) {
$data['value'] = sanitize_text_field( $field_submit['other'] );
// Save the flag that the saved value is from the other option field.
$data['is_other'] = true;
foreach ( $field['choices'] as $choice ) {
if ( isset( $choice['other'] ) ) {
$data['value_raw'] = $choice['label'];
$data['image'] = ! empty( $field['choices_images'] ) && ! empty( $choice['image'] ) ? esc_url_raw( $choice['image'] ) : '';
break;
}
}
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
/**
* Export entry field data.
*
* @since 1.9.8.3
*
* @param array|mixed $field Field data.
*
* @return array
*/
public function export_entry_field_data( $field ): array {
$field = (array) $field;
if ( empty( $field['is_other'] ) ) {
return $field;
}
$value = (string) ( $field['value'] ?? '' );
$value_raw = (string) ( $field['value_raw'] ?? '' );
$field['value'] = $value_raw . ': ' . $value;
return $field;
}
/**
* Include a radio field in allowed field types for keyword search.
*
* @since 1.9.8.3
*
* @param array|mixed $fields Array of field types.
*
* @return array
*/
public function add_field_to_anti_spam_keyword_filter( $fields ): array {
$fields[] = $this->type;
return $fields;
}
/**
* Generate the HTML value for a field.
*
* It overrides the parent method because of fields with Other choices.
*
* @since 1.9.8.3
*
* @param mixed $value The value of the field.
* @param array $field Field data and settings.
* @param array $form_data Optional. Additional form data.
* @param string $context Optional. The context in which the value is being rendered.
*
* @return string
*/
public function field_html_value( $value, $field, $form_data = [], $context = '' ) {
if ( empty( $field['type'] ) || $field['type'] !== $this->type ) {
return $value;
}
$other_value = $this->get_other_choice_value( $field );
$field_value = $other_value ?? $value ?? '';
return parent::field_html_value(
$field_value,
$field,
$form_data,
$context
);
}
/**
* Return choice value, including Other label if applicable.
* It overrides the parent method because of fields with Other choices.
*
* @since 1.9.8.3
*
* @param array $field Field settings.
* @param array $form_data Form data.
*
* @return string
*/
protected function get_choices_value( array $field, array $form_data ): string {
$other_value = $this->get_other_choice_value( $field );
return $other_value ?? parent::get_choices_value( $field, $form_data );
}
/**
* Retrieve the value for the "Other" choice option in a field.
*
* This method handles the retrieval of the "Other" choice value, considering
* both raw and processed values. It supports cases where the "Other" choice
* is enabled and attempts to construct a meaningful representation of the value
* based on the available inputs.
*
* @since 1.9.8.3
*
* @param array $field Field data.
*
* @return string|null Returns the constructed "Other" choice value if available,
* or null if the "Other" choice is not enabled.
*/
private function get_other_choice_value( array $field ): ?string {
// Bail out early if it's not an Other choice.
if ( empty( $field['is_other'] ) ) {
return null;
}
$value = $field['value'] ?? '';
$value_raw = $field['value_raw'] ?? '';
if ( wpforms_is_empty_string( $value_raw ) ) {
return (string) $value;
}
// Return a value with a value_raw as a prefix only if both are not empty.
if ( ! wpforms_is_empty_string( $value ) ) {
return sprintf( '%1$s: %2$s', $value_raw, $value );
}
return (string) $value_raw;
}
/**
* Adjust the entry field before saving.
*
* It's necessary for fields with other choices.
*
* @since 1.9.8.3
*
* @param array $field Field data.
* @param array $form_data Form data.
* @param int $entry_id Entry ID.
*
* @return array
* @noinspection PhpUnused
*/
public function save_field( $field, $form_data, $entry_id ) {
if ( empty( $field['type'] ) || $field['type'] !== $this->type ) {
return $field;
}
// Save `value_raw: value` for fields with Other choices.
// It's necessary for search functionality on the Form Entries Overview table.
// Admins should be able to search for entries that have used an other value.
$other_value = $this->get_other_choice_value( $field );
if ( $other_value !== null ) {
$field['value'] = $other_value;
}
return $field;
}
}
new WPForms_Field_Radio();

View File

@@ -0,0 +1,795 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
/**
* Dropdown field.
*
* @since 1.0.0
*/
class WPForms_Field_Select extends WPForms_Field {
/**
* The 'Choices JS' version.
*
* @since 1.6.3
*/
public const CHOICES_VERSION = '10.2.0';
/**
* Classic (old) style.
*
* @since 1.6.1
*
* @var string
*/
public const STYLE_CLASSIC = 'classic';
/**
* Modern style.
*
* @since 1.6.1
*
* @var string
*/
public const STYLE_MODERN = 'modern';
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Define field type information.
$this->name = esc_html__( 'Dropdown', 'wpforms-lite' );
$this->keywords = esc_html__( 'choice', 'wpforms-lite' );
$this->type = 'select';
$this->icon = 'fa-caret-square-o-down';
$this->order = 70;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
// Define additional field properties.
add_filter( 'wpforms_field_properties_' . $this->type, [ $this, 'field_properties' ], 5, 3 );
// Form frontend CSS enqueues.
add_action( 'wpforms_frontend_css', [ $this, 'enqueue_frontend_css' ] );
// Form frontend JS enqueues.
add_action( 'wpforms_frontend_js', [ $this, 'enqueue_frontend_js' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
}
/**
* Define additional field properties.
*
* @since 1.5.0
*
* @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 ) {
// Remove primary input.
unset( $properties['inputs']['primary'] );
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Set options container (<select>) properties.
$properties['input_container'] = [
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
],
];
// Set properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
'value' => isset( $field['show_values'] ) ? $choice['value'] : $choice['label'],
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
}
// Add a class that changes the field size.
if ( ! empty( $field['size'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-' . esc_attr( $field['size'] );
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Add additional class for container.
if (
! empty( $field['style'] ) &&
in_array( $field['style'], [ self::STYLE_CLASSIC, self::STYLE_MODERN ], true )
) {
$properties['container']['class'][] = "wpforms-field-select-style-{$field['style']}";
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*
* @noinspection HtmlUnknownTarget*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$show_values = $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => $field['show_values'] ?? '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $show_values,
]
);
}
// Multiple options selection.
$fld = $this->field_element(
'toggle',
$field,
[
'slug' => 'multiple',
'value' => ! empty( $field['multiple'] ),
'desc' => esc_html__( 'Multiple Options Selection', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Allow users to select multiple choices in this field.', 'wpforms-lite' ) . '<br>' .
sprintf(
wp_kses( /* translators: %s - URL to WPForms.com doc article. */
esc_html__( 'For details, including how this looks and works for your site\'s visitors, please check out <a href="%s" target="_blank" rel="noopener noreferrer">our doc</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-allow-multiple-selections-to-a-dropdown-field-in-wpforms/', 'Field Options', 'Multiple Options Selection Documentation' ) )
),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'multiple',
'content' => $fld,
]
);
// Style.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'style',
'value' => esc_html__( 'Style', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Classic style is the default one generated by your browser. Modern has a fresh look and displays all selected options in a single row.', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'select',
$field,
[
'slug' => 'style',
'value' => ! empty( $field['style'] ) ? $field['style'] : self::STYLE_CLASSIC,
'options' => [
self::STYLE_CLASSIC => esc_html__( 'Classic', 'wpforms-lite' ),
self::STYLE_MODERN => esc_html__( 'Modern', 'wpforms-lite' ),
],
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'style',
'content' => $lbl . $fld,
]
);
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
* @since 1.6.1 Added a `Modern` style select support.
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
$args = [];
// Label.
$this->field_preview_option( 'label', $field );
// Prepare arguments.
$args['modern'] = false;
if (
! empty( $field['style'] ) &&
$field['style'] === self::STYLE_MODERN
) {
$args['modern'] = true;
$args['class'] = 'choicesjs-select';
}
// Choices.
$this->field_preview_option( 'choices', $field, $args );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
* @since 1.5.0 Converted to a new format, where all the data are taken not from $deprecated, but field properties.
* @since 1.6.1 Added multiple select support.
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated array of field attributes.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$container = $field['properties']['input_container'];
$field_placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$is_multiple = ! empty( $field['multiple'] );
$is_modern = ! empty( $field['style'] ) && $field['style'] === self::STYLE_MODERN;
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on the Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
if ( ! empty( $field['properties']['input_container']['class'] ) && in_array( 'wpforms-field-required', $field['properties']['input_container']['class'], true ) ) {
$container['attr']['required'] = 'required';
}
// If it's multiple select.
if ( $is_multiple ) {
$container['attr']['multiple'] = 'multiple';
// Change a name attribute.
if ( ! empty( $container['attr']['name'] ) ) {
$container['attr']['name'] .= '[]';
}
}
// Add a class for Choices.js initialization.
if ( $is_modern ) {
$container['class'][] = 'choicesjs-select';
// Add a size-class to the data attribute - it is used when Choices.js is initialized.
if ( ! empty( $field['size'] ) ) {
$container['data']['size-class'] = 'wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] );
}
$container['data']['search-enabled'] = $this->is_choicesjs_search_enabled( count( $choices ) );
}
$has_default = false;
// Check to see if any of the options were selected by default.
foreach ( $choices as $choice ) {
if ( ! empty( $choice['default'] ) ) {
$has_default = true;
break;
}
}
// Preselect default if no other choices were marked as default.
printf(
'<select %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
// Optional placeholder.
if ( ! empty( $field_placeholder ) || $is_modern ) {
printf(
'<option value="" class="placeholder" disabled %s>%s</option>',
selected( false, $has_default || $is_multiple, false ),
esc_html( $field_placeholder )
);
}
// Build the select options.
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
$value = isset( $choice['attr']['value'] ) && ! wpforms_is_empty_string( $choice['attr']['value'] ) ? $choice['attr']['value'] : $label;
$data = $choice['container']['data'] ?? [];
$data_html = '';
if ( ! empty( $data ) ) {
$data_html = wpforms_html_attributes( '', '', $data );
}
$selected = $choice['attr']['selected'] ?? false;
$selected_html = $selected ? ' selected="selected"' : '';
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
printf(
'<option value="%1$s" %2$s class="%3$s" %4$s %5$s>%6$s</option>',
esc_attr( $value ),
selected( true, ! empty( $choice['default'] ), false ),
esc_attr( implode( ' ', $choice['container']['class'] ) ),
$data_html,
$selected_html,
wp_kses(
$label,
[
'span' => [
'class' => [],
],
]
)
);
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</select>';
}
/**
* Validate field.
*
* @since 1.8.2
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
// Skip validation if the field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
parent::validate( $field_id, $field_submit, $form_data );
}
/**
* Format and sanitize field.
*
* @since 1.0.2
* @since 1.6.1 Added support for multiple values.
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (selected option).
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$multiple = ! empty( $field['multiple'] );
$name = sanitize_text_field( $field['label'] );
$value = [];
// Convert the submitted field value to array.
if ( ! is_array( $field_submit ) ) {
$field_submit = [ $field_submit ];
}
$value_raw = wpforms_sanitize_array_combine( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( $dynamic === 'post_type' && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type (like for a `Checkboxes` field).
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = $value_raw;
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$posts = [];
foreach ( $field_submit as $id ) {
$post = get_post( $id );
if ( ! empty( $post ) && ! is_wp_error( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$posts[] = esc_html( wpforms_get_post_title( $post ) );
}
}
$data['value'] = ! empty( $posts ) ? wpforms_sanitize_array_combine( $posts ) : '';
} elseif ( $dynamic === 'taxonomy' && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy (like for a `Checkboxes` field).
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = $value_raw;
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$terms = [];
foreach ( $field_submit as $id ) {
$term = get_term( $id, $field['dynamic_taxonomy'] );
if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
$terms[] = esc_html( wpforms_get_term_name( $term ) );
}
}
$data['value'] = ! empty( $terms ) ? wpforms_sanitize_array_combine( $terms ) : '';
} else {
// Normal processing, dynamic population is off.
// If show_values is true, that means values posted are the raw values
// and not the labels. So we need to get the label values.
if ( ! empty( $field['show_values'] ) && (int) $field['show_values'] === 1 ) {
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $choice ) {
if ( $item === $choice['value'] ) {
$value[] = $choice['label'];
break;
}
}
}
$data['value'] = ! empty( $value ) ? wpforms_sanitize_array_combine( $value ) : '';
} else {
$data['value'] = $value_raw;
}
}
// Backward compatibility: for single dropdown save a string, for multiple - array.
if ( ! $multiple && is_array( $data ) && ( 1 === count( $data ) ) ) {
$data = reset( $data );
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
/**
* Form frontend CSS enqueues.
*
* @since 1.6.1
*
* @param array $forms Forms on the current page.
*/
public function enqueue_frontend_css( $forms ) {
$has_modern_select = false;
foreach ( $forms as $form ) {
if ( $this->is_field_style( $form, self::STYLE_MODERN ) ) {
$has_modern_select = true;
break;
}
}
if ( $has_modern_select || wpforms()->obj( 'frontend' )->assets_global() ) {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-choicesjs',
WPFORMS_PLUGIN_URL . "assets/css/choices{$min}.css",
[],
self::CHOICES_VERSION
);
}
}
/**
* Form frontend JS enqueues.
*
* @since 1.6.1
*
* @param array $forms Forms on the current page.
*/
public function enqueue_frontend_js( $forms ) {
$has_modern_select = false;
foreach ( $forms as $form ) {
if ( $this->is_field_style( $form, self::STYLE_MODERN ) ) {
$has_modern_select = true;
break;
}
}
if ( $has_modern_select || wpforms()->obj( 'frontend' )->assets_global() ) {
$this->enqueue_choicesjs_once( $forms );
}
}
/**
* Load WPForms Gutenberg block scripts.
*
* @since 1.8.1
*/
public function enqueue_block_editor_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-choicesjs',
WPFORMS_PLUGIN_URL . "assets/css/choices{$min}.css",
[],
self::CHOICES_VERSION
);
$this->enqueue_choicesjs_once( [] );
}
/**
* Whether the provided form has a dropdown field with a specified style.
*
* @since 1.6.1
*
* @param array $form Form data.
* @param string $style Desired field style.
*
* @return bool
*/
protected function is_field_style( $form, $style ) {
$is_field_style = false;
if ( empty( $form['fields'] ) ) {
return false;
}
foreach ( (array) $form['fields'] as $field ) {
if (
! empty( $field['type'] ) &&
$field['type'] === $this->type &&
! empty( $field['style'] ) &&
sanitize_key( $style ) === $field['style']
) {
$is_field_style = true;
break;
}
}
return $is_field_style;
}
/**
* Get a field name for an ajax error message.
*
* @since 1.6.3
*
* @param string|mixed $name Field name for error triggered.
* @param array $field Field settings.
* @param array $props List of properties.
* @param string|string[] $error Error message.
*
* @return string
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function ajax_error_field_name( $name, $field, $props, $error ) {
$name = (string) $name;
if ( ! isset( $field['type'] ) || $field['type'] !== 'select' ) {
return $name;
}
if ( ! empty( $field['multiple'] ) ) {
$input = isset( $props['inputs'] ) ? end( $props['inputs'] ) : [];
return isset( $input['attr']['name'] ) ? $input['attr']['name'] . '[]' : '';
}
return $name;
}
}
new WPForms_Field_Select();

View File

@@ -0,0 +1,558 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Single line text field.
*
* @since 1.0.0
*/
class WPForms_Field_Text extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Single Line Text', 'wpforms-lite' );
$this->type = 'text';
$this->icon = 'fa-text-width';
$this->order = 30;
// Define additional field properties.
add_filter( 'wpforms_field_properties_text', [ $this, 'field_properties' ], 5, 3 );
add_action( 'wpforms_frontend_js', [ $this, 'frontend_js' ] );
}
/**
* Convert mask formatted for jquery.inputmask into the format used by amp-inputmask.
*
* Note that amp-inputmask does not yet support all of the options that jquery.inputmask provides.
* In particular, amp-inputmask doesn't provides:
* - Upper-alphabetical mask.
* - Upper-alphanumeric mask.
* - Advanced Input Masks with arbitrary repeating groups.
*
* @link https://amp.dev/documentation/components/amp-inputmask
* @link https://wpforms.com/docs/how-to-use-custom-input-masks/
*
* @param string $mask Mask formatted for jquery.inputmask.
* @return array {
* Mask and placeholder.
*
* @type string $mask Mask for amp-inputmask.
* @type string $placeholder Placeholder derived from mask if one is not supplied.
* }
*/
protected function convert_mask_to_amp_inputmask( $mask ) {
$placeholder = '';
// Convert jquery.inputmask format into amp-inputmask format.
$amp_mask = '';
$req_mask_mapping = [
'9' => '0', // Numeric.
'a' => 'L', // Alphabetical (a-z or A-Z).
'A' => 'L', // Upper-alphabetical (A-Z). Note: AMP does not have an uppercase-alphabetical mask type, so same as previous.
'*' => 'A', // Alphanumeric (0-9, a-z, A-Z).
'&' => 'A', // Upper-alphanumeric (A-Z, 0-9). Note: AMP does not have an uppercase-alphanumeric mask type, so same as previous.
' ' => '_', // Automatically insert spaces.
];
$opt_mask_mapping = [
'9' => '9', // The user may optionally add a numeric character.
'a' => 'l', // The user may optionally add an alphabetical character.
'A' => 'l', // The user may optionally add an alphabetical character.
'*' => 'a', // The user may optionally add an alphanumeric character.
'&' => 'a', // The user may optionally add an alphanumeric character.
];
$placeholder_mapping = [
'9' => '0',
'a' => 'a',
'A' => 'a',
'*' => '_',
'&' => '_',
];
$is_inside_optional = false;
$last_mask_token = null;
for ( $i = 0, $len = strlen( $mask ); $i < $len; $i++ ) {
if ( '[' === $mask[ $i ] ) {
$is_inside_optional = true;
$placeholder .= $mask[ $i ];
continue;
} elseif ( ']' === $mask[ $i ] ) {
$is_inside_optional = false;
$placeholder .= $mask[ $i ];
continue;
} elseif ( isset( $last_mask_token ) && preg_match( '/^\{(?P<n>\d+)(?:,(?P<m>\d+))?\}/', substr( $mask, $i ), $matches ) ) {
$amp_mask .= str_repeat( $req_mask_mapping[ $last_mask_token ], $matches['n'] );
$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['n'] );
if ( isset( $matches['m'] ) ) {
$amp_mask .= str_repeat( $opt_mask_mapping[ $last_mask_token ], $matches['m'] );
$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['m'] );
}
$i += strlen( $matches[0] ) - 1;
$last_mask_token = null; // Reset.
continue;
}
if ( '\\' === $mask[ $i ] ) {
$amp_mask .= '\\';
$i++;
if ( ! isset( $mask[ $i ] ) ) {
continue;
}
$amp_mask .= $mask[ $i ];
} else {
// Remember this token in case it is a mask.
if ( isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {
$last_mask_token = $mask[ $i ];
}
if ( $is_inside_optional && isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {
$amp_mask .= $opt_mask_mapping[ $mask[ $i ] ];
} elseif ( isset( $req_mask_mapping[ $mask[ $i ] ] ) ) {
$amp_mask .= $req_mask_mapping[ $mask[ $i ] ];
} else {
$amp_mask .= '\\' . $mask[ $i ];
}
}
if ( isset( $placeholder_mapping[ $mask[ $i ] ] ) ) {
$placeholder .= $placeholder_mapping[ $mask[ $i ] ];
} else {
$placeholder .= $mask[ $i ];
}
}
return [ $amp_mask, $placeholder ];
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @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 ) {
// Input primary: Detect custom input mask.
if ( empty( $field['input_mask'] ) ) {
return $properties;
}
// Add class that will trigger custom mask.
$properties['inputs']['primary']['class'][] = 'wpforms-masked-input';
if ( wpforms_is_amp() ) {
return $this->get_amp_input_mask_properties( $properties, $field );
}
$properties['inputs']['primary']['data']['rule-inputmask-incomplete'] = true;
if ( strpos( $field['input_mask'], 'alias:' ) !== false ) {
$mask = str_replace( 'alias:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-alias'] = $mask;
return $properties;
}
if ( strpos( $field['input_mask'], 'regex:' ) !== false ) {
$mask = str_replace( 'regex:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-regex'] = $mask;
return $properties;
}
if ( strpos( $field['input_mask'], 'date:' ) !== false ) {
$mask = str_replace( 'date:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-alias'] = 'datetime';
$properties['inputs']['primary']['data']['inputmask-inputformat'] = $mask;
/**
* Some datetime formats include letters, so we need to switch inputmode to text.
* For instance:
* tt is am/pm
* TT is AM/PM
*/
$properties['inputs']['primary']['data']['inputmask-inputmode'] = preg_match( '/[tT]/', $mask ) ? 'text' : 'numeric';
return $properties;
}
$properties['inputs']['primary']['data']['inputmask-mask'] = $field['input_mask'];
return $properties;
}
/**
* Define additional field properties for the inputmask on AMP pages.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Field settings.
*
* @return array
*/
private function get_amp_input_mask_properties( $properties, $field ) {
list( $amp_mask, $placeholder ) = $this->convert_mask_to_amp_inputmask( $field['input_mask'] );
$properties['inputs']['primary']['attr']['mask'] = $amp_mask;
if ( empty( $properties['inputs']['primary']['attr']['placeholder'] ) ) {
$properties['inputs']['primary']['attr']['placeholder'] = $placeholder;
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Limit length.
$args = [
'slug' => 'limit_enabled',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'limit_enabled',
'value' => isset( $field['limit_enabled'] ),
'desc' => esc_html__( 'Limit Length', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to limit text length by characters or words count.', 'wpforms-lite' ),
],
false
),
];
$this->field_element( 'row', $field, $args );
$count = $this->field_element(
'text',
$field,
[
'type' => 'number',
'slug' => 'limit_count',
'attrs' => [
'min' => 1,
'step' => 1,
'pattern' => '[0-9]',
],
'value' => ! empty( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 1,
],
false
);
$mode = $this->field_element(
'select',
$field,
[
'slug' => 'limit_mode',
'value' => ! empty( $field['limit_mode'] ) ? esc_attr( $field['limit_mode'] ) : 'characters',
'options' => [
'characters' => esc_html__( 'Characters', 'wpforms-lite' ),
'words' => esc_html__( 'Words', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'limit_controls',
'class' => ! isset( $field['limit_enabled'] ) ? 'wpforms-hide' : '',
'content' => $count . $mode,
];
$this->field_element( 'row', $field, $args );
// Default value.
$this->field_option( 'default_value', $field );
// Input Mask.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'input_mask',
'value' => esc_html__( 'Input Mask', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Enter your custom input mask.', 'wpforms-lite' ),
'after_tooltip' => '<a href="' . esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-use-custom-input-masks/', 'Field Options', 'Input Mask Documentation' ) ) . '" class="after-label-description" target="_blank" rel="noopener noreferrer">' . esc_html__( 'See Examples & Docs', 'wpforms-lite' ) . '</a>',
],
false
);
$fld = $this->field_element(
'text',
$field,
[
'slug' => 'input_mask',
'value' => ! empty( $field['input_mask'] ) ? esc_attr( $field['input_mask'] ) : '',
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'input_mask',
'content' => $lbl . $fld,
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Define data.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
echo '<input type="text" placeholder="' . esc_attr( $placeholder ) . '" value="' . esc_attr( $default_value ) . '" class="primary-input" readonly>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
if ( isset( $field['limit_enabled'] ) ) {
$limit_count = isset( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 0;
$limit_mode = isset( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$primary['data']['form-id'] = $form_data['id'];
$primary['data']['field-id'] = $field['id'];
if ( 'characters' === $limit_mode ) {
$primary['class'][] = 'wpforms-limit-characters-enabled';
$primary['attr']['maxlength'] = $limit_count;
$primary['data']['text-limit'] = $limit_count;
} else {
$primary['class'][] = 'wpforms-limit-words-enabled';
$primary['data']['text-limit'] = $limit_count;
}
}
// Primary field.
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
$primary['required'] // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
/**
* Enqueue frontend limit option js.
*
* @since 1.5.6
*
* @param array $forms Forms on the current page.
*/
public function frontend_js( $forms ) {
// Get fields.
$fields = array_map(
function( $form ) {
return empty( $form['fields'] ) ? [] : $form['fields'];
},
(array) $forms
);
// Make fields flat.
$fields = array_reduce(
$fields,
function( $accumulator, $current ) {
return array_merge( $accumulator, $current );
},
[]
);
// Leave only fields with limit.
$fields = array_filter(
$fields,
function( $field ) {
return $field['type'] === $this->type && isset( $field['limit_enabled'] ) && ! empty( $field['limit_count'] );
}
);
if ( count( $fields ) ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-text-limit',
WPFORMS_PLUGIN_URL . "assets/js/frontend/fields/text-limit.es5{$min}.js",
[],
WPFORMS_VERSION,
$this->load_script_in_footer()
);
}
}
/**
* Format and sanitize field.
*
* @since 1.5.6
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
$name = ! empty( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
// Sanitize.
$value = sanitize_text_field( $field_submit );
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => $name,
'value' => $value,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Validate field on form submit.
*
* @since 1.6.2
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
parent::validate( $field_id, $field_submit, $form_data );
if ( empty( $form_data['fields'][ $field_id ] ) || empty( $form_data['fields'][ $field_id ]['limit_enabled'] ) ) {
return;
}
$field = $form_data['fields'][ $field_id ];
$limit = absint( $field['limit_count'] );
$mode = ! empty( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$value = sanitize_text_field( $field_submit );
if ( 'characters' === $mode ) {
if ( mb_strlen( str_replace( "\r\n", "\n", $value ) ) > $limit ) {
/* translators: %s - limit characters number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d character.', 'Text can\'t exceed %d characters.', $limit, 'wpforms-lite' ), $limit );
return;
}
} else {
if ( wpforms_count_words( $value ) > $limit ) {
/* translators: %s - limit words number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d word.', 'Text can\'t exceed %d words.', $limit, 'wpforms-lite' ), $limit );
return;
}
}
}
}
new WPForms_Field_Text();

View File

@@ -0,0 +1,380 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Paragraph text field.
*
* @since 1.0.0
*/
class WPForms_Field_Textarea extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Paragraph Text', 'wpforms-lite' );
$this->keywords = esc_html__( 'textarea', 'wpforms-lite' );
$this->type = 'textarea';
$this->icon = 'fa-paragraph';
$this->order = 50;
add_action( 'wpforms_frontend_js', [ $this, 'frontend_js' ] );
}
/**
* Get the value, that is used to prefill via dynamic or fallback population.
* Based on field data and current properties.
*
* @since 1.6.4
*
* @param string $raw_value Value from a GET param, always a string.
* @param string $input Represent a subfield inside the field. May be empty.
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return array Modified field properties.
*/
protected function get_field_populated_single_property_value( $raw_value, $input, $properties, $field ) {
if ( ! is_string( $raw_value ) ) {
return $properties;
}
if (
! empty( $input ) &&
isset( $properties['inputs'][ $input ] )
) {
$properties['inputs'][ $input ]['attr']['value'] = wpforms_sanitize_textarea_field( wp_unslash( $raw_value ) );
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Limit length.
$args = [
'slug' => 'limit_enabled',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'limit_enabled',
'value' => isset( $field['limit_enabled'] ) ? '1' : '0',
'desc' => esc_html__( 'Limit Length', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to limit text length by characters or words count.', 'wpforms-lite' ),
],
false
),
];
$this->field_element( 'row', $field, $args );
$count = $this->field_element(
'text',
$field,
[
'type' => 'number',
'slug' => 'limit_count',
'attrs' => [
'min' => 1,
'step' => 1,
'pattern' => '[0-9]',
],
'value' => ! empty( $field['limit_count'] ) ? $field['limit_count'] : 1,
],
false
);
$mode = $this->field_element(
'select',
$field,
[
'slug' => 'limit_mode',
'value' => ! empty( $field['limit_mode'] ) ? esc_attr( $field['limit_mode'] ) : 'characters',
'options' => [
'characters' => esc_html__( 'Characters', 'wpforms-lite' ),
'words' => esc_html__( 'Words', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'limit_controls',
'class' => ! isset( $field['limit_enabled'] ) ? 'wpforms-hide' : '',
'content' => $count . $mode,
];
$this->field_element( 'row', $field, $args );
// Default value.
$this->field_option( 'default_value', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
echo '<textarea placeholder="' . esc_attr( $placeholder ) . '" class="primary-input" readonly>' . esc_textarea( $default_value ) . '</textarea>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
$value = '';
if ( isset( $primary['attr']['value'] ) ) {
$value = esc_textarea( html_entity_decode( $primary['attr']['value'] ) );
unset( $primary['attr']['value'] );
}
if ( isset( $field['limit_enabled'] ) ) {
$limit_count = isset( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 0;
$limit_mode = isset( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$primary['data']['form-id'] = $form_data['id'];
$primary['data']['field-id'] = $field['id'];
if ( 'characters' === $limit_mode ) {
$primary['class'][] = 'wpforms-limit-characters-enabled';
$primary['attr']['maxlength'] = $limit_count;
$primary['data']['text-limit'] = $limit_count;
} else {
$primary['class'][] = 'wpforms-limit-words-enabled';
$primary['data']['text-limit'] = $limit_count;
}
}
// Primary field.
printf(
'<textarea %s %s>%s</textarea>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
$primary['required'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$value // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
/**
* Enqueue frontend limit option js.
*
* @since 1.5.6
*
* @param array $forms Forms on the current page.
*/
public function frontend_js( $forms ) {
// Get fields.
$fields = array_map(
function( $form ) {
return empty( $form['fields'] ) ? [] : $form['fields'];
},
(array) $forms
);
// Make fields flat.
$fields = array_reduce(
$fields,
function( $accumulator, $current ) {
return array_merge( $accumulator, $current );
},
[]
);
// Leave only fields with limit.
$fields = array_filter(
$fields,
function( $field ) {
return $field['type'] === $this->type && isset( $field['limit_enabled'] );
}
);
if ( count( $fields ) ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-text-limit',
WPFORMS_PLUGIN_URL . "assets/js/frontend/fields/text-limit.es5{$min}.js",
[],
WPFORMS_VERSION,
$this->load_script_in_footer()
);
}
}
/**
* Format and sanitize field.
*
* @since 1.5.6
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
if ( is_array( $field_submit ) ) {
$field_submit = implode( "\r\n", array_filter( $field_submit ) );
}
$name = ! empty( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
// Sanitize but keep line breaks.
$value = wpforms_sanitize_textarea_field( $field_submit );
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => $name,
'value' => $value,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Validate field on form submit.
*
* @since 1.6.2
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
parent::validate( $field_id, $field_submit, $form_data );
if ( empty( $form_data['fields'][ $field_id ] ) || empty( $form_data['fields'][ $field_id ]['limit_enabled'] ) ) {
return;
}
if ( is_array( $field_submit ) ) {
$field_submit = implode( "\r\n", array_filter( $field_submit ) );
}
$field = $form_data['fields'][ $field_id ];
$limit = absint( $field['limit_count'] );
$mode = ! empty( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$value = wpforms_sanitize_textarea_field( $field_submit );
if ( 'characters' === $mode ) {
if ( mb_strlen( str_replace( "\r\n", "\n", $value ) ) > $limit ) {
/* translators: %s - limit characters number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d character.', 'Text can\'t exceed %d characters.', $limit, 'wpforms-lite' ), $limit );
return;
}
} else {
if ( wpforms_count_words( $value ) > $limit ) {
/* translators: %s - limit words number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d word.', 'Text can\'t exceed %d words.', $limit, 'wpforms-lite' ), $limit );
return;
}
}
}
}
new WPForms_Field_Textarea();

View File

@@ -0,0 +1,7 @@
<?php
/**
* Helper functions to work with multidimensional arrays easier.
*
* @since 1.5.6
* @since 1.8.0 Moved to /includes/functions/list.php
*/

View File

@@ -0,0 +1,30 @@
<?php
/**
* Global functions used in core plugin and addons.
*
* @since 1.0.0
* @since 1.8.0 Split into multiple files, see `includes/functions/`.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/functions/access.php';
require_once __DIR__ . '/functions/builder.php';
require_once __DIR__ . '/functions/checks.php';
require_once __DIR__ . '/functions/colors.php';
require_once __DIR__ . '/functions/data-presets.php';
require_once __DIR__ . '/functions/date-time.php';
require_once __DIR__ . '/functions/debug.php';
require_once __DIR__ . '/functions/education.php';
require_once __DIR__ . '/functions/escape-sanitize.php';
require_once __DIR__ . '/functions/filesystem-media.php';
require_once __DIR__ . '/functions/form-fields.php';
require_once __DIR__ . '/functions/forms.php';
require_once __DIR__ . '/functions/list.php';
require_once __DIR__ . '/functions/payments.php';
require_once __DIR__ . '/functions/plugins.php';
require_once __DIR__ . '/functions/privacy.php';
require_once __DIR__ . '/functions/providers.php';
require_once __DIR__ . '/functions/utilities.php';

View File

@@ -0,0 +1,394 @@
<?php
/**
* Helper functions to work with licenses, permissions and capabilities.
*
* @since 1.8.0
*/
/**
* Determine if the plugin/addon installations are allowed.
*
* @since 1.6.2.3
*
* @param string $type Should be `plugin` or `addon`.
*
* @return bool
*/
function wpforms_can_install( $type ): bool {
return wpforms_can_do( 'install', $type );
}
/**
* Determine if the plugin/addon activations are allowed.
*
* @since 1.7.3
*
* @param string $type Should be `plugin` or `addon`.
*
* @return bool
*/
function wpforms_can_activate( $type ): bool {
return wpforms_can_do( 'activate', $type );
}
/**
* Determine if the plugin/addon installations/activations are allowed.
*
* @since 1.7.3
*
* @internal Use wpforms_can_activate() or wpforms_can_install() instead.
*
* @param string $what Should be 'activate' or 'install'.
* @param string $type Should be `plugin` or `addon`.
*
* @return bool
*/
function wpforms_can_do( $what, $type ): bool {
if ( ! in_array( $what, [ 'install', 'activate' ], true ) ) {
return false;
}
if ( ! in_array( $type, [ 'plugin', 'addon' ], true ) ) {
return false;
}
$capability = $what . '_plugins';
if ( ! current_user_can( $capability ) ) {
return false;
}
// Determine whether file modifications are allowed and it is activation permissions checking.
if ( $what === 'install' && ! wp_is_file_mod_allowed( 'wpforms_can_install' ) ) {
return false;
}
// All plugin checks are done.
if ( $type === 'plugin' ) {
return true;
}
// Addons require additional license checks.
$license = get_option( 'wpforms_license', [] );
// Allow addons installation if the license is not expired, enabled and valid.
return empty( $license['is_expired'] ) && empty( $license['is_disabled'] ) && empty( $license['is_invalid'] );
}
/**
* 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
* @since 1.8.0 The WPFORMS_LICENSE_KEY constant has higher priority than the DB option.
*
* @return string
*/
function wpforms_get_license_key(): string {
// Allow wp-config constant to pass key.
if ( defined( 'WPFORMS_LICENSE_KEY' ) && WPFORMS_LICENSE_KEY ) {
return WPFORMS_LICENSE_KEY;
}
return (string) wpforms_setting( 'key', '', 'wpforms_license' );
}
/**
* Get when WPForms was first installed.
*
* @since 1.6.0
*
* @param string $type Specific installation 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 installation 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 ];
}
/**
* Get the default capability to manage everything for WPForms.
*
* @since 1.4.4
*
* @return string
*/
function wpforms_get_capability_manage_options(): string {
/**
* Filters the default capability to manage everything for WPForms.
* By default, it is 'manage_options'.
*
* @since 1.4.4
*
* @param string $capability Default capability to manage everything for WPForms.
*/
return (string) 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 names can be used.
* Only WPForms capabilities get processed.
*
* @since 1.4.4
*
* @param array|string $caps Capability name(s).
* @param int|mixed $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 ): bool {
static $results;
$id = (int) $id;
$caps_str = is_array( $caps ) ? implode( ' ', $caps ) : (string) $caps;
$hash = md5( $caps_str . $id );
// Return a cached result.
if ( isset( $results[ $hash ] ) ) {
return $results[ $hash ];
}
$access = wpforms()->obj( 'access' );
if ( ! $access || ! method_exists( $access, 'current_user_can' ) ) {
return false;
}
$user_can = $access->current_user_can( $caps, $id );
/**
* Filters the result of the WPForms permissions check for the currently logged-in user.
*
* @since 1.4.4
*
* @param bool $user_can Whether the current user has the capability.
* @param array|string $caps Capability name(s).
* @param int $id ID of the specific object to check against if capability is a "meta" cap.
*/
$results[ $hash ] = apply_filters( 'wpforms_current_user_can', $user_can, $caps, $id );
return $results[ $hash ];
}
/**
* Search for posts editable by the user.
*
* @since 1.7.9
*
* @param string $search_term Optional search term. Default ''.
* @param array $args Args {
* Optional. An array of arguments.
*
* @type string $post_type Post type to search for.
* @type string[] $post_status Post status to search for.
* @type int $count Number of results to return. Default 20.
* }
*
* @return array
* @noinspection PhpTernaryExpressionCanBeReducedToShortVersionInspection
* @noinspection ElvisOperatorCanBeUsedInspection
*/
function wpforms_search_posts( $search_term = '', $args = [] ): array {
global $wpdb;
$default_args = [
'post_type' => 'page',
'post_status' => [ 'publish' ],
'count' => 20,
];
$args = wp_parse_args( $args, $default_args );
// @todo: add trash access capabilities to MySQL.
// See edit_post/edit_page case in map_meta_cap().
$args['post_status'] = array_diff( $args['post_status'], [ 'trash' ] );
$user = wp_get_current_user();
$user_id = $user->ID ?? 0;
$post_type = get_post_type_object( $args['post_type'] );
if ( ! $user || ! $user_id || ! $post_type || $args['count'] <= 0 ) {
return [];
}
$last_changed = wp_cache_get_last_changed( 'posts' );
$key = __FUNCTION__ . ":$search_term:$last_changed";
$cache_posts = wp_cache_get( $key, '', false, $found );
if ( $found ) {
return (array) $cache_posts;
}
$post_title_where = $search_term ? $wpdb->prepare(
'post_title LIKE %s AND',
'%' . $wpdb->esc_like( $search_term ) . '%'
) :
'';
$post_statuses = array_intersect( array_keys( get_post_statuses() ), $args['post_status'] );
$post_statuses = wpforms_wpdb_prepare_in( $post_statuses );
$policy_id = (int) get_option( 'wp_page_for_privacy_policy' );
$can_delete_published_posts = (int) $user->has_cap( $post_type->cap->delete_published_posts );
$can_delete_posts = (int) $user->has_cap( $post_type->cap->delete_posts );
$can_delete_others_posts = (int) $user->has_cap( $post_type->cap->delete_others_posts );
$can_delete_private_posts = (int) $user->has_cap( $post_type->cap->delete_private_posts );
$can_edit_policy = (int) $user->has_cap( map_meta_cap( 'manage_privacy_options', $user_id )[0] );
// For the case when the user is post author.
$capability_author_where = "post_author = $user_id AND
( ( post_status IN ( 'publish', 'future' ) AND $can_delete_published_posts ) OR
( ( post_status NOT IN ( 'publish', 'future', 'trash' ) ) AND $can_delete_posts )
)";
// For the case when accessing someone other's post.
$capability_other_where = "post_author != $user_id AND
$can_delete_others_posts AND
( ( post_status IN ( 'publish', 'future' ) AND $can_delete_published_posts ) OR
( ( post_status IN ( 'private' ) ) AND $can_delete_private_posts )
)";
// For privacy policy page.
$capability_policy_where = "ID = $policy_id AND $can_edit_policy";
$capability_where = '( ' .
'(' . $capability_author_where . ') OR ' .
'(' . $capability_other_where . ') OR ' .
'(' . $capability_policy_where . ')' .
' )';
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$posts = $wpdb->get_results(
$wpdb->prepare(
"SELECT ID, post_title, post_author
FROM $wpdb->posts
WHERE $post_title_where
post_type = %s AND
post_status IN ( $post_statuses ) AND
$capability_where
ORDER BY post_title LIMIT %d",
$args['post_type'],
absint( $args['count'] )
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$posts = $posts ? $posts : [];
$posts = array_map(
static function ( $post ) {
$post->post_title = wpforms_get_post_title( $post );
unset( $post->post_author );
return $post;
},
$posts
);
wp_cache_set( $key, $posts );
return $posts;
}
/**
* Search pages by search term and return an array containing
* `value` and `label` which is the post ID and post title respectively.
*
* @since 1.7.9
*
* @param string $search_term The search term.
* @param array $args Optional. An array of arguments.
*
* @return array
*/
function wpforms_search_pages_for_dropdown( $search_term, $args = [] ): array {
$search_results = wpforms_search_posts( $search_term, $args );
$result_pages = [];
// Prepare for ChoicesJS render.
foreach ( $search_results as $search_result ) {
$result_pages[] = [
'value' => absint( $search_result->ID ),
'label' => esc_html( $search_result->post_title ),
];
}
return $result_pages;
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Helpers functions for builder.
*
* @since 1.9.6.1
*/
/**
* Outputs a button element to display the connection status for a given connection.
*
* @since 1.9.6.1
*
* @param string $connection_id The unique identifier for the connection.
* @param string $name The name attribute value to be used for the status input field.
* @param bool $is_active Connection status, where true represents active and false represents inactive.
*/
function wpforms_connection_status_button( string $connection_id, string $name, bool $is_active ) {
$label = $is_active ? __( 'Active', 'wpforms-lite' ) : __( 'Inactive', 'wpforms-lite' );
$title = $is_active ? __( 'Deactivate', 'wpforms-lite' ) : __( 'Activate', 'wpforms-lite' );
printf(
'<span class="wpforms-builder-settings-block-status wpforms-badge wpforms-badge-sm wpforms-badge-%1$s wpforms-status-button" title="%5$s" data-active="%2$s" data-connection-id="%6$s">%3$s<i class="wpforms-status-label">%4$s</i></span>',
sanitize_html_class( $is_active ? 'green' : 'silver' ),
esc_attr( $is_active ),
$is_active ? '<i class="fa fa-check"></i>' : '<i class="fa fa-times"></i>',
esc_html( $label ),
esc_attr( $title ),
esc_attr( $connection_id )
);
printf( '<input type="hidden" name="%1$s" id="wpforms-connection-status-%2$s" value="%3$d">', esc_attr( $name ), esc_attr( $connection_id ), absint( $is_active ) );
}

View File

@@ -0,0 +1,602 @@
<?php
/**
* Helper functions to perform various checks across the core plugin and addons.
*
* @since 1.8.0
*/
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpUndefinedNamespaceInspection */
/** @noinspection PhpUndefinedClassInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Tasks\Tasks;
use WPForms\Vendor\TrueBV\Punycode;
/**
* Check if a string is a valid URL.
*
* @since 1.0.0
* @since 1.5.8 Changed the pattern used to validate the URL.
*
* @param string $url Input URL.
*
* @return bool
* @noinspection RegExpUnnecessaryNonCapturingGroup
* @noinspection RegExpRedundantEscape
*/
function wpforms_is_url( $url ): bool {
// The pattern taken from https://gist.github.com/dperini/729294.
// It is the best choice according to the https://mathiasbynens.be/demo/url-regex.
$pattern = '%^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\x{00a1}-\x{ffff}][a-z0-9\x{00a1}-\x{ffff}_-]{0,62})?[a-z0-9\x{00a1}-\x{ffff}]\.)+(?:[a-z\x{00a1}-\x{ffff}]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$%iu';
if ( preg_match( $pattern, trim( $url ) ) ) {
return true;
}
return false;
}
/**
* Verify that an email is valid.
* See the linked RFC.
*
* @see https://www.rfc-editor.org/rfc/inline-errata/rfc3696.html
*
* @since 1.7.3
*
* @param string $email Email address to verify.
*
* @return string|false Returns a valid email address on success, false on failure.
*/
function wpforms_is_email( $email ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
static $punycode;
// Do not allow callables, arrays, and objects.
if ( ! is_scalar( $email ) ) {
return false;
}
// Allow smart tags in the email address.
if ( preg_match( '/{.+?}/', $email ) ) {
return $email;
}
// Email can't be longer than 254 octets,
// otherwise it can't be used to send an email address (limitation in the MAIL and RCPT commands).
// 1 octet = 8 bits = 1 byte.
if ( strlen( $email ) > 254 ) {
return false;
}
$email_arr = explode( '@', $email );
if ( count( $email_arr ) !== 2 ) {
return false;
}
[ $local, $domain ] = $email_arr;
/**
* RFC requires local part to be no longer than 64 octets.
* Punycode library checks for 63 octets.
*
* @link https://github.com/true/php-punycode/blob/master/src/Punycode.php#L182.
*/
if ( strlen( $local ) > 63 ) {
return false;
}
$domain_arr = explode( '.', $domain );
foreach ( $domain_arr as $domain_label ) {
$domain_label = trim( $domain_label );
if ( ! $domain_label ) {
return false;
}
// The RFC says: 'A DNS label may be no more than 63 octets long'.
if ( strlen( $domain_label ) > 63 ) {
return false;
}
}
if ( ! $punycode ) {
$punycode = new Punycode();
}
/**
* The wp_mail() uses phpMailer, which uses is_email() as verification callback.
* For verification, phpMailer sends the email address where the domain part is punycode encoded only.
* We follow here the same principle.
*/
$email_check = $local . '@' . $punycode->encode( $domain );
// Other limitations are checked by the native WordPress function is_email().
return is_email( $email_check ) ? $local . '@' . $domain : false;
}
/**
* Check whether the string is json-encoded.
*
* @since 1.7.5
*
* @param string $value A string.
*
* @return bool
*/
function wpforms_is_json( $value ): bool {
return (
is_string( $value ) &&
is_array( json_decode( $value, true ) ) &&
json_last_error() === JSON_ERROR_NONE
);
}
/**
* 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 ): bool {
$is_amp = false;
// Check for AMP by AMP Project Contributors.
if ( function_exists( 'amp_is_request' ) && amp_is_request() ) {
$is_amp = true;
}
if ( $is_amp && $check_theme_support ) {
$is_amp = current_theme_supports( 'amp' );
}
/**
* Filters AMP flag.
*
* @since 1.4.1
*
* @param bool $is_amp Current page AMP status.
*
* @return bool
*/
return (bool) apply_filters( 'wpforms_is_amp', $is_amp );
}
/**
* 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 = '' ): bool {
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$page = ( (array) ( $_REQUEST['page'] ?? '' ) )[0];
// Check against basic requirements.
if (
strpos( $page, 'wpforms' ) === false ||
! is_admin()
) {
return false;
}
// Check against page slug identifier.
if (
( ! empty( $slug ) && $_REQUEST['page'] !== 'wpforms-' . $slug ) ||
( empty( $slug ) && $_REQUEST['page'] === 'wpforms-builder' )
) {
return false;
}
// Check against sublevel page view.
if (
! empty( $view ) &&
( empty( $_REQUEST['view'] ) || $_REQUEST['view'] !== $view )
) {
return false;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
return true;
}
/**
* Check if a string is empty.
*
* @since 1.5.0
*
* @param string $value String to test.
*
* @return bool
*/
function wpforms_is_empty_string( $value ): bool {
// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
return $value === '';
}
/**
* Determine if the request is a rest API call.
*
* Case #1: After WP_REST_Request initialization
* Case #2: Support "plain" permalink settings
* Case #3: It can happen that WP_Rewrite is not yet initialized,
* so do this (wp-settings.php)
* Case #4: URL Path begins with wp-json/ (your REST prefix)
* Also supports WP installations in sub folders
*
* @since 1.8.8
*
* @return bool True if the request is a REST API call, false if not.
* @author matzeeable
*/
function wpforms_is_rest(): bool {
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
return false;
}
// Case #1.
if ( defined( 'REST_REQUEST' ) && constant( 'REST_REQUEST' ) ) {
return true;
}
// Case #2.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$rest_route = isset( $_GET['rest_route'] ) ?
filter_input( INPUT_GET, 'rest_route', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) :
'';
if ( strpos( trim( $rest_route, '\\/' ), rest_get_url_prefix() ) === 0 ) {
return true;
}
// Case #3.
global $wp_rewrite;
if ( $wp_rewrite === null ) {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_rewrite = new WP_Rewrite();
}
// Case #4.
$current_url = (string) wp_parse_url( add_query_arg( [] ), PHP_URL_PATH );
$rest_url = wp_parse_url( trailingslashit( rest_url() ), PHP_URL_PATH );
return strpos( $current_url, $rest_url ) === 0;
}
/**
* Determine if the request is a WPForms related rest API call.
*
* NOTE: The function shouldn't be used before the `rest_api_init` action.
*
* @since 1.9.6.1
*
* @return bool True if the request is a WPForms related rest API call, false if not.
*/
function wpforms_is_wpforms_rest(): bool {
if ( ! wpforms_is_rest() ) {
return false;
}
$rest_url = wp_parse_url( trailingslashit( rest_url() ) );
$current_url = wp_parse_url( trailingslashit( wpforms_current_url() ) );
$rest_url['path'] = $rest_url['path'] ?? '';
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$is_rest_plain = $rest_url['path'] === '/index.php' && ! empty( $_GET['rest_route'] );
$is_rest_post_name = strpos( $rest_url['path'], '/wp-json/' ) !== false;
if ( $is_rest_plain ) {
$rest_route = sanitize_text_field( wp_unslash( $_GET['rest_route'] ) );
return strpos( $rest_route, '/wpforms/' ) !== false;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( $is_rest_post_name ) {
return strpos( $current_url['path'] ?? '', '/wpforms/' ) !== false;
}
return false;
}
/**
* Determine if the request is WPForms AJAX.
*
* @since 1.8.0
* @since 1.9.1 Added an optional parameter to check for a specific action.
*
* @param string $action Certain AJAX action to check. Optional. Default is empty.
*
* @return bool
*/
function wpforms_is_ajax( string $action = '' ): bool {
if ( ! wp_doing_ajax() ) {
return false;
}
// Make sure the request target is admin-ajax.php.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) && basename( sanitize_text_field( wp_normalize_path( $_SERVER['SCRIPT_FILENAME'] ) ) ) !== 'admin-ajax.php' ) {
return false;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$request_action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : '';
$is_wpforms_action = strpos( $request_action, 'wpforms_' ) === 0;
if ( empty( $action ) ) {
return $is_wpforms_action;
}
return $is_wpforms_action && $action === $request_action;
}
/**
* Determine if request is frontend AJAX.
*
* @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.
* @since 1.8.0 Added clear separation between frontend and admin AJAX requests, see `wpforms_is_admin_ajax()`.
*
* @return bool
*/
function wpforms_is_frontend_ajax(): bool {
if ( wpforms_is_ajax() && ! wpforms_is_admin_ajax() ) {
return true;
}
// Try detecting a frontend AJAX call indirectly by comparing the current action
// with a known frontend actions list in case there's no HTTP referer.
$ref = wp_get_raw_referer();
if ( $ref ) {
return false;
}
$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',
];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : '';
/**
* Allow modifying the list of frontend AJAX actions.
*
* This filter may be running as early as `plugins_loaded` hook.
* Please mind the hook order when using it.
*
* @since 1.6.5
*
* @param array $frontend_actions A list of frontend actions.
*/
$frontend_actions = (array) apply_filters( 'wpforms_is_frontend_ajax_frontend_actions', $frontend_actions );
return in_array( $action, $frontend_actions, true );
}
/**
* Determine if request is admin AJAX.
*
* @since 1.8.0
*
* @return bool
*/
function wpforms_is_admin_ajax(): bool {
if ( ! wpforms_is_ajax() ) {
return false;
}
$ref = wp_get_raw_referer();
if ( ! $ref ) {
return false;
}
$path = wp_parse_url( $ref, PHP_URL_PATH );
$admin_path = wp_parse_url( admin_url(), PHP_URL_PATH );
// Is an admin AJAX call if HTTP referer contain an admin path.
return strpos( $path, $admin_path ) !== false;
}
/**
* Check if Gutenberg is active.
*
* @since 1.6.2
*
* @return bool True if Gutenberg is active.
* @noinspection PhpUndefinedFunctionInspection
*/
function wpforms_is_gutenberg_active(): bool {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
if ( is_plugin_active( 'classic-editor/classic-editor.php' ) ) {
return in_array( get_option( 'classic-editor-replace' ), [ 'no-replace', 'block' ], true );
}
if ( is_plugin_active( 'disable-gutenberg/disable-gutenberg.php' ) ) {
return ! disable_gutenberg();
}
return true;
}
/**
* Check if website support Divi Builder.
*
* @since 1.9.2.3
*
* @return bool True if Divi builder plugin or Divi or Extra theme is active.
*/
function wpforms_is_divi_active(): bool {
if ( function_exists( 'et_divi_builder_init_plugin' ) ) {
return true;
}
$allow_themes = [ 'Divi', 'Extra' ];
$theme_name = get_template();
return in_array( $theme_name, $allow_themes, true );
}
/**
* Determines whether the current request is a WP CLI request.
*
* @since 1.7.6
*
* @return bool
*/
function wpforms_doing_wp_cli(): bool {
return defined( 'WP_CLI' ) && WP_CLI;
}
/**
* Determines whether the Action Scheduler task is executing.
*
* @since 1.9.4
*
* @return bool
*/
function wpforms_doing_scheduled_action(): bool {
return class_exists( Tasks::class ) && Tasks::is_executing();
}
/**
* Determines whether search functionality is enabled for Choices.js elements in the admin area.
*
* @since 1.8.3
*
* @param array $data Data to be displayed in the dropdown.
*
* @return string
*/
function wpforms_choices_js_is_search_enabled( $data ): string {
/**
* Filter max number of items at which no search box is displayed.
*
* @since 1.8.3
*
* @param int $count Max items count.
*/
return count( $data ) >= apply_filters( 'wpforms_choices_js_is_search_enabled_max_limit', 20 ) ? 'true' : 'false';
}
/**
* Check if a form is a template.
*
* @since 1.8.8
*
* @param int|WP_Post $form Form ID or object.
*
* @return bool True if the form is a template.
*/
function wpforms_is_form_template( $form ): bool {
$template_post_type = 'wpforms-template';
if ( $form instanceof WP_Post ) {
return $form->post_type === $template_post_type;
}
return $template_post_type === get_post_type( $form );
}
/**
* Checks if the current screen is using the block editor.
*
* @since 1.8.8
*
* @return bool True if the current screen is using the block editor, false otherwise.
*/
function wpforms_is_block_editor(): bool {
$screen = get_current_screen();
return $screen && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor();
}
/**
* Check for the editor page.
*
* @since 1.9.0
*
* @return bool True if the page is in the editor, false otherwise.
*/
function wpforms_is_editor_page(): bool {
$rest_request = defined( 'REST_REQUEST' ) && REST_REQUEST;
// phpcs:ignore WordPress.Security.NonceVerification
$context = isset( $_REQUEST['context'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['context'] ) ) : '';
$is_gutenberg = $rest_request && $context === 'edit';
return $is_gutenberg || wpforms_is_elementor_editor() || wpforms_is_divi_editor();
}
/**
* Determines whether the current context is the Divi editor.
*
* @since 1.9.4
*
* @return bool
*/
function wpforms_is_divi_editor(): bool {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
return ! empty( $_GET['et_fb'] ) || ( isset( $_POST['action'] ) && sanitize_key( $_POST['action'] ) === 'wpforms_divi_preview' );
}
/**
* Determines whether the current request is being made within the Elementor editor.
*
* @since 1.10.0
*
* @return bool
*/
function wpforms_is_elementor_editor(): bool {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
return ( ! empty( $_POST['action'] ) && $_POST['action'] === 'elementor_ajax' ) || ( ! empty( $_GET['action'] ) && $_GET['action'] === 'elementor' );
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* Helper functions to work with colors.
*
* @since 1.8.0
*/
/**
* Detect if we should use a light or dark color based on the color given.
*
* @link https://docs.woocommerce.com/wc-apidocs/source-function-wc_light_or_dark.html#608-627
*
* @since 1.2.5
*
* @param mixed $color Color value.
* @param string $dark Dark color value (default: '#000000').
* @param string $light Light color value (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;
}
/**
* Convert hex color value to RGB.
*
* @since 1.7.9
* @since 1.8.5 New param and return type were added.
*
* @param string $hex Color value in hex format.
* @param bool $as_string Whether to return the RGB value as a string or array.
*
* @return string|array Color value in RGB format.
*/
function wpforms_hex_to_rgb( $hex, $as_string = true ) {
$hex = ltrim( $hex, '#' );
// Convert shorthand colors to full format, e.g. "FFF" -> "FFFFFF".
$rgb_parts = preg_replace( '~^(.)(.)(.)$~', '$1$1$2$2$3$3', $hex );
$rgb = [];
$rgb['R'] = hexdec( $rgb_parts[0] . $rgb_parts[1] );
$rgb['G'] = hexdec( $rgb_parts[2] . $rgb_parts[3] );
$rgb['B'] = hexdec( $rgb_parts[4] . $rgb_parts[5] );
// Return the RGB value as a string.
if ( $as_string ) {
return sprintf(
'%1$d, %2$d, %3$d',
$rgb['R'],
$rgb['G'],
$rgb['B']
);
}
return $rgb; // This is an array.
}
/**
* Get a lighter color hex value.
*
* @since 1.8.5
*
* @param string $color Color hex value.
* @param int $factor Factor to lighten the color.
*
* @return string Lighter color hex value.
*/
function wpforms_hex_lighter( $color, $factor = 30 ) {
$base = wpforms_hex_to_rgb( $color, false );
// Leave if we can't get the RGB values.
if ( empty( $base ) || count( $base ) !== 3 ) {
return '';
}
$hex = '#';
foreach ( $base as $channel ) {
$amount = 255 - $channel;
$amount = $amount / 100;
$amount = round( floatval( $amount * $factor ) );
$new_decimal = $channel + $amount;
$new_hex_component = dechex( $new_decimal );
if ( strlen( $new_hex_component ) < 2 ) {
$new_hex_component = '0' . $new_hex_component;
}
$hex .= $new_hex_component;
}
return $hex;
}
/**
* Get a darker color hex value.
*
* @since 1.8.5
*
* @param string $color Color hex value.
* @param int $factor Factor to darken the color.
*
* @return string Darker color hex value.
*/
function wpforms_hex_darker( $color, $factor = 30 ) {
$base = wpforms_hex_to_rgb( $color, false );
// Leave if we can't get the RGB values.
if ( empty( $base ) || count( $base ) !== 3 ) {
return '';
}
$hex = '#';
foreach ( $base as $channel ) {
$amount = $channel / 100;
$amount = round( floatval( $amount * $factor ) );
$new_decimal = $channel - $amount;
$new_hex_component = dechex( $new_decimal );
if ( strlen( $new_hex_component ) < 2 ) {
$new_hex_component = '0' . $new_hex_component;
}
$hex .= $new_hex_component;
}
return $hex;
}
/**
* Generate a contrasting color based on the given color.
*
* This function calculates a contrasting color to ensure readability based on the provided color.
*
* @since 1.8.5
*
* @param string $color The original color value. Color hex value.
* @param int $light_factor The factor to lighten the color.
* @param int $dark_factor The factor to darken the color.
*
* @return string The contrasting color value.
*/
function wpforms_generate_contrasting_color( $color, $light_factor = 30, $dark_factor = 30 ) {
$is_dark = wpforms_light_or_dark( $color, 'light', 'dark' ) === 'dark';
return $is_dark ? wpforms_hex_lighter( $color, $light_factor ) : wpforms_hex_darker( $color, $dark_factor );
}

View File

@@ -0,0 +1,445 @@
<?php
/**
* Helper functions to get data presets.
*
* @since 1.8.0
*/
/**
* US States.
*
* @since 1.0.0
*
* @return array
*/
function wpforms_us_states() {
$states = [
'AL' => esc_html__( 'Alabama', 'wpforms-lite' ),
'AK' => esc_html__( 'Alaska', 'wpforms-lite' ),
'AZ' => esc_html__( 'Arizona', 'wpforms-lite' ),
'AR' => esc_html__( 'Arkansas', 'wpforms-lite' ),
'CA' => esc_html__( 'California', 'wpforms-lite' ),
'CO' => esc_html__( 'Colorado', 'wpforms-lite' ),
'CT' => esc_html__( 'Connecticut', 'wpforms-lite' ),
'DE' => esc_html__( 'Delaware', 'wpforms-lite' ),
'DC' => esc_html__( 'District of Columbia', 'wpforms-lite' ),
'FL' => esc_html__( 'Florida', 'wpforms-lite' ),
'GA' => esc_html_x( 'Georgia', 'US State', 'wpforms-lite' ),
'HI' => esc_html__( 'Hawaii', 'wpforms-lite' ),
'ID' => esc_html__( 'Idaho', 'wpforms-lite' ),
'IL' => esc_html__( 'Illinois', 'wpforms-lite' ),
'IN' => esc_html__( 'Indiana', 'wpforms-lite' ),
'IA' => esc_html__( 'Iowa', 'wpforms-lite' ),
'KS' => esc_html__( 'Kansas', 'wpforms-lite' ),
'KY' => esc_html__( 'Kentucky', 'wpforms-lite' ),
'LA' => esc_html__( 'Louisiana', 'wpforms-lite' ),
'ME' => esc_html__( 'Maine', 'wpforms-lite' ),
'MD' => esc_html__( 'Maryland', 'wpforms-lite' ),
'MA' => esc_html__( 'Massachusetts', 'wpforms-lite' ),
'MI' => esc_html__( 'Michigan', 'wpforms-lite' ),
'MN' => esc_html__( 'Minnesota', 'wpforms-lite' ),
'MS' => esc_html__( 'Mississippi', 'wpforms-lite' ),
'MO' => esc_html__( 'Missouri', 'wpforms-lite' ),
'MT' => esc_html__( 'Montana', 'wpforms-lite' ),
'NE' => esc_html__( 'Nebraska', 'wpforms-lite' ),
'NV' => esc_html__( 'Nevada', 'wpforms-lite' ),
'NH' => esc_html__( 'New Hampshire', 'wpforms-lite' ),
'NJ' => esc_html__( 'New Jersey', 'wpforms-lite' ),
'NM' => esc_html__( 'New Mexico', 'wpforms-lite' ),
'NY' => esc_html__( 'New York', 'wpforms-lite' ),
'NC' => esc_html__( 'North Carolina', 'wpforms-lite' ),
'ND' => esc_html__( 'North Dakota', 'wpforms-lite' ),
'OH' => esc_html__( 'Ohio', 'wpforms-lite' ),
'OK' => esc_html__( 'Oklahoma', 'wpforms-lite' ),
'OR' => esc_html__( 'Oregon', 'wpforms-lite' ),
'PA' => esc_html__( 'Pennsylvania', 'wpforms-lite' ),
'RI' => esc_html__( 'Rhode Island', 'wpforms-lite' ),
'SC' => esc_html__( 'South Carolina', 'wpforms-lite' ),
'SD' => esc_html__( 'South Dakota', 'wpforms-lite' ),
'TN' => esc_html__( 'Tennessee', 'wpforms-lite' ),
'TX' => esc_html__( 'Texas', 'wpforms-lite' ),
'UT' => esc_html__( 'Utah', 'wpforms-lite' ),
'VT' => esc_html__( 'Vermont', 'wpforms-lite' ),
'VA' => esc_html__( 'Virginia', 'wpforms-lite' ),
'WA' => esc_html__( 'Washington', 'wpforms-lite' ),
'WV' => esc_html__( 'West Virginia', 'wpforms-lite' ),
'WI' => esc_html__( 'Wisconsin', 'wpforms-lite' ),
'WY' => esc_html__( 'Wyoming', 'wpforms-lite' ),
];
return apply_filters( 'wpforms_us_states', $states );
}
/**
* Countries.
*
* @since 1.0.0
*
* @return array
*/
function wpforms_countries() {
$countries = [
'AF' => esc_html__( 'Afghanistan', 'wpforms-lite' ),
'AX' => esc_html__( 'Åland Islands', 'wpforms-lite' ),
'AL' => esc_html__( 'Albania', 'wpforms-lite' ),
'DZ' => esc_html__( 'Algeria', 'wpforms-lite' ),
'AS' => esc_html__( 'American Samoa', 'wpforms-lite' ),
'AD' => esc_html__( 'Andorra', 'wpforms-lite' ),
'AO' => esc_html__( 'Angola', 'wpforms-lite' ),
'AI' => esc_html__( 'Anguilla', 'wpforms-lite' ),
'AQ' => esc_html__( 'Antarctica', 'wpforms-lite' ),
'AG' => esc_html__( 'Antigua and Barbuda', 'wpforms-lite' ),
'AR' => esc_html__( 'Argentina', 'wpforms-lite' ),
'AM' => esc_html__( 'Armenia', 'wpforms-lite' ),
'AW' => esc_html__( 'Aruba', 'wpforms-lite' ),
'AU' => esc_html__( 'Australia', 'wpforms-lite' ),
'AT' => esc_html__( 'Austria', 'wpforms-lite' ),
'AZ' => esc_html__( 'Azerbaijan', 'wpforms-lite' ),
'BS' => esc_html__( 'Bahamas', 'wpforms-lite' ),
'BH' => esc_html__( 'Bahrain', 'wpforms-lite' ),
'BD' => esc_html__( 'Bangladesh', 'wpforms-lite' ),
'BB' => esc_html__( 'Barbados', 'wpforms-lite' ),
'BY' => esc_html__( 'Belarus', 'wpforms-lite' ),
'BE' => esc_html__( 'Belgium', 'wpforms-lite' ),
'BZ' => esc_html__( 'Belize', 'wpforms-lite' ),
'BJ' => esc_html__( 'Benin', 'wpforms-lite' ),
'BM' => esc_html__( 'Bermuda', 'wpforms-lite' ),
'BT' => esc_html__( 'Bhutan', 'wpforms-lite' ),
'BO' => esc_html__( 'Bolivia (Plurinational State of)', 'wpforms-lite' ),
'BQ' => esc_html__( 'Bonaire, Saint Eustatius and Saba', 'wpforms-lite' ),
'BA' => esc_html__( 'Bosnia and Herzegovina', 'wpforms-lite' ),
'BW' => esc_html__( 'Botswana', 'wpforms-lite' ),
'BV' => esc_html__( 'Bouvet Island', 'wpforms-lite' ),
'BR' => esc_html__( 'Brazil', 'wpforms-lite' ),
'IO' => esc_html__( 'British Indian Ocean Territory', 'wpforms-lite' ),
'BN' => esc_html__( 'Brunei Darussalam', 'wpforms-lite' ),
'BG' => esc_html__( 'Bulgaria', 'wpforms-lite' ),
'BF' => esc_html__( 'Burkina Faso', 'wpforms-lite' ),
'BI' => esc_html__( 'Burundi', 'wpforms-lite' ),
'CV' => esc_html__( 'Cabo Verde', 'wpforms-lite' ),
'KH' => esc_html__( 'Cambodia', 'wpforms-lite' ),
'CM' => esc_html__( 'Cameroon', 'wpforms-lite' ),
'CA' => esc_html__( 'Canada', 'wpforms-lite' ),
'KY' => esc_html__( 'Cayman Islands', 'wpforms-lite' ),
'CF' => esc_html__( 'Central African Republic', 'wpforms-lite' ),
'TD' => esc_html__( 'Chad', 'wpforms-lite' ),
'CL' => esc_html__( 'Chile', 'wpforms-lite' ),
'CN' => esc_html__( 'China', 'wpforms-lite' ),
'CX' => esc_html__( 'Christmas Island', 'wpforms-lite' ),
'CC' => esc_html__( 'Cocos (Keeling) Islands', 'wpforms-lite' ),
'CO' => esc_html__( 'Colombia', 'wpforms-lite' ),
'KM' => esc_html__( 'Comoros', 'wpforms-lite' ),
'CG' => esc_html__( 'Congo', 'wpforms-lite' ),
'CD' => esc_html__( 'Congo (Democratic Republic of the)', 'wpforms-lite' ),
'CK' => esc_html__( 'Cook Islands', 'wpforms-lite' ),
'CR' => esc_html__( 'Costa Rica', 'wpforms-lite' ),
'CI' => esc_html__( 'Côte d\'Ivoire', 'wpforms-lite' ),
'HR' => esc_html__( 'Croatia', 'wpforms-lite' ),
'CU' => esc_html__( 'Cuba', 'wpforms-lite' ),
'CW' => esc_html__( 'Curaçao', 'wpforms-lite' ),
'CY' => esc_html__( 'Cyprus', 'wpforms-lite' ),
'CZ' => esc_html__( 'Czech Republic', 'wpforms-lite' ),
'DK' => esc_html__( 'Denmark', 'wpforms-lite' ),
'DJ' => esc_html__( 'Djibouti', 'wpforms-lite' ),
'DM' => esc_html__( 'Dominica', 'wpforms-lite' ),
'DO' => esc_html__( 'Dominican Republic', 'wpforms-lite' ),
'EC' => esc_html__( 'Ecuador', 'wpforms-lite' ),
'EG' => esc_html__( 'Egypt', 'wpforms-lite' ),
'SV' => esc_html__( 'El Salvador', 'wpforms-lite' ),
'GQ' => esc_html__( 'Equatorial Guinea', 'wpforms-lite' ),
'ER' => esc_html__( 'Eritrea', 'wpforms-lite' ),
'EE' => esc_html__( 'Estonia', 'wpforms-lite' ),
'ET' => esc_html__( 'Ethiopia', 'wpforms-lite' ),
'FK' => esc_html__( 'Falkland Islands (Malvinas)', 'wpforms-lite' ),
'FO' => esc_html__( 'Faroe Islands', 'wpforms-lite' ),
'FJ' => esc_html__( 'Fiji', 'wpforms-lite' ),
'FI' => esc_html__( 'Finland', 'wpforms-lite' ),
'FR' => esc_html__( 'France', 'wpforms-lite' ),
'GF' => esc_html__( 'French Guiana', 'wpforms-lite' ),
'PF' => esc_html__( 'French Polynesia', 'wpforms-lite' ),
'TF' => esc_html__( 'French Southern Territories', 'wpforms-lite' ),
'GA' => esc_html__( 'Gabon', 'wpforms-lite' ),
'GM' => esc_html__( 'Gambia', 'wpforms-lite' ),
'GE' => esc_html_x( 'Georgia', 'Country', 'wpforms-lite' ),
'DE' => esc_html__( 'Germany', 'wpforms-lite' ),
'GH' => esc_html__( 'Ghana', 'wpforms-lite' ),
'GI' => esc_html__( 'Gibraltar', 'wpforms-lite' ),
'GR' => esc_html__( 'Greece', 'wpforms-lite' ),
'GL' => esc_html__( 'Greenland', 'wpforms-lite' ),
'GD' => esc_html__( 'Grenada', 'wpforms-lite' ),
'GP' => esc_html__( 'Guadeloupe', 'wpforms-lite' ),
'GU' => esc_html__( 'Guam', 'wpforms-lite' ),
'GT' => esc_html__( 'Guatemala', 'wpforms-lite' ),
'GG' => esc_html__( 'Guernsey', 'wpforms-lite' ),
'GN' => esc_html__( 'Guinea', 'wpforms-lite' ),
'GW' => esc_html__( 'Guinea-Bissau', 'wpforms-lite' ),
'GY' => esc_html__( 'Guyana', 'wpforms-lite' ),
'HT' => esc_html__( 'Haiti', 'wpforms-lite' ),
'HM' => esc_html__( 'Heard Island and McDonald Islands', 'wpforms-lite' ),
'HN' => esc_html__( 'Honduras', 'wpforms-lite' ),
'HK' => esc_html__( 'Hong Kong', 'wpforms-lite' ),
'HU' => esc_html__( 'Hungary', 'wpforms-lite' ),
'IS' => esc_html__( 'Iceland', 'wpforms-lite' ),
'IN' => esc_html__( 'India', 'wpforms-lite' ),
'ID' => esc_html__( 'Indonesia', 'wpforms-lite' ),
'IR' => esc_html__( 'Iran (Islamic Republic of)', 'wpforms-lite' ),
'IQ' => esc_html__( 'Iraq', 'wpforms-lite' ),
'IE' => esc_html__( 'Ireland (Republic of)', 'wpforms-lite' ),
'IM' => esc_html__( 'Isle of Man', 'wpforms-lite' ),
'IL' => esc_html__( 'Israel', 'wpforms-lite' ),
'IT' => esc_html__( 'Italy', 'wpforms-lite' ),
'JM' => esc_html__( 'Jamaica', 'wpforms-lite' ),
'JP' => esc_html__( 'Japan', 'wpforms-lite' ),
'JE' => esc_html__( 'Jersey', 'wpforms-lite' ),
'JO' => esc_html__( 'Jordan', 'wpforms-lite' ),
'KZ' => esc_html__( 'Kazakhstan', 'wpforms-lite' ),
'KE' => esc_html__( 'Kenya', 'wpforms-lite' ),
'KI' => esc_html__( 'Kiribati', 'wpforms-lite' ),
'KP' => esc_html__( 'Korea (Democratic People\'s Republic of)', 'wpforms-lite' ),
'KR' => esc_html__( 'Korea (Republic of)', 'wpforms-lite' ),
'XK' => esc_html__( 'Kosovo', 'wpforms-lite' ),
'KW' => esc_html__( 'Kuwait', 'wpforms-lite' ),
'KG' => esc_html__( 'Kyrgyzstan', 'wpforms-lite' ),
'LA' => esc_html__( 'Lao People\'s Democratic Republic', 'wpforms-lite' ),
'LV' => esc_html__( 'Latvia', 'wpforms-lite' ),
'LB' => esc_html__( 'Lebanon', 'wpforms-lite' ),
'LS' => esc_html__( 'Lesotho', 'wpforms-lite' ),
'LR' => esc_html__( 'Liberia', 'wpforms-lite' ),
'LY' => esc_html__( 'Libya', 'wpforms-lite' ),
'LI' => esc_html__( 'Liechtenstein', 'wpforms-lite' ),
'LT' => esc_html__( 'Lithuania', 'wpforms-lite' ),
'LU' => esc_html__( 'Luxembourg', 'wpforms-lite' ),
'MO' => esc_html__( 'Macao', 'wpforms-lite' ),
'MK' => esc_html__( 'North Macedonia (Republic of)', 'wpforms-lite' ),
'MG' => esc_html__( 'Madagascar', 'wpforms-lite' ),
'MW' => esc_html__( 'Malawi', 'wpforms-lite' ),
'MY' => esc_html__( 'Malaysia', 'wpforms-lite' ),
'MV' => esc_html__( 'Maldives', 'wpforms-lite' ),
'ML' => esc_html__( 'Mali', 'wpforms-lite' ),
'MT' => esc_html__( 'Malta', 'wpforms-lite' ),
'MH' => esc_html__( 'Marshall Islands', 'wpforms-lite' ),
'MQ' => esc_html__( 'Martinique', 'wpforms-lite' ),
'MR' => esc_html__( 'Mauritania', 'wpforms-lite' ),
'MU' => esc_html__( 'Mauritius', 'wpforms-lite' ),
'YT' => esc_html__( 'Mayotte', 'wpforms-lite' ),
'MX' => esc_html__( 'Mexico', 'wpforms-lite' ),
'FM' => esc_html__( 'Micronesia (Federated States of)', 'wpforms-lite' ),
'MD' => esc_html__( 'Moldova (Republic of)', 'wpforms-lite' ),
'MC' => esc_html__( 'Monaco', 'wpforms-lite' ),
'MN' => esc_html__( 'Mongolia', 'wpforms-lite' ),
'ME' => esc_html__( 'Montenegro', 'wpforms-lite' ),
'MS' => esc_html__( 'Montserrat', 'wpforms-lite' ),
'MA' => esc_html__( 'Morocco', 'wpforms-lite' ),
'MZ' => esc_html__( 'Mozambique', 'wpforms-lite' ),
'MM' => esc_html__( 'Myanmar', 'wpforms-lite' ),
'NA' => esc_html__( 'Namibia', 'wpforms-lite' ),
'NR' => esc_html__( 'Nauru', 'wpforms-lite' ),
'NP' => esc_html__( 'Nepal', 'wpforms-lite' ),
'NL' => esc_html__( 'Netherlands', 'wpforms-lite' ),
'NC' => esc_html__( 'New Caledonia', 'wpforms-lite' ),
'NZ' => esc_html__( 'New Zealand', 'wpforms-lite' ),
'NI' => esc_html__( 'Nicaragua', 'wpforms-lite' ),
'NE' => esc_html__( 'Niger', 'wpforms-lite' ),
'NG' => esc_html__( 'Nigeria', 'wpforms-lite' ),
'NU' => esc_html__( 'Niue', 'wpforms-lite' ),
'NF' => esc_html__( 'Norfolk Island', 'wpforms-lite' ),
'MP' => esc_html__( 'Northern Mariana Islands', 'wpforms-lite' ),
'NO' => esc_html__( 'Norway', 'wpforms-lite' ),
'OM' => esc_html__( 'Oman', 'wpforms-lite' ),
'PK' => esc_html__( 'Pakistan', 'wpforms-lite' ),
'PW' => esc_html__( 'Palau', 'wpforms-lite' ),
'PS' => esc_html__( 'Palestine (State of)', 'wpforms-lite' ),
'PA' => esc_html__( 'Panama', 'wpforms-lite' ),
'PG' => esc_html__( 'Papua New Guinea', 'wpforms-lite' ),
'PY' => esc_html__( 'Paraguay', 'wpforms-lite' ),
'PE' => esc_html__( 'Peru', 'wpforms-lite' ),
'PH' => esc_html__( 'Philippines', 'wpforms-lite' ),
'PN' => esc_html__( 'Pitcairn', 'wpforms-lite' ),
'PL' => esc_html__( 'Poland', 'wpforms-lite' ),
'PT' => esc_html__( 'Portugal', 'wpforms-lite' ),
'PR' => esc_html__( 'Puerto Rico', 'wpforms-lite' ),
'QA' => esc_html__( 'Qatar', 'wpforms-lite' ),
'RE' => esc_html__( 'Réunion', 'wpforms-lite' ),
'RO' => esc_html__( 'Romania', 'wpforms-lite' ),
'RU' => esc_html__( 'Russian Federation', 'wpforms-lite' ),
'RW' => esc_html__( 'Rwanda', 'wpforms-lite' ),
'BL' => esc_html__( 'Saint Barthélemy', 'wpforms-lite' ),
'SH' => esc_html__( 'Saint Helena, Ascension and Tristan da Cunha', 'wpforms-lite' ),
'KN' => esc_html__( 'Saint Kitts and Nevis', 'wpforms-lite' ),
'LC' => esc_html__( 'Saint Lucia', 'wpforms-lite' ),
'MF' => esc_html__( 'Saint Martin (French part)', 'wpforms-lite' ),
'PM' => esc_html__( 'Saint Pierre and Miquelon', 'wpforms-lite' ),
'VC' => esc_html__( 'Saint Vincent and the Grenadines', 'wpforms-lite' ),
'WS' => esc_html__( 'Samoa', 'wpforms-lite' ),
'SM' => esc_html__( 'San Marino', 'wpforms-lite' ),
'ST' => esc_html__( 'Sao Tome and Principe', 'wpforms-lite' ),
'SA' => esc_html__( 'Saudi Arabia', 'wpforms-lite' ),
'SN' => esc_html__( 'Senegal', 'wpforms-lite' ),
'RS' => esc_html__( 'Serbia', 'wpforms-lite' ),
'SC' => esc_html__( 'Seychelles', 'wpforms-lite' ),
'SL' => esc_html__( 'Sierra Leone', 'wpforms-lite' ),
'SG' => esc_html__( 'Singapore', 'wpforms-lite' ),
'SX' => esc_html__( 'Sint Maarten (Dutch part)', 'wpforms-lite' ),
'SK' => esc_html__( 'Slovakia', 'wpforms-lite' ),
'SI' => esc_html__( 'Slovenia', 'wpforms-lite' ),
'SB' => esc_html__( 'Solomon Islands', 'wpforms-lite' ),
'SO' => esc_html__( 'Somalia', 'wpforms-lite' ),
'ZA' => esc_html__( 'South Africa', 'wpforms-lite' ),
'GS' => esc_html__( 'South Georgia and the South Sandwich Islands', 'wpforms-lite' ),
'SS' => esc_html__( 'South Sudan', 'wpforms-lite' ),
'ES' => esc_html__( 'Spain', 'wpforms-lite' ),
'LK' => esc_html__( 'Sri Lanka', 'wpforms-lite' ),
'SD' => esc_html__( 'Sudan', 'wpforms-lite' ),
'SR' => esc_html__( 'Suriname', 'wpforms-lite' ),
'SJ' => esc_html__( 'Svalbard and Jan Mayen', 'wpforms-lite' ),
'SZ' => esc_html__( 'Eswatini (Kingdom of)', 'wpforms-lite' ),
'SE' => esc_html__( 'Sweden', 'wpforms-lite' ),
'CH' => esc_html__( 'Switzerland', 'wpforms-lite' ),
'SY' => esc_html__( 'Syrian Arab Republic', 'wpforms-lite' ),
'TW' => esc_html__( 'Taiwan, Republic of China', 'wpforms-lite' ),
'TJ' => esc_html__( 'Tajikistan', 'wpforms-lite' ),
'TZ' => esc_html__( 'Tanzania (United Republic of)', 'wpforms-lite' ),
'TH' => esc_html__( 'Thailand', 'wpforms-lite' ),
'TL' => esc_html__( 'Timor-Leste', 'wpforms-lite' ),
'TG' => esc_html__( 'Togo', 'wpforms-lite' ),
'TK' => esc_html__( 'Tokelau', 'wpforms-lite' ),
'TO' => esc_html__( 'Tonga', 'wpforms-lite' ),
'TT' => esc_html__( 'Trinidad and Tobago', 'wpforms-lite' ),
'TN' => esc_html__( 'Tunisia', 'wpforms-lite' ),
'TR' => esc_html__( 'Türkiye', 'wpforms-lite' ),
'TM' => esc_html__( 'Turkmenistan', 'wpforms-lite' ),
'TC' => esc_html__( 'Turks and Caicos Islands', 'wpforms-lite' ),
'TV' => esc_html__( 'Tuvalu', 'wpforms-lite' ),
'UG' => esc_html__( 'Uganda', 'wpforms-lite' ),
'UA' => esc_html__( 'Ukraine', 'wpforms-lite' ),
'AE' => esc_html__( 'United Arab Emirates', 'wpforms-lite' ),
'GB' => esc_html__( 'United Kingdom of Great Britain and Northern Ireland', 'wpforms-lite' ),
'US' => esc_html__( 'United States of America', 'wpforms-lite' ),
'UM' => esc_html__( 'United States Minor Outlying Islands', 'wpforms-lite' ),
'UY' => esc_html__( 'Uruguay', 'wpforms-lite' ),
'UZ' => esc_html__( 'Uzbekistan', 'wpforms-lite' ),
'VU' => esc_html__( 'Vanuatu', 'wpforms-lite' ),
'VA' => esc_html__( 'Vatican City State', 'wpforms-lite' ),
'VE' => esc_html__( 'Venezuela (Bolivarian Republic of)', 'wpforms-lite' ),
'VN' => esc_html__( 'Vietnam', 'wpforms-lite' ),
'VG' => esc_html__( 'Virgin Islands (British)', 'wpforms-lite' ),
'VI' => esc_html__( 'Virgin Islands (U.S.)', 'wpforms-lite' ),
'WF' => esc_html__( 'Wallis and Futuna', 'wpforms-lite' ),
'EH' => esc_html__( 'Western Sahara', 'wpforms-lite' ),
'YE' => esc_html__( 'Yemen', 'wpforms-lite' ),
'ZM' => esc_html__( 'Zambia', 'wpforms-lite' ),
'ZW' => esc_html__( 'Zimbabwe', 'wpforms-lite' ),
];
return apply_filters( 'wpforms_countries', $countries );
}
/**
* Calendar Months.
*
* @since 1.3.7
*
* @return array
*/
function wpforms_months() {
$months = [
'Jan' => esc_html__( 'January', 'wpforms-lite' ),
'Feb' => esc_html__( 'February', 'wpforms-lite' ),
'Mar' => esc_html__( 'March', 'wpforms-lite' ),
'Apr' => esc_html__( 'April', 'wpforms-lite' ),
'May' => esc_html__( 'May', 'wpforms-lite' ),
'Jun' => esc_html__( 'June', 'wpforms-lite' ),
'Jul' => esc_html__( 'July', 'wpforms-lite' ),
'Aug' => esc_html__( 'August', 'wpforms-lite' ),
'Sep' => esc_html__( 'September', 'wpforms-lite' ),
'Oct' => esc_html__( 'October', 'wpforms-lite' ),
'Nov' => esc_html__( 'November', 'wpforms-lite' ),
'Dec' => esc_html__( 'December', 'wpforms-lite' ),
];
return apply_filters( 'wpforms_months', $months );
}
/**
* Calendar Days.
*
* @since 1.3.7
*
* @return array
*/
function wpforms_days() {
$days = [
'Sun' => esc_html__( 'Sunday', 'wpforms-lite' ),
'Mon' => esc_html__( 'Monday', 'wpforms-lite' ),
'Tue' => esc_html__( 'Tuesday', 'wpforms-lite' ),
'Wed' => esc_html__( 'Wednesday', 'wpforms-lite' ),
'Thu' => esc_html__( 'Thursday', 'wpforms-lite' ),
'Fri' => esc_html__( 'Friday', 'wpforms-lite' ),
'Sat' => esc_html__( 'Saturday', 'wpforms-lite' ),
];
return apply_filters( 'wpforms_days', $days );
}
/**
* 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',
'Y/m/d' => 'Y/m/d',
'm.d.Y' => 'm.d.Y',
'd.m.Y' => 'd.m.Y',
'Y.m.d' => 'Y.m.d',
'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',
]
);
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Helper functions to work with dates, time and timezones.
*
* @since 1.8.0
*/
/**
* 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 ( is_numeric( $date ) ) {
$date = (int) $date;
}
if ( is_string( $date ) ) {
$date = strtotime( $date );
}
if ( $gmt_offset ) {
$date += (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
}
if ( $format === '' ) {
return sprintf( /* translators: %1$s - formatted date, %2$s - formatted time. */
__( '%1$s at %2$s', 'wpforms-lite' ),
date_i18n( get_option( 'date_format' ), $date ),
date_i18n( get_option( 'time_format' ), $date )
);
}
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 = (string) get_option( 'date_format', 'M j, Y' );
}
return wpforms_datetime_format( $date, $format, $gmt_offset );
}
/**
* Return time formatted as expected.
*
* @since 1.8.5
*
* @param string|int $date Date to format.
* @param string $format Optional. Format for the time.
* @param bool $gmt_offset Optional. GTM offset.
*
* @return string
*/
function wpforms_time_format( $date, $format = '', $gmt_offset = false ) {
if ( $format === '' ) {
$format = (string) get_option( 'time_format', 'g:ia' );
}
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;
}

View File

@@ -0,0 +1,227 @@
<?php
/**
* Helper logging and debug functions.
*
* @since 1.8.0
*/
use WPForms\Logger\Log;
/**
* Check whether the plugin works in a debug mode.
*
* @since 1.2.3
*
* @return bool
*/
function wpforms_debug(): bool {
$debug = false;
if ( ( defined( 'WPFORMS_DEBUG' ) && true === WPFORMS_DEBUG ) && is_super_admin() ) {
$debug = true;
}
/**
* Filters wpforms_debug status.
*
* @since 1.2.3
*
* @param bool $debug WPForms debug status.
*/
return (bool) 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 $do_echo Whether to print or return. The default is to print.
*
* @return string|void
*/
function wpforms_debug_data( $data, bool $do_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(
'<style>
.wpforms-debug {
line-height: 0;
}
.wpforms-debug textarea {
background: #f6f7f7 !important;
margin: 20px 0 0 0;
width: 100%%;
height: 500px;
font-size: 12px;
font-family: Consolas, Menlo, Monaco, monospace;
direction: ltr;
unicode-bidi: embed;
line-height: 1.4;
padding: 10px;
border-radius: 0;
border-color: #c3c4c7;
box-sizing: border-box;
}
.postbox .wpforms-debug {
padding: 6px;
}
.postbox .wpforms-debug:not(:first-of-type) {
padding-top: 0;
}
.postbox .wpforms-debug textarea {
margin-top: 0 !important;
}
</style>
<div class="wpforms-debug">
<textarea readonly>=================== WPFORMS DEBUG ===================%s</textarea>
</div>',
"\n\n" . esc_html( $data )
);
/**
* Allow developers to determine whether the debug data should be displayed.
* Works only in debug mode (`WPFORMS_DEBUG` constant is `true`).
*
* @since 1.6.8
*
* @param bool $allow_display True by default.
*/
$allow_display = apply_filters( 'wpforms_debug_data_allow_display', true );
if ( $do_echo && $allow_display ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $output;
} else {
return $output;
}
}
/**
* Log helper.
*
* @since 1.0.0
*
* @param string $title Title of a log message.
* @param mixed $message Content of a log message.
* @param array $args Expected keys: type, form_id, meta, parent, force.
*/
function wpforms_log( $title = '', $message = '', $args = [] ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// Skip if logs disabled in Tools -> Logs.
if ( empty( $args['force'] ) && ! wpforms_setting( 'logs-enable' ) ) {
return;
}
// Require log title.
if ( empty( $title ) ) {
return;
}
/**
* Compare error levels to determine if we should log.
* Current supported levels:
* - Conditional Logic (conditional_logic)
* - Entries (entry)
* - Errors (error)
* - Payments (payment)
* - Providers (provider)
* - Security (security)
* - Spam (spam)
* - Log (log)
*/
$types = ! empty( $args['type'] ) ? (array) $args['type'] : [ 'error' ];
// Skip invalid logs types.
$log_types = Log::get_log_types();
foreach ( $types as $key => $type ) {
if ( ! isset( $log_types[ $type ] ) ) {
unset( $types[ $key ] );
}
}
if ( empty( $types ) ) {
return;
}
/**
* Filter log message.
*
* @since 1.8.2
*
* @param mixed $message Log message.
* @param string $title Log title.
* @param array $args Log arguments.
*/
$message = apply_filters( 'wpforms_log_message', $message, $title, $args );
// Make arrays and objects look nice.
if ( is_array( $message ) || is_object( $message ) ) {
$message = '<pre>' . esc_html( print_r( $message, true ) ) . '</pre>'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
}
// Filter logs types from Tools -> Logs page.
$logs_types = wpforms_setting( 'logs-types' );
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->ID ?? 0;
$current_user_roles = $current_user->roles ?? [];
$logs_user_roles = wpforms_setting( 'logs-user-roles' );
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' );
if ( $logs_users && ! in_array( $current_user_id, $logs_users, true ) ) {
return;
}
$log = wpforms()->obj( 'log' );
if ( ! $log || ! 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
);
}
/**
* Wrapper for set_time_limit to see if it is enabled.
*
* @since 1.6.4
*
* @param int $limit Time limit.
*/
function wpforms_set_time_limit( $limit = 0 ) {
if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) {
@set_time_limit( $limit ); // @codingStandardsIgnoreLine
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Helpers functions for the Education pages.
*
* @since 1.8.2.2
*/
/**
* Get the button.
*
* @since 1.8.2.2
* @since 1.9.6.1 Add $license_level parameter.
*
* @param string $action Action to perform.
* @param bool $plugin_allow Is plugin allowed.
* @param string $path Plugin file.
* @param string $url URL for download plugin.
* @param array $utm UTM parameters.
* @param string $license_level License level.
*/
function wpforms_edu_get_button( $action, $plugin_allow, $path, $url, $utm, $license_level = '' ) {
// If the user is not allowed to use the plugin, show the upgrade button.
if ( ! $plugin_allow ) {
wpforms_edu_get_upgrade_button( $utm, [], $license_level );
return;
}
$status = 'inactive';
$data_plugin = $path;
$title = esc_html__( 'Activate', 'wpforms-lite' );
$can_install = wpforms_can_install( 'addon' );
if ( $action === 'install' ) {
$status = 'download';
$data_plugin = $url;
$title = esc_html__( 'Install & Activate', 'wpforms-lite' );
}
?>
<?php if ( $action === 'install' && ! $can_install ) : ?>
<div class="wpforms-notice wpforms-error">
<p><?php esc_html_e( 'Plugin installation is disabled for this site.', 'wpforms-lite' ); ?></p>
</div>
<?php else : ?>
<button
class="status-<?php echo esc_attr( $status ); ?> wpforms-btn wpforms-btn-lg wpforms-btn-blue wpforms-education-toggle-plugin-btn"
data-type="addon"
data-action="<?php echo esc_attr( $action ); ?>"
data-plugin="<?php echo esc_attr( $data_plugin ); ?>">
<i></i><?php echo esc_html( $title ); ?>
<?php endif; ?>
<?php
}
/**
* Get the upgrade button.
*
* @since 1.8.2.2
* @since 1.9.6.1 Add $license_level parameter.
*
* @param array $utm UTM parameters.
* @param array $classes Classes.
* @param string $license_level License level.
*/
function wpforms_edu_get_upgrade_button( $utm, $classes = [], $license_level = '' ) {
$utm_medium = isset( $utm['medium'] ) ? $utm['medium'] : '';
$utm_content = isset( $utm['content'] ) ? $utm['content'] : '';
$default_classes = [ 'wpforms-btn', 'wpforms-btn-lg', 'wpforms-btn-orange' ];
$default_classes[] = ! wpforms()->is_pro() ? 'wpforms-upgrade-modal' : '';
$btn_classes = array_merge( $default_classes, (array) $classes );
$upgrade_button_label = esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' );
if ( ! empty( $license_level ) && is_string( $license_level ) ) {
$upgrade_button_label = sprintf(
/* translators: %s: License name. */
esc_html__( 'Upgrade to WPForms %s', 'wpforms-lite' ),
esc_html( ucfirst( $license_level ) )
);
}
?>
<a
href="<?php echo esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content ) ); ?>"
target="_blank"
rel="noopener noreferrer"
class="<?php echo esc_attr( implode( ' ', array_filter( $btn_classes ) ) ); ?>">
<?php echo esc_html( $upgrade_button_label ); ?>
</a>
<?php
}

View File

@@ -0,0 +1,633 @@
<?php
/**
* Helper functions to clean and sanitize data, escape it and prepare the output.
*
* @since 1.8.0
*/
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpUndefinedNamespaceInspection */
/** @noinspection PhpUndefinedClassInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Helpers\Templates;
use WPForms\Vendor\HTMLPurifier;
use WPForms\Vendor\HTMLPurifier_Config;
use WPForms\Helpers\File;
/**
* Decode special characters, both alpha- (<) and numeric-based (').
* Sanitize recursively, preserve new lines.
* Handle all the possible mixed variations of < and `&lt;` 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 &lt; etc.
* This iteration may do nothing when the string already comes with &lt; and &gt; only.
*/
$string = wpforms_sanitize_text_deeply( $string, true );
// Now we need to convert the string without tags: &lt; 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 );
}
/**
* Sanitize key, primarily used for looking up options.
*
* @since 1.3.9
*
* @param string $key Key name.
*
* @return string
*/
function wpforms_sanitize_key( $key = '' ) {
return preg_replace( '/[^a-zA-Z0-9_\-\.\:\/]/', '', $key );
}
/**
* Sanitize hex color.
*
* @since 1.2.1
*
* @param string $color Color value.
*
* @return string
*/
function wpforms_sanitize_hex_color( $color ) {
if ( empty( $color ) ) {
return '';
}
// 3 or 6 hex digits, or the empty string.
if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
return $color;
}
return '';
}
/**
* Sanitize error message, primarily used during form frontend output.
*
* @since 1.3.7
* @since 1.7.6 Expand list of allowed HTML tags and attributes.
*
* @param string $error Error message.
*
* @return string
*/
function wpforms_sanitize_error( $error = '' ) {
$allow = [
'a' => [
'href' => [],
'title' => [],
'target' => [],
'rel' => [],
],
'br' => [],
'em' => [],
'strong' => [],
'del' => [],
'p' => [
'style' => [],
],
'blockquote' => [],
'ul' => [],
'ol' => [],
'li' => [],
'span' => [
'style' => [],
],
];
return wp_kses( $error, $allow );
}
/**
* Sanitize a string, that can be a multiline.
*
* @uses wpforms_sanitize_text_deeply()
*
* @since 1.4.1
*
* @param string $string String to deeply sanitize.
*
* @return string Sanitized string, or empty string if not a string provided.
*/
function wpforms_sanitize_textarea_field( $string ) {
return wpforms_sanitize_text_deeply( $string, true );
}
/**
* Deeply sanitize the string, preserve newlines if needed.
* Prevent maliciously prepared strings from containing HTML tags.
*
* @since 1.6.0
*
* @param string $string String to deeply sanitize.
* @param bool $keep_newlines Whether to keep newlines. Default: false.
*
* @return string Sanitized string, or empty string if not a string provided.
*/
function wpforms_sanitize_text_deeply( $string, $keep_newlines = false ) {
if ( is_object( $string ) || is_array( $string ) ) {
return '';
}
$string = (string) $string;
$keep_newlines = (bool) $keep_newlines;
$new_value = _sanitize_text_fields( $string, $keep_newlines );
if ( strlen( $new_value ) !== strlen( $string ) ) {
$new_value = wpforms_sanitize_text_deeply( $new_value, $keep_newlines );
}
return $new_value;
}
/**
* Sanitize an HTML string with a set of allowed HTML tags.
*
* @since 1.7.0
*
* @param string $value String to sanitize.
*
* @return string Sanitized string.
*/
function wpforms_sanitize_richtext_field( $value ) {
$count = 1;
$value = convert_invalid_entities( $value );
// Remove 'script' and 'style' tags recursively.
while ( $count ) {
$value = preg_replace( '@<(script|style)[^>]*?>.*?</\\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
* @since 1.9.1 Removed new lines after adding paragraphs and breaks tags.
*
* @param string $value Text to escape.
*
* @return string Escaped text.
*/
function wpforms_esc_richtext_field( $value ) {
$value = wpautop( wpforms_sanitize_richtext_field( $value ) );
return trim( str_replace( [ "\r\n", "\r", "\n" ], '', $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',
'tfoot',
'th',
'tr',
'td',
'abbr',
'address',
'sub',
'sup',
'ins',
'figure',
'figcaption',
'caption',
'div',
],
array_fill_keys(
[ 'align', 'class', 'id', 'style', 'src', 'rel', 'alt', 'href', 'target', 'width', 'height', 'title', 'cite', 'start', 'reversed', 'datetime', 'scope', 'colspan', 'rowspan' ],
[]
)
);
/**
* 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 Data to sanitize.
*
* @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 ) );
}
/**
* Format, sanitize, and return/echo HTML element ID, classes, attributes,
* and data attributes.
*
* @since 1.3.7
*
* @param string $id HTML id attribute value.
* @param array $class A list of classnames for the class attribute.
* @param array $datas Data attributes.
* @param array $atts Any additional HTML attributes and their values.
* @param bool $echo Whether to echo the output or just return it. Defaults to return.
*
* @return string|void
*/
function wpforms_html_attributes( $id = '', $class = [], $datas = [], $atts = [], $echo = false ) {
$id = trim( $id );
$parts = [];
if ( ! empty( $id ) ) {
$id = sanitize_html_class( $id );
if ( ! empty( $id ) ) {
$parts[] = 'id="' . $id . '"';
}
}
if ( ! empty( $class ) ) {
$class = wpforms_sanitize_classes( $class, true );
if ( ! empty( $class ) ) {
$parts[] = 'class="' . $class . '"';
}
}
if ( ! empty( $datas ) ) {
foreach ( $datas as $data => $val ) {
$parts[] = 'data-' . sanitize_html_class( $data ) . '="' . esc_attr( $val ) . '"';
}
}
if ( ! empty( $atts ) ) {
foreach ( $atts as $att => $val ) {
if ( '0' === (string) $val || ! empty( $val ) ) {
if ( $att[0] === '[' ) {
// Handle special case for bound attributes in AMP.
$escaped_att = '[' . sanitize_html_class( trim( $att, '[]' ) ) . ']';
} else {
$escaped_att = sanitize_html_class( $att );
}
$parts[] = $escaped_att . '="' . esc_attr( $val ) . '"';
}
}
}
$output = implode( ' ', $parts );
if ( $echo ) {
echo trim( $output ); // phpcs:ignore
} else {
return trim( $output );
}
}
/**
* Sanitize string of CSS classes.
*
* @since 1.2.1
*
* @param array|string $classes CSS classes.
* @param bool $convert True will convert strings to array and vice versa.
*
* @return string|array
*/
function wpforms_sanitize_classes( $classes, $convert = false ) {
$array = is_array( $classes );
$css = [];
if ( ! empty( $classes ) ) {
if ( ! $array ) {
$classes = explode( ' ', trim( $classes ) );
}
foreach ( array_unique( $classes ) as $class ) {
if ( ! empty( $class ) ) {
$css[] = sanitize_html_class( $class );
}
}
}
if ( $array ) {
return $convert ? implode( ' ', $css ) : $css;
}
return $convert ? $css : implode( ' ', $css );
}
/**
* 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 = [], $extract = false ) {
return Templates::get_html( $template_name, $args, $extract );
}
/**
* Alias for default readonly function.
*
* @since 1.6.9
*
* @param mixed $readonly One of the values to compare.
* @param mixed $current The other value to compare if not just true.
* @param bool $echo Whether to echo or just return the string.
*
* @return string HTML attribute or empty string.
*/
function wpforms_readonly( $readonly, $current = true, $echo = true ) {
if ( function_exists( 'wp_readonly' ) ) {
return wp_readonly( $readonly, $current, $echo );
}
return __checked_selected_helper( $readonly, $current, $echo, 'readonly' );
}
/**
* 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',
[ ' <span class="wpforms-required-label">*</span>' ],
'1.4.8 of the WPForms plugin',
'wpforms_get_field_required_label'
);
return apply_filters( 'wpforms_get_field_required_label', $label_html );
}
/**
* Escape unselected choices for radio/checkbox fields.
*
* @since 1.8.3
*
* @param string $formatted_field HTML field.
*
* @return string
*/
function wpforms_esc_unselected_choices( $formatted_field ) {
$allowed_html = wp_kses_allowed_html( 'post' );
$allowed_html['input'] = [
'type' => [],
'disabled' => [],
'checked' => [],
'class' => [],
'value' => [],
];
$allowed_html['label'] = [];
return wp_kses( $formatted_field, $allowed_html );
}
/**
* Decode HTML entities in a string.
* Do it cycle to decode all possible entities, including cases like `&amp;lt;`.
*
* @since 1.9.2.3
*
* @param string $html HTML.
* @param int $flags Flags.
* @param string|null $encoding Encoding.
*
* @return string
* @noinspection PhpMissingParamTypeInspection
*/
function wpforms_html_entity_decode_deep( string $html, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, $encoding = null ): string {
do {
$previous_html = $html;
$html = html_entity_decode( $html, $flags, $encoding );
} while ( $html !== $previous_html );
return $html;
}
/**
* Sanitize form data.
*
* @since 1.9.3
*
* @param array $data Form data.
*
* @return array
*/
function wpforms_sanitize_form_data( array $data ): array {
foreach ( $data['fields'] as & $field ) {
$field = wpforms_sanitize_field( $field );
}
unset( $field );
return $data;
}
/**
* Sanitize form field.
*
* @since 1.9.3
*
* @param array $field Field.
*
* @return array
*/
function wpforms_sanitize_field( array $field ): array {
$raw_field_options = [
'html' => [ 'code' ],
];
$field_type = $field['type'] ?? '';
$raw_options = $raw_field_options[ $field_type ] ?? [];
/**
* Filter raw options for a field type.
* Allows modifying options that should not be sanitized.
*
* @since 1.9.3
*
* @param array $raw_options Raw options.
* @param string $field_type Field type.
*/
$raw_options = (array) apply_filters( 'wpforms_raw_options', $raw_options, $field_type );
$purifier = wpforms_get_html_purifier();
$decode_and_purify = static function ( $item ) use ( $purifier ) {
return $purifier->purify( wpforms_html_entity_decode_deep( $item ) );
};
foreach ( $field as $option => & $value ) {
if ( in_array( $option, $raw_options, true ) ) {
continue;
}
$value = wp_unslash( $value );
if ( is_array( $value ) ) {
array_walk_recursive( $value, $decode_and_purify );
} else {
$value = $decode_and_purify( $value );
}
$value = wp_slash( $value );
}
unset( $value );
return $field;
}
/**
* Get allowed HTML purifier object.
*
* @since 1.9.3
*
* @return HTMLPurifier
*/
function wpforms_get_html_purifier(): HTMLPurifier {
static $purifier;
if ( $purifier ) {
return $purifier;
}
require_once WPFORMS_PLUGIN_DIR . '/vendor_prefixed/ezyang/htmlpurifier/library/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$cache_dir = trailingslashit( File::get_cache_dir() ) . 'htmlpurifier';
// Make sure the cache directory exists.
File::mkdir( $cache_dir );
$config->set( 'Cache.SerializerPath', $cache_dir );
$config->set( 'Attr.AllowedRel', 'noopener,noreferrer,external,follow,nofollow,ugc,sponsored,tag' );
$config->set( 'Attr.AllowedFrameTargets', [ '_blank', '_self', '_parent', '_top' ] );
$config->set( 'HTML.TargetNoopener', false );
$config->set( 'HTML.TargetNoreferrer', false );
$purifier = new HTMLPurifier( $config );
return $purifier;
}

View File

@@ -0,0 +1,301 @@
<?php
/**
* Helper functions to work with filesystem, uploads and media files.
*
* @since 1.8.0
*/
use WPForms\Helpers\File;
/**
* 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 index.php file in the specified directory if it doesn't exist.
*
* @since 1.8.7
*
* @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_php_file( string $path ) {
if ( ! is_dir( $path ) || is_link( $path ) ) {
return false;
}
$index_file = wp_normalize_path( trailingslashit( $path ) . 'index.php' );
// Do nothing if index.php exists in the directory.
if ( file_exists( $index_file ) ) {
return false;
}
$data = '<?php
header( $_SERVER[\'SERVER_PROTOCOL\'] . \' 404 Not Found\' );
header( \'Status: 404 Not Found\' );
';
// Create index.php.
return file_put_contents( $index_file, $data ); // 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(): bool {
/**
* Whether to create upload dir .htaccess file.
*
* @since 1.6.1
*
* @param bool $allow True or false.
*/
if ( ! apply_filters( 'wpforms_create_upload_dir_htaccess_file', true ) ) {
return false;
}
$htaccess_file = File::get_upload_dir() . '.htaccess';
$cache_key = 'upload_htaccess_file';
if ( File::is_file_updated( $htaccess_file, $cache_key ) ) {
return true;
}
if ( ! function_exists( 'insert_with_markers' ) ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
}
/**
* Filters upload dir .htaccess file content.
*
* @since 1.6.1
*
* @param bool $allow True or false.
*/
$contents = apply_filters(
'wpforms_create_upload_dir_htaccess_file_content',
'# Disable PHP and Python scripts parsing.
<Files *>
SetHandler none
SetHandler default-handler
RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
RemoveType .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
</Files>
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
<IfModule mod_php8.c>
php_flag engine off
</IfModule>
<IfModule headers_module>
Header set X-Robots-Tag "noindex"
</IfModule>'
);
$created = insert_with_markers( $htaccess_file, 'WPForms', $contents );
if ( $created ) {
File::save_file_updated_stat( $htaccess_file, $cache_key );
}
return $created;
}
/**
* Create .htaccess file in the WPForms cache directory.
*
* @since 1.8.7
*
* @return bool True when the .htaccess file exists, false on failure.
*/
function wpforms_create_cache_dir_htaccess_file(): bool {
/**
* Whether to create cache dir .htaccess file.
*
* @since 1.8.7
*
* @param bool $allow True or false.
*/
if ( ! apply_filters( 'wpforms_create_cache_dir_htaccess_file', true ) ) {
return false;
}
$htaccess_file = File::get_cache_dir() . '.htaccess';
if ( File::is_file_updated( $htaccess_file, 'cache_htaccess_file' ) ) {
return true;
}
if ( ! function_exists( 'insert_with_markers' ) ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
}
/**
* Filters cache dir .htaccess file content.
*
* @since 1.8.7
*
* @param bool $allow True or false.
*/
$contents = apply_filters(
'wpforms_create_cache_dir_htaccess_file_content',
'# Disable access for any file in the cache dir.
# Apache 2.2
<IfModule !authz_core_module>
Deny from all
</IfModule>
# Apache 2.4+
<IfModule authz_core_module>
Require all denied
</IfModule>'
);
$created = insert_with_markers( $htaccess_file, 'WPForms', $contents );
if ( $created ) {
File::save_file_updated_stat( $htaccess_file );
}
return $created;
}
/**
* Convert a file size provided, such as "2M", to bytes.
*
* @link http://stackoverflow.com/a/22500394
*
* @since 1.0.0
*
* @param string $size File size.
*
* @return int
*/
function wpforms_size_to_bytes( $size ) {
if ( is_numeric( $size ) ) {
return $size;
}
$suffix = substr( $size, - 1 );
$value = substr( $size, 0, - 1 );
switch ( strtoupper( $suffix ) ) {
case 'P':
$value *= 1024;
case 'T':
$value *= 1024;
case 'G':
$value *= 1024;
case 'M':
$value *= 1024;
case 'K':
$value *= 1024;
break;
}
return $value;
}
/**
* Convert a file size provided, such as "2M", to bytes.
*
* @link http://stackoverflow.com/a/22500394
*
* @since 1.0.0
*
* @param bool $bytes Whether the value should be in bytes or formatted.
*
* @return false|string|int
*/
function wpforms_max_upload( $bytes = false ) {
$max = wp_max_upload_size();
if ( $bytes ) {
return $max;
}
return size_format( $max );
}

View File

@@ -0,0 +1,770 @@
<?php
/**
* Helper functions to work with form fields, generic and specific to certain field types.
*
* @since 1.8.0
*/
/**
* 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(): bool {
/**
* Filter to show or hide the "Show Values" toggle for checkbox, radio, or select fields in form builder.
*
* @since 1.5.0
*
* @param bool $show Show or hide the "Show Values" toggle.
*/
return (bool) apply_filters( 'wpforms_fields_show_options_setting', false );
}
/**
* 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 = [] ) {
if ( empty( $field['dynamic_choices'] ) ) {
return false;
}
$choices = [];
if ( $field['dynamic_choices'] === 'post_type' ) {
if ( empty( $field['dynamic_post_type'] ) ) {
return false;
}
$posts = wpforms_get_hierarchical_object(
/**
* Filter the arguments used to retrieve posts for dynamic choices.
*
* @since 1.4.5
*
* @param array $args Array of arguments for retrieving posts.
* @param array $field Field settings.
* @param int $form_id Form ID.
*/
apply_filters(
'wpforms_dynamic_choice_post_type_args',
[
'post_type' => $field['dynamic_post_type'],
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
],
$field,
$form_id
),
true
);
foreach ( $posts as $post ) {
$choices[] = [
'value' => $post->ID,
'label' => wpforms_get_post_title( $post ),
'depth' => isset( $post->depth ) ? absint( $post->depth ) : 1,
];
}
} elseif ( $field['dynamic_choices'] === 'taxonomy' ) {
if ( empty( $field['dynamic_taxonomy'] ) ) {
return false;
}
$terms = wpforms_get_hierarchical_object(
/**
* Filter the arguments used to retrieve terms for dynamic choices.
*
* @since 1.4.5
*
* @param array $args Array of arguments for retrieving terms.
* @param array $field Field settings.
* @param array $form_data Form data.
*/
apply_filters(
'wpforms_dynamic_choice_taxonomy_args',
[
'taxonomy' => $field['dynamic_taxonomy'],
'hide_empty' => false,
],
$field,
$form_data
),
true
);
foreach ( $terms as $term ) {
$choices[] = [
'value' => $term->term_id,
'label' => wpforms_get_term_name( $term ),
'depth' => isset( $term->depth ) ? absint( $term->depth ) : 1,
];
}
}
return $choices;
}
/**
* Build and return either a taxonomy or post type object nested to accommodate any hierarchy.
*
* @since 1.3.9
* @since 1.5.0 Return an 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 ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.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 ) {
// The current WP_Post or WP_Term object to operate on.
$current = $child;
// The current object's parent is already in the list of parents or children.
if ( ! empty( $parents[ $child->{$ref_parent} ] ) || ! empty( $children[ $child->{$ref_parent} ] ) ) {
continue;
}
do {
// Set the current object to the previous iteration's parent object.
$current = ! empty( $args['post_type'] ) ? get_post( $current->{$ref_parent} ) : get_term( $current->{$ref_parent} );
if ( $current->{$ref_parent} === 0 ) {
// We've reached the top of the hierarchy.
$parents[ $current->{$ref_id} ] = $current;
$parents[ $current->{$ref_id} ]->ID = (int) $current->{$ref_id};
} else {
// We're still in the middle of the hierarchy.
$children[ $current->{$ref_id} ] = $current;
$children[ $current->{$ref_id} ]->ID = (int) $current->{$ref_id};
}
} while ( $current->{$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 two 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 $parent ) {
if ( $parent->ID === $child->{$ref_parent} ) {
$parent->children = $parent->children ?? [];
$parent->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 $h_array Hierarchical 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( $h_array, &$output, $ref_name = 'name', $level = 0 ) {
/**
* Filter the hierarchical object indicator.
*
* @since 1.3.9
*
* @param string $indicator Hierarchical object indicator.
*/
$indicator = (string) apply_filters( 'wpforms_hierarchical_object_indicator', '&mdash;' );
foreach ( $h_array as $item ) {
$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 );
}
}
}
/**
* Get sanitized post title or "no title" placeholder.
*
* The placeholder is prepended with the post ID.
*
* @since 1.7.6
*
* @param WP_Post|mixed $post Post object.
*
* @return string Post title.
*/
function wpforms_get_post_title( $post ): string {
return (
wpforms_is_empty_string( trim( $post->post_title ) )
/* translators: %d - post ID. */
? sprintf( __( '#%d (no title)', 'wpforms-lite' ), absint( $post->ID ) )
: $post->post_title
);
}
/**
* Get a sanitized term name or "no name" placeholder.
*
* The placeholder is prepended with the term ID.
*
* @since 1.7.6
*
* @param WP_Term $term Term object.
*
* @return string Term name.
*/
function wpforms_get_term_name( WP_Term $term ): string {
return (
wpforms_is_empty_string( trim( $term->name ) )
/* translators: %d - taxonomy term ID. */
? sprintf( __( '#%d (no name)', 'wpforms-lite' ), absint( $term->term_id ) )
: trim( $term->name )
);
}
/**
* Return information about pages if the form has multiple pages.
*
* @since 1.3.7
*
* @param WP_Post|array $form Form data.
*
* @return false|array Page Break details or false.
*/
function wpforms_get_pagebreak_details( $form = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
if ( ! wpforms()->is_pro() ) {
return false;
}
$details = [];
$pages = 1;
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 $field ) {
if ( $field['type'] !== 'pagebreak' ) {
continue;
}
if ( empty( $field['position'] ) ) {
++$pages;
$details['total'] = $pages;
$details['pages'][] = $field;
} elseif ( $field['position'] === 'top' ) {
$details['top'] = $field;
} elseif ( $field['position'] === 'bottom' ) {
$details['bottom'] = $field;
}
}
if ( ! empty( $details ) ) {
$details['top'] = empty( $details['top'] ) ? [] : $details['top'];
$details['bottom'] = empty( $details['bottom'] ) ? [] : $details['bottom'];
$details['current'] = 1;
return $details;
}
return false;
}
/**
* Return available builder fields.
*
* @since 1.8.5
*
* @param string $group Group name.
*
* @return array
*/
function wpforms_get_builder_fields( string $group = '' ): array {
$fields = [
'standard' => [
'group_name' => esc_html__( 'Standard Fields', 'wpforms-lite' ),
'fields' => [],
],
'fancy' => [
'group_name' => esc_html__( 'Fancy Fields', 'wpforms-lite' ),
'fields' => [],
],
'payment' => [
'group_name' => esc_html__( 'Payment Fields', 'wpforms-lite' ),
'fields' => [],
],
];
/**
* Allows developers to modify the content of the Add Field tab.
*
* With this filter, developers can add their own fields or even fields groups.
*
* @since 1.4.0
*
* @param array $fields {
* Fields data multidimensional array.
*
* @param array $standard Standard fields group.
* @param string $group_name Group name.
* @param array $fields Fields array.
*
* @param array $fancy Fancy fields group.
* @param string $group_name Group name.
* @param array $fields Fields array.
*
* @param array $payment Payment fields group.
* @param string $group_name Group name.
* @param array $fields Fields array.
* }
*/
$fields = apply_filters( 'wpforms_builder_fields_buttons', $fields ); // phpcs:ignore WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
// If a group is not specified, return all fields.
if ( empty( $group ) ) {
return $fields;
}
// If a group is specified, return only fields from that group.
if ( isset( $fields[ $group ] ) ) {
return $fields[ $group ]['fields'];
}
return [];
}
/**
* Get payments fields.
*
* @since 1.8.5
*
* @return array
*/
function wpforms_get_payments_fields(): array {
// Some fields are added dynamically only when the corresponding payment add-on is active.
// However, we need to be aware of all possible payment fields, even if they are not currently available.
return [
'payment-single',
'payment-multiple',
'payment-checkbox',
'payment-select',
'payment-total',
'payment-coupon',
'credit-card', // Legacy Credit Card field.
'authorize_net',
'paypal-commerce',
'square',
'stripe-credit-card',
];
}
/**
* Validate field ID for the repeater field.
*
* @since 1.8.9
*
* @param mixed $field_id Field ID.
*
* @return int|string
*/
function wpforms_validate_field_id( $field_id ) {
return (
wpforms_is_repeater_child_field( $field_id ) ?
preg_replace( '/[^0-9_]/', '', $field_id ) :
absint( $field_id )
);
}
/**
* Check if field ID is a repeater field.
*
* @since 1.8.9
*
* @param int|string|array $field Field.
*
* @return bool
*/
function wpforms_is_repeater_child_field( $field ): bool {
$field_id = (string) ( $field['id'] ?? $field );
$pattern = '/^(\d+_\d+)(_\d+)*$/';
return preg_match( $pattern, $field_id ) === 1;
}
/**
* Get repeater field IDs.
*
* @since 1.8.9
*
* @param int|string|array $field Field ID.
*
* @return int[]
*/
function wpforms_get_repeater_field_ids( $field ): array {
$field_id = (string) ( is_array( $field ) ? $field['id'] : $field );
$field_id_arr = explode( '_', $field_id );
$original_id = (int) $field_id_arr[0];
$index_id = (int) ( $field_id_arr[1] ?? 0 );
return compact( 'original_id', 'index_id' );
}
/**
* Get the correct value for field with raw value available.
*
* @since 1.8.9
*
* @param array $field Entry field.
* @param array $form_data Form data and settings.
*
* @return string
*/
function wpforms_get_choices_value( array $field, array $form_data ): string {
$show_values = ! empty( $form_data['fields'][ $field['id'] ]['show_values'] );
$is_dynamic = ! empty( $field['dynamic'] );
$value = $field['value'] ?? '';
if ( $show_values && ! $is_dynamic && ! wpforms_is_empty_string( $field['value_raw'] ?? '' ) ) {
$value = $field['value_raw'];
}
if ( $is_dynamic ) {
$value = $field['value_raw'] ?? $value;
}
return (string) $value;
}
/**
* Check whether the field type is in the list of types that support the Show Values option.
*
* @since 1.10.0
*
* @param array $field Field data.
*
* @return bool True if the field type supports Show Values, false otherwise.
*/
function wpforms_is_support_show_values( array $field ): bool {
static $supported_types = [ 'select', 'radio', 'checkbox' ];
return in_array( $field['type'] ?? '', $supported_types, true );
}
/**
* Determine if the field was repeated.
*
* @since 1.8.9
*
* @param int $field_id Field ID.
* @param array $fields List of fields.
*
* @return bool
*/
function wpforms_is_repeated_field( int $field_id, array $fields ): bool {
$prefix = $field_id . '_';
foreach ( $fields as $key => $field ) {
if ( strpos( $key, $prefix ) === 0 ) {
return true;
}
}
return false;
}
/**
* Get field types where user can select more than one item.
*
* Note: this list does not include the File Upload field, even though it is a multi-field.
*
* @since 1.9.0
*
* @return array
*/
function wpforms_get_multi_fields(): array {
return [
'checkbox',
'select',
'payment-checkbox',
];
}
/**
* Get column width.
*
* @since 1.9.3
*
* @param array $column Column data.
*
* @return float
*/
function wpforms_get_column_width( array $column ): float {
$preset_width = ! empty( $column['width_preset'] ) ? (int) $column['width_preset'] : 50;
if ( $preset_width === 33 ) {
$preset_width = 33.33333;
} elseif ( $preset_width === 67 ) {
$preset_width = 66.66666;
}
if ( ! empty( $column['width_custom'] ) ) {
$preset_width = $column['width_custom'];
}
return (float) $preset_width;
}
/**
* Parse a field ID into its components.
*
* @since 1.9.6
*
* @param string|int $field_id Field ID to parse.
*
* @return array Parsed field components.
*/
function wpforms_parse_field_id( $field_id ): array {
$field_parts = (array) explode( '.', (string) $field_id );
$field_id = (int) $field_parts[0];
$field_key = isset( $field_parts[1] ) && $field_parts[1] !== 'full' ? $field_parts[1] : 'value';
return [
'id' => $field_id,
'key' => $field_key,
];
}
/**
* Get icon SVG by its name, style and size.
*
* @since 1.10.0
*
* @param string $icon Icon name.
* @param string $style Icon style.
* @param int $size Icon font size in pixels.
*/
function wpforms_get_icon_svg( string $icon, string $style, int $size ): string {
// Sanitize inputs.
$icon = sanitize_key( $icon );
$style = sanitize_key( $style );
$size = absint( $size );
if ( $size <= 0 ) {
$size = 32;
}
$upload_dir = wpforms_upload_dir();
$cache_base_path = $upload_dir['path'] . '/icon-choices';
$filename = wp_normalize_path( (string) realpath( "$cache_base_path/svgs/$style/$icon.svg" ) );
$allowed_dir = wp_normalize_path( (string) realpath( $cache_base_path . '/svgs' ) );
// Verify the file is within the allowed directory.
if ( strpos( $filename, $allowed_dir ) !== 0 ) {
return '';
}
if ( ! is_file( $filename ) || ! is_readable( $filename ) ) {
return '';
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$svg = (string) file_get_contents( $filename );
if ( strpos( $svg, '<svg' ) === false ) {
return '';
}
$height = $size;
$width = $height * 1.25; // Icon width is equal or 25% larger/smaller than height. We force the largest value for all icons.
return str_replace( 'viewBox=', 'width="' . $width . '" height="' . $height . '" viewBox=', $svg );
}

View File

@@ -0,0 +1,612 @@
<?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',
'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 );
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 );
}

View File

@@ -0,0 +1,324 @@
<?php
/**
* Helper functions to work with multidimensional arrays easier.
*
* @since 1.5.6
*/
/**
* Determine whether the given value is array accessible.
*
* @since 1.5.6
*
* @param mixed $value Checkin to accessible.
*
* @return bool
*/
function wpforms_list_accessible( $value ) {
return is_array( $value ) || $value instanceof ArrayAccess;
}
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* @since 1.5.6
*
* @param array $array Existing array.
* @param string $key Path to set.
* @param mixed $value Value to set.
* @param string $separator Separator.
*
* @return array New array.
*/
function wpforms_list_set( $array, $key, $value, $separator = '.' ) {
if ( ! wpforms_list_accessible( $array ) ) {
return $value;
}
if ( $key === null ) {
$array = $value;
return $array;
}
$keys = explode( $separator, $key );
$count_keys = count( $keys );
$values = array_values( $keys );
$last_key = $values[ $count_keys - 1 ];
$tmp_array = &$array;
for ( $i = 0; $i < $count_keys - 1; $i ++ ) {
$k = $keys[ $i ];
$tmp_array = &$tmp_array[ $k ];
}
$tmp_array[ $last_key ] = $value;
return $array;
}
/**
* Determine if the given key exists in the provided array.
*
* @since 1.5.6
*
* @param ArrayAccess|array $array Existing array.
* @param string|int $key To check.
*
* @return bool
*/
function wpforms_list_exists( $array, $key ) {
if ( ! wpforms_list_accessible( $array ) ) {
return false;
}
if ( $array instanceof ArrayAccess ) {
return $array->offsetExists( $key );
}
return array_key_exists( $key, $array );
}
/**
* Get an item from an array using "dot" notation.
*
* @since 1.5.6
*
* @param ArrayAccess|array $array Where we want to get.
* @param string $key Key with dot's.
* @param mixed $default Value.
*
* @return mixed
*/
function wpforms_list_get( $array, $key, $default = null ) {
if ( ! wpforms_list_accessible( $array ) ) {
return $default;
}
if ( $key === null ) {
return $array;
}
if ( ! is_string( $key ) ) {
return $default;
}
if ( wpforms_list_exists( $array, $key ) ) {
return $array[ $key ];
}
foreach ( explode( '.', $key ) as $segment ) {
if ( wpforms_list_accessible( $array ) && wpforms_list_exists( $array, $segment ) ) {
$array = $array[ $segment ];
} else {
return $default;
}
}
return $array;
}
/**
* Check if an item exists in an array using "dot" notation.
*
* @since 1.5.6
*
* @param ArrayAccess|array $array To check.
* @param string $key Keys with dot's.
*
* @return bool
*/
function wpforms_list_has( $array, $key ) {
if ( ! $array ) {
return false;
}
if ( $key === null || ! is_string( $key ) ) {
return false;
}
if ( wpforms_list_exists( $array, $key ) ) {
return true;
}
foreach ( explode( '.', $key ) as $segment ) {
if ( wpforms_list_accessible( $array ) && wpforms_list_exists( $array, $segment ) ) {
$array = $array[ $segment ];
} else {
return false;
}
}
return true;
}
/**
* Determine if an array is associative.
*
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
*
* @since 1.5.6
*
* @param array $array To check.
*
* @return bool
*/
function wpforms_list_is_assoc( $array ) {
$keys = array_keys( $array );
return array_keys( $keys ) !== $keys;
}
/**
* Get a subset of the items from the given array.
*
* @since 1.5.6
*
* @param array $array To get.
* @param array|string $keys To filter.
*
* @return array
*/
function wpforms_list_only( $array, $keys ) {
return array_intersect_key( $array, array_flip( (array) $keys ) );
}
/**
* Remove one or many array items from a given array using "dot" notation.
*
* @since 1.5.6
*
* @param array $array To forget.
* @param array|string $keys To exclude.
*
* @return array
*/
function wpforms_list_forget( $array, $keys ) {
$tmp_array = &$array;
$keys = (array) $keys;
if ( count( $keys ) === 0 ) {
return $array;
}
foreach ( $keys as $key ) {
// if the exact key exists in the top-level, remove it.
if ( wpforms_list_exists( $array, $key ) ) {
unset( $array[ $key ] );
continue;
}
$parts = explode( '.', $key );
$count_keys = count( $parts );
$values = array_values( $parts );
$last_key = $values[ $count_keys - 1 ];
for ( $i = 0; $i < $count_keys - 1; $i ++ ) {
$k = $parts[ $i ];
$tmp_array = &$tmp_array[ $k ];
}
unset( $tmp_array[ $last_key ] );
}
return $array;
}
/**
* Insert a value or key/value pair after a specific key in an array.
* If key doesn't exist, value is appended to the end of the array.
*
* @since 1.5.8
*
* @param array $target Array where to insert.
* @param string $key Insert after key.
* @param array $data Array to insert.
*
* @return array
*/
function wpforms_list_insert_after( array $target, string $key, array $data ): array {
return wpforms_list_insert( $target, $key, $data, 'after' );
}
/**
* Insert a value or key/value pair before a specific key in an array.
* If key doesn't exist, value is prepended to the beginning of the array.
*
* @since 1.8.9
*
* @param array $target Array where to insert.
* @param string $key Insert before key.
* @param array $data Array to insert.
*
* @return array
*/
function wpforms_list_insert_before( array $target, string $key, array $data ): array {
return wpforms_list_insert( $target, $key, $data, 'before' );
}
/**
* Insert a value or key/value pair before or after a specific key in an array.
* If key doesn't exist, value is appended to the end of the array.
*
* @since 1.8.9
*
* @param array $target Array where to insert.
* @param string $key Insert before/after key.
* @param array $data Array to insert.
* @param string $position Position to insert before/after.
*
* @return array
*/
function wpforms_list_insert( array $target, string $key, array $data, string $position ): array {
$position = strtolower( $position );
$keys = array_keys( $target );
$index = array_search( $key, $keys, true );
$pos = 0;
if ( $position === 'before' ) {
$pos = $index === false ? 0 : $index;
} elseif ( $position === 'after' ) {
$pos = $index === false ? count( $target ) : $index + 1;
}
return array_merge( array_slice( $target, 0, $pos ), $data, array_slice( $target, $pos ) );
}
/**
* Cleanup $items array recursively removing from it all keys not existing in the $default array.
*
* @since 1.7.2
*
* @param array $items Items.
* @param array $default Default items.
*
* @return array
*/
function wpforms_list_intersect_key( $items, $default ) {
if ( ! is_array( $items ) ) {
return $items;
}
$items = array_intersect_key( $items, $default );
foreach ( $items as $key => &$item ) {
$item = wpforms_list_intersect_key( $item, $default[ $key ] );
}
return $items;
}

View File

@@ -0,0 +1,807 @@
<?php
/**
* Payment related functions.
*
* @since 1.8.2
*/
/**
* Get supported currencies.
*
* @since 1.2.4
*
* @return array
*/
function wpforms_get_currencies() {
$currencies = [
'USD' => [
'name' => esc_html__( 'U.S. Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'GBP' => [
'name' => esc_html__( 'Pound Sterling', 'wpforms-lite' ),
'symbol' => '&pound;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'EUR' => [
'name' => esc_html__( 'Euro', 'wpforms-lite' ),
'symbol' => '&euro;',
'symbol_pos' => 'right',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'AUD' => [
'name' => esc_html__( 'Australian Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'BRL' => [
'name' => esc_html__( 'Brazilian Real', 'wpforms-lite' ),
'symbol' => 'R$',
'symbol_pos' => 'left',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'BGN' => [
'name' => esc_html__( 'Bulgarian Lev', 'wpforms-lite' ),
'symbol' => 'лв',
'symbol_pos' => 'right',
'thousands_separator' => ' ',
'decimal_separator' => ',',
'decimals' => 2,
],
'CAD' => [
'name' => esc_html__( 'Canadian Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'CRC' => [
'name' => esc_html__( 'Costa Rican Colón', 'wpforms-lite' ),
'symbol' => '&#8353;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'XAF' => [
'name' => esc_html__( 'Central African CFA Franc', 'wpforms-lite' ),
'symbol' => 'F.CFA ',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '',
'decimals' => 0,
],
'CZK' => [
'name' => esc_html__( 'Czech Koruna', 'wpforms-lite' ),
'symbol' => '&#75;&#269;',
'symbol_pos' => 'right',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'DKK' => [
'name' => esc_html__( 'Danish Krone', 'wpforms-lite' ),
'symbol' => 'kr.',
'symbol_pos' => 'right',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'HKD' => [
'name' => esc_html__( 'Hong Kong Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'HUF' => [
'name' => esc_html__( 'Hungarian Forint', 'wpforms-lite' ),
'symbol' => 'Ft',
'symbol_pos' => 'right',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'INR' => [
'name' => esc_html__( 'Indian Rupee', 'wpforms-lite' ),
'symbol' => '&#8377;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'ILS' => [
'name' => esc_html__( 'Israeli New Sheqel', 'wpforms-lite' ),
'symbol' => '&#8362;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'JPY' => [
'name' => esc_html__( 'Japanese Yen', 'wpforms-lite' ),
'symbol' => '&#165;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '',
'decimals' => 0,
],
'MYR' => [
'name' => esc_html__( 'Malaysian Ringgit', 'wpforms-lite' ),
'symbol' => '&#82;&#77;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'MXN' => [
'name' => esc_html__( 'Mexican Peso', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'NOK' => [
'name' => esc_html__( 'Norwegian Krone', 'wpforms-lite' ),
'symbol' => 'Kr',
'symbol_pos' => 'left',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'NZD' => [
'name' => esc_html__( 'New Zealand Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'PHP' => [
'name' => esc_html__( 'Philippine Peso', 'wpforms-lite' ),
'symbol' => 'Php',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'PLN' => [
'name' => esc_html__( 'Polish Zloty', 'wpforms-lite' ),
'symbol' => '&#122;&#322;',
'symbol_pos' => 'left',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'RON' => [
'name' => esc_html__( 'Romanian Leu', 'wpforms-lite' ),
'symbol' => 'lei',
'symbol_pos' => 'right',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'RUB' => [
'name' => esc_html__( 'Russian Ruble', 'wpforms-lite' ),
'symbol' => 'pyб',
'symbol_pos' => 'right',
'thousands_separator' => ' ',
'decimal_separator' => '.',
'decimals' => 2,
],
'SAR' => [
'name' => esc_html__( 'Saudi Arabian Riyal', 'wpforms-lite' ),
'symbol' => '&#65020;',
'symbol_pos' => 'right',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'SGD' => [
'name' => esc_html__( 'Singapore Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'RSD' => [
'name' => esc_html__( 'Serbian Dinar', 'wpforms-lite' ),
'symbol' => 'din. ',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'ZAR' => [
'name' => esc_html__( 'South African Rand', 'wpforms-lite' ),
'symbol' => 'R',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'KRW' => [
'name' => esc_html__( 'South Korean Won', 'wpforms-lite' ),
'symbol' => '&#8361;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '',
'decimals' => 0,
],
'LKR' => [
'name' => esc_html__( 'Sri Lankan Rupee', 'wpforms-lite' ),
'symbol' => 'Rs ',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'SEK' => [
'name' => esc_html__( 'Swedish Krona', 'wpforms-lite' ),
'symbol' => 'Kr',
'symbol_pos' => 'right',
'thousands_separator' => '.',
'decimal_separator' => ',',
'decimals' => 2,
],
'CHF' => [
'name' => esc_html__( 'Swiss Franc', 'wpforms-lite' ),
'symbol' => 'CHF',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'TWD' => [
'name' => esc_html__( 'Taiwan New Dollar', 'wpforms-lite' ),
'symbol' => '&#36;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'THB' => [
'name' => esc_html__( 'Thai Baht', 'wpforms-lite' ),
'symbol' => '&#3647;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'TRY' => [
'name' => esc_html__( 'Turkish Lira', 'wpforms-lite' ),
'symbol' => '&#8378;',
'symbol_pos' => 'left',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
'AED' => [
'name' => esc_html__( 'United Arab Emirates Dirham', 'wpforms-lite' ),
'symbol' => '&#1583;.&#1573;',
'symbol_pos' => 'right',
'thousands_separator' => ',',
'decimal_separator' => '.',
'decimals' => 2,
],
];
/**
* Filter for currencies supported in WPForms payments.
*
* @since 1.2.4
*
* @param array $currencies List of currencies.
*/
return array_change_key_case( (array) apply_filters( 'wpforms_currencies', $currencies ), CASE_UPPER );
}
/**
* Sanitize amount by stripping out thousands separators.
*
* @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/formatting.php#L24
*
* @since 1.2.6
*
* @param string $amount Price amount.
* @param string $currency Currency ISO code (USD, EUR, etc).
*
* @return string $amount
*/
function wpforms_sanitize_amount( $amount, $currency = '' ) {
if ( empty( $currency ) ) {
$currency = wpforms_get_currency();
}
$raw_amount = $amount;
$currency = strtoupper( $currency );
$currencies = wpforms_get_currencies();
$thousands_sep = $currencies[ $currency ]['thousands_separator'] ?? ',';
$decimal_sep = $currencies[ $currency ]['decimal_separator'] ?? '.';
/**
* Filter the raw price amount before sanitization.
*
* @since 1.8.9.4
*
* @param string $amount Raw price amount.
* @param string $currency Currency ISO code (USD, EUR, etc).
* @param string $thousands_sep Thousands separator.
* @param string $decimal_sep Decimal separator.
*
* @return string
*/
$amount = (string) apply_filters( 'wpforms_sanitize_amount_before', $amount, $currency, $thousands_sep, $decimal_sep );
// Sanitize the amount.
if ( $decimal_sep === ',' && strpos( $amount, $decimal_sep ) !== false ) {
if ( ( $thousands_sep === '.' || $thousands_sep === ' ' ) && strpos( $amount, $thousands_sep ) !== false ) {
$amount = str_replace( $thousands_sep, '', $amount );
} elseif ( empty( $thousands_sep ) && strpos( $amount, '.' ) !== false ) {
$amount = str_replace( '.', '', $amount );
}
$amount = str_replace( $decimal_sep, '.', $amount );
} elseif ( $thousands_sep === ',' && strpos( $amount, $thousands_sep ) !== false ) {
$amount = str_replace( $thousands_sep, '', $amount );
}
// Remove current symbol.
$amount = str_replace( $currencies[ $currency ]['symbol'], '', $amount );
/**
* Remove any characters that are not a digit, a decimal point, or a minus sign.
*
* E is an exponent notation. Float number can be written in the form 2E-13, which means 2 * 10^-13.
* 0-9 is digits.
* . is decimal point.
* - is minus sign.
*/
$sanitized_amount = (string) preg_replace( '/[^E0-9.-]/', '', $amount );
/**
* Set correct currency decimals.
*
* @since 1.6.6
*
* @param int $decimals Default number of decimals.
* @param string $sanitized_amount Price amount.
*/
$decimals = (int) apply_filters(
'wpforms_sanitize_amount_decimals',
wpforms_get_currency_decimals( $currency ),
$sanitized_amount
);
/**
* Filter the sanitized amount.
*
* @since 1.8.9.4
*
* @param string $sanitized_amount Sanitized price amount.
* @param string $raw_amount Raw price amount.
* @param string $currency Currency ISO code (USD, EUR, etc).
* @param int $decimals Number of decimals.
*
* @return string
*/
return (string) apply_filters(
'wpforms_sanitize_amount',
number_format( (float) $sanitized_amount, $decimals, '.', '' ),
$raw_amount,
$currency,
$decimals
);
}
/**
* Return a nicely formatted amount.
*
* @since 1.2.6
*
* @param string $amount Price amount.
* @param bool $symbol Currency symbol ($, €).
* @param string $currency Currency ISO code (USD, EUR, etc).
*
* @return string $amount Newly formatted amount or Price Not Available
*/
function wpforms_format_amount( $amount, $symbol = false, $currency = '' ) {
if ( empty( $currency ) ) {
$currency = wpforms_get_currency();
}
$currency = strtoupper( $currency );
$currencies = wpforms_get_currencies();
$thousands_sep = isset( $currencies[ $currency ]['thousands_separator'] ) ? $currencies[ $currency ]['thousands_separator'] : ',';
$decimal_sep = isset( $currencies[ $currency ]['decimal_separator'] ) ? $currencies[ $currency ]['decimal_separator'] : '.';
$sep_found = ! empty( $decimal_sep ) ? strpos( $amount, $decimal_sep ) : false;
// Format the amount.
if (
$decimal_sep === ',' &&
$sep_found !== false
) {
$whole = substr( $amount, 0, $sep_found );
$part = substr( $amount, $sep_found + 1, ( strlen( $amount ) - 1 ) );
$amount = $whole . '.' . $part;
}
// Strip "," (comma) from the amount (if set as the thousands' separator).
if (
$thousands_sep === ',' &&
strpos( $amount, $thousands_sep ) !== false
) {
$amount = (float) str_replace( ',', '', $amount );
}
if ( empty( $amount ) ) {
$amount = 0;
}
/** This filter is documented in wpforms_sanitize_amount function above. */
$decimals = (int) apply_filters(
'wpforms_sanitize_amount_decimals',
wpforms_get_currency_decimals( $currency ),
$amount
);
$number = number_format( (float) $amount, $decimals, $decimal_sep, $thousands_sep );
// Display a symbol, if any.
if ( $symbol && isset( $currencies[ $currency ]['symbol_pos'] ) ) {
/**
* Filter for currency symbol padding.
*
* @since 1.2.6
*
* @param string $symbol_padding Currency symbol padding.
*/
$symbol_padding = apply_filters( 'wpforms_currency_symbol_padding', ' ' );
if ( $currencies[ $currency ]['symbol_pos'] === 'right' ) {
$number .= $symbol_padding . $currencies[ $currency ]['symbol'];
} else {
$number = $currencies[ $currency ]['symbol'] . $number;
}
}
return $number;
}
/**
* Get default number of decimals for a given currency.
* If not provided inside the currency, default value is used, which is 2.
*
* @since 1.6.6
*
* @param array|string $currency Currency data we are getting decimals for.
*
* @return int
*/
function wpforms_get_currency_decimals( $currency ) {
if ( is_string( $currency ) ) {
$currencies = wpforms_get_currencies();
$currency_code = strtoupper( $currency );
$currency = isset( $currencies[ $currency_code ] ) ? $currencies[ $currency_code ] : [];
}
/**
* Get currency decimals.
*
* @since 1.6.6
*
* @param int $decimals Default number of decimals.
* @param array|string $currency Currency data we are getting decimals for.
*/
return (int) apply_filters(
'wpforms_get_currency_decimals',
isset( $currency['decimals'] ) ? $currency['decimals'] : 2,
$currency
);
}
/**
* Get payments currency.
* If the currency not available anymore 'USD' used as default.
*
* @since 1.6.6
*
* @return string
*/
function wpforms_get_currency() {
$currency = wpforms_setting( 'currency' );
$currencies = wpforms_get_currencies();
/**
* Get payments currency.
*
* @since 1.6.6
*
* @param string $currency Payments currency.
* @param array $currencies Available currencies.
*/
return apply_filters(
'wpforms_get_currency',
isset( $currencies[ $currency ] ) ? $currency : 'USD',
$currencies
);
}
/**
* Return recognized payment field types.
*
* @since 1.0.0
*
* @return array
*/
function wpforms_payment_fields() {
/**
* Filters the recognized payment field types.
*
* @since 1.0.0
*
* @param array $fields Payment field types.
*/
return (array) apply_filters(
'wpforms_payment_fields',
[ 'payment-single', 'payment-multiple', 'payment-checkbox', 'payment-select' ]
);
}
/**
* Check if form or entry contains payment.
*
* @since 1.0.0
*
* @param string $type Either 'entry' or 'form'.
* @param array $data List of form fields.
*
* @return bool
*/
function wpforms_has_payment( $type = 'entry', $data = [] ) {
$payment = false;
$payment_fields = wpforms_payment_fields();
if ( ! empty( $data['fields'] ) ) {
$data = $data['fields'];
}
if ( empty( $data ) ) {
return false;
}
foreach ( $data as $field ) {
if ( isset( $field['type'] ) && in_array( $field['type'], $payment_fields, true ) ) {
// For entries, only return true if the payment field has an amount.
if (
$type === 'form' ||
(
$type === 'entry' &&
! empty( $field['amount'] ) &&
! empty( wpforms_sanitize_amount( $field['amount'] ) )
)
) {
$payment = true;
break;
}
}
}
return $payment;
}
/**
* Check to see if a form has an active payment gateway configured.
*
* @since 1.4.5
*
* @param array $form_data Form data and settings.
*
* @return bool
*/
function wpforms_has_payment_gateway( $form_data ) {
// PayPal Standard check.
if ( ! empty( $form_data['payments']['paypal_standard']['enable'] ) ) {
return true;
}
// Stripe Check.
if ( ! empty( $form_data['payments']['stripe']['enable'] ) ) {
return true;
}
/**
* Allow modifying whether a form has an active payment gateway.
*
* @since 1.4.5
*
* @param bool $result True if a form has an active payment gateway.
* @param array $form_data Form data and settings.
*/
return (bool) apply_filters( 'wpforms_has_payment_gateway', false, $form_data );
}
/**
* Get payment total amount from entry.
*
* @since 1.0.0
* @since 1.8.2.2 Added PHP max() function before returning a total.
*
* @param array $fields List of fields.
*
* @return string
*/
function wpforms_get_total_payment( $fields ) {
$fields = wpforms_get_payment_items( $fields );
$total = 0;
if ( empty( $fields ) ) {
return false;
}
foreach ( $fields as $field ) {
// Skip the field hidden by conditional logic.
if ( isset( $field['visible'] ) && $field['visible'] === false ) {
continue;
}
if ( empty( $field['amount'] ) ) {
continue;
}
$amount = wpforms_sanitize_amount( $field['amount'] );
if ( ! empty( $field['quantity'] ) ) {
$amount *= (int) $field['quantity'];
}
$total += $amount;
}
$total = max( 0, $total );
return wpforms_sanitize_amount( $total );
}
/**
* Get payment fields in an entry.
*
* @since 1.0.0
*
* @param array $fields List of fields.
*
* @return array|bool False if no fields provided, otherwise array.
*/
function wpforms_get_payment_items( $fields = [] ) {
if ( empty( $fields ) ) {
return false;
}
$payment_fields = wpforms_payment_fields();
foreach ( $fields as $id => $field ) {
if (
empty( $field['type'] ) ||
empty( $field['amount'] ) ||
! in_array( $field['type'], $payment_fields, true ) ||
empty( wpforms_sanitize_amount( $field['amount'] ) ) ||
( isset( $field['quantity'] ) && ! $field['quantity'] )
) {
// Remove all non-payment fields as well as payment fields with no amount or empty quantity.
unset( $fields[ $id ] );
}
}
return $fields;
}
/**
* Determine if field has quantity enabled.
*
* @since 1.8.7
*
* @param array $field Field data.
* @param array $form_data Form data.
*
* @return bool
*/
function wpforms_payment_has_quantity( array $field, array $form_data ): bool {
if ( ! isset( $field['id'] ) ) {
return false;
}
if ( isset( $field['quantity'] ) ) {
return true;
}
$field_settings = $form_data['fields'][ $field['id'] ] ?? [];
if ( empty( $field_settings['enable_quantity'] ) ) {
return false;
}
// Quantity is available only for `single` format of the Single payment field.
if ( $field_settings['type'] === 'payment-single' && $field_settings['format'] !== 'single' ) {
return false;
}
// Otherwise return true.
// It covers the Dropdown Items field (and others where the quantity will be supported).
return true;
}
/**
* Formatted payment field value with quantity.
*
* @since 1.8.7
*
* @param array $field Field data.
*
* @return string
*/
function wpforms_payment_format_quantity( array $field ): string {
if ( empty( $field['value'] ) ) {
return '';
}
return sprintf( /* translators: %1$s - payment amount; %2$d - payment quantity. */
esc_html__( '%1$s &times; %2$d', 'wpforms-lite' ),
$field['value'],
$field['quantity'] ?? 1
);
}
/**
* Get the multiplier for a given currency based on its decimal places.
*
* This function returns a scaling factor used to convert between
* the smallest currency unit (e.g., cents for USD) and the standard
* representation.
* For example,
* - USD (2 decimal places) → returns 100
* - JPY (0 decimal places) → returns 1
*
* @since 1.9.5
*
* @param string $currency Currency.
*
* @return int
*/
function wpforms_get_currency_multiplier( string $currency = '' ): int {
if ( ! $currency ) {
$currency = wpforms_get_currency();
}
return (int) str_pad( 1, wpforms_get_currency_decimals( strtolower( $currency ) ) + 1, 0, STR_PAD_RIGHT );
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Helper functions to perform various plugins and addons related actions.
*
* @since 1.8.2.2
*/
use WPForms\Requirements\Requirements;
/**
* Check if addon met requirements.
*
* @since 1.8.2.2
*
* @param array $requirements Addon requirements.
*
* @return bool
*/
function wpforms_requirements( array $requirements ): bool {
return Requirements::get_instance()->validate( $requirements );
}
/**
* Determine if an addon is active and passed all requirements.
*
* @since 1.9.2
*
* @param string $addon_slug Addon slug without `wpforms-` prefix.
*
* @return bool
*/
function wpforms_is_addon_initialized( string $addon_slug ): bool {
$basename = sprintf( 'wpforms-%1$s/wpforms-%1$s.php', $addon_slug );
if ( is_multisite() ) {
$active_plugins = (array) get_option( 'active_plugins', [] );
if ( in_array( $basename, $active_plugins, true ) ) {
return true;
}
}
return Requirements::get_instance()->is_validated( $basename );
}
/**
* Check addon requirements and activate addon or plugin.
*
* @since 1.8.4
* @since 1.9.2 Keep addons active even if they don't meet requirements.
*
* @param string $plugin Path to the plugin file relative to the plugins' directory.
*
* @return null|WP_Error Null on success, WP_Error on invalid file.
*/
function wpforms_activate_plugin( string $plugin ) {
$activate = activate_plugin( $plugin );
if ( is_wp_error( $activate ) ) {
return $activate;
}
$requirements = Requirements::get_instance();
if ( $requirements->is_validated( $plugin ) ) {
return null;
}
return new WP_Error( 'wpforms_addon_incompatible', $requirements->get_notice( $plugin ) );
}
/**
* Compares two "PHP-standardized" version number strings.
*
* Removes any "-RCn", "-beta" from version numbers first.
*
* @since 1.9.4
*
* @param string $version1 Version number.
* @param string $version2 Version number.
* @param string $operator Comparison operator.
*
* @return bool
*/
function wpforms_version_compare( $version1, $version2, $operator ): bool {
// If the version is not a string, return false.
if ( ! is_string( $version1 ) || ! is_string( $version2 ) ) {
return false;
}
// Strip dash and anything after it.
$clean_version_number = function ( $version ) {
return preg_replace( '/-.+/', '', $version );
};
return version_compare(
$clean_version_number( $version1 ),
$clean_version_number( $version2 ),
$operator
);
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Helper functions related to privacy, geolocation and user data.
*
* @since 1.8.0
*/
/**
* Get the user IP address.
*
* @since 1.2.5
* @since 1.7.3 Improve the IP detection quality by taking care of proxies (e.g. when the site is behind Cloudflare).
*
* Code based on the:
* - WordPress method \WP_Community_Events::get_unsafe_client_ip
* - Cloudflare documentation https://support.cloudflare.com/hc/en-us/articles/206776727
*
* @return string
*/
function wpforms_get_ip(): string {
$ip = '127.0.0.1';
$address_headers = [
'HTTP_TRUE_CLIENT_IP',
'HTTP_CF_CONNECTING_IP',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
];
foreach ( $address_headers as $header ) {
if ( empty( $_SERVER[ $header ] ) ) {
continue;
}
/*
* HTTP_X_FORWARDED_FOR can contain a chain of comma-separated addresses, with or without spaces.
* The first address is the original client. It can't be trusted for authenticity,
* but we don't need to for this purpose.
*/
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$address_chain = explode( ',', wp_unslash( $_SERVER[ $header ] ) );
$ip = filter_var( trim( $address_chain[0] ), FILTER_VALIDATE_IP );
break;
}
/**
* Filter detected IP address.
*
* @since 1.2.5
*
* @param string $ip IP address.
*/
return (string) filter_var( apply_filters( 'wpforms_get_ip', $ip ), FILTER_VALIDATE_IP );
}
/**
* Determine if collecting user's IP is allowed by GDPR setting (globally or per form).
* Majority of our users have GDPR disabled.
* So we remove this data from the request only when it's not needed:
* 1) when GDPR is enabled AND globally disabled user details storage;
* 2) when GDPR is enabled AND IP address processing is disabled on per form basis.
*
* @since 1.6.6
*
* @param array $form_data Form settings.
*
* @return bool
*/
function wpforms_is_collecting_ip_allowed( $form_data = [] ) {
if (
wpforms_setting( 'gdpr', false ) &&
(
wpforms_setting( 'gdpr-disable-details', false ) ||
( ! empty( $form_data ) && ! empty( $form_data['settings']['disable_ip'] ) )
)
) {
return false;
}
return true;
}
/**
* Determine if collecting cookies is allowed by GDPR setting.
*
* @since 1.7.5
*
* @return bool
*/
function wpforms_is_collecting_cookies_allowed() {
return ! ( wpforms_setting( 'gdpr', false ) && wpforms_setting( 'gdpr-disable-uuid', false ) );
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Helper functions to work with Providers API.
*
* @since 1.8.0
*/
/**
* 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', [] );
}
/**
* 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 ) ) {
$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 ] );
}
/**
* A collection of service providers used for dependency injection or service registration
* within the application.
*
* @since 1.9.6
*
* @param array $providers List of all registered providers.
* @param string $provider Provider slug.
* @param array $options Provider options data. If false - provider will be removed.
* @param string $id Provider connection ID.
*/
$providers = (array) apply_filters( 'wpforms_update_providers_options', $providers, $provider, $options, $id );
update_option( 'wpforms_providers', $providers );
}

View File

@@ -0,0 +1,438 @@
<?php
/**
* Generic helper functions.
*
* @since 1.8.0
*/
use WPForms\Helpers\Chain;
/**
* Get a suffix for assets, `.min` if debug is disabled.
*
* @since 1.4.1
*
* @return string
*/
function wpforms_get_min_suffix(): string {
$script_debug = false;
if ( ( defined( 'WPFORMS_SCRIPT_DEBUG' ) && WPFORMS_SCRIPT_DEBUG ) && is_super_admin() ) {
$script_debug = true;
}
/**
* Filters wpforms script debug status.
*
* @since 1.9.9
*
* @param bool $script_debug WPForms script debug status.
*/
$script_debug = (bool) apply_filters( 'wpforms_script_debug', $script_debug );
return $script_debug ? '' : '.min';
}
/**
* Chain monad, useful for chaining certain array or string related functions.
*
* @since 1.5.6
*
* @param mixed $value Any data.
*
* @return Chain
*/
function wpforms_chain( $value ) {
return Chain::of( $value );
}
/**
* Convert object to an array.
*
* @since 1.1.7
*
* @param object $object Object to convert.
*
* @return mixed
*/
function wpforms_object_to_array( $object ) {
if ( ! is_object( $object ) && ! is_array( $object ) ) {
return $object;
}
if ( is_object( $object ) ) {
$object = get_object_vars( $object );
}
return array_map( 'wpforms_object_to_array', $object );
}
/**
* 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 ( $position === 'after' ) {
$key_pos ++;
}
if ( $key_pos !== false ) {
$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 Any 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;
}
/**
* 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;
}
/**
* Link a list of words or phrases with commas, but the last one with a conjunction.
*
* For example:
* [ 'Sullie', 'Pattie', 'me' ] with 'and' conjunction becomes 'Sullie, Pattie and me'.
* [ 'Sullie', 'Pattie', 'me' ] with 'or' conjunction becomes 'Sullie, Pattie or me'.
*
* @since 1.8.0
*
* @param array $list A list of words or phrases to link together.
* @param string $conjunction Coordinating conjunction to use for last word or phrase (usually and, or).
* The string is expected to be translatable.
*
* @return string Linked words and/or phrases.
*/
function wpforms_conjunct( $list, $conjunction ) {
$last_chunk = array_pop( $list );
return $list ?
sprintf( '%s %s %s', implode( ', ', $list ), $conjunction, $last_chunk ) :
$last_chunk;
}
/**
* Get the current URL.
*
* @since 1.0.0
* @since 1.7.2 Refactored based on the `home_url` function.
*
* @return string
*/
function wpforms_current_url() {
$parsed_home_url = wp_parse_url( home_url() );
$url = $parsed_home_url['scheme'] . '://' . $parsed_home_url['host'];
if ( ! empty( $parsed_home_url['port'] ) ) {
$url .= ':' . $parsed_home_url['port'];
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$url .= wp_unslash( $_SERVER['REQUEST_URI'] );
return esc_url_raw( $url );
}
/**
* Add UTM tags to a link that allows detecting traffic sources for our or partners' websites.
*
* @since 1.7.5
*
* @param string $link Link to which you need to add UTM tags.
* @param string $medium The page or location description. Check your current page and try to find
* and use an already existing medium for links otherwise, use a page name.
* @param string $content The feature's name, the button's content, the link's text, or something
* else that describes the element that contains the link.
* @param string $term Additional information for the content that makes the link more unique.
*
* @return string
*/
function wpforms_utm_link( $link, $medium, $content = '', $term = '' ) {
return add_query_arg(
array_filter(
[
'utm_campaign' => wpforms_is_pro() ? 'plugin' : 'liteplugin',
'utm_source' => strpos( $link, 'https://wpforms.com' ) === 0 ? 'WordPress' : 'wpformsplugin',
'utm_medium' => rawurlencode( $medium ),
'utm_content' => rawurlencode( $content ),
'utm_term' => rawurlencode( $term ),
'utm_locale' => wpforms_sanitize_key( get_locale() ),
]
),
$link
);
}
/**
* Get the link to the WPForms review page on WordPress.org.
*
* @since 1.9.8.6
*
* @return string
*/
function wpforms_wp_org_review_link(): string {
$link = 'https://wordpress.org/support/plugin/wpforms-lite/reviews/#new-post';
$link = add_query_arg(
[ 'filter' => 5 ],
$link
);
return esc_url( $link );
}
/**
* Check if it is a Pro version of the plugin.
* This function be used on very early stage of WPForms loading, to check the requirements.
* At requirements' check, the wpforms() function is not defined.
*
* @since 1.9.6
*
* @return bool
*/
function wpforms_is_pro(): bool {
if ( function_exists( 'wpforms' ) ) {
return wpforms()->is_pro();
}
// This filter is documented in \WPForms\WPForms::is_pro.
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return apply_filters( 'wpforms_allow_pro_version', file_exists( WPFORMS_PLUGIN_DIR . 'pro/wpforms-pro.php' ) );
}
/**
* Modify the default USer-Agent generated by wp_remote_*() to include additional information.
*
* @since 1.7.5.2
*
* @return string
*/
function wpforms_get_default_user_agent() {
$license_type = wpforms()->is_pro() ? ucwords( (string) wpforms_get_license_type() ) : 'Lite';
return 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) . '; WPForms/' . $license_type . '-' . WPFORMS_VERSION;
}
/**
* 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;
}
/**
* Changes array of items into string of items, separated by comma and sql-escaped.
*
* @see https://coderwall.com/p/zepnaw
*
* @since 1.7.4
*
* @param mixed|array $items Item(s) to be joined into string.
* @param string $format Can be %s or %d.
*
* @return string Items separated by comma and sql-escaped.
*/
function wpforms_wpdb_prepare_in( $items, $format = '%s' ) {
global $wpdb;
$items = (array) $items;
$how_many = count( $items );
if ( $how_many === 0 ) {
return '';
}
$placeholders = array_fill( 0, $how_many, $format );
$prepared_format = implode( ',', $placeholders );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->prepare( $prepared_format, $items );
}
/**
* Get the render engine slug according to the Modern Markup setting value and corresponding filter.
*
* @since 1.8.1
*
* @return string
*/
function wpforms_get_render_engine() {
$render_engine = empty( wpforms_setting( 'modern-markup', false ) ) ? 'classic' : 'modern';
/**
* Filter current render engine slug.
* Allows addons to use their own frontend rendering engine.
*
* @since 1.8.1
*
* @param string $render_engine Render engine slug.
*/
$render_engine = apply_filters( 'wpforms_get_render_engine', $render_engine );
return $render_engine;
}
/**
* Get comma-separated list string from an array.
*
* @since 1.9.0
*
* @param array $arr Array containing a list.
* @param bool $sep Separator of the last element.
*
* @return string
*/
function wpforms_list_array( array $arr, bool $sep = true ): string {
$separator = $sep ?
__( 'and', 'wpforms-lite' ) :
__( 'or', 'wpforms-lite' );
$last = array_slice( $arr, - 1 );
$first = implode( ', ', array_slice( $arr, 0, - 1 ) );
$both = array_filter( array_merge( [ $first ], $last ) );
return implode( ' ' . $separator . ' ', $both );
}
/**
* Retrieve a value from a nested array using a dot-notated path.
* This function navigates through a multidimensional array using a specified
* path and delimiter to fetch the desired value.
* If the path does not exist,
* a default value will be returned.
*
* @since 1.9.8.6
*
* @param array $data The array to search within.
* @param string $path The dot-notated path specifying the array key(s).
* @param mixed $default_value The default value to return if the path does not exist.
* @param string $delimiter The delimiter used to separate path segments (default: '.').
*
* @return mixed The value from the array at the specified path, or the default value if not found.
*/
function wpforms_array_get_by_path( array $data, string $path, $default_value = null, string $delimiter = '.' ) {
if ( $path === '' ) {
return $data;
}
$segments = explode( $delimiter, $path );
$current = $data;
foreach ( $segments as $seg ) {
// Convert "0", "1" to a number for indexed arrays.
$key = is_numeric( $seg ) ? (int) $seg : $seg;
if ( is_array( $current ) && array_key_exists( $key, $current ) ) {
$current = $current[ $key ];
} else {
return $default_value;
}
}
return $current;
}

View File

@@ -0,0 +1,130 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Register and setup WPForms as a Visual Composer element.
*
* @since 1.3.0
*
* @noinspection PhpUndefinedFunctionInspection
*/
function wpforms_visual_composer_shortcode() {
if ( ! is_user_logged_in() ) {
return;
}
$form_obj = wpforms()->obj( 'form' );
if ( ! $form_obj ) {
return;
}
$wpf = $form_obj->get(
'',
[
'orderby' => 'title',
]
);
if ( ! empty( $wpf ) ) {
$forms = [
esc_html__( 'Select a form to display', 'wpforms-lite' ) => '',
];
foreach ( $wpf as $form ) {
$forms[ $form->post_title ] = $form->ID;
}
} else {
$forms = [
esc_html__( 'No forms found', 'wpforms-lite' ) => '',
];
}
vc_map(
[
'name' => esc_html__( 'WPForms', 'wpforms-lite' ),
'base' => 'wpforms',
'icon' => WPFORMS_PLUGIN_URL . 'assets/images/sullie-vc.png',
'category' => esc_html__( 'Content', 'wpforms-lite' ),
'description' => esc_html__( 'Add your form', 'wpforms-lite' ),
'params' => [
[
'type' => 'dropdown',
'heading' => esc_html__( 'Form', 'wpforms-lite' ),
'param_name' => 'id',
'value' => $forms,
'save_always' => true,
'description' => esc_html__( 'Select a form to add it to your post or page.', 'wpforms-lite' ),
'admin_label' => true,
],
[
'type' => 'dropdown',
'heading' => esc_html__( 'Display Form Name', 'wpforms-lite' ),
'param_name' => 'title',
'value' => [
// phpcs:ignore WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
esc_html__( 'No', 'wpforms-lite' ) => 'false',
esc_html__( 'Yes', 'wpforms-lite' ) => 'true',
],
'save_always' => true,
'description' => esc_html__( 'Would you like to display the forms name?', 'wpforms-lite' ),
'dependency' => [
'element' => 'id',
'not_empty' => true,
],
],
[
'type' => 'dropdown',
'heading' => esc_html__( 'Display Form Description', 'wpforms-lite' ),
'param_name' => 'description',
'value' => [
// phpcs:ignore WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
esc_html__( 'No', 'wpforms-lite' ) => 'false',
esc_html__( 'Yes', 'wpforms-lite' ) => 'true',
],
'save_always' => true,
'description' => esc_html__( 'Would you like to display the form description?', 'wpforms-lite' ),
'dependency' => [
'element' => 'id',
'not_empty' => true,
],
],
],
]
);
}
add_action( 'vc_before_init', 'wpforms_visual_composer_shortcode' );
/**
* Load our basic CSS when in Visual Composer's frontend editor.
*
* @since 1.3.0
*/
function wpforms_visual_composer_shortcode_css() {
$min = wpforms_get_min_suffix();
// Load CSS per global setting.
if ( wpforms_setting( 'disable-css', '1' ) === '1' ) {
wp_enqueue_style(
'wpforms-full',
WPFORMS_PLUGIN_URL . "assets/css/frontend/classic/wpforms-full{$min}.css",
[],
WPFORMS_VERSION
);
}
if ( wpforms_setting( 'disable-css', '1' ) === '2' ) {
wp_enqueue_style(
'wpforms-base',
WPFORMS_PLUGIN_URL . "assets/css/frontend/classic/wpforms-base{$min}.css",
[],
WPFORMS_VERSION
);
}
}
add_action( 'vc_load_iframe_jscss', 'wpforms_visual_composer_shortcode_css' );

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,969 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Admin\Notice;
use WPForms\Integrations\ConstantContact\V3\Core;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Constant Contact integration.
*
* @since 1.3.6
*/
class WPForms_Constant_Contact extends WPForms_Provider {
/**
* Current form ID.
*
* @since 1.9.0.4
*
* @var int
*/
private $form_id = 0;
/**
* Current entry ID.
*
* @since 1.9.0.4
*
* @var int
*/
private $entry_id = 0;
/**
* Provider access token.
*
* @since 1.3.6
*
* @var string
*/
public $access_token;
/**
* Provider API key.
*
* @since 1.3.6
*
* @var string
*/
public $api_key = 'c58xq3r27udz59h9rrq7qnvf';
/**
* Sign up link.
*
* @since 1.3.6
*
* @var string
*/
public $sign_up = 'https://constant-contact.evyy.net/c/11535/341874/3411?sharedid=wpforms';
/**
* Constructor.
*
* Empty to overload parent constructor and allow method to be instantiated without running parents' logic.
*
* @since 1.9.3
*
* @noinspection MagicMethodsValidityInspection
* @noinspection PhpMissingParentConstructorInspection
*/
public function __construct() {}
/**
* Setup.
*
* @since 1.9.3
*/
public function setup() {
parent::__construct();
}
/**
* Initialize.
*
* @since 1.3.6
*/
public function init() { //phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
$name_append = ( defined( 'WPFORMS_DEBUG' ) && WPFORMS_DEBUG ) ? ' (V2)' : '';
$this->version = '1.3.6';
$this->name = 'Constant Contact' . $name_append;
$this->slug = 'constant-contact';
$this->priority = 14;
$this->icon = WPFORMS_PLUGIN_URL . 'assets/images/icon-provider-constant-contact.png';
if ( is_admin() ) {
// Admin notice requesting connecting.
$this->connect_request();
add_action( 'wpforms_admin_notice_dismiss_ajax', [ $this, 'connect_dismiss' ] );
add_filter(
"wpforms_providers_provider_settings_formbuilder_display_content_default_screen_{$this->slug}",
[ $this, 'builder_settings_default_content' ]
);
// Provide option to override sign up link.
$sign_up = get_option( 'wpforms_constant_contact_signup', false );
if ( $sign_up ) {
$this->sign_up = esc_html( $sign_up );
}
}
}
/**
* Process and submit entry to provider.
*
* @since 1.3.6
*
* @param array $fields List of fields with their data and settings.
* @param array $entry Submitted entry values.
* @param array $form_data Form data and settings.
* @param int $entry_id Saved entry ID.
*
* @return void
*/
public function process_entry( $fields, $entry, $form_data, $entry_id = 0 ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded
// Only run if this form has a connections for this provider.
if ( empty( $form_data['providers'][ $this->slug ] ) ) {
return;
}
/*
* Fire for each connection.
*/
foreach ( $form_data['providers'][ $this->slug ] as $connection ) :
// Before proceeding make sure required fields are configured.
if ( empty( $connection['fields']['email'] ) ) {
continue;
}
// Setup basic data.
$list_id = $connection['list_id'];
$account_id = $connection['account_id'];
$email_data = explode( '.', $connection['fields']['email'] );
$email_id = $email_data[0];
$email = $fields[ $email_id ]['value'];
$this->api_connect( $account_id );
// Email is required and Access token are required.
if ( empty( $email ) || empty( $this->access_token ) ) {
continue;
}
// Check for conditionals.
$pass = $this->process_conditionals( $fields, $entry, $form_data, $connection );
if ( ! $pass ) {
wpforms_log(
sprintf( 'The Constant Contact connection %s was not processed due to conditional logic.', $connection['name'] ?? '' ),
$fields,
[
'type' => [ 'provider', 'conditional_logic' ],
'parent' => $entry_id,
'form_id' => $form_data['id'],
]
);
continue;
}
$this->form_id = $form_data['id'] ?? 0;
$this->entry_id = $entry_id;
$contact = $this->request(
add_query_arg( 'email', rawurlencode( $email ), 'https://api.constantcontact.com/v2/contacts' )
);
if ( is_wp_error( $contact ) ) {
continue;
}
/*
* Setup Merge Vars
*/
$merge_vars = [];
foreach ( $connection['fields'] as $name => $merge_var ) {
// Don't include Email or Full name fields.
if ( $name === 'email' ) {
continue;
}
// Check if merge var is mapped.
if ( empty( $merge_var ) ) {
continue;
}
$merge_var = explode( '.', $merge_var );
$id = $merge_var[0];
$key = ! empty( $merge_var[1] ) ? $merge_var[1] : 'value';
// Check if mapped form field has a value.
if ( empty( $fields[ $id ][ $key ] ) ) {
continue;
}
$value = $fields[ $id ][ $key ];
// Constant Contact doesn't native URL field so it has to be
// stored in a custom field.
if ( $name === 'url' ) {
$merge_vars['custom_fields'] = [
[
'name' => 'custom_field_1',
'value' => $value,
],
];
continue;
}
// Constant Contact stores name in two fields, so we have to
// separate it.
if ( $name === 'full_name' ) {
$names = explode( ' ', $value );
if ( ! empty( $names[0] ) ) {
$merge_vars['first_name'] = $names[0];
}
if ( ! empty( $names[1] ) ) {
$merge_vars['last_name'] = $names[1];
}
continue;
}
// Constant Contact stores address in multiple fields, so we
// have to separate it.
if ( $name === 'address' ) {
// Only support Address fields.
if ( $fields[ $id ]['type'] !== 'address' ) {
continue;
}
// Postal code may be in extended US format.
$postal = [
'code' => '',
'subcode' => '',
];
if ( ! empty( $fields[ $id ]['postal'] ) ) {
$p = explode( '-', $fields[ $id ]['postal'] );
$postal['code'] = ! empty( $p[0] ) ? $p[0] : '';
$postal['subcode'] = ! empty( $p[1] ) ? $p[1] : '';
}
$merge_vars['addresses'] = [
[
'address_type' => 'BUSINESS',
'city' => ! empty( $fields[ $id ]['city'] ) ? $fields[ $id ]['city'] : '',
'country_code' => ! empty( $fields[ $id ]['country'] ) ? $fields[ $id ]['country'] : '',
'line1' => ! empty( $fields[ $id ]['address1'] ) ? $fields[ $id ]['address1'] : '',
'line2' => ! empty( $fields[ $id ]['address2'] ) ? $fields[ $id ]['address2'] : '',
'postal_code' => $postal['code'],
'state' => ! empty( $fields[ $id ]['state'] ) ? $fields[ $id ]['state'] : '',
'sub_postal_code' => $postal['subcode'],
],
];
continue;
}
$merge_vars[ $name ] = $value;
}
/*
* Process in API
*/
// If we have a previous contact, only update the list association.
if ( ! empty( $contact['results'] ) ) {
$data = $contact['results'][0];
// Check if they are already assigned to lists.
if ( ! empty( $data['lists'] ) ) {
$has_list = false;
foreach ( $data['lists'] as $list ) {
if ( isset( $list['id'] ) && (string) $list_id === (string) $list['id'] ) {
$has_list = true;
}
}
if ( ! $has_list ) {
$data['lists'][ count( $data['lists'] ) ] = [
'id' => $list_id,
'status' => 'ACTIVE',
];
}
} else {
// Add the contact to the list.
$data['lists'][0] = [
'id' => $list_id,
'status' => 'ACTIVE',
];
}
// Combine merge vars into data before sending.
$data = array_merge( $data, $merge_vars );
// Args to use.
$args = [
'body' => $data,
'method' => 'PUT',
];
$this->request( 'https://api.constantcontact.com/v2/contacts/' . $data['id'] . '?action_by=ACTION_BY_VISITOR', $args );
} else {
// Add a new contact.
$data = [
'email_addresses' => [ [ 'email_address' => $email ] ],
'lists' => [ [ 'id' => $list_id ] ],
];
// Combine merge vars into data before sending.
$data = array_merge( $data, $merge_vars );
// Args to use.
$args = [
'body' => $data,
'method' => 'POST',
];
$this->request( 'https://api.constantcontact.com/v2/contacts?action_by=ACTION_BY_VISITOR', $args );
}
endforeach;
}
/************************************************************************
* API methods - these methods interact directly with the provider API. *
************************************************************************/
/**
* Authenticate with the API.
*
* @since 1.3.6
*
* @param array $data Contact data.
* @param string $form_id Form ID.
*
* @return WP_Error|string Unique ID or error object
* @noinspection NonSecureUniqidUsageInspection
*/
public function api_auth( $data = [], $form_id = '' ) {
$this->form_id = (int) $form_id;
$this->access_token = $data['authcode'] ?? '';
$user = $this->get_account_information();
if ( is_wp_error( $user ) ) {
return $user;
}
$id = uniqid();
wpforms_update_providers_options(
$this->slug,
[
'access_token' => sanitize_text_field( $data['authcode'] ),
'label' => sanitize_text_field( $data['label'] ),
'date' => time(),
'email' => sanitize_text_field( $user['email'] ),
],
$id
);
return $id;
}
/**
* Get account information.
*
* @since 1.7.6
*
* @return array|WP_Error
*/
public function get_account_information() {
return $this->request( 'https://api.constantcontact.com/v2/account/info' );
}
/**
* Establish connection object to API.
*
* @since 1.3.6
*
* @param string $account_id Account ID.
*
* @return mixed array or error object.
*/
public function api_connect( $account_id ) {
if ( ! empty( $this->api[ $account_id ] ) ) {
return $this->api[ $account_id ];
}
$providers = wpforms_get_providers_options();
if ( ! empty( $providers[ $this->slug ][ $account_id ] ) ) {
$this->api[ $account_id ] = true;
$this->access_token = $providers[ $this->slug ][ $account_id ]['access_token'];
} else {
return $this->error( 'API error' );
}
}
/**
* Retrieve provider account lists.
*
* @since 1.3.6
*
* @param string $connection_id Connection ID.
* @param string $account_id Account ID.
*
* @return array|WP_Error array or error object
*/
public function api_lists( $connection_id = '', $account_id = '' ) {
if ( $account_id && empty( $this->access_token ) ) {
$this->api_connect( $account_id );
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$this->form_id = ! empty( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
return $this->request( 'https://api.constantcontact.com/v2/lists' );
}
/**
* Retrieve provider account list fields.
*
* @since 1.3.6
*
* @param string $connection_id Connection ID.
* @param string $account_id Account ID.
* @param string $list_id List ID.
*
* @return array array or error object
*/
public function api_fields( $connection_id = '', $account_id = '', $list_id = '' ) {
return [
[
'name' => 'Email',
'field_type' => 'email',
'req' => '1',
'tag' => 'email',
],
[
'name' => 'Full Name',
'field_type' => 'name',
'tag' => 'full_name',
],
[
'name' => 'First Name',
'field_type' => 'first',
'tag' => 'first_name',
],
[
'name' => 'Last Name',
'field_type' => 'last',
'tag' => 'last_name',
],
[
'name' => 'Phone',
'field_type' => 'text',
'tag' => 'work_phone',
],
[
'name' => 'Website',
'field_type' => 'text',
'tag' => 'url',
],
[
'name' => 'Address',
'field_type' => 'address',
'tag' => 'address',
],
[
'name' => 'Job Title',
'field_type' => 'text',
'tag' => 'job_title',
],
[
'name' => 'Company',
'field_type' => 'text',
'tag' => 'company_name',
],
];
}
/*************************************************************************
* Output methods - these methods generally return HTML for the builder. *
*************************************************************************/
/**
* Provider account authorize fields HTML.
*
* @since 1.3.6
*
* @return string
*/
public function output_auth() {
$providers = wpforms_get_providers_options();
$class = ! empty( $providers[ $this->slug ] ) ? 'hidden' : '';
ob_start();
?>
<div class="wpforms-provider-account-add <?php echo sanitize_html_class( $class ); ?> wpforms-connection-block">
<h4><?php esc_html_e( 'Add New Account', 'wpforms-lite' ); ?></h4>
<p>
<?php esc_html_e( 'Please fill out all of the fields below to register your new Constant Contact account.', 'wpforms-lite' ); ?>
<br>
<a href="<?php echo esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-connect-constant-contact-with-wpforms/', 'Marketing Integrations', 'Constant Contact Documentation' ) ); ?>" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Click here for documentation on connecting WPForms with Constant Contact.', 'wpforms-lite' ); ?>
</a>
</p>
<p class="wpforms-alert wpforms-alert-warning">
<?php esc_html_e( 'Because Constant Contact requires external authentication, you will need to register WPForms with Constant Contact before you can proceed.', 'wpforms-lite' ); ?>
</p>
<p>
<strong>
<a onclick="window.open(this.href,'','resizable=yes,location=no,width=750,height=500,status'); return false" href="https://oauth2.constantcontact.com/oauth2/oauth/siteowner/authorize?response_type=code&client_id=c58xq3r27udz59h9rrq7qnvf&redirect_uri=https://wpforms.com/oauth/constant-contact/" class="btn">
<?php esc_html_e( 'Click here to register with Constant Contact', 'wpforms-lite' ); ?>
</a>
</strong>
</p>
<?php
printf(
'<input type="text" data-name="authcode" placeholder="%s %s *" class="wpforms-required">',
esc_attr( $this->name ),
esc_attr__( 'Authorization Code', 'wpforms-lite' )
);
printf(
'<input type="text" data-name="label" placeholder="%s %s *" class="wpforms-required">',
esc_attr( $this->name ),
esc_attr__( 'Account Nickname', 'wpforms-lite' )
);
printf(
'<button data-provider="%s">%s</button>',
esc_attr( $this->slug ),
esc_html__( 'Connect', 'wpforms-lite' )
);
?>
</div>
<?php
return ob_get_clean();
}
/**
* Provider account list groups HTML.
*
* @since 1.3.6
*
* @param string $connection_id Connection ID.
* @param array $connection Connection data.
*
* @return string
*/
public function output_groups( $connection_id = '', $connection = [] ) {
// No groups or segments for this provider.
return '';
}
/**
* Default content for the provider settings panel in the form builder.
*
* @since 1.6.8
*
* @param string $content Default content.
*
* @return string
* @noinspection HtmlUnknownTarget
*/
public function builder_settings_default_content( $content ) {
ob_start();
?>
<p>
<a href="<?php echo esc_url( $this->sign_up ); ?>" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Try Constant Contact for Free', 'wpforms-lite' ); ?>
</a>
</p>
<p>
<?php
printf(
'<a href="%s" target="_blank" rel="noopener noreferrer" class="secondary-text">%s</a>',
esc_url( admin_url( 'admin.php?page=wpforms-page&view=constant-contact' ) ),
esc_html__( 'Learn more about the power of email marketing.', 'wpforms-lite' )
);
?>
</p>
<?php
return $content . ob_get_clean();
}
/**
* Display content inside the panel sidebar area.
*
* @since 1.0.0
*/
public function builder_sidebar() {
if ( ! empty( wpforms_get_providers_options( Core::SLUG ) ) ) {
return;
}
parent::builder_sidebar();
}
/*************************************************************************
* Integrations tab methods - these methods relate to the settings page. *
*************************************************************************/
/**
* AJAX to add a provider from the settings integrations tab.
*
* @since 1.7.6
*/
public function integrations_tab_add() {
// phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
if ( $_POST['provider'] !== $this->slug ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
$data = ! empty( $_POST['data'] ) ? wp_parse_args( wp_unslash( $_POST['data'] ) ) : [];
if ( empty( $data['authcode'] ) ) {
wp_send_json_error(
[
'error_msg' => esc_html__( 'The "Authorization Code" is required.', 'wpforms-lite' ),
]
);
}
if ( empty( $data['label'] ) ) {
wp_send_json_error(
[
'error_msg' => esc_html__( 'The "Account Nickname" is required.', 'wpforms-lite' ),
]
);
}
parent::integrations_tab_add();
}
/**
* Form fields to add a new provider account.
*
* @since 1.3.6
* @noinspection HtmlUnknownTarget
*/
public function integrations_tab_new_form() {
printf(
'<p>' . wp_kses( /* translators: %1$s - Documentation URL. */
__(
'If you need help connecting WPForms to Constant Contact, <a href="%1$s" rel="noopener noreferrer" target="_blank">read our documentation</a>.',
'wpforms-lite'
),
[
'a' => [
'href' => [],
'rel' => [],
'target' => [],
],
]
) . '</p>',
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-connect-constant-contact-with-wpforms/', 'Settings - Integration', 'Constant Contact Documentation' ) )
);
?>
<p class="wpforms-alert wpforms-alert-warning">
<?php esc_html_e( 'Because Constant Contact requires external authentication, you will need to register WPForms with Constant Contact before you can proceed.', 'wpforms-lite' ); ?>
</p>
<p>
<strong>
<a onclick="window.open(this.href,'','resizable=yes,location=no,width=800,height=600,status'); return false" href="https://oauth2.constantcontact.com/oauth2/oauth/siteowner/authorize?response_type=code&client_id=c58xq3r27udz59h9rrq7qnvf&redirect_uri=https://wpforms.com/oauth/constant-contact/" class="btn">
<?php esc_html_e( 'Click here to register with Constant Contact', 'wpforms-lite' ); ?>
</a>
</strong>
</p>
<?php
printf(
'<input type="text" name="authcode" placeholder="%s %s *" class="wpforms-required">',
esc_attr( $this->name ),
esc_attr__( 'Authorization Code', 'wpforms-lite' )
);
printf(
'<input type="text" name="label" placeholder="%s %s *" class="wpforms-required">',
esc_attr( $this->name ),
esc_attr__( 'Account Nickname', 'wpforms-lite' )
);
}
/**
* Add provider to the Settings Integrations tab.
*
* @since 1.9.3
*
* @param array $active Array of active connections.
* @param array $settings Array of all connection settings.
*/
public function integrations_tab_options( $active, $settings ) {
if ( ! empty( wpforms_get_providers_options( Core::SLUG ) ) ) {
return;
}
parent::integrations_tab_options( $active, $settings );
}
/************************
* Other functionality. *
************************/
/**
* Add admin notices to connect to Constant Contact.
*
* @since 1.3.6
* @noinspection HtmlUnknownTarget
*/
public function connect_request() {
// Only consider showing the review request to admin users.
if ( ! is_super_admin() ) {
return;
}
// Don't display on WPForms admin content pages.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_GET['wpforms-page'] ) ) {
return;
}
// Don't display if user is about to connect via Settings page.
if ( ! empty( $_GET['wpforms-integration'] ) && $this->slug === $_GET['wpforms-integration'] ) {
return;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// Only display the notice if the Constant Contact option is set and
// there are previous Constant Contact connections created.
// Please do not delete 'wpforms_constant_contact' option check from the code.
$cc_notice = get_option( 'wpforms_constant_contact', false );
$providers = wpforms_get_providers_options();
if ( ! $cc_notice || ! empty( $providers[ $this->slug ] ) ) {
return;
}
// Output the notice message.
$connect = admin_url( 'admin.php?page=wpforms-settings&view=integrations&wpforms-integration=constant-contact#!wpforms-tab-providers' );
$learn_more = admin_url( 'admin.php?page=wpforms-page&view=constant-contact' );
ob_start();
?>
<p>
<?php
echo wp_kses(
__( 'Get the most out of the <strong>WPForms</strong> plugin &mdash; use it with an active Constant Contact account.', 'wpforms-lite' ),
[
'strong' => [],
]
);
?>
</p>
<p>
<a href="<?php echo esc_url( $this->sign_up ); ?>" class="button-primary" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Try Constant Contact for Free', 'wpforms-lite' ); ?>
</a>
<a href="<?php echo esc_url( $connect ); ?>" class="button-secondary">
<?php esc_html_e( 'Connect your existing account', 'wpforms-lite' ); ?>
</a>
<?php
echo wp_kses(
sprintf( /* translators: %s - WPForms Constant Contact internal URL. */
__( 'Learn More about the <a href="%s">power of email marketing</a>', 'wpforms-lite' ),
esc_url( $learn_more )
),
[
'a' => [
'href' => [],
],
]
);
?>
</p>
<style>
.wpforms-constant-contact-notice p:first-of-type {
margin: 16px 0 8px;
}
.wpforms-constant-contact-notice p:last-of-type {
margin: 8px 0 16px;
}
.wpforms-constant-contact-notice .button-primary,
.wpforms-constant-contact-notice .button-secondary {
margin: 0 10px 0 0;
}
</style>
<?php
$notice = ob_get_clean();
Notice::info(
$notice,
[
'dismiss' => Notice::DISMISS_GLOBAL,
'slug' => 'constant_contact_connect',
'autop' => false,
'class' => 'wpforms-constant-contact-notice',
]
);
}
/**
* Dismiss the Constant Contact admin notice.
*
* @since 1.3.6
* @since 1.6.7.1 Added parameter $notice_id.
*
* @param string $notice_id Notice ID (slug).
*/
public function connect_dismiss( $notice_id = '' ) {
if ( $notice_id !== 'global-constant_contact_connect' ) {
return;
}
delete_option( 'wpforms_constant_contact' );
wp_send_json_success();
}
/**
* Request to the Constant Contact API.
*
* @since 1.9.0.4
*
* @param string $url Request URL.
* @param array $args Request arguments.
*
* @return array|WP_Error
*/
private function request( string $url, array $args = [] ) {
$args['method'] = $args['method'] ?? 'GET';
$args['headers']['Authorization'] = 'Bearer ' . $this->access_token;
$args['headers']['Content-Type'] = 'application/json';
if ( isset( $args['body'] ) ) {
$args['body'] = wp_json_encode( $args['body'] );
}
$url = add_query_arg( 'api_key', $this->api_key, $url );
$response = wp_remote_request( $url, $args );
$response = is_wp_error( $response ) ? $response : (array) $response;
return $this->process_response( $response );
}
/**
* Process response.
*
* @since 1.9.0.4
*
* @param array|WP_Error $response Response.
*
* @return array|WP_Error
*/
public function process_response( $response ) {
if ( is_wp_error( $response ) ) {
$this->log_error( $response );
return $response;
}
// Body may be set here to an array or null.
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body ) || isset( $body[0]['error_key'] ) ) {
$error_message = $body[0]['error_message'] ?? '';
$error = new WP_Error( $this->slug . '_error', $error_message );
$this->log_error( $error );
return $error;
}
return $body;
}
/**
* Log error message.
*
* @since 1.9.0.4
*
* @param WP_Error $error Error.
*
* @return void
*/
public function log_error( WP_Error $error ) {
wpforms_log(
'Constant Contact API Error',
$error->get_error_message(),
[
'type' => [ 'provider', 'error' ],
'parent' => $this->entry_id,
'form_id' => $this->form_id,
]
);
}
}
( new WPForms_Constant_Contact() )->setup();

View File

@@ -0,0 +1,361 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/**
* Base form template.
*
* @since 1.0.0
*/
abstract class WPForms_Template {
/**
* Full name of the template, e.g. "Contact Form".
*
* @since 1.0.0
*
* @var string
*/
public $name;
/**
* Slug of the template, e.g. "contact-form" - no spaces.
*
* @since 1.0.0
*
* @var string
*/
public $slug;
/**
* Source of the template.
*
* @since 1.6.8
*
* @var array
*/
public $source;
/**
* Categories array.
*
* @since 1.6.8
*
* @var array
*/
public $categories;
/**
* Short description of the template.
*
* @since 1.0.0
*
* @var string
*/
public $description = '';
/**
* Short description of the fields included with the template.
*
* @since 1.0.0
*
* @var string
*/
public $includes = '';
/**
* URL of the icon to display in the admin area.
*
* @since 1.0.0
*
* @var string
*/
public $icon = '';
/**
* Form template preview URL.
*
* @since 1.7.5.3
*
* @var string
*/
public $url = '';
/**
* Form template thumbnail URL.
*
* @since 1.8.2
*
* @var string
*/
public $thumbnail = '';
/**
* Array of data that is assigned to the post_content on form creation.
*
* @since 1.0.0
*
* @var array
*/
public $data;
/**
* Priority to show in the list of available templates.
*
* @since 1.0.0
*
* @var int
*/
public $priority = 20;
/**
* Core or additional template.
*
* @since 1.4.0
*
* @var bool
*/
public $core = false;
/**
* Modal message to display when the template is applied.
*
* @since 1.0.0
*
* @var array
*/
public $modal = '';
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function __construct() {
// Bootstrap.
$this->init();
// Hooks.
$this->hooks();
}
/**
* Let's get started.
*
* @since 1.0.0
*/
public function init() {}
/**
* Register hooks.
*
* @since 1.9.9
*/
private function hooks(): void {
$type = $this->core ? '_core' : '';
add_filter( "wpforms_form_templates{$type}", [ $this, 'template_details' ], $this->priority );
add_filter( 'wpforms_create_form_args', [ $this, 'template_data' ], 10, 2 );
add_filter( 'wpforms_save_form_args', [ $this, 'template_replace' ], 10, 3 );
add_filter( 'wpforms_builder_template_active', [ $this, 'template_active' ], 10, 2 );
}
/**
* Add basic template details to the Add New Form admin screen.
*
* @since 1.0.0
*
* @param array|mixed $templates Templates array.
*
* @return array
*/
public function template_details( $templates ): array {
$templates = (array) $templates;
$templates[] = [
'name' => $this->name,
'slug' => $this->slug,
'source' => $this->source,
'categories' => $this->categories,
'description' => $this->description,
'includes' => $this->includes,
'icon' => $this->icon,
'url' => ! empty( $this->url ) ? $this->url : '',
'plugin_dir' => $this->get_plugin_dir(),
'thumbnail' => ! empty( $this->thumbnail ) ? $this->thumbnail : '',
];
return $templates;
}
/**
* Get the directory name of the plugin in which the current template resides.
*
* @since 1.6.9
*
* @return string
*/
private function get_plugin_dir(): string {
$reflection = new ReflectionClass( $this );
$template_file_path = wp_normalize_path( $reflection->getFileName() );
// Cutting out the WP_PLUGIN_DIR from the beginning of the template file path.
$template_file_path = preg_replace( '{^' . wp_slash( wp_normalize_path( WP_PLUGIN_DIR ) ) . '}', '', $template_file_path );
$template_file_chunks = explode( '/', $template_file_path );
return $template_file_chunks[1];
}
/**
* Add template data when a form is created.
*
* @since 1.0.0
*
* @param array $args Create form arguments.
* @param array $data Template data.
*
* @return array
*/
public function template_data( $args, $data ): array {
if ( empty( $data['template'] ) || $data['template'] !== $this->slug ) {
return $args;
}
// Enable Notifications by default.
$this->data['settings']['notification_enable'] = $this->data['settings']['notification_enable'] ?? '1';
/**
* Allow modifying form data when a template is applied to the new form.
*
* @since 1.9.0
*
* @param array $form_data New form data.
*/
$this->data = (array) apply_filters( 'wpforms_templates_class_base_template_modify_data', $this->data );
$args['post_content'] = wpforms_encode( $this->data );
return $args;
}
/**
* Replace the template on post update if triggered.
*
* @since 1.0.0
*
* @param array $form Form post data.
* @param array $data Form data.
* @param array $args Update form arguments.
*
* @return array
* @noinspection PhpMissingParamTypeInspection
*/
public function template_replace( $form, $data, $args ): array {
// We should proceed only if the template slug passed via $args['template'] is equal to the current template slug.
// This will work only for offline templates: Blank Form, all the Addons Templates, and all the custom templates.
// All the online (modern) templates use the hash as the identifier,
// and they are handled by `\WPForms\Admin\Builder\Templates::apply_to_existing_form()`.
if ( empty( $args['template'] ) || $args['template'] !== $this->slug ) {
return $form;
}
$form_data = wpforms_decode( wp_unslash( $form['post_content'] ) );
// Something is wrong with the form data.
if ( empty( $form_data ) ) {
return $form;
}
// Compile the new form data while preserving the necessary data from the existing form.
$new = $this->data;
$new['id'] = $form_data['id'] ?? 0;
$new['settings'] = $form_data['settings'] ?? [];
$new['payments'] = $form_data['payments'] ?? [];
$new['meta'] = $form_data['meta'] ?? [];
$template_id = $this->data['meta']['template'] ?? '';
// Preserve template ID `wpforms-user-template-{$form_id}` when overwriting it with the core template.
if ( wpforms_is_form_template( $form['ID'] ) ) {
$template_id = $form_data['meta']['template'] ?? '';
}
$new['meta']['template'] = $template_id;
/**
* Allow modifying form data when a template is replaced.
*
* @since 1.7.9
*
* @param array $new Updated form data.
* @param array $form_data Current form data.
* @param array $template Template data.
*/
$new = (array) apply_filters( 'wpforms_templates_class_base_template_replace_modify_data', $new, $form_data, $this );
// Update the form with new data.
$form['post_content'] = wpforms_encode( $new );
return $form;
}
/**
* Pass information about the active template back to the builder.
*
* @since 1.0.0
*
* @param array|mixed $details Details.
* @param WP_Post|null|false $form Form data.
*
* @return array
*/
public function template_active( $details, $form ): array {
$details = (array) $details;
if ( ! $form ) {
return [];
}
$form_data = wpforms_decode( $form->post_content );
if ( empty( $this->modal ) || empty( $form_data['meta']['template'] ) || $this->slug !== $form_data['meta']['template'] ) {
return $details;
}
$display = $this->template_modal_conditional( $form_data );
return [
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'includes' => $this->includes,
'icon' => $this->icon,
'modal' => $this->modal,
'modal_display' => $display,
];
}
/**
* Conditional logic to determine whether the template informational modal screens
* should be displayed.
*
* @since 1.0.0
*
* @param array $form_data Form data and settings.
*
* @return bool
*/
public function template_modal_conditional( $form_data ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return false;
}
}

View File

@@ -0,0 +1,78 @@
<?php
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/** @noinspection PhpIllegalPsrClassPathInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Blank form template.
*
* @since 1.0.0
*/
class WPForms_Template_Blank extends WPForms_Template {
/**
* Template slug.
*
* @since 1.9.2
*/
const SLUG = 'blank';
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
$this->priority = 1;
$this->name = esc_html__( 'Blank Form', 'wpforms-lite' );
$this->slug = 'blank';
$this->source = 'wpforms-core';
$this->categories = 'all';
$this->description = esc_html__( 'The blank form allows you to create any type of form using our drag & drop builder.', 'wpforms-lite' );
$this->includes = '';
$this->icon = '';
$this->modal = '';
$this->core = true;
$this->data = self::get_data();
}
/**
* Get template data.
*
* @since 1.9.2
*
* @return array
*/
public static function get_data(): array {
return [
'field_id' => '1',
'fields' => [],
'settings' => [
'antispam_v3' => '1',
'ajax_submit' => '1',
'confirmation_message_scroll' => '1',
'submit_text_processing' => esc_html__( 'Sending...', 'wpforms-lite' ),
'store_spam_entries' => '1',
'anti_spam' => [
'time_limit' => [
'enable' => '1',
'duration' => '2',
],
],
],
'meta' => [
'template' => self::SLUG,
],
];
}
}
new WPForms_Template_Blank();

View File

@@ -0,0 +1,86 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Simple Contact Form Template for WPForms.
*
* @since 1.7.5.3
*/
class WPForms_Template_Simple_Contact_Form extends WPForms_Template {
/**
* Primary class constructor.
*
* @since 1.7.5.3
*/
public function init() {
$this->name = esc_html__( 'Simple Contact Form', 'wpforms-lite' );
$this->priority = 1;
$this->source = 'wpforms-core';
$this->categories = 'all';
$this->core = true;
$this->slug = 'simple-contact-form-template';
$this->url = 'https://wpforms.com/templates/simple-contact-form-template/';
$this->description = esc_html__( 'Collect the names, emails, and messages from site visitors that need to talk to you.', 'wpforms-lite' );
$this->thumbnail = esc_url( WPFORMS_PLUGIN_URL . 'assets/images/thumbnail-simple-contact-form-template.jpg' );
$this->data = [
'fields' => [
'1' => [
'id' => '1',
'type' => 'name',
'format' => 'first-last',
'label' => esc_html__( 'Name', 'wpforms-lite' ),
'required' => '1',
'size' => 'medium',
],
'2' => [
'id' => '2',
'type' => 'email',
'label' => esc_html__( 'Email', 'wpforms-lite' ),
'required' => '1',
'size' => 'medium',
],
'3' => [
'id' => '3',
'type' => 'textarea',
'label' => esc_html__( 'Comment or Message', 'wpforms-lite' ),
'size' => 'medium',
'placeholder' => '',
'css' => '',
],
],
'field_id' => 4,
'settings' => [
'form_desc' => '',
'submit_text' => esc_html__( 'Submit', 'wpforms-lite' ),
'submit_text_processing' => esc_html__( 'Sending...', 'wpforms-lite' ),
'antispam_v3' => '1',
'notification_enable' => '1',
'notifications' => [
'1' => [
'email' => '{admin_email}',
'replyto' => '{field_id="2"}',
'message' => '{all_fields}',
],
],
'confirmations' => [
'1' => [
'type' => 'message',
'message' => esc_html__( 'Thanks for contacting us! We will be in touch with you shortly.', 'wpforms-lite' ),
'message_scroll' => '1',
],
],
'ajax_submit' => '1',
],
'meta' => [
'template' => $this->slug,
],
];
}
}
new WPForms_Template_Simple_Contact_Form();