Commit realizado el 12:13:52 08-04-2024

This commit is contained in:
Pagina Web Monito
2024-04-08 12:13:55 -04:00
commit 0c33094de9
7815 changed files with 1365694 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
<?php
namespace WPForms\Access;
/**
* Access/Capability management.
*
* @since 1.5.8
*/
class Capabilities {
/**
* Init class.
*
* @since 1.5.8
*/
public function init() {
}
/**
* Init conditions.
*
* @since 1.5.8.2
*/
public function init_allowed() {
return false;
}
/**
* Check permissions for currently logged in user.
*
* @since 1.5.8
*
* @param array|string $caps Capability name(s).
* @param int $id Optional. 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
*/
public function current_user_can( $caps = [], $id = 0 ) {
return \current_user_can( \wpforms_get_capability_manage_options() );
}
/**
* Get a first valid capability from an array of capabilities.
*
* @since 1.5.8
*
* @param array $caps Array of capabilities to check.
*
* @return string
*/
public function get_menu_cap( $caps ) {
return \wpforms_get_capability_manage_options();
}
}

View File

@@ -0,0 +1,436 @@
<?php
namespace WPForms\Admin\Addons;
/**
* Addons data handler.
*
* @since 1.6.6
*/
class Addons {
/**
* Basic license.
*
* @since 1.8.2
*/
const BASIC = 'basic';
/**
* Plus license.
*
* @since 1.8.2
*/
const PLUS = 'plus';
/**
* Pro license.
*
* @since 1.8.2
*/
const PRO = 'pro';
/**
* Elite license.
*
* @since 1.8.2
*/
const ELITE = 'elite';
/**
* Agency license.
*
* @since 1.8.2
*/
const AGENCY = 'agency';
/**
* Ultimate license.
*
* @since 1.8.2
*/
const ULTIMATE = 'ultimate';
/**
* Addons cache object.
*
* @since 1.6.6
*
* @var AddonsCache
*/
private $cache;
/**
* All Addons data.
*
* @since 1.6.6
*
* @var array
*/
private $addons;
/**
* Available addons data.
*
* @since 1.6.6
*
* @var array
*/
private $available_addons;
/**
* Determine if the class is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' );
return $has_permissions && $allowed_requests;
}
/**
* Initialize class.
*
* @since 1.6.6
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->cache = wpforms()->get( 'addons_cache' );
$this->addons = $this->cache->get();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.6
*/
protected function hooks() {
add_action( 'admin_init', [ $this, 'get_available' ] );
/**
* Fire before admin addons init.
*
* @since 1.6.7
*/
do_action( 'wpforms_admin_addons_init' );
}
/**
* Get all addons data as array.
*
* @since 1.6.6
*
* @param bool $force_cache_update Determine if we need to update cache. Default is `false`.
*
* @return array
*/
public function get_all( $force_cache_update = false ) {
if ( $force_cache_update ) {
$this->cache->update( true );
$this->addons = $this->cache->get();
}
return $this->addons;
}
/**
* Get filtered addons data.
*
* Usage:
* ->get_filtered( $this->addons, [ 'category' => 'payments' ] ) - addons for the payments panel.
* ->get_filtered( $this->addons, [ 'license' => 'elite' ] ) - addons available for 'elite' license.
*
* @since 1.6.6
*
* @param array $addons Raw addons data.
* @param array $args Arguments array.
*
* @return array Addons data filtered according to given arguments.
*/
private function get_filtered( $addons, $args ) {
if ( empty( $addons ) ) {
return [];
}
$default_args = [
'category' => '',
'license' => '',
];
$args = wp_parse_args( $args, $default_args );
$filtered_addons = [];
foreach ( $addons as $addon ) {
foreach ( [ 'category', 'license' ] as $arg_key ) {
if (
! empty( $args[ $arg_key ] ) &&
! empty( $addon[ $arg_key ] ) &&
is_array( $addon[ $arg_key ] ) &&
in_array( strtolower( $args[ $arg_key ] ), $addon[ $arg_key ], true )
) {
$filtered_addons[] = $addon;
}
}
}
return $filtered_addons;
}
/**
* Get available addons data by category.
*
* @since 1.6.6
*
* @param string $category Addon category.
*
* @return array.
*/
public function get_by_category( $category ) {
return $this->get_filtered( $this->available_addons, [ 'category' => $category ] );
}
/**
* Get available addons data by license.
*
* @since 1.6.6
*
* @param string $license Addon license.
*
* @return array.
* @noinspection PhpUnused
*/
public function get_by_license( $license ) {
return $this->get_filtered( $this->available_addons, [ 'license' => $license ] );
}
/**
* Get available addons data by slugs.
*
* @since 1.6.8
*
* @param array $slugs Addon slugs.
*
* @return array
*/
public function get_by_slugs( $slugs ) {
if ( empty( $slugs ) || ! is_array( $slugs ) ) {
return [];
}
$result_addons = [];
foreach ( $slugs as $slug ) {
$addon = $this->get_addon( $slug );
if ( ! empty( $addon ) ) {
$result_addons[] = $addon;
}
}
return $result_addons;
}
/**
* Get available addon data by slug.
*
* @since 1.6.6
*
* @param string $slug Addon slug, can be both "wpforms-drip" and "drip".
*
* @return array Single addon data. Empty array if addon is not found.
*/
public function get_addon( $slug ) {
$slug = 'wpforms-' . str_replace( 'wpforms-', '', sanitize_key( $slug ) );
$addon = ! empty( $this->available_addons[ $slug ] ) ? $this->available_addons[ $slug ] : [];
// In case if addon is "not available" let's try to get and prepare addon data from all addons.
if ( empty( $addon ) ) {
$addon = ! empty( $this->addons[ $slug ] ) ? $this->prepare_addon_data( $this->addons[ $slug ] ) : [];
}
return $addon;
}
/**
* Get license level of the addon.
*
* @since 1.6.6
*
* @param array|string $addon Addon data array OR addon slug.
*
* @return string License level: pro | elite.
*/
private function get_license_level( $addon ) {
if ( empty( $addon ) ) {
return '';
}
$levels = [ self::BASIC, self::PLUS, self::PRO, self::ELITE, self::AGENCY, self::ULTIMATE ];
$license = '';
$addon_license = $this->get_addon_license( $addon );
foreach ( $levels as $level ) {
if ( in_array( $level, $addon_license, true ) ) {
$license = $level;
break;
}
}
if ( empty( $license ) ) {
return '';
}
return in_array( $license, [ self::BASIC, self::PLUS, self::PRO ], true ) ? self::PRO : self::ELITE;
}
/**
* Get addon license.
*
* @since 1.8.2
*
* @param array|string $addon Addon data array OR addon slug.
*
* @return array
*/
private function get_addon_license( $addon ) {
$addon = is_string( $addon ) ? $this->get_addon( $addon ) : $addon;
return $this->default_data( $addon, 'license', [] );
}
/**
* Determine if user's license level has access.
*
* @since 1.6.6
*
* @param array|string $addon Addon data array OR addon slug.
*
* @return bool
*/
protected function has_access( $addon ) {
return false;
}
/**
* Return array of addons available to display. All data prepared and normalized.
* "Available to display" means that addon need to be displayed as education item (addon is not installed or not activated).
*
* @since 1.6.6
*
* @return array
*/
public function get_available() {
if ( empty( $this->addons ) || ! is_array( $this->addons ) ) {
return [];
}
if ( empty( $this->available_addons ) ) {
$this->available_addons = array_map( [ $this, 'prepare_addon_data' ], $this->addons );
$this->available_addons = array_filter(
$this->available_addons,
static function( $addon ) {
return isset( $addon['status'], $addon['plugin_allow'] ) && ( $addon['status'] !== 'active' || ! $addon['plugin_allow'] );
}
);
}
return $this->available_addons;
}
/**
* Prepare addon data.
*
* @since 1.6.6
*
* @param array $addon Addon data.
*
* @return array Extended addon data.
*/
protected function prepare_addon_data( $addon ) {
if ( empty( $addon ) ) {
return [];
}
$addon['title'] = $this->default_data( $addon, 'title', '' );
$addon['slug'] = $this->default_data( $addon, 'slug', '' );
// We need the cleared name of the addon, without the ' addon' suffix, for further use.
$addon['name'] = preg_replace( '/ addon$/i', '', $addon['title'] );
$addon['modal_name'] = sprintf( /* translators: %s - addon name. */
esc_html__( '%s addon', 'wpforms-lite' ),
$addon['name']
);
$addon['clear_slug'] = str_replace( 'wpforms-', '', $addon['slug'] );
$addon['utm_content'] = ucwords( str_replace( '-', ' ', $addon['clear_slug'] ) );
$addon['license'] = $this->default_data( $addon, 'license', [] );
$addon['license_level'] = $this->get_license_level( $addon );
$addon['icon'] = $this->default_data( $addon, 'icon', '' );
$addon['path'] = sprintf( '%1$s/%1$s.php', $addon['slug'] );
$addon['video'] = $this->default_data( $addon, 'video', '' );
$addon['plugin_allow'] = $this->has_access( $addon );
$addon['status'] = 'missing';
$addon['action'] = 'upgrade';
$addon['page_url'] = $this->default_data( $addon, 'url', '' );
$addon['doc_url'] = $this->default_data( $addon, 'doc', '' );
$addon['url'] = '';
static $nonce = '';
$nonce = empty( $nonce ) ? wp_create_nonce( 'wpforms-admin' ) : $nonce;
$addon['nonce'] = $nonce;
return $addon;
}
/**
* Get default data.
*
* @since 1.8.2
*
* @param array $addon Addon data.
* @param string $key Key.
* @param mixed $default Default data.
*
* @return array|string|mixed
*/
private function default_data( $addon, $key, $default ) {
if ( is_string( $default ) ) {
return ! empty( $addon[ $key ] ) ? $addon[ $key ] : $default;
}
if ( is_array( $default ) ) {
return ! empty( $addon[ $key ] ) ? (array) $addon[ $key ] : $default;
}
return $addon[ $key ];
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace WPForms\Admin\Addons;
use WPForms\Helpers\CacheBase;
/**
* Addons cache handler.
*
* @since 1.6.6
*/
class AddonsCache extends CacheBase {
/**
* Determine if the class is allowed to load.
*
* @since 1.6.8
*
* @return bool
*/
protected function allow_load() {
if ( wp_doing_cron() || wpforms_doing_wp_cli() ) {
return true;
}
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' );
return $has_permissions && $allowed_requests;
}
/**
* Provide settings.
*
* @since 1.6.6
*
* @return array Settings array.
*/
protected function setup() {
return [
// Remote source URL.
'remote_source' => 'https://wpforms.com/wp-content/addons.json',
// Addons cache file name.
'cache_file' => 'addons.json',
/**
* Time-to-live of the addons cache file in seconds.
*
* This applies to `uploads/wpforms/cache/addons.json` file.
*
* @since 1.6.8
*
* @param integer $cache_ttl Cache time-to-live, in seconds.
* Default value: WEEK_IN_SECONDS.
*/
'cache_ttl' => (int) apply_filters( 'wpforms_admin_addons_cache_ttl', WEEK_IN_SECONDS ),
// Scheduled update action.
'update_action' => 'wpforms_admin_addons_cache_update',
];
}
/**
* Prepare addons data to store in a local cache -
* generate addons icon image file name for further use.
*
* @since 1.6.6
*
* @param array $data Raw addons data.
*
* @return array Prepared data for caching (with icons).
*/
protected function prepare_cache_data( $data ) {
if ( empty( $data ) || ! is_array( $data ) ) {
return [];
}
$addons_cache = [];
foreach ( $data as $addon ) {
// Addon icon.
$addon['icon'] = str_replace( 'wpforms-', 'addon-icon-', $addon['slug'] ) . '.png';
// Special case for Sendinblue addon. The service was renamed to Brevo, but we keep the old slug for compatibility.
if ( $addon['slug'] === 'wpforms-sendinblue' ) {
$addon['icon'] = str_replace( 'sendinblue', 'brevo', $addon['icon'] );
}
// Use slug as a key for further usage.
$addons_cache[ $addon['slug'] ] = $addon;
}
return $addons_cache;
}
}

View File

@@ -0,0 +1,439 @@
<?php
namespace WPForms\Admin;
use WP_Admin_Bar;
/**
* WPForms admin bar menu.
*
* @since 1.6.0
*/
class AdminBarMenu {
/**
* Initialize class.
*
* @since 1.6.0
*/
public function init() {
if ( ! $this->has_access() ) {
return;
}
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.6.0
*/
public function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_css' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_css' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_js' ] );
add_action( 'admin_bar_menu', [ $this, 'register' ], 999 );
add_action( 'wpforms_wp_footer_end', [ $this, 'menu_forms_data_html' ] );
}
/**
* Determine whether the current user has access to see admin bar menu.
*
* @since 1.6.0
*
* @return bool
*/
public function has_access() {
$access = false;
if (
is_admin_bar_showing() &&
wpforms_current_user_can() &&
! wpforms_setting( 'hide-admin-bar', false )
) {
$access = true;
}
return (bool) apply_filters( 'wpforms_admin_adminbarmenu_has_access', $access );
}
/**
* Determine whether new notifications are available.
*
* @since 1.6.0
*
* @return bool
*/
public function has_notifications() {
return wpforms()->get( 'notifications' )->get_count();
}
/**
* Enqueue CSS styles.
*
* @since 1.6.0
*/
public function enqueue_css() {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-admin-bar',
WPFORMS_PLUGIN_URL . "assets/css/admin-bar{$min}.css",
[],
WPFORMS_VERSION
);
// Apply WordPress pre/post 5.7 accent color, only when admin bar is displayed on the frontend or we're
// inside the Form Builder - it does not load some WP core admin styles, including themes.
if ( wpforms_is_admin_page( 'builder' ) || ! is_admin() ) {
wp_add_inline_style(
'wpforms-admin-bar',
sprintf(
'#wpadminbar .wpforms-menu-notification-counter, #wpadminbar .wpforms-menu-notification-indicator {
background-color: %s !important;
color: #ffffff !important;
}',
version_compare( get_bloginfo( 'version' ), '5.7', '<' ) ? '#ca4a1f' : '#d63638'
)
);
}
}
/**
* Enqueue JavaScript files.
*
* @since 1.6.5
*/
public function enqueue_js() {
// In WordPress 5.3.1 the `admin-bar.js` file was rewritten and removed all jQuery dependencies.
$is_wp_531_plus = version_compare( get_bloginfo( 'version' ), '5.3.1', '>=' );
$inline_script = sprintf(
"( function() {
function wpforms_admin_bar_menu_init() {
var template = document.getElementById( 'tmpl-wpforms-admin-menubar-data' ),
notifications = document.getElementById( 'wp-admin-bar-wpforms-notifications' );
if ( ! template ) {
return;
}
if ( ! notifications ) {
var menu = document.getElementById( 'wp-admin-bar-wpforms-menu-default' );
if ( ! menu ) {
return;
}
menu.insertAdjacentHTML( 'afterBegin', template.innerHTML );
} else {
notifications.insertAdjacentHTML( 'afterend', template.innerHTML );
}
};
%s
}() );",
$is_wp_531_plus ? "document.addEventListener( 'DOMContentLoaded', wpforms_admin_bar_menu_init );" : "if ( typeof( jQuery ) != 'undefined' ) { jQuery( wpforms_admin_bar_menu_init ); } else { document.addEventListener( 'DOMContentLoaded', wpforms_admin_bar_menu_init ); }"
);
wp_add_inline_script( 'admin-bar', $inline_script, 'before' );
}
/**
* Register and render admin bar menu items.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function register( WP_Admin_Bar $wp_admin_bar ) {
$items = (array) apply_filters(
'wpforms_admin_adminbarmenu_register',
[
'main_menu',
'notification_menu',
'all_forms_menu',
'all_payments_menu',
'add_new_menu',
'community_menu',
'support_menu',
],
$wp_admin_bar
);
foreach ( $items as $item ) {
$this->{ $item }( $wp_admin_bar );
do_action( "wpforms_admin_adminbarmenu_register_{$item}_after", $wp_admin_bar );
}
}
/**
* Render primary top-level admin bar menu item.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function main_menu( WP_Admin_Bar $wp_admin_bar ) {
$indicator = '';
$notifications = $this->has_notifications();
if ( $notifications ) {
$count = $notifications < 10 ? $notifications : '!';
$indicator = ' <div class="wp-core-ui wp-ui-notification wpforms-menu-notification-counter">' . $count . '</div>';
}
$wp_admin_bar->add_menu(
[
'id' => 'wpforms-menu',
'title' => 'WPForms' . $indicator,
'href' => admin_url( 'admin.php?page=wpforms-overview' ),
]
);
}
/**
* Render Notifications admin bar menu item.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function notification_menu( WP_Admin_Bar $wp_admin_bar ) {
if ( ! $this->has_notifications() ) {
return;
}
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-notifications',
'title' => esc_html__( 'Notifications', 'wpforms-lite' ) . ' <div class="wp-core-ui wp-ui-notification wpforms-menu-notification-indicator"></div>',
'href' => admin_url( 'admin.php?page=wpforms-overview' ),
]
);
}
/**
* Render All Forms admin bar menu item.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function all_forms_menu( WP_Admin_Bar $wp_admin_bar ) {
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-forms',
'title' => esc_html__( 'All Forms', 'wpforms-lite' ),
'href' => admin_url( 'admin.php?page=wpforms-overview' ),
]
);
}
/**
* Render All Payments admin bar menu item.
*
* @since 1.8.4
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function all_payments_menu( WP_Admin_Bar $wp_admin_bar ) {
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-payments',
'title' => esc_html__( 'Payments', 'wpforms-lite' ),
'href' => add_query_arg(
[
'page' => 'wpforms-payments',
],
admin_url( 'admin.php' )
),
]
);
}
/**
* Render Add New admin bar menu item.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function add_new_menu( WP_Admin_Bar $wp_admin_bar ) {
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-add-new',
'title' => esc_html__( 'Add New', 'wpforms-lite' ),
'href' => admin_url( 'admin.php?page=wpforms-builder' ),
]
);
}
/**
* Render Community admin bar menu item.
*
* @since 1.6.0
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function community_menu( WP_Admin_Bar $wp_admin_bar ) {
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-community',
'title' => esc_html__( 'Community', 'wpforms-lite' ),
'href' => 'https://www.facebook.com/groups/wpformsvip/',
'meta' => [
'target' => '_blank',
'rel' => 'noopener noreferrer',
],
]
);
}
/**
* Render Support admin bar menu item.
*
* @since 1.6.0
* @since 1.7.4 Update the `Support` item title to `Help Docs`.
*
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
*/
public function support_menu( WP_Admin_Bar $wp_admin_bar ) {
$href = add_query_arg(
[
'utm_campaign' => wpforms()->is_pro() ? 'plugin' : 'liteplugin',
'utm_medium' => 'admin-bar',
'utm_source' => 'WordPress',
'utm_content' => 'Documentation',
],
'https://wpforms.com/docs/'
);
$wp_admin_bar->add_menu(
[
'parent' => 'wpforms-menu',
'id' => 'wpforms-help-docs',
'title' => esc_html__( 'Help Docs', 'wpforms-lite' ),
'href' => $href,
'meta' => [
'target' => '_blank',
'rel' => 'noopener noreferrer',
],
]
);
}
/**
* Get form data for JS to modify the admin bar menu.
*
* @since 1.6.5
* @since 1.8.4 Added the View Payments link.
*
* @param array $forms Forms array.
*
* @return array
*/
protected function get_forms_data( $forms ) {
$data = [
'has_notifications' => $this->has_notifications(),
'edit_text' => esc_html__( 'Edit Form', 'wpforms-lite' ),
'entry_text' => esc_html__( 'View Entries', 'wpforms-lite' ),
'payment_text' => esc_html__( 'View Payments', 'wpforms-lite' ),
'survey_text' => esc_html__( 'Survey Results', 'wpforms-lite' ),
'forms' => [],
];
$admin_url = admin_url( 'admin.php' );
foreach ( $forms as $form ) {
$form_id = absint( $form['id'] );
if ( empty( $form_id ) ) {
continue;
}
/* translators: %d - form ID. */
$form_title = sprintf( esc_html__( 'Form ID: %d', 'wpforms-lite' ), $form_id );
if ( ! empty( $form['settings']['form_title'] ) ) {
$form_title = wp_html_excerpt(
sanitize_text_field( $form['settings']['form_title'] ),
99,
'&hellip;'
);
}
$has_payments = wpforms()->get( 'payment' )->get_by( 'form_id', $form_id );
$data['forms'][] = apply_filters(
'wpforms_admin_adminbarmenu_get_form_data',
[
'form_id' => $form_id,
'title' => $form_title,
'edit_url' => add_query_arg(
[
'page' => 'wpforms-builder',
'view' => 'fields',
'form_id' => $form_id,
],
$admin_url
),
'payments_url' => $has_payments ? add_query_arg(
[
'page' => 'wpforms-payments',
'form_id' => $form_id,
],
$admin_url
) : '',
]
);
}
return $data;
}
/**
* Add form(s) data to the page.
*
* @since 1.6.5
*
* @param array $forms Forms array.
*/
public function menu_forms_data_html( $forms ) {
if ( empty( $forms ) ) {
return;
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin-bar-menu',
[
'forms_data' => $this->get_forms_data( $forms ),
],
true
);
}
}

View File

@@ -0,0 +1,373 @@
<?php
namespace WPForms\Admin\Builder;
use WPForms\Forms\Akismet;
use WPForms_Builder_Panel_Settings;
/**
* AntiSpam class.
*
* @since 1.7.8
*/
class AntiSpam {
/**
* Form data and settings.
*
* @since 1.7.8
*
* @var array
*/
private $form_data;
/**
* Init class.
*
* @since 1.7.8
*/
public function init() {
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.7.8
*/
protected function hooks() {
add_action( 'wpforms_form_settings_panel_content', [ $this, 'panel_content' ], 10, 2 );
}
/**
* Add a content for `Spam Protection and Security` panel.
*
* @since 1.7.8
*
* @param WPForms_Builder_Panel_Settings $instance Settings panel instance.
*/
public function panel_content( $instance ) {
$this->form_data = $instance->form_data;
echo '<div class="wpforms-panel-content-section wpforms-panel-content-section-anti_spam">';
echo '<div class="wpforms-panel-content-section-title">';
esc_html_e( 'Spam Protection and Security', 'wpforms-lite' );
echo '</div>';
$antispam = wpforms_panel_field(
'toggle',
'settings',
'antispam',
$this->form_data,
__( 'Enable anti-spam protection', 'wpforms-lite' ),
[
'tooltip' => __( 'Turn on invisible spam protection.', 'wpforms-lite' ),
],
false
);
wpforms_panel_fields_group(
$antispam,
[
'description' => __( 'Behind-the-scenes spam filtering that\'s invisible to your visitors.', 'wpforms-lite' ),
'title' => __( 'Protection', 'wpforms-lite' ),
]
);
if ( ! empty( $this->form_data['settings']['honeypot'] ) ) {
wpforms_panel_field(
'toggle',
'settings',
'honeypot',
$this->form_data,
__( 'Enable anti-spam honeypot', 'wpforms-lite' )
);
}
$this->akismet_settings();
$this->store_spam_entries_settings();
$this->time_limit_settings();
$this->captcha_settings();
/**
* Fires once in the end of content panel before Also Available section.
*
* @since 1.7.8
*
* @param array $form_data Form data and settings.
*/
do_action( 'wpforms_admin_builder_anti_spam_panel_content', $this->form_data );
wpforms_panel_fields_group(
$this->get_also_available_block(),
[
'unfoldable' => true,
'default' => 'opened',
'group' => 'also_available',
'title' => __( 'Also Available', 'wpforms-lite' ),
'borders' => [ 'top' ],
]
);
echo '</div>';
}
/**
* Check if it is a new setup.
*
* @since 1.8.3
*
* @return bool
*/
private function is_new_setup() {
$form_counts = wp_count_posts( 'wpforms' );
$form_counts = array_filter( (array) $form_counts );
return empty( $form_counts );
}
/**
* Output the *CAPTCHA settings.
*
* @since 1.7.8
*/
private function captcha_settings() {
$captcha_settings = wpforms_get_captcha_settings();
if (
empty( $captcha_settings['provider'] ) ||
$captcha_settings['provider'] === 'none' ||
empty( $captcha_settings['site_key'] ) ||
empty( $captcha_settings['secret_key'] )
) {
return;
}
$captcha_types = [
'hcaptcha' => __( 'Enable hCaptcha', 'wpforms-lite' ),
'turnstile' => __( 'Enable Cloudflare Turnstile', 'wpforms-lite' ),
'recaptcha' => [
'v2' => __( 'Enable Google Checkbox v2 reCAPTCHA', 'wpforms-lite' ),
'invisible' => __( 'Enable Google Invisible v2 reCAPTCHA', 'wpforms-lite' ),
'v3' => __( 'Enable Google v3 reCAPTCHA', 'wpforms-lite' ),
],
];
$is_recaptcha = $captcha_settings['provider'] === 'recaptcha';
$captcha_types = $is_recaptcha ? $captcha_types['recaptcha'] : $captcha_types;
$captcha_key = $is_recaptcha ? $captcha_settings['recaptcha_type'] : $captcha_settings['provider'];
$label = ! empty( $captcha_types[ $captcha_key ] ) ? $captcha_types[ $captcha_key ] : '';
$recaptcha = wpforms_panel_field(
'toggle',
'settings',
'recaptcha',
$this->form_data,
$label,
[
'data' => [
'provider' => $captcha_settings['provider'],
],
'tooltip' => __( 'Enable third-party CAPTCHAs to prevent form submissions from bots.', 'wpforms-lite' ),
],
false
);
wpforms_panel_fields_group(
$recaptcha,
[
'description' => __( 'Automated tests that help to prevent bots from submitting your forms.', 'wpforms-lite' ),
'title' => __( 'CAPTCHA', 'wpforms-lite' ),
'borders' => [ 'top' ],
]
);
}
/**
* Output the Spam Entries Store settings.
*
* @since 1.8.3
*/
public function store_spam_entries_settings() {
// Enable storing entries by default for new setup.
$store_spam_entries = ! empty( $this->form_data['settings']['store_spam_entries'] )
? $this->form_data['settings']['store_spam_entries']
: $this->is_new_setup();
wpforms_panel_field(
'toggle',
'settings',
'store_spam_entries',
$this->form_data,
__( 'Store spam entries in the database', 'wpforms-lite' ),
[
'value' => $store_spam_entries,
]
);
}
/**
* Output the Time Limit settings.
*
* @since 1.8.3
*/
private function time_limit_settings() {
wpforms_panel_field(
'toggle',
'anti_spam',
'enable',
$this->form_data,
__( 'Enable minimum time to submit', 'wpforms-lite' ),
[
'parent' => 'settings',
'subsection' => 'time_limit',
'tooltip' => __( 'Set a minimum amount of time a user must spend on a form before submitting.', 'wpforms-lite' ),
'input_class' => 'wpforms-panel-field-toggle-next-field',
]
);
wpforms_panel_field(
'text',
'anti_spam',
'duration',
$this->form_data,
__( 'Minimum time to submit', 'wpforms-lite' ),
[
'parent' => 'settings',
'subsection' => 'time_limit',
'type' => 'number',
'min' => 1,
'default' => 3,
'after' => sprintf( '<span class="wpforms-panel-field-after">%s</span>', __( 'seconds', 'wpforms-lite' ) ),
]
);
}
/**
* Output the Akismet settings.
*
* @since 1.7.8
*/
private function akismet_settings() {
if ( ! Akismet::is_installed() ) {
return;
}
$args = [];
if ( ! Akismet::is_configured() ) {
$args['data']['akismet-status'] = 'akismet_no_api_key';
}
if ( ! Akismet::is_activated() ) {
$args['data']['akismet-status'] = 'akismet_not_activated';
}
// If Akismet isn't available, disable the Akismet toggle.
if ( isset( $args['data'] ) ) {
$args['input_class'] = 'wpforms-akismet-disabled';
$args['value'] = '0';
}
wpforms_panel_field(
'toggle',
'settings',
'akismet',
$this->form_data,
__( 'Enable Akismet anti-spam protection', 'wpforms-lite' ),
$args
);
}
/**
* Get the Also Available block.
*
* @since 1.7.8
*
* @return string
*/
private function get_also_available_block() {
$get_started_button_text = __( 'Get Started &rarr;', 'wpforms-lite' );
$upgrade_to_pro_text = __( 'Upgrade to Pro', 'wpforms-lite' );
$captcha_settings = wpforms_get_captcha_settings();
$upgrade_url = 'https://wpforms.com/lite-upgrade/';
$utm_medium = 'Builder Settings';
$blocks = [
'country_filter' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/country-filter.svg',
'title' => __( 'Country Filter', 'wpforms-lite' ),
'description' => __( 'Stop spam at its source. Allow or deny entries from specific countries.', 'wpforms-lite' ),
'link' => wpforms_utm_link( $upgrade_url, $utm_medium, 'Country Filter Feature' ),
'link_text' => $upgrade_to_pro_text,
'class' => 'wpforms-panel-content-also-available-item-upgrade-to-pro',
'show' => ! wpforms()->is_pro(),
],
'keyword_filter' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/keyword-filter.svg',
'title' => __( 'Keyword Filter', 'wpforms-lite' ),
'description' => __( 'Block form entries that contain specific words or phrases that you define.', 'wpforms-lite' ),
'link' => wpforms_utm_link( $upgrade_url, $utm_medium, 'Keyword Filter Feature' ),
'link_text' => $upgrade_to_pro_text,
'class' => 'wpforms-panel-content-also-available-item-upgrade-to-pro',
'show' => ! wpforms()->is_pro(),
],
'custom_captcha' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/custom-captcha.svg',
'title' => __( 'Custom Captcha', 'wpforms-lite' ),
'description' => __( 'Ask custom questions or require your visitor to answer a random math puzzle.', 'wpforms-lite' ),
'link' => wpforms()->is_pro() ? '#' : wpforms_utm_link( $upgrade_url, $utm_medium, 'Custom Captcha Addon' ),
'link_text' => wpforms()->is_pro() ? __( 'Add to Form', 'wpforms-lite' ) : $upgrade_to_pro_text,
'class' => wpforms()->is_pro() ? 'wpforms-panel-content-also-available-item-add-captcha' : 'wpforms-panel-content-also-available-item-upgrade-to-pro',
'show' => true,
],
'reCAPTCHA' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/recaptcha.svg',
'title' => 'reCAPTCHA',
'description' => __( 'Add Google\'s free anti-spam service and choose between visible or invisible CAPTCHAs.','wpforms-lite' ),
'link' => wpforms_utm_link( 'https://wpforms.com/docs/how-to-set-up-and-use-recaptcha-in-wpforms/', $utm_medium, 'reCAPTCHA Feature' ),
'link_text' => $get_started_button_text,
'show' => $captcha_settings['provider'] !== 'recaptcha' || empty( wpforms_setting( 'captcha-provider' ) ),
],
'hCaptcha' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/hcaptcha.svg',
'title' => 'hCaptcha',
'description' => __( 'Turn on free, privacy-oriented spam prevention that displays a visual CAPTCHA.','wpforms-lite' ),
'link' => wpforms_utm_link( 'https://wpforms.com/docs/how-to-set-up-and-use-hcaptcha-in-wpforms/', $utm_medium, 'hCaptcha Feature' ),
'link_text' => $get_started_button_text,
'show' => $captcha_settings['provider'] !== 'hcaptcha',
],
'turnstile' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/cloudflare.svg',
'title' => 'Cloudflare Turnstile',
'description' => __( 'Enable free, CAPTCHA-like spam protection that protects data privacy.','wpforms-lite' ),
'link' => wpforms_utm_link( 'https://wpforms.com/docs/setting-up-cloudflare-turnstile/', $utm_medium, 'Cloudflare Turnstile Feature' ),
'link_text' => $get_started_button_text,
'show' => $captcha_settings['provider'] !== 'turnstile',
],
'akismet' => [
'logo' => WPFORMS_PLUGIN_URL . 'assets/images/anti-spam/akismet.svg',
'title' => 'Akismet',
'description' => __( 'Integrate the powerful spam-fighting service trusted by millions of sites.','wpforms-lite' ),
'link' => wpforms_utm_link( 'https://wpforms.com/docs/setting-up-akismet-anti-spam-protection/', $utm_medium, 'Akismet Feature' ),
'link_text' => $get_started_button_text,
'show' => ! Akismet::is_installed(),
],
];
return wpforms_render(
'builder/antispam/also-available',
[ 'blocks' => $blocks ],
true
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
<?php
namespace WPForms\Admin\Builder;
use WPForms\Helpers\CacheBase;
/**
* Form Builder Help Cache.
*
* @since 1.8.2
*/
class HelpCache extends CacheBase {
/**
* Determine if the class is allowed to load.
*
* @since 1.8.2
*
* @return bool
*/
protected function allow_load() {
if ( wp_doing_cron() || wpforms_doing_wp_cli() ) {
return true;
}
if ( ! wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] ) ) {
return false;
}
return wpforms_is_admin_page( 'builder' );
}
/**
* Setup settings and other things.
*
* @since 1.8.2
*/
protected function setup() {
return [
'remote_source' => 'https://wpforms.com/wp-content/docs.json',
'cache_file' => 'docs.json',
/**
* Allow modifying Help Docs cache TTL (time to live).
*
* @since 1.6.3
*
* @param int $cache_ttl Cache TTL in seconds. Defaults to 1 week.
*/
'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_help_cache_ttl', WEEK_IN_SECONDS ),
'update_action' => 'wpforms_builder_help_cache_update',
];
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace WPForms\Admin\Builder\Notifications\Advanced;
use WPForms_Builder_Panel_Settings;
use WPForms\Emails\Helpers;
use WPForms\Admin\Education\Helpers as EducationHelpers;
/**
* Email Template.
* This class will register the Email Template field in the "Notification" → "Advanced" settings.
* The Email Template field will allow users to override the default email template for a specific notification.
*
* @since 1.8.5
*/
class EmailTemplate {
/**
* Initialize class.
*
* @since 1.8.5
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.5
*/
private function hooks() {
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_assets' ] );
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'builder_footer_scripts' ] );
add_filter( 'wpforms_lite_admin_education_builder_notifications_advanced_settings_content', [ $this, 'settings' ], 5, 3 );
add_filter( 'wpforms_pro_admin_builder_notifications_advanced_settings_content', [ $this, 'settings' ], 5, 3 );
}
/**
* Enqueue assets for the builder.
*
* @since 1.8.5
*/
public function builder_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-builder-email-template',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/builder/email-template{$min}.js",
[ 'jquery', 'jquery-confirm', 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-builder-email-template',
'wpforms_builder_email_template',
[
'is_pro' => wpforms()->is_pro(),
'templates' => Helpers::get_email_template_choices( false ),
]
);
}
/**
* Output Email Template modal.
*
* @since 1.8.5
*/
public function builder_footer_scripts() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'builder/notifications/email-template-modal',
[
'pro_badge' => ! wpforms()->is_pro() ? EducationHelpers::get_badge( 'Pro' ) : '',
],
true
);
}
/**
* Add Email Template settings.
*
* @since 1.8.5
*
* @param string $content Notification → Advanced content.
* @param WPForms_Builder_Panel_Settings $settings Builder panel settings.
* @param int $id Notification id.
*
* @return string
*/
public function settings( $content, $settings, $id ) {
// Retrieve email template choices and disabled choices.
// A few of the email templates are only available in the Pro version and will be disabled for non-Pro users.
// The disabled choices will be added to the select field with a "(Pro)" label appended to the name.
list( $options, $disabled_options ) = $this->get_email_template_options();
// Add Email Template field.
$content .= wpforms_panel_field(
'select',
'notifications',
'template',
$settings->form_data,
esc_html__( 'Email Template', 'wpforms-lite' ),
[
'default' => '',
'options' => $options,
'disabled_options' => $disabled_options,
'class' => 'wpforms-panel-field-email-template-wrap',
'input_class' => 'wpforms-panel-field-email-template',
'parent' => 'settings',
'subsection' => $id,
'after' => $this->get_template_modal_link_content(),
'tooltip' => esc_html__( 'Override the default email template for this specific notification.', 'wpforms-lite' ),
],
false
);
return $content;
}
/**
* Get Email template choices.
*
* This function will return an array of email template choices and an array of disabled choices.
* The disabled choices are templates that are only available in the Pro version.
*
* @since 1.8.5
*
* @return array
*/
private function get_email_template_options() {
// Retrieve the available email template choices.
$choices = Helpers::get_email_template_choices( false );
// If there are no templates or the choices are not an array, return empty arrays.
if ( empty( $choices ) || ! is_array( $choices ) ) {
return [ [], [] ];
}
// Check if the Pro version is active.
$is_pro = wpforms()->is_pro();
// Initialize arrays for options and disabled options.
$options = [];
$disabled_options = [];
// Iterate through the templates and build the $options array.
foreach ( $choices as $key => $choice ) {
$value = esc_attr( $key );
$name = esc_html( $choice['name'] );
$is_disabled = ! $is_pro && isset( $choice['is_pro'] ) && $choice['is_pro'];
// If the option is disabled for non-Pro users, add it to the disabled options array.
if ( $is_disabled ) {
$disabled_options[] = $value;
}
// Build the $options array with appropriate labels.
// Pro badge labels are not meant to be translated.
$options[ $key ] = $is_disabled ? sprintf( '%s (Pro)', $name ) : $name;
}
// Add an empty option to the beginning of the $options array.
// This is a placeholder option that will be replaced with the default template name.
$options = array_merge( [ '' => esc_html__( 'Default Template', 'wpforms-lite' ) ], $options );
// Return the options and disabled options arrays.
return [ $options, $disabled_options ];
}
/**
* Get Email template modal link content.
*
* @since 1.8.5
*
* @return string
*/
private function get_template_modal_link_content() {
return wpforms_render( 'builder/notifications/email-template-link' );
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace WPForms\Admin\Builder;
/**
* Form Builder Keyboard Shortcuts modal content.
*
* @since 1.6.9
*/
class Shortcuts {
/**
* Initialize class.
*
* @since 1.6.9
*/
public function init() {
// Terminate initialization if not in builder.
if ( ! wpforms_is_admin_page( 'builder' ) ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.9
*/
private function hooks() {
add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ], 10, 2 );
add_action( 'wpforms_admin_page', [ $this, 'output' ], 30 );
}
/**
* Get shortcuts list.
*
* @since 1.6.9
*
* @return array
*/
private function get_list() {
return [
'left' => [
'ctrl s' => __( 'Save Form', 'wpforms-lite' ),
'ctrl p' => __( 'Preview Form', 'wpforms-lite' ),
'ctrl b' => __( 'Embed Form', 'wpforms-lite' ),
'ctrl f' => __( 'Search Fields', 'wpforms-lite' ),
],
'right' => [
'ctrl h' => __( 'Open Help', 'wpforms-lite' ),
'ctrl t' => __( 'Toggle Sidebar', 'wpforms-lite' ),
'ctrl e' => __( 'View Entries', 'wpforms-lite' ),
'ctrl q' => __( 'Close Builder', 'wpforms-lite' ),
],
];
}
/**
* Add Form builder strings.
*
* @since 1.6.9
*
* @param array $strings Form Builder strings.
* @param \WP_Post|bool $form Form object.
*
* @return array
*/
public function builder_strings( $strings, $form ) {
$strings['shortcuts_modal_title'] = esc_html__( 'Keyboard Shortcuts', 'wpforms-lite' );
$strings['shortcuts_modal_msg'] = esc_html__( 'Handy shortcuts for common actions in the builder.', 'wpforms-lite' );
return $strings;
}
/**
* Generate and output shortcuts modal content as the wp.template.
*
* @since 1.6.9
*/
public function output() {
echo '
<script type="text/html" id="tmpl-wpforms-builder-keyboard-shortcuts">
<div class="wpforms-columns wpforms-columns-2">';
foreach ( $this->get_list() as $list ) {
echo "<ul class='wpforms-column'>";
foreach ( $list as $key => $label ) {
$key = explode( ' ', $key );
printf(
'<li>
%1$s
<span>
<i>%2$s</i><i>%3$s</i>
</span>
</li>',
esc_html( $label ),
esc_html( $key[0] ),
esc_html( $key[1] )
);
}
echo '</ul>';
}
echo '
</div>
</script>';
}
}

View File

@@ -0,0 +1,245 @@
<?php
namespace WPForms\Admin\Builder;
use WPForms\Helpers\CacheBase;
use WPForms\Tasks\Actions\AsyncRequestTask;
/**
* Single template cache handler.
*
* @since 1.6.8
*/
class TemplateSingleCache extends CacheBase {
/**
* Template Id (hash).
*
* @since 1.6.8
*
* @var string
*/
private $id;
/**
* License data (`key` and `type`).
*
* @since 1.6.8
*
* @var array
*/
private $license;
/**
* Determine if the class is allowed to load.
*
* @since 1.6.8
*
* @return bool
*/
protected function allow_load() {
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' );
$allow = wp_doing_cron() || wpforms_doing_wp_cli() || ( $has_permissions && $allowed_requests );
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
/**
* Whether to allow to load this class.
*
* @since 1.7.2
*
* @param bool $allow True or false.
*/
return (bool) apply_filters( 'wpforms_admin_builder_templatesinglecache_allow_load', $allow );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Re-initialize object with the particular template.
*
* @since 1.6.8
*
* @param string $template_id Template ID (hash).
* @param array $license License data.
*
* @return TemplateSingleCache
*/
public function instance( $template_id, $license ) {
$this->id = $template_id;
$this->license = $license;
$this->init();
return $this;
}
/**
* Provide settings.
*
* @since 1.6.8
*
* @return array Settings array.
*/
protected function setup() {
return [
// Remote source URL.
'remote_source' => $this->remote_source(),
// Cache file.
'cache_file' => $this->get_cache_file_name(),
// This filter is documented in wpforms/src/Admin/Builder/TemplatesCache.php.
// phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_templates_cache_ttl', WEEK_IN_SECONDS ),
];
}
/**
* Generate single template remote URL.
*
* @since 1.6.8
*
* @param bool $cache True if the cache arg should be appended to the URL.
*
* @return string
*/
private function remote_source( $cache = false ) {
if ( ! isset( $this->license['key'] ) ) {
return '';
}
$args = [
'license' => $this->license['key'],
'site' => site_url(),
];
if ( $cache ) {
$args['cache'] = 1;
}
return add_query_arg(
$args,
'https://wpforms.com/templates/api/get/' . $this->id
);
}
/**
* Get cached data.
*
* @since 1.8.2
*
* @return array Cached data.
*/
public function get() {
$data = parent::get();
if ( parent::$updated === false ) {
$this->update_usage_tracking();
}
return $data;
}
/**
* Sends a request to update the form template usage tracking database.
*
* @since 1.7.5
*/
private function update_usage_tracking() {
$tasks = wpforms()->get( 'tasks' );
if ( ! $tasks ) {
return;
}
$url = $this->remote_source( true );
$args = [ 'blocking' => false ];
$tasks->create( AsyncRequestTask::ACTION )->async()->params( $url, $args )->register();
}
/**
* Get cache directory path.
*
* @since 1.6.8
*/
protected function get_cache_dir() {
return parent::get_cache_dir() . 'templates/';
}
/**
* Generate single template cache file name.
*
* @since 1.6.8
*
* @return string.
*/
private function get_cache_file_name() {
return sanitize_key( $this->id ) . '.json';
}
/**
* Prepare data to store in a local cache.
*
* @since 1.6.8
*
* @param array $data Raw data received by the remote request.
*
* @return array Prepared data for caching.
*/
protected function prepare_cache_data( $data ) {
if (
empty( $data ) ||
! is_array( $data ) ||
empty( $data['status'] ) ||
$data['status'] !== 'success' ||
empty( $data['data'] )
) {
return [];
}
$cache_data = $data['data'];
$cache_data['data'] = empty( $cache_data['data'] ) ? [] : $cache_data['data'];
$cache_data['data']['settings'] = empty( $cache_data['data']['settings'] ) ? [] : $cache_data['data']['settings'];
$cache_data['data']['settings']['ajax_submit'] = '1';
// Strip the word "Template" from the end of the template name and form title setting.
$cache_data['name'] = preg_replace( '/\sTemplate$/', '', $cache_data['name'] );
$cache_data['data']['settings']['form_title'] = $cache_data['name'];
// Unset `From Name` field of the notification settings.
// By default, the builder will use the `blogname` option value.
unset( $cache_data['data']['settings']['notifications'][1]['sender_name'] );
return $cache_data;
}
/**
* Wipe cache of an empty templates.
*
* @since 1.7.5
*/
public function wipe_empty_templates_cache() {
$cache_dir = $this->get_cache_dir();
$files = glob( $cache_dir . '*.json' );
foreach ( $files as $filename ) {
$content = file_get_contents( $filename );
if ( empty( $content ) || trim( $content ) === '[]' ) {
unlink( $filename );
}
}
}
}

View File

@@ -0,0 +1,967 @@
<?php
namespace WPForms\Admin\Builder;
use WP_Query;
/**
* Templates class.
*
* @since 1.6.8
*/
class Templates {
/**
* All templates data from API.
*
* @since 1.6.8
*
* @var array
*/
private $api_templates;
/**
* Template categories data.
*
* @since 1.6.8
*
* @var array
*/
private $categories;
/**
* Template subcategories data.
*
* @since 1.8.4
*
* @var array
*/
private $subcategories;
/**
* License data.
*
* @since 1.6.8
*
* @var array
*/
private $license;
/**
* All licenses list.
*
* @since 1.6.8
*
* @var array
*/
private $all_licenses;
/**
* Favorite templates option.
*
* @since 1.7.7
*
* @var string
*/
const FAVORITE_TEMPLATES_OPTION = 'wpforms_favorite_templates';
/**
* Determine if the class is allowed to load.
*
* @since 1.6.8
*
* @return bool
*/
private function allow_load() {
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' );
$allow = $has_permissions && $allowed_requests;
/**
* Whether to allow the form templates functionality to load.
*
* @since 1.7.2
*
* @param bool $allow True or false.
*/
return (bool) apply_filters( 'wpforms_admin_builder_templates_allow_load', $allow );
}
/**
* Initialize class.
*
* @since 1.6.8
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->init_license_data();
$this->init_templates_data();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.8
*/
protected function hooks() {
add_action( 'admin_init', [ $this, 'create_form_on_request' ], 100 );
add_filter( 'wpforms_form_templates_core', [ $this, 'add_templates_to_setup_panel' ], 20 );
add_filter( 'wpforms_create_form_args', [ $this, 'apply_to_new_form' ], 10, 2 );
add_filter( 'wpforms_save_form_args', [ $this, 'apply_to_existing_form' ], 10, 3 );
add_action( 'admin_print_scripts', [ $this, 'upgrade_banner_template' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
add_action( 'wp_ajax_wpforms_templates_favorite', [ $this, 'ajax_save_favorites' ] );
}
/**
* Enqueue assets for the Setup panel.
*
* @since 1.7.7
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'listjs',
WPFORMS_PLUGIN_URL . 'assets/lib/list.min.js',
[ 'jquery' ],
'2.3.0'
);
wp_enqueue_script(
'wpforms-form-templates',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/form-templates{$min}.js",
[ 'listjs' ],
WPFORMS_VERSION,
true
);
$strings = [
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'admin_nonce' => wp_create_nonce( 'wpforms-admin' ),
'nonce' => wp_create_nonce( 'wpforms-form-templates' ),
'can_install_addons' => wpforms_can_install( 'addon' ),
'activating' => esc_html__( 'Activating', 'wpforms-lite' ),
'cancel' => esc_html__( 'Cancel', 'wpforms-lite' ),
'heads_up' => esc_html__( 'Heads Up!', 'wpforms-lite' ),
'install_confirm' => esc_html__( 'Yes, install and activate', 'wpforms-lite' ),
'ok' => esc_html__( 'Ok', 'wpforms-lite' ),
'template_addons_error' => esc_html__( 'Could not install OR activate all the required addons. Please download from wpforms.com and install them manually. Would you like to use the template anyway?', 'wpforms-lite' ),
'use_template' => esc_html__( 'Yes, use template', 'wpforms-lite' ),
];
if ( $strings['can_install_addons'] ) {
/* translators: %1$s - template name, %2$s - addon name(s). */
$strings['template_addon_prompt'] = esc_html( sprintf( __( 'The %1$s template requires the %2$s. Would you like to install and activate it?', 'wpforms-lite' ), '%template%', '%addons%' ) );
/* translators: %1$s - template name, %2$s - addon name(s). */
$strings['template_addons_prompt'] = esc_html( sprintf( __( 'The %1$s template requires the %2$s. Would you like to install and activate all the required addons?', 'wpforms-lite' ), '%template%', '%addons%' ) );
} else {
/* translators: %s - addon name(s). */
$strings['template_addon_prompt'] = esc_html( sprintf( __( "To use all of the features in this template, you'll need the %s. Contact your site administrator to install it, then try opening this template again.", 'wpforms-lite' ), '%addons%' ) );
/* translators: %s - addon name(s). */
$strings['template_addons_prompt'] = esc_html( sprintf( __( "To use all of the features in this template, you'll need the %s. Contact your site administrator to install them, then try opening this template again.", 'wpforms-lite' ), '%addons%' ) );
}
wp_localize_script(
'wpforms-form-templates',
'wpforms_form_templates',
$strings
);
wp_localize_script(
'wpforms-form-templates',
'wpforms_addons',
$this->get_localized_addons()
);
}
/**
* Get localized addons.
*
* @since 1.8.2
*
* @return array
*/
private function get_localized_addons() {
return wpforms_chain( wpforms()->get( 'addons' )->get_available() )
->map(
static function( $addon ) {
return [
'title' => $addon['title'],
'action' => $addon['action'],
'url' => $addon['url'],
];
}
)
->value();
}
/**
* Init license data.
*
* @since 1.6.8
*/
private function init_license_data() {
$this->all_licenses = [ 'lite', 'basic', 'plus', 'pro', 'elite', 'agency', 'ultimate' ];
// User license data.
$this->license['key'] = wpforms_get_license_key();
$this->license['type'] = wpforms_get_license_type();
$this->license['type'] = in_array( $this->license['type'], [ 'agency', 'ultimate' ], true ) ? 'elite' : $this->license['type'];
$this->license['type'] = empty( $this->license['type'] ) ? 'lite' : $this->license['type'];
$this->license['index'] = array_search( $this->license['type'], $this->all_licenses, true );
}
/**
* Init templates and categories data.
*
* @since 1.6.8
*/
private function init_templates_data() {
// Get cached templates data.
$cache_data = wpforms()->get( 'builder_templates_cache' )->get();
$templates_all = ! empty( $cache_data['templates'] ) ? $this->sort_templates_by_created_at( $cache_data['templates'] ) : [];
$this->categories = ! empty( $cache_data['categories'] ) ? $cache_data['categories'] : [];
$this->subcategories = ! empty( $cache_data['subcategories'] ) ? $cache_data['subcategories'] : [];
// Higher priority templates slugs.
// These remote templates are the replication of the default templates,
// which were previously included with the WPForms plugin.
$higher_templates_slugs = [
'simple-contact-form-template',
'request-a-quote-form-template',
'donation-form-template',
'billing-order-form-template',
'newsletter-signup-form-template',
'suggestion-form-template',
];
$templates_higher = [];
$templates_access = [];
$templates_denied = [];
/**
* The form template was moved to wpforms/includes/templates/class-simple-contact-form.php file.
*
* @since 1.7.5.3
*/
unset( $templates_all['simple-contact-form-template'] );
foreach ( $templates_all as $i => $template ) {
$template['has_access'] = $this->has_access( $template );
$template['favorite'] = $this->is_favorite( $i );
$template['license'] = $this->get_license_level( $template );
$template['source'] = 'wpforms-api';
$template['categories'] = ! empty( $template['categories'] ) ? array_keys( $template['categories'] ) : [];
$is_higher = in_array( $i, $higher_templates_slugs, true );
if ( $template['has_access'] ) {
if ( $is_higher ) {
$templates_higher[ $i ] = $template;
} else {
$templates_access[ $i ] = $template;
}
} else {
if ( $is_higher ) {
$templates_denied = array_merge( [ $i => $template ], $templates_denied );
} else {
$templates_denied[ $i ] = $template;
}
}
}
// Sort higher priority templates according to the slugs order.
$templates_higher = array_replace( array_flip( $higher_templates_slugs ), $templates_higher );
$templates_higher = array_filter( $templates_higher, 'is_array' );
// Finally, merge templates from API.
$this->api_templates = array_merge( $templates_higher, $templates_access, $templates_denied );
}
/**
* Sort templates by their created_at value in ascending order.
*
* @since 1.8.4
*
* @param array $templates Templates to be sorted.
*
* @return array Sorted templates.
*/
private function sort_templates_by_created_at( array $templates ): array {
uasort(
$templates,
static function ( $template_a, $template_b ) {
if ( $template_a['created_at'] === $template_b['created_at'] ) {
return 0;
}
return $template_a['created_at'] < $template_b['created_at'] ? -1 : 1;
}
);
return $templates;
}
/**
* Determine if user's license level has access to the template.
*
* @since 1.6.8
*
* @param array $template Template data.
*
* @return bool
*/
private function has_access( $template ) {
if ( ! empty( $template['has_access'] ) ) {
return true;
}
$template_licenses = empty( $template['license'] ) ? [] : array_map( 'strtolower', (array) $template['license'] );
$has_access = true;
foreach ( $template_licenses as $template_license ) {
$has_access = $this->license['index'] >= array_search( $template_license, $this->all_licenses, true );
if ( $has_access ) {
break;
}
}
return $has_access;
}
/**
* Get favorites templates list.
*
* @since 1.7.7
*
* @param bool $all Optional. True for getting all favorites lists. False by default.
*
* @return array
*/
public function get_favorites_list( $all = false ) {
$favorites_list = (array) get_option( self::FAVORITE_TEMPLATES_OPTION, [] );
if ( $all ) {
return $favorites_list;
}
$user_id = get_current_user_id();
return isset( $favorites_list[ $user_id ] ) ? $favorites_list[ $user_id ] : [];
}
/**
* Determine if template is marked as favorite.
*
* @since 1.7.7
*
* @param string $template_slug Template slug.
*
* @return bool
*/
public function is_favorite( $template_slug ) {
static $favorites;
if ( ! $favorites ) {
$favorites = $this->get_favorites_list();
}
return isset( $favorites[ $template_slug ] );
}
/**
* Save favorites templates.
*
* @since 1.7.7
*/
public function ajax_save_favorites() {
if ( ! check_ajax_referer( 'wpforms-form-templates', 'nonce', false ) ) {
wp_send_json_error();
}
if ( ! isset( $_POST['slug'], $_POST['favorite'] ) ) {
wp_send_json_error();
}
$favorites = $this->get_favorites_list( true );
$user_id = get_current_user_id();
$template_slug = sanitize_text_field( wp_unslash( $_POST['slug'] ) );
$is_favorite = sanitize_key( $_POST['favorite'] ) === 'true';
$is_exists = isset( $favorites[ $user_id ][ $template_slug ] );
if ( $is_favorite && $is_exists ) {
wp_send_json_success();
}
if ( $is_favorite ) {
$favorites[ $user_id ][ $template_slug ] = true;
} elseif ( $is_exists ) {
unset( $favorites[ $user_id ][ $template_slug ] );
}
update_option( self::FAVORITE_TEMPLATES_OPTION, $favorites );
wp_send_json_success();
}
/**
* Determine if the template exists and the customer has access to it.
*
* @since 1.7.5.3
*
* @param string $slug Template slug or ID.
*
* @return bool
*/
public function is_valid_template( $slug ) {
$template = $this->get_template_by_id( $slug );
if ( ! $template ) {
return ! empty( $this->get_template_by_slug( $slug ) );
}
$has_cache = wpforms()->get( 'builder_template_single' )->instance( $template['id'], $this->license )->get();
return $this->has_access( $template ) && $has_cache;
}
/**
* Determine license level of the template.
*
* @since 1.6.8
*
* @param array $template Template data.
*
* @return string
*/
private function get_license_level( $template ) {
$licenses_pro = [ 'basic', 'plus', 'pro' ];
$licenses_template = (array) $template['license'];
if (
empty( $template['license'] ) ||
in_array( 'lite', $licenses_template, true )
) {
return '';
}
foreach ( $licenses_pro as $license ) {
if ( in_array( $license, $licenses_template, true ) ) {
return 'pro';
}
}
return 'elite';
}
/**
* Get categories data.
*
* @since 1.6.8
*
* @return array
*/
public function get_categories() {
return $this->categories;
}
/**
* Get subcategories data.
*
* @since 1.8.4
*
* @return array
*/
public function get_subcategories() {
return $this->subcategories;
}
/**
* Get templates data.
*
* @since 1.6.8
*
* @return array
*/
public function get_templates() {
static $templates = [];
if ( ! empty( $templates ) ) {
return $templates;
}
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
/**
* Form templates available in the WPForms core plugin.
*
* @since 1.4.0
*
* @param array $templates Core templates data.
*/
$core_templates = (array) apply_filters( 'wpforms_form_templates_core', [] );
/**
* Form templates available with the WPForms addons.
* Allows developers to provide additional templates with an addons.
*
* @since 1.4.0
*
* @param array $templates Addons templates data.
*/
$additional_templates = (array) apply_filters( 'wpforms_form_templates', [] );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
$templates = array_merge( $core_templates, $additional_templates );
return $templates;
}
/**
* Get single template data.
*
* @since 1.6.8
*
* @param string $slug Template slug OR Id.
*
* @return array
*/
public function get_template( $slug ) {
$template = $this->get_template_by_slug( $slug );
if ( ! $template ) {
$template = $this->get_template_by_id( $slug );
}
if ( empty( $template ) ) {
return [];
}
if ( empty( $template['id'] ) ) {
return $template;
}
// Attempt to get template with form data (if available).
$full_template = wpforms()
->get( 'builder_template_single' )
->instance( $template['id'], $this->license )
->get();
if ( ! empty( $full_template['data'] ) ) {
return $full_template;
}
return $template;
}
/**
* Get template data by slug.
*
* @since 1.7.5.3
*
* @param string $slug Template slug.
*
* @return array
*/
private function get_template_by_slug( $slug ) {
foreach ( $this->get_templates() as $template ) {
if ( ! empty( $template['slug'] ) && $template['slug'] === $slug ) {
return $template;
}
}
return [];
}
/**
* Get template data by Id.
*
* @since 1.6.8
*
* @param string $id Template id.
*
* @return array
*/
private function get_template_by_id( $id ) {
foreach ( $this->api_templates as $template ) {
if ( ! empty( $template['id'] ) && $template['id'] === $id ) {
return $template;
}
}
return [];
}
/**
* Add templates to the list on the Setup panel.
*
* @since 1.6.8
*
* @param array $templates Templates list.
*
* @return array
*/
public function add_templates_to_setup_panel( $templates ) {
return array_merge( $templates, $this->api_templates );
}
/**
* Add template data when form is created.
*
* @since 1.6.8
*
* @param array $args Create form arguments.
* @param array $data Template data.
*
* @return array
*/
public function apply_to_new_form( $args, $data ) {
if ( empty( $data ) || empty( $data['template'] ) ) {
return $args;
}
$template = $this->get_template( $data['template'] );
if (
empty( $template['data'] ) ||
! $this->has_access( $template )
) {
return $args;
}
$template['data']['meta']['template'] = $template['id'];
// Enable Notifications by default.
$template['data']['settings']['notification_enable'] = isset( $template['data']['settings']['notification_enable'] )
? $template['data']['settings']['notification_enable']
: 1;
// Unset settings that should be defined locally.
unset(
$template['data']['settings']['form_title'],
$template['data']['settings']['conversational_forms_title'],
$template['data']['settings']['form_pages_title']
);
// Unset certain values for each Notification, since:
// - Email Subject Line field (subject) depends on the form name that is generated from the template name and form_id.
// - From Name field (sender_name) depends on the blog name and can be replaced by WP Mail SMTP plugin.
// - From Email field (sender_address) depends on the internal logic and can be replaced by WP Mail SMTP plugin.
if ( ! empty( $template['data']['settings']['notifications'] ) ) {
foreach ( (array) $template['data']['settings']['notifications'] as $key => $notification ) {
unset(
$template['data']['settings']['notifications'][ $key ]['subject'],
$template['data']['settings']['notifications'][ $key ]['sender_name'],
$template['data']['settings']['notifications'][ $key ]['sender_address']
);
}
}
// Encode template data to post content.
$args['post_content'] = wpforms_encode( $template['data'] );
return $args;
}
/**
* Add template data when form is updated.
*
* @since 1.6.8
*
* @param array $form Form post data.
* @param array $data Form data.
* @param array $args Update form arguments.
*
* @return array
*/
public function apply_to_existing_form( $form, $data, $args ) {
if ( empty( $args ) || empty( $args['template'] ) ) {
return $form;
}
$template = $this->get_template( $args['template'] );
if (
empty( $template['data'] ) ||
! $this->has_access( $template )
) {
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 preserving needed data from the existing form.
$new = $template['data'];
$new['id'] = isset( $form['ID'] ) ? $form['ID'] : 0;
$new['field_id'] = isset( $form_data['field_id'] ) ? $form_data['field_id'] : 0;
$new['settings'] = isset( $form_data['settings'] ) ? $form_data['settings'] : [];
$new['payments'] = isset( $form_data['payments'] ) ? $form_data['payments'] : [];
$new['meta'] = isset( $form_data['meta'] ) ? $form_data['meta'] : [];
$new['meta']['template'] = $template['id'];
/**
* Allow modifying form data when a new template is applied.
*
* @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_admin_builder_templates_apply_to_existing_form_modify_data', $new, $form_data, $template );
// Update the form with new data.
$form['post_content'] = wpforms_encode( $new );
return $form;
}
/**
* Create a form on request.
*
* @since 1.6.8
*/
public function create_form_on_request() {
$template = $this->get_template_on_request();
// Just return if template not found OR user doesn't have access.
if ( empty( $template['has_access'] ) ) {
return;
}
// Check if the template requires some addons.
if ( $this->check_template_required_addons( $template ) ) {
return;
}
// Set form title equal to the template's name.
$form_title = ! empty( $template['name'] ) ? $template['name'] : esc_html__( 'New form', '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()->get( 'form' )->add(
$form_title,
[],
[
'template' => $template['id'],
]
);
// Return if something wrong.
if ( ! $form_id ) {
return;
}
// Update form title if duplicated.
if ( $title_exists ) {
wpforms()->get( 'form' )->update(
$form_id,
[
'settings' => [
'form_title' => $form_title . ' (ID #' . $form_id . ')',
],
]
);
}
$this->create_form_on_request_redirect( $form_id );
}
/**
* Get template data before creating a new form on request.
*
* @since 1.6.8
*
* @return array|bool Template OR false.
*/
private function get_template_on_request() {
if ( ! wpforms_is_admin_page( 'builder' ) || ! wpforms_is_admin_page( 'templates' ) ) {
return false;
}
if ( ! wpforms_current_user_can( 'create_forms' ) ) {
return false;
}
$form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! empty( $form_id ) ) {
return false;
}
$view = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'setup'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $view !== 'setup' ) {
return false;
}
$template_id = isset( $_GET['template_id'] ) ? sanitize_key( $_GET['template_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// Attempt to get the template.
$template = $this->get_template( $template_id );
// Just return if template is not found.
if ( empty( $template ) ) {
return false;
}
return $template;
}
/**
* Redirect after creating the form.
*
* @since 1.6.8
*
* @param integer $form_id Form ID.
*/
private function create_form_on_request_redirect( $form_id ) {
// Redirect to the builder if possible.
if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
wp_safe_redirect(
add_query_arg(
[
'view' => 'fields',
'form_id' => $form_id,
'newform' => '1',
],
admin_url( 'admin.php?page=wpforms-builder' )
)
);
exit;
}
// Redirect to the forms overview admin page if possible.
if ( wpforms_current_user_can( 'view_forms' ) ) {
wp_safe_redirect(
admin_url( 'admin.php?page=wpforms-overview' )
);
exit;
}
// Finally, redirect to the admin dashboard.
wp_safe_redirect( admin_url() );
exit;
}
/**
* Check if the template requires some addons and then redirect to the builder for further interaction if needed.
*
* @since 1.6.8
*
* @param array $template Template data.
*
* @return bool True if template requires some addons that are not yet installed and/or activated.
*/
private function check_template_required_addons( $template ) {
// Return false if none addons required.
if ( empty( $template['addons'] ) ) {
return false;
}
$required_addons = wpforms()->get( 'addons' )->get_by_slugs( $template['addons'] );
foreach ( $required_addons as $i => $addon ) {
if ( empty( $addon['action'] ) || ! in_array( $addon['action'], [ 'install', 'activate' ], true ) ) {
unset( $required_addons[ $i ] );
}
}
// Return false if not need to install or activate any addons.
// We can proceed with creating the form directly in this process.
if ( empty( $required_addons ) ) {
return false;
}
// Otherwise return true.
return true;
}
/**
* Template for upgrade banner.
*
* @since 1.7.7
*/
public function upgrade_banner_template() {
if ( in_array( wpforms_get_license_type(), [ 'pro', 'elite', 'agency', 'ultimate' ], true ) ) {
return;
}
$medium = wpforms_is_admin_page( 'templates' ) ? 'Form Templates Subpage' : 'Builder Templates';
?>
<script type="text/html" id="tmpl-wpforms-templates-upgrade-banner">
<div class="wpforms-template-upgrade-banner">
<div class="wpforms-template-content">
<h3>
<?php
/* translators: %d - templates count. */
printf( esc_html__( 'Get Access to Our Library of %d Pre-Made Form Templates', 'wpforms-lite' ), count( $this->get_templates() ) );
?>
</h3>
<p>
<?php esc_html_e( 'Never start from scratch again! While WPForms Lite allows you to create any type of form, you can save even more time with WPForms Pro. Upgrade to access hundreds more form templates and advanced form fields.', 'wpforms-lite' ); ?>
</p>
</div>
<div class="wpforms-template-upgrade-button">
<a href="<?php echo esc_url( wpforms_admin_upgrade_link( $medium, 'Upgrade to Pro' ) ); ?>" class="wpforms-btn wpforms-btn-orange wpforms-btn-md" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Upgrade to PRO', 'wpforms-lite' ); ?></a>
</div>
</div>
</script>
<?php
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace WPForms\Admin\Builder;
use WPForms\Helpers\CacheBase;
/**
* Form templates cache handler.
*
* @since 1.6.8
*/
class TemplatesCache extends CacheBase {
/**
* Determine if the class is allowed to load.
*
* @since 1.6.8
*
* @return bool
*/
protected function allow_load() {
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_requests = wpforms_is_admin_ajax() || wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page( 'templates' );
$allow = wp_doing_cron() || wpforms_doing_wp_cli() || ( $has_permissions && $allowed_requests );
/**
* Whether to load this class.
*
* @since 1.7.2
*
* @param bool $allow True or false.
*/
return (bool) apply_filters( 'wpforms_admin_builder_templatescache_allow_load', $allow ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Provide settings.
*
* @since 1.6.8
*
* @return array Settings array.
*/
protected function setup() {
return [
// Remote source URL.
'remote_source' => 'https://wpforms.com/templates/api/get/',
// Cache file.
'cache_file' => 'templates.json',
/**
* Time-to-live of the templates cache files in seconds.
*
* This applies to `uploads/wpforms/cache/templates.json`
* and all *.json files in `uploads/wpforms/cache/templates/` directory.
*
* @since 1.6.8
*
* @param integer $cache_ttl Cache time-to-live, in seconds.
* Default value: WEEK_IN_SECONDS.
*/
'cache_ttl' => (int) apply_filters( 'wpforms_admin_builder_templates_cache_ttl', WEEK_IN_SECONDS ),
// Scheduled update action.
'update_action' => 'wpforms_admin_builder_templates_cache_update',
];
}
/**
* Prepare data to store in a local cache.
*
* @since 1.6.8
*
* @param array $data Raw data received by the remote request.
*
* @return array Prepared data for caching.
*/
protected function prepare_cache_data( $data ) {
if (
empty( $data ) ||
! is_array( $data ) ||
empty( $data['status'] ) ||
$data['status'] !== 'success' ||
empty( $data['data'] )
) {
return [];
}
$cache_data = $data['data'];
// Strip the word "Template" from the end of each template name.
foreach ( $cache_data['templates'] as $slug => $template ) {
$cache_data['templates'][ $slug ]['name'] = preg_replace( '/\sTemplate$/', '', $template['name'] );
}
return $cache_data;
}
}

View File

@@ -0,0 +1,580 @@
<?php
namespace WPForms\Admin\Builder\Traits;
/**
* Trait ContentInput.
*
* @since 1.7.8
*/
trait ContentInput {
/**
* Translatable strings.
*
* @since 1.7.8
*
* @var null|array Translatable strings.
*/
private static $translatable_strings;
/**
* Constructor overloader to register trait specific hooks.
*
* @since 1.7.8
*
* @param bool $init Pass false to allow to shortcut the whole initialization, if needed.
*/
public function __construct( $init = true ) {
if ( ! $init ) {
return;
}
$this->content_input_hooks();
parent::__construct( $init );
}
/**
* Register hooks.
*
* @since 1.7.8
*/
private function content_input_hooks() {
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_enqueues' ] );
add_action( 'wpforms_builder_print_footer_scripts', [ $this, 'content_editor_tools_template' ] );
add_filter( 'wpforms_builder_field_option_class', [ $this, 'builder_field_option_class' ], 10, 2 );
add_filter( 'wpforms_builder_strings', [ $this, 'content_builder_strings' ], 10, 2 );
add_filter( 'editor_stylesheets', [ $this, 'editor_stylesheets' ] );
add_filter( 'media_view_strings', [ $this, 'edit_media_view_strings' ], 10, 2 );
add_filter( 'teeny_mce_buttons', [ $this, 'teeny_mce_buttons' ], 10, 2 );
}
/**
* Content field option.
*
* @since 1.7.8
*
* @param array $field Field data and settings.
*/
private function field_option_content( array $field ) {
$value = ( isset( $field['content'] ) && ! wpforms_is_empty_string( $field['content'] ) ) ? wp_kses( $field['content'], $this->get_allowed_html_tags() ) : '';
$output = $this->field_element(
'row',
$field,
[
'slug' => 'content',
'content' => $this->get_content_editor( $value, $field ),
],
false
);
$output .= wpforms_render(
'fields/content/action-buttons',
[
'id' => $field['id'],
'preview' => $this->get_input_string( 'preview' ),
'expand' => $this->get_input_string( 'expand' ),
],
true
);
printf( '<div class="wpforms-expandable-editor">%s</div><div class="wpforms-expandable-editor-clear"></div>', $output ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Add class name to the field option top element.
*
* @since 1.7.8
*
* @param string $class CSS classes.
* @param array $field Field data.
*/
public function builder_field_option_class( $class, $field ) {
return $this->type === $field['type'] ? $class . ' wpforms-field-has-tinymce' : $class;
}
/**
* Localized strings for content-field JS script.
*
* @since 1.7.8
*
* @param array $strings Localized strings.
* @param array $form The form element.
*
* @return array
* @noinspection PhpUnusedParameterInspection
*/
public function content_builder_strings( $strings, $form ) {
$strings['content_field'] = [
'collapse' => wp_strip_all_tags( $this->get_input_string( 'collapse' ) ),
'expand' => wp_strip_all_tags( $this->get_input_string( 'expand' ) ),
'editor_default_value' => wp_kses( $this->get_input_string( 'editor_default_value' ), $this->get_allowed_html_tags() ),
'content_editor_plugins' => $this->content_editor_plugins(),
'content_editor_toolbar' => $this->content_editor_toolbar(),
'content_editor_css_url' => $this->content_css_url(),
'editor_height' => $this->get_editor_height(),
'allowed_html' => array_keys( $this->get_allowed_html_tags() ),
'invalid_elements' => $this->get_invalid_elements(),
'quicktags_buttons' => $this->get_quicktags_buttons(),
'body_class' => $this->get_editor_body_class(),
];
$strings = $this->add_supported_field_type( $strings, $this->type );
return $strings;
}
/**
* Add editor stylesheet.
*
* @since 1.7.8
*
* @param array $stylesheets Editor stylesheets.
*
* @return array
*/
public function editor_stylesheets( $stylesheets ) {
if ( wpforms_is_admin_page( 'builder' ) ) {
$stylesheets[] = $this->content_css_url();
}
return $stylesheets;
}
/**
* Edit some media view strings to reference a form instead of a page/post.
*
* @since 1.7.8
*
* @param array $strings List of media view strings.
* @param WP_Post $post Post object.
*
* @return array Modified media view strings.
*/
public function edit_media_view_strings( $strings, $post ) {
if ( wpforms_is_admin_page( 'builder' ) ) {
$strings['insertIntoPost'] = esc_html__( 'Insert into form', 'wpforms-lite' );
$strings['uploadedToThisPost'] = esc_html__( 'Uploaded to this form', 'wpforms-lite' );
}
return $strings;
}
/**
* Remove fullscreen button if this is other tinymce editor instance than content field editor.
*
* @since 1.7.8
*
* @param array $buttons Array of editor buttons.
* @param string $editor_id Editor textarea ID.
*
* @return array
*/
public function teeny_mce_buttons( $buttons, $editor_id ) {
$is_other_editor = strpos( $editor_id, 'wpforms_panel_' ) === 0 || $editor_id === 'entry_note';
$key = array_search( 'fullscreen', $buttons, true );
if ( $is_other_editor && $key !== false ) {
unset( $buttons[ $key ] );
}
return $buttons;
}
/**
* Get default content editor plugins.
*
* @since 1.7.8
*
* @return array Plugins array.
*/
private function content_editor_plugins() {
$plugins = [
'charmap',
'colorpicker',
'hr',
'link',
'image',
'lists',
'paste',
'tabfocus',
'textcolor',
'wordpress',
'wpemoji',
'wptextpattern',
'wpeditimage',
];
/**
* Get content editor plugins filter.
*
* @since 1.7.8
*
* @param array $plugins Plugins array.
*/
return (array) apply_filters( 'wpforms_builder_content_input_get_content_editor_plugins', $plugins );
}
/**
* Get default content editor toolbar.
*
* @since 1.7.8
*
* @return array Toolbar buttons array.
*/
private function content_editor_toolbar() {
$toolbar = [
'formatselect',
'bold',
'italic',
'underline',
'strikethrough',
'forecolor',
'link',
'bullist',
'numlist',
'blockquote',
'alignleft',
'aligncenter',
'alignright',
];
/**
* Get content editor toolbar buttons filter.
*
* @since 1.7.8
*
* @param array $toolbar Toolbar buttons array.
*/
return (array) apply_filters( 'wpforms_builder_content_input_get_content_editor_toolbar', $toolbar );
}
/**
* Enqueue wpforms-content-field script.
*
* @since 1.7.8
*
* @param string $view Current view.
*
* @noinspection PhpUnusedParameterInspection, PhpUnnecessaryCurlyVarSyntaxInspection
*/
public function builder_enqueues( $view ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-content-field',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/fields/content-field{$min}.js",
[ 'wpforms-builder', 'editor', 'quicktags' ],
WPFORMS_VERSION,
true
);
// Enqueue editor styles explicitly. Hack for broken styles when Content field is deleted and Settings > Confirmation editor get broken.
wp_enqueue_style(
'wpforms-editor-styles',
includes_url( 'css/editor.css' )
);
}
/**
* Content editor tools template.
*
* @since 1.7.8
*/
public function content_editor_tools_template() {
?>
<script type="text/html" id="tmpl-wpforms-content-editor-tools">
<div id="wp-wpforms-field-{{data.optionId}}-content-editor-tools" class="wp-editor-tools hide-if-no-js">
<div id="wp-wpforms-field-{{data.optionId}}-content-media-buttons" class="wp-media-buttons">
<button type="button" id="insert-media-button" class="button insert-media add_media" data-editor="wpforms-field-{{data.optionId}}-content">
<span class="wp-media-buttons-icon"></span>
<?php esc_html_e( 'Add Media', 'wpforms-lite' ); ?>
</button>
</div>
<div class="wp-editor-tabs">
<button type="button" id="wpforms-field-{{data.optionId}}-content-tmce" class="wp-switch-editor switch-tmce" data-wp-editor-id="wpforms-field-{{data.optionId}}-content">
<?php esc_html_e( 'Visual', 'wpforms-lite' ); ?>
</button>
<button type="button" id="wpforms-field-{{data.optionId}}-content-html" class="wp-switch-editor switch-html" data-wp-editor-id="wpforms-field-{{data.optionId}}-content">
<?php esc_html_e( 'Text', 'wpforms-lite' ); ?>
</button>
</div>
</div>
</script>
<?php
}
/**
* Register types in JS localisation to use in WPFormsContentField.
*
* @since 1.7.8
*
* @param array $strings Localized strings.
* @param string $type Field type.
*
* @return array
*/
private function add_supported_field_type( $strings, $type ) {
$other_supported_field_types = isset( $strings['content_input']['supported_field_types'] ) ? $strings['content_input']['supported_field_types'] : [];
$strings['content_input'] = [
'supported_field_types' => array_merge( $other_supported_field_types, [ $type ] ),
];
return $strings;
}
/**
* Get translatable string.
*
* @since 1.7.8
*
* @param string $key String key.
*
* @return string
*/
private function get_input_string( $key ) {
if ( ! self::$translatable_strings ) {
self::$translatable_strings = [
'editor_default_value' => __( '<h4>Add Text and Images to Your Form With Ease</h4> <p>To get started, replace this text with your own.</p>', 'wpforms-lite' ),
'expand' => __( 'Expand Editor', 'wpforms-lite' ),
'collapse' => __( 'Collapse Editor', 'wpforms-lite' ),
'preview' => __( 'Update Preview', 'wpforms-lite' ),
];
}
return isset( self::$translatable_strings[ $key ] ) ? self::$translatable_strings[ $key ] : '';
}
/**
* Show field preview in the right builder panel.
*
* @since 1.7.8
*
* @param array $field Field data.
*/
private function content_input_preview( $field ) {
$content = isset( $field['content'] ) ? $field['content'] : $this->get_input_string( 'editor_default_value' );
?>
<div class="wpforms-field-content-preview">
<?php echo wp_kses( $this->do_caption_shortcode( wpautop( $content ) ), $this->get_allowed_html_tags() ); ?>
<div class="wpforms-field-content-preview-end"></div>
</div>
<?php
}
/**
* Check if shortcode is [caption] and if not, return processed content string.
*
* @since 1.7.8
*
* @param false|string $return Short-circuit return value. Either false or the value to replace the shortcode with.
* @param string $tag Shortcode name.
* @param array|string $attr Shortcode attributes array or empty string.
* @param array $m Regular expression match array.
*
* @return false|string
*/
public function short_circuit_shortcodes( $return, $tag, $attr, $m ) {
return $tag !== 'caption' ? $m[0] : false;
}
/**
* Check if shortcode is [caption] and if not, short-circuit processing the shortcode.
*
* @since 1.7.8
*
* @param string $content Editor content.
*
* @return string
*/
private function do_caption_shortcode( $content ) {
/**
* Check if user allowed to execute all shortcodes on content field value.
*
* @since 1.7.8
*
* @param bool $bool Boolean if shortcodes should be executed.
*/
if ( apply_filters( 'wpforms_content_input_value_do_shortcode', false ) && ! wpforms_is_admin_page( 'builder' ) ) {
return do_shortcode( $content );
}
add_filter( 'pre_do_shortcode_tag', [ $this, 'short_circuit_shortcodes' ], 10, 4 );
$content = do_shortcode( $content );
remove_filter( 'pre_do_shortcode_tag', [ $this, 'short_circuit_shortcodes' ] );
return $content;
}
/**
* Get TinyMCE editor for content field.
*
* @since 1.7.8
*
* @param string $value Field value.
* @param array $field Field data.
*
* @return string
*/
private function get_content_editor( $value, $field ) {
/*
Heads up, if you are going to edit editor settings, bear in mind editor is instantiated in two places:
- PHP instance in \WPForms\Admin\Builder\Traits\ContentInput::get_content_editor
- JS instance in WPForms.Admin.Builder.ContentField.initTinyMCE
*/
$settings = [
'media_buttons' => true,
'drag_drop_upload' => true,
'textarea_name' => "fields[{$field['id']}][content]",
'editor_height' => $this->get_editor_height(),
'editor_class' => ! empty( $field['required'] ) ? 'wpforms-field-required' : '',
'tinymce' => [
'init_instance_callback' => 'wpformsContentFieldTinyMCECallback',
'plugins' => implode( ',', $this->content_editor_plugins() ),
'toolbar1' => implode( ',', $this->content_editor_toolbar() ),
'invalid_elements' => $this->get_invalid_elements(),
'relative_urls' => false,
'remove_script_host' => false,
'object_resizing' => false,
'body_class' => $this->get_editor_body_class(),
],
'quicktags' => [
'buttons' => $this->get_quicktags_buttons(),
],
];
ob_start();
wp_editor( $value, 'wpforms-field-option-' . $field['id'] . '-content', $settings );
return ob_get_clean();
}
/**
* Get invalid HTML in content editor.
*
* @since 1.7.8
*
* @return string Invalid HTML elements.
*/
private function get_invalid_elements() {
return 'form,input,textarea,select,option,script,embed,iframe';
}
/**
* Get list of quicktags buttons.
*
* @since 1.7.8
*
* @return string Quicktags buttons.
*/
private function get_quicktags_buttons() {
$quicktag_buttons = [
'strong',
'em',
'block',
'del',
'ins',
'img',
'ul',
'ol',
'li',
'code',
'link',
'close',
];
/**
* Get list of quicktags buttons filter.
*
* @since 1.7.8
*
* @param string $quicktags_buttons Comma separated list of quicktags buttons.
*/
return implode( ',', apply_filters( 'wpforms_builder_content_input_get_quicktags_buttons', $quicktag_buttons ) );
}
/**
* Get content CSS url.
*
* @since 1.7.8
*
* @return string
*/
private function content_css_url() {
$min = wpforms_get_min_suffix();
return WPFORMS_PLUGIN_URL . "assets/css/builder/content-editor{$min}.css";
}
/**
* Get content editor height.
*
* @since 1.7.8
*
* @retun int Editor textarea height.
*/
private function get_editor_height() {
/**
* Get content editor height filter.
*
* @since 1.7.8
*
* @param int $height Editor textarea height.
*/
return (int) apply_filters( 'wpforms_builder_content_input_get_editor_height', 204 );
}
/**
* Get allowed HTML tags for Content Input Field.
*
* @since 1.7.8
*
* @return array
*/
private function get_allowed_html_tags() {
/**
* Filter allowed HTML tags in the content field input.
*
* @since 1.7.8
*
* @param array $allowed_tags Allowed tags.
*/
return (array) apply_filters( 'wpforms_builder_content_input_get_allowed_html_tags', wpforms_get_allowed_html_tags_for_richtext_field() );
}
/**
* Get editor body class.
*
* @since 1.7.9
*
* @return string
*/
private function get_editor_body_class() {
return 'wpforms-content-field-editor-body';
}
}

View File

@@ -0,0 +1,752 @@
<?php
namespace WPForms\Admin;
/**
* Challenge and guide a user to set up a first form once WPForms is installed.
*
* @since 1.5.0
* @since 1.6.2 Challenge v2
*/
class Challenge {
/**
* Number of minutes to complete the Challenge.
*
* @since 1.5.0
*
* @var int
*/
protected $minutes = 5;
/**
* Initialize.
*
* @since 1.6.2
*/
public function init() {
if ( current_user_can( wpforms_get_capability_manage_options() ) ) {
$this->hooks();
}
}
/**
* Hooks.
*
* @since 1.5.0
*/
public function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'wpforms_builder_init', [ $this, 'init_challenge' ] );
add_action( 'admin_footer', [ $this, 'challenge_html' ] );
add_action( 'wpforms_welcome_intro_after', [ $this, 'welcome_html' ] );
add_action( 'wp_ajax_wpforms_challenge_save_option', [ $this, 'save_challenge_option_ajax' ] );
add_action( 'wp_ajax_wpforms_challenge_send_contact_form', [ $this, 'send_contact_form_ajax' ] );
}
/**
* Check if the current page is related to Challenge.
*
* @since 1.5.0
*/
public function is_challenge_page() {
return wpforms_is_admin_page() ||
$this->is_builder_page() ||
$this->is_form_embed_page();
}
/**
* Check if the current page is a forms builder page related to Challenge.
*
* @since 1.5.0
*
* @return bool
*/
public function is_builder_page() {
if ( ! wpforms_is_admin_page( 'builder' ) ) {
return false;
}
if ( ! $this->challenge_active() && ! $this->challenge_inited() ) {
return false;
}
$step = (int) $this->get_challenge_option( 'step' );
$form_id = (int) $this->get_challenge_option( 'form_id' );
if ( $form_id && $step < 2 ) {
return false;
}
$current_form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$is_new_form = isset( $_GET['newform'] ) ? (int) $_GET['newform'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $is_new_form && $step !== 2 ) {
return false;
}
if ( ! $is_new_form && $form_id !== $current_form_id && $step >= 2 ) {
// In case if user skipped the Challenge by closing the browser window or exiting the builder,
// we need to set the previous Challenge as `canceled`.
// Otherwise, the Form Embed Wizard will think that the Challenge is active.
$this->set_challenge_option(
[
'status' => 'skipped',
'finished_date_gmt' => current_time( 'mysql', true ),
]
);
return false;
}
return true;
}
/**
* Check if the current page is a form embed page edit related to Challenge.
*
* @since 1.5.0
*
* @return bool
*/
public function is_form_embed_page() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
if ( ! function_exists( 'get_current_screen' ) || ! is_admin() || ! is_user_logged_in() ) {
return false;
}
$screen = get_current_screen();
if ( ! isset( $screen->id ) || $screen->id !== 'page' || ! $this->challenge_active() ) {
return false;
}
$step = $this->get_challenge_option( 'step' );
if ( ! in_array( $step, [ 3, 4, 5 ], true ) ) {
return false;
}
$embed_page = $this->get_challenge_option( 'embed_page' );
$is_embed_page = false;
if ( isset( $screen->action ) && $screen->action === 'add' && $embed_page === 0 ) {
$is_embed_page = true;
}
if ( isset( $_GET['post'] ) && $embed_page === (int) $_GET['post'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$is_embed_page = true;
}
if ( $is_embed_page && $step < 4 ) {
$this->set_challenge_option( [ 'step' => 4 ] );
}
return $is_embed_page;
}
/**
* Load scripts and styles.
*
* @since 1.5.0
*/
public function enqueue_scripts() {
if ( ! $this->challenge_can_start() && ! $this->challenge_active() ) {
return;
}
$min = wpforms_get_min_suffix();
if ( $this->is_challenge_page() ) {
wp_enqueue_style(
'wpforms-challenge',
WPFORMS_PLUGIN_URL . "assets/css/challenge{$min}.css",
[],
WPFORMS_VERSION
);
wp_enqueue_script(
'wpforms-challenge-admin',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/challenge/challenge-admin{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-challenge-admin',
'wpforms_challenge_admin',
[
'nonce' => wp_create_nonce( 'wpforms_challenge_ajax_nonce' ),
'minutes_left' => absint( $this->minutes ),
'option' => $this->get_challenge_option(),
]
);
}
if ( $this->is_builder_page() || $this->is_form_embed_page() ) {
wp_enqueue_style(
'tooltipster',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css',
null,
'4.2.6'
);
wp_enqueue_script(
'tooltipster',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js',
[ 'jquery' ],
'4.2.6',
true
);
wp_enqueue_script(
'wpforms-challenge-core',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/challenge/challenge-core{$min}.js",
[ 'jquery', 'tooltipster', 'wpforms-challenge-admin' ],
WPFORMS_VERSION,
true
);
}
if ( $this->is_builder_page() ) {
wp_enqueue_script(
'wpforms-challenge-builder',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/challenge/challenge-builder{$min}.js",
[ 'jquery', 'tooltipster', 'wpforms-challenge-core', 'wpforms-builder' ],
WPFORMS_VERSION,
true
);
}
if ( $this->is_form_embed_page() ) {
wp_enqueue_style(
'wpforms-font-awesome',
WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/font-awesome.min.css',
null,
'4.7.0'
);
wp_enqueue_script(
'wpforms-challenge-embed',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/challenge/challenge-embed{$min}.js",
[ 'jquery', 'tooltipster', 'wpforms-challenge-core' ],
WPFORMS_VERSION,
true
);
}
}
/**
* Get 'wpforms_challenge' option schema.
*
* @since 1.5.0
*
* @return array
*/
public function get_challenge_option_schema() {
return [
'status' => '',
'step' => 0,
'user_id' => get_current_user_id(),
'form_id' => 0,
'embed_page' => 0,
'embed_page_title' => '',
'started_date_gmt' => '',
'finished_date_gmt' => '',
'seconds_spent' => 0,
'seconds_left' => 0,
'feedback_sent' => false,
'feedback_contact_me' => false,
'window_closed' => '',
];
}
/**
* Get Challenge parameter(s) from Challenge option.
*
* @since 1.5.0
*
* @param array|string|null $query Query using 'wpforms_challenge' schema keys.
*
* @return array|mixed
*/
public function get_challenge_option( $query = null ) {
if ( ! $query ) {
return get_option( 'wpforms_challenge' );
}
$return_single = false;
if ( ! is_array( $query ) ) {
$return_single = true;
$query = [ $query ];
}
$query = array_flip( $query );
$option = get_option( 'wpforms_challenge' );
if ( ! $option || ! is_array( $option ) ) {
return array_intersect_key( $this->get_challenge_option_schema(), $query );
}
$result = array_intersect_key( $option, $query );
if ( $return_single ) {
$result = reset( $result );
}
return $result;
}
/**
* Set Challenge parameter(s) to Challenge option.
*
* @since 1.5.0
*
* @param array $query Query using 'wpforms_challenge' schema keys.
*/
public function set_challenge_option( $query ) {
if ( empty( $query ) || ! is_array( $query ) ) {
return;
}
$schema = $this->get_challenge_option_schema();
$replace = array_intersect_key( $query, $schema );
if ( ! $replace ) {
return;
}
// Validate and sanitize the data.
foreach ( $replace as $key => $value ) {
if ( in_array( $key, [ 'step', 'user_id', 'form_id', 'embed_page', 'seconds_spent', 'seconds_left' ], true ) ) {
$replace[ $key ] = absint( $value );
continue;
}
if ( in_array( $key, [ 'feedback_sent', 'feedback_contact_me' ], true ) ) {
$replace[ $key ] = wp_validate_boolean( $value );
continue;
}
$replace[ $key ] = sanitize_text_field( $value );
}
$option = get_option( 'wpforms_challenge' );
$option = ! $option || ! is_array( $option ) ? $schema : $option;
update_option( 'wpforms_challenge', array_merge( $option, $replace ) );
}
/**
* Check if any forms are present on a site.
*
* @since 1.5.0
*
* @retun bool
*/
public function website_has_forms() {
return (bool) wpforms()->get( 'form' )->get(
'',
[
'numberposts' => 1,
'nopaging' => false,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'suppress_filters' => true,
]
);
}
/**
* Check if Challenge was started.
*
* @since 1.5.0
*
* @return bool
*/
public function challenge_started() {
return $this->get_challenge_option( 'status' ) === 'started';
}
/**
* Check if Challenge was initialized.
*
* @since 1.6.2
*
* @return bool
*/
public function challenge_inited() {
return $this->get_challenge_option( 'status' ) === 'inited';
}
/**
* Check if Challenge was paused.
*
* @since 1.6.2
*
* @return bool
*/
public function challenge_paused() {
return $this->get_challenge_option( 'status' ) === 'paused';
}
/**
* Check if Challenge was finished.
*
* @since 1.5.0
*
* @return bool
*/
public function challenge_finished() {
$status = $this->get_challenge_option( 'status' );
return in_array( $status, [ 'completed', 'canceled', 'skipped' ], true );
}
/**
* Check if Challenge is in progress.
*
* @since 1.5.0
*
* @return bool
*/
public function challenge_active() {
return ( $this->challenge_inited() || $this->challenge_started() || $this->challenge_paused() ) && ! $this->challenge_finished();
}
/**
* Force Challenge to start.
*
* @since 1.6.2
*
* @return bool
*/
public function challenge_force_start() {
/**
* Allow force start Challenge for testing purposes.
*
* @since 1.6.2.2
*
* @param bool $is_forced True if Challenge should be started. False by default.
*/
return (bool) apply_filters( 'wpforms_admin_challenge_force_start', false );
}
/**
* Check if Challenge can be started.
*
* @since 1.5.0
*
* @return bool
*/
public function challenge_can_start() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
static $can_start = null;
if ( $can_start !== null ) {
return $can_start;
}
if ( $this->challenge_force_skip() ) {
$can_start = false;
}
// Challenge is only available for WPForms admin pages.
if ( ! wpforms_is_admin_page() && ! wpforms_is_admin_page( 'builder' ) ) {
$can_start = false;
return $can_start;
}
if ( $this->challenge_force_start() && ! $this->is_builder_page() && ! $this->is_form_embed_page() ) {
$can_start = true;
// No need to check something else in this case.
return $can_start;
}
if ( $this->challenge_finished() ) {
$can_start = false;
}
if ( $this->website_has_forms() ) {
$can_start = false;
}
if ( $can_start === null ) {
$can_start = true;
}
return $can_start;
}
/**
* Start the Challenge in Form Builder.
*
* @since 1.5.0
*/
public function init_challenge() {
if ( ! $this->challenge_can_start() ) {
return;
}
$this->set_challenge_option(
wp_parse_args(
[ 'status' => 'inited' ],
$this->get_challenge_option_schema()
)
);
}
/**
* Include Challenge HTML.
*
* @since 1.5.0
*/
public function challenge_html() {
if ( $this->challenge_force_skip() || ( $this->challenge_finished() && ! $this->challenge_force_start() ) ) {
return;
}
if ( wpforms_is_admin_page() && ! wpforms_is_admin_page( 'getting-started' ) && $this->challenge_can_start() ) {
// Before showing the Challenge in the `start` state we should reset the option.
// In this way we ensure the Challenge will not appear somewhere in the builder where it is not should be.
$this->set_challenge_option( [ 'status' => '' ] );
$this->challenge_modal_html( 'start' );
}
if ( $this->is_builder_page() ) {
$this->challenge_modal_html( 'progress' );
$this->challenge_builder_templates_html();
}
if ( $this->is_form_embed_page() ) {
$this->challenge_modal_html( 'progress' );
$this->challenge_embed_templates_html();
}
}
/**
* Include Challenge main modal window HTML.
*
* @since 1.5.0
*
* @param string $state State of Challenge ('start' or 'progress').
*/
public function challenge_modal_html( $state ) {
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'admin/challenge/modal',
[
'state' => $state,
'step' => $this->get_challenge_option( 'step' ),
'minutes' => $this->minutes,
],
true
);
}
/**
* Include Challenge HTML templates specific to Form Builder.
*
* @since 1.5.0
*/
public function challenge_builder_templates_html() {
echo wpforms_render( 'admin/challenge/builder' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Include Challenge HTML templates specific to form embed page.
*
* @since 1.5.0
*/
public function challenge_embed_templates_html() {
/**
* Filter the content of the Challenge Congrats popup footer.
*
* @since 1.7.4
*
* @param string $footer Footer markup.
*/
$congrats_popup_footer = apply_filters( 'wpforms_admin_challenge_embed_template_congrats_popup_footer', '' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/challenge/embed',
[
'minutes' => $this->minutes,
'congrats_popup_footer' => $congrats_popup_footer,
],
true
);
}
/**
* Include Challenge CTA on WPForms welcome activation screen.
*
* @since 1.5.0
*/
public function welcome_html() {
if ( $this->challenge_can_start() ) {
echo wpforms_render( 'admin/challenge/welcome' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Save Challenge data via AJAX.
*
* @since 1.5.0
*/
public function save_challenge_option_ajax() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
check_admin_referer( 'wpforms_challenge_ajax_nonce' );
if ( empty( $_POST['option_data'] ) ) {
wp_send_json_error();
}
$schema = $this->get_challenge_option_schema();
$query = [];
foreach ( $schema as $key => $value ) {
if ( isset( $_POST['option_data'][ $key ] ) ) {
$query[ $key ] = sanitize_text_field( wp_unslash( $_POST['option_data'][ $key ] ) );
}
}
if ( empty( $query ) ) {
wp_send_json_error();
}
if ( ! empty( $query['status'] ) && $query['status'] === 'started' ) {
$query['started_date_gmt'] = current_time( 'mysql', true );
}
if ( ! empty( $query['status'] ) && in_array( $query['status'], [ 'completed', 'canceled', 'skipped' ], true ) ) {
$query['finished_date_gmt'] = current_time( 'mysql', true );
}
if ( ! empty( $query['status'] ) && $query['status'] === 'skipped' ) {
$query['started_date_gmt'] = current_time( 'mysql', true );
$query['finished_date_gmt'] = $query['started_date_gmt'];
}
$this->set_challenge_option( $query );
wp_send_json_success();
}
/**
* Send contact form to wpforms.com via AJAX.
*
* @since 1.5.0
*/
public function send_contact_form_ajax() {
check_admin_referer( 'wpforms_challenge_ajax_nonce' );
$url = 'https://wpforms.com/wpforms-challenge-feedback/';
$message = ! empty( $_POST['contact_data']['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['contact_data']['message'] ) ) : '';
$email = '';
if (
( ! empty( $_POST['contact_data']['contact_me'] ) && $_POST['contact_data']['contact_me'] === 'true' )
|| wpforms()->is_pro()
) {
$current_user = wp_get_current_user();
$email = $current_user->user_email;
$this->set_challenge_option( [ 'feedback_contact_me' => true ] );
}
if ( empty( $message ) && empty( $email ) ) {
wp_send_json_error();
}
$data = [
'body' => [
'wpforms' => [
'id' => 296355,
'submit' => 'wpforms-submit',
'fields' => [
2 => $message,
3 => $email,
4 => $this->get_challenge_license_type(),
5 => wpforms()->version,
6 => wpforms_get_license_key(),
],
],
],
];
$response = wp_remote_post( $url, $data );
if ( is_wp_error( $response ) ) {
wp_send_json_error();
}
$this->set_challenge_option( [ 'feedback_sent' => true ] );
wp_send_json_success();
}
/**
* Get the current WPForms license type as it pertains to the challenge feedback form.
*
* @since 1.8.1
*
* @return string The currently active license type.
*/
private function get_challenge_license_type() {
$license_type = wpforms_get_license_type();
if ( $license_type === false ) {
$license_type = wpforms()->is_pro() ? 'Unknown' : 'Lite';
}
return ucfirst( $license_type );
}
/**
* Force WPForms Challenge to skip.
*
* @since 1.7.6
*
* @return bool
*/
private function challenge_force_skip() {
return defined( 'WPFORMS_SKIP_CHALLENGE' ) && WPFORMS_SKIP_CHALLENGE;
}
}

View File

@@ -0,0 +1,301 @@
<?php
namespace WPForms\Admin\Dashboard;
/**
* Class Widget.
*
* @since 1.7.3
*/
abstract class Widget {
/**
* Instance slug.
*
* @since 1.7.4
*
* @var string
*/
const SLUG = 'dash_widget';
/**
* Save a widget meta for a current user using AJAX.
*
* @since 1.7.4
*/
public function save_widget_meta_ajax() {
check_ajax_referer( 'wpforms_' . static::SLUG . '_nonce' );
$meta = ! empty( $_POST['meta'] ) ? sanitize_key( $_POST['meta'] ) : '';
$value = ! empty( $_POST['value'] ) ? absint( $_POST['value'] ) : 0;
$this->widget_meta( 'set', $meta, $value );
exit();
}
/**
* Get/set a widget meta.
*
* @since 1.7.4
*
* @param string $action Possible value: 'get' or 'set'.
* @param string $meta Meta name.
* @param int $value Value to set.
*
* @return mixed
*/
protected function widget_meta( $action, $meta, $value = 0 ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$allowed_actions = [ 'get', 'set' ];
if ( ! in_array( $action, $allowed_actions, true ) ) {
return false;
}
$defaults = [
'timespan' => $this->get_timespan_default(),
'active_form_id' => 0,
'hide_recommended_block' => 0,
'hide_graph' => 0,
'color_scheme' => 1, // 1 - wpforms, 2 - wp
'graph_style' => 2, // 1 - bar, 2 - line
];
if ( ! array_key_exists( $meta, $defaults ) ) {
return false;
}
$meta_key = 'wpforms_' . static::SLUG . '_' . $meta;
if ( $action === 'get' ) {
$meta_value = absint( get_user_meta( get_current_user_id(), $meta_key, true ) );
// Return a default value from $defaults if $meta_value is empty.
return empty( $meta_value ) ? $defaults[ $meta ] : $meta_value;
}
$value = absint( $value );
if ( $action === 'set' && ! empty( $value ) ) {
return update_user_meta( get_current_user_id(), $meta_key, $value );
}
if ( $action === 'set' && empty( $value ) ) {
return delete_user_meta( get_current_user_id(), $meta_key );
}
return false;
}
/**
* Get the default timespan option.
*
* @since 1.7.4
*
* @return int|null
*/
protected function get_timespan_default() {
$options = $this->get_timespan_options();
$default = reset( $options );
return is_numeric( $default ) ? $default : null;
}
/**
* Get timespan options (in days).
*
* @since 1.7.4
*
* @return array
*/
protected function get_timespan_options() {
$default = [ 7, 30 ];
$options = $default;
// Apply deprecated filters.
if ( function_exists( 'apply_filters_deprecated' ) ) {
// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
$options = apply_filters_deprecated( 'wpforms_dash_widget_chart_timespan_options', [ $options ], '5.0', 'wpforms_dash_widget_timespan_options' );
$options = apply_filters_deprecated( 'wpforms_dash_widget_forms_list_timespan_options', [ $options ], '5.0', 'wpforms_dash_widget_timespan_options' );
// phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
} else {
// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
$options = apply_filters( 'wpforms_dash_widget_chart_timespan_options', $options );
$options = apply_filters( 'wpforms_dash_widget_forms_list_timespan_options', $options );
// phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
}
if ( ! is_array( $options ) ) {
$options = $default;
}
$widget_slug = static::SLUG;
// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
$options = apply_filters( "wpforms_{$widget_slug}_timespan_options", $options );
// phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
if ( ! is_array( $options ) ) {
return [];
}
$options = array_filter( $options, 'is_numeric' );
return empty( $options ) ? $default : $options;
}
/**
* Widget settings HTML.
*
* @since 1.7.4
*
* @param bool $enabled Is form fields should be enabled.
*/
protected function widget_settings_html( $enabled = true ) {
$graph_style = $this->widget_meta( 'get', 'graph_style' );
$color_scheme = $this->widget_meta( 'get', 'color_scheme' );
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'admin/dashboard/widget/settings',
[
'graph_style' => $graph_style,
'color_scheme' => $color_scheme,
'enabled' => $enabled,
],
true
);
}
/**
* Return randomly chosen one of recommended plugins.
*
* @since 1.7.3
*
* @return array
*/
final protected function get_recommended_plugin() {
$plugins = [
'google-analytics-for-wordpress/googleanalytics.php' => [
'name' => __( 'MonsterInsights', 'wpforms-lite' ),
'slug' => 'google-analytics-for-wordpress',
'more' => 'https://www.monsterinsights.com/',
'pro' => [
'file' => 'google-analytics-premium/googleanalytics-premium.php',
],
],
'all-in-one-seo-pack/all_in_one_seo_pack.php' => [
'name' => __( 'AIOSEO', 'wpforms-lite' ),
'slug' => 'all-in-one-seo-pack',
'more' => 'https://aioseo.com/',
'pro' => [
'file' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
],
],
'coming-soon/coming-soon.php' => [
'name' => __( 'SeedProd', 'wpforms-lite' ),
'slug' => 'coming-soon',
'more' => 'https://www.seedprod.com/',
'pro' => [
'file' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
],
],
'wp-mail-smtp/wp_mail_smtp.php' => [
'name' => __( 'WP Mail SMTP', 'wpforms-lite' ),
'slug' => 'wp-mail-smtp',
'more' => 'https://wpmailsmtp.com/',
'pro' => [
'file' => 'wp-mail-smtp-pro/wp_mail_smtp.php',
],
],
];
$installed = get_plugins();
foreach ( $plugins as $id => $plugin ) {
if ( isset( $installed[ $id ] ) ) {
unset( $plugins[ $id ] );
}
if ( isset( $plugin['pro']['file'], $installed[ $plugin['pro']['file'] ] ) ) {
unset( $plugins[ $id ] );
}
}
return $plugins ? $plugins[ array_rand( $plugins ) ] : [];
}
/**
* Timespan select HTML.
*
* @since 1.7.4
*
* @param int $active_form_id Currently preselected form ID.
* @param bool $enabled If the select menu items should be enabled.
*/
protected function timespan_select_html( $active_form_id, $enabled = true ) {
?>
<select id="wpforms-dash-widget-timespan" class="wpforms-dash-widget-select-timespan" title="<?php esc_attr_e( 'Select timespan', 'wpforms-lite' ); ?>"
<?php echo ! empty( $active_form_id ) ? 'data-active-form-id="' . absint( $active_form_id ) . '"' : ''; ?>>
<?php $this->timespan_options_html( $this->get_timespan_options(), $enabled ); ?>
</select>
<?php
}
/**
* Timespan select options HTML.
*
* @since 1.7.4
*
* @param array $options Timespan options (in days).
* @param bool $enabled If the select menu items should be enabled.
*/
protected function timespan_options_html( $options, $enabled = true ) {
$timespan = $this->widget_meta( 'get', 'timespan' );
foreach ( $options as $option ) :
?>
<option value="<?php echo absint( $option ); ?>" <?php selected( $timespan, absint( $option ) ); ?> <?php disabled( ! $enabled ); ?>>
<?php /* translators: %d - number of days. */ ?>
<?php echo esc_html( sprintf( _n( 'Last %d day', 'Last %d days', absint( $option ), 'wpforms-lite' ), absint( $option ) ) ); ?>
</option>
<?php
endforeach;
}
/**
* Check if the current page is a dashboard page.
*
* @since 1.8.3
*
* @return bool
*/
protected function is_dashboard_page() {
global $pagenow;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return $pagenow === 'index.php' && empty( $_GET['page'] );
}
/**
* Check if is a dashboard widget ajax request.
*
* @since 1.8.3
*
* @return bool
*/
protected function is_dashboard_widget_ajax_request() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return wpforms_is_admin_ajax() && isset( $_REQUEST['action'] ) && strpos( sanitize_key( $_REQUEST['action'] ), 'wpforms_dash_widget' ) !== false;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace WPForms\Admin\Education;
/**
* Base class for all "addon item" type Education features.
*
* @since 1.6.6
*/
abstract class AddonsItemBase implements EducationInterface {
/**
* Instance of the Education\Core class.
*
* @since 1.6.6
*
* @var \WPForms\Admin\Education\Core
*/
protected $education;
/**
* Instance of the Education\Addons class.
*
* @since 1.6.6
*
* @var \WPForms\Admin\Addons\Addons
*/
protected $addons;
/**
* Template name for rendering single addon item.
*
* @since 1.6.6
*
* @var string
*/
protected $single_addon_template;
/**
* Indicate if current Education feature is allowed to load.
* Should be called from the child feature class.
*
* @since 1.6.6
*
* @return bool
*/
abstract public function allow_load();
/**
* Init.
*
* @since 1.6.6
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
// Store the instance of the Education core class.
$this->education = wpforms()->get( 'education' );
// Store the instance of the Education\Addons class.
$this->addons = wpforms()->get( 'addons' );
// Define hooks.
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.6
*/
abstract public function hooks();
/**
* Display single addon item.
*
* @since 1.6.6
*
* @param array $addon Addon data.
*/
protected function display_single_addon( $addon ) {
/**
* Filter to disallow addons to be displayed in the Education feature.
*
* @since 1.8.2
*
* @param bool $display Whether to hide the addon.
* @param array $slug Addon data.
*/
$is_disallowed = (bool) apply_filters( 'wpforms_admin_education_addons_item_base_display_single_addon_hide', false, $addon );
if ( empty( $addon ) || $is_disallowed ) {
return;
}
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$this->single_addon_template,
$addon,
true
);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace WPForms\Admin\Education;
/**
* Base class for all "addons list" type Education features.
*
* @since 1.6.6
*/
abstract class AddonsListBase extends AddonsItemBase {
/**
* Display addons.
*
* @since 1.6.6
*/
public function display_addons() {
array_map( [ $this, 'display_single_addon' ], (array) $this->get_addons() );
}
/**
* Get addons.
*
* @since 1.6.6
*
* @return array Addons array.
*/
abstract protected function get_addons();
/**
* Ensure that we do not display activated addon items if those addons are not allowed according to the current license.
*
* @since 1.6.6
*
* @param string $hook Hook name.
*/
protected function filter_not_allowed_addons( $hook ) {
$edu_addons = wp_list_pluck( $this->get_addons(), 'slug' );
foreach ( $edu_addons as $i => $addon ) {
$edu_addons[ $i ] = strtolower( preg_replace( '/[^a-zA-Z0-9]+/', '', $addon ) );
}
if ( empty( $GLOBALS['wp_filter'][ $hook ]->callbacks ) ) {
return;
}
foreach ( $GLOBALS['wp_filter'][ $hook ]->callbacks as $priority => $hooks ) {
foreach ( $hooks as $name => $arr ) {
$class = ! empty( $arr['function'][0] ) && is_object( $arr['function'][0] ) ? strtolower( get_class( $arr['function'][0] ) ) : '';
$class = explode( '\\', $class )[0];
$class = preg_replace( '/[^a-zA-Z0-9]+/', '', $class );
if ( in_array( $class, $edu_addons, true ) ) {
unset( $GLOBALS['wp_filter'][ $hook ]->callbacks[ $priority ][ $name ] );
}
}
}
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace WPForms\Admin\Education\Admin\Settings;
use WPForms\Admin\Education\AddonsItemBase;
/**
* Admin/Settings/Geolocation Education feature for Lite and Pro.
*
* @since 1.6.6
*/
class Geolocation extends AddonsItemBase {
/**
* Slug.
*
* @since 1.6.6
*/
const SLUG = 'geolocation';
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
add_filter( 'wpforms_settings_defaults', [ $this, 'add_sections' ] );
}
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
return wpforms_is_admin_page( 'settings', 'geolocation' );
}
/**
* Enqueues.
*
* @since 1.6.6
*/
public function enqueues() {
// Lity - lightbox for images.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
null,
'3.0.0'
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
'3.0.0',
true
);
}
/**
* Preview of education features for customers with not enough permissions.
*
* @since 1.6.6
*
* @param array $settings Settings sections.
*
* @return array
*/
public function add_sections( $settings ) {
$addon = $this->addons->get_addon( 'geolocation' );
if (
empty( $addon ) ||
empty( $addon['status'] ) ||
empty( $addon['action'] )
) {
return $settings;
}
$section_rows = [
'heading',
'screenshots',
'caps',
'submit',
];
foreach ( $section_rows as $section_row ) {
$settings[ self::SLUG ][ self::SLUG . '-' . $section_row ] = [
'id' => self::SLUG . '-' . $section_row,
'content' => wpforms_render( 'education/admin/settings/geolocation/' . $section_row, $addon, true ),
'type' => 'content',
'no_label' => true,
'class' => [ $section_row, 'wpforms-setting-row-education' ],
];
}
return $settings;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace WPForms\Admin\Education\Admin\Settings;
use \WPForms\Admin\Education\AddonsListBase;
/**
* Base class for Admin/Integrations feature for Lite and Pro.
*
* @since 1.6.6
*/
class Integrations extends AddonsListBase {
/**
* Template for rendering single addon item.
*
* @since 1.6.6
*
* @var string
*/
protected $single_addon_template = 'education/admin/settings/integrations-item';
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wpforms_settings_providers', [ $this, 'filter_addons' ], 1 );
add_action( 'wpforms_settings_providers', [ $this, 'display_addons' ], 500 );
}
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
return wpforms_is_admin_page( 'settings', 'integrations' );
}
/**
* Get addons for the Settings/Integrations tab.
*
* @since 1.6.6
*
* @return array Addons data.
*/
protected function get_addons() {
return $this->addons->get_by_category( 'providers' );
}
/**
* Ensure that we do not display activated addon items if those addons are not allowed according to the current license.
*
* @since 1.6.6
*/
public function filter_addons() {
$this->filter_not_allowed_addons( 'wpforms_settings_providers' );
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace WPForms\Admin\Education\Admin\Settings;
use WPForms\Admin\Education\EducationInterface;
/**
* SMTP education notice.
*
* @since 1.8.1
*/
class SMTP implements EducationInterface {
/**
* Indicate if Education core is allowed to load.
*
* @since 1.8.1
*
* @return bool
*/
public function allow_load() {
if ( ! wpforms_can_install( 'plugin' ) || ! wpforms_can_activate( 'plugin' ) ) {
return false;
}
$user_id = get_current_user_id();
$dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true );
if ( ! empty( $dismissed['edu-smtp-notice'] ) ) {
return false;
}
$active_plugins = get_option( 'active_plugins', [] );
$allowed_plugins = [
'wp-mail-smtp/wp_mail_smtp.php',
'wp-mail-smtp-pro/wp_mail_smtp.php',
];
return ! array_intersect( $active_plugins, $allowed_plugins );
}
/**
* Init.
*
* @since 1.8.1
*/
public function init() {
}
/**
* Get notice template.
*
* @since 1.8.1
*
* @return string
*/
public function get_template() {
if ( ! $this->allow_load() ) {
return '';
}
return wpforms_render( 'education/admin/settings/smtp-notice' );
}
}

View File

@@ -0,0 +1,271 @@
<?php
namespace WPForms\Admin\Education\Builder;
use WPForms\Admin\Education\AddonsItemBase;
use WPForms\Admin\Education\Helpers;
/**
* Builder/Calculations Education feature for Lite and Pro.
*
* @since 1.8.4.1
*/
class Calculations extends AddonsItemBase {
/**
* Support calculations in these field types.
*
* @since 1.8.4.1
*
* @var array
*/
const ALLOWED_FIELD_TYPES = [ 'text', 'textarea', 'number', 'hidden', 'payment-single' ];
/**
* Field types that should display educational notice in the basic field options tab.
*
* @since 1.8.4.1
*
* @var array
*/
const BASIC_OPTIONS_NOTICE_FIELD_TYPES = [ 'number', 'payment-single' ];
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.8.4.1
*
* @return bool
*/
public function allow_load() {
return wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_ajax();
}
/**
* Hooks.
*
* @since 1.8.4.1
*/
public function hooks() {
add_action( 'wpforms_field_options_bottom_basic-options', [ $this, 'basic_options' ], 20, 2 );
add_action( 'wpforms_field_options_bottom_advanced-options', [ $this, 'advanced_options' ], 20, 2 );
}
/**
* Display notice on basic options.
*
* @since 1.8.4.1
*
* @param array $field Field data.
* @param object $instance Builder instance.
*
* @noinspection HtmlUnknownTarget
* @noinspection PhpUnusedParameterInspection
*/
public function basic_options( $field, $instance ) {
// Display notice in basic options only in numbers and payment-single fields.
if ( ! in_array( $field['type'], self::BASIC_OPTIONS_NOTICE_FIELD_TYPES, true ) ) {
return;
}
$dismissed = get_user_meta( get_current_user_id(), 'wpforms_dismissed', true );
$form_id = $instance->form_id ?? 0;
$dismiss_section = "builder-form-$form_id-field-options-calculations-notice";
// Check whether it is dismissed.
if ( ! empty( $dismissed[ 'edu-' . $dismiss_section ] ) ) {
return;
}
// Display notice only if Calculations addon is released (available in `addons.json` file).
$addon = $this->addons->get_addon( 'calculations' );
if ( ! $addon ) {
return;
}
$notice = sprintf(
wp_kses( /* translators: %1$s - link to the WPForms.com doc article. */
__( 'Easily perform calculations based on user input. Head over to the <a href="#advanced-tab">Advanced Tab</a> to get started or read <a href="%1$s" target="_blank" rel="noopener noreferrer">our documentation</a> to learn more.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'rel' => [],
'target' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/calculations-addon/', 'Calculations Education', 'Calculations Documentation' ) )
);
printf(
'<div class="wpforms-alert-info wpforms-alert wpforms-educational-alert wpforms-calculations wpforms-dismiss-container">
<button type="button" class="wpforms-dismiss-button" title="%1$s" data-section="%2$s"></button>
<span class="wpforms-educational-badge wpforms-educational-badge-green">%3$s</span>
<h4>%4$s</h4>
<p>%5$s</p>
</div>',
esc_html__( 'Dismiss this notice.', 'wpforms-lite' ),
esc_attr( $dismiss_section ),
esc_html__( 'New feature', 'wpforms-lite' ),
esc_html__( 'Calculations Are Here!', 'wpforms-lite' ),
$notice // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
/**
* Display advanced options.
*
* @since 1.8.4.1
*
* @param array $field Field data.
* @param object $instance Builder instance.
*/
public function advanced_options( $field, $instance ) {
if ( ! in_array( $field['type'], self::ALLOWED_FIELD_TYPES, true ) ) {
return;
}
$addon = $this->addons->get_addon( 'calculations' );
if ( ! $this->is_edu_required_by_status( $addon ) ) {
return;
}
$row_args = $this->get_row_attributes( $addon );
$row_args['content'] = $instance->field_element(
'toggle',
$field,
$this->get_field_attributes( $field, $addon ),
false
);
$instance->field_element( 'row', $field, $row_args );
}
/**
* Get row attributes.
*
* @since 1.8.4.1
*
* @param array $addon Addon data.
*
* @return array
*/
private function get_row_attributes( $addon ) {
$default = [
'slug' => 'calculation_is_enabled',
];
if ( $addon['plugin_allow'] && $addon['action'] === 'install' ) {
return wp_parse_args(
[
'data' => [
'action' => 'install',
'name' => $addon['modal_name'],
'url' => $addon['url'],
'nonce' => wp_create_nonce( 'wpforms-admin' ),
'license' => $addon['license_level'],
],
'class' => 'education-modal',
],
$default
);
}
if ( $addon['plugin_allow'] && $addon['action'] === 'activate' ) {
return wp_parse_args(
[
'data' => [
'action' => 'activate',
'name' => sprintf( /* translators: %s - addon name. */
esc_html__( '%s addon', 'wpforms-lite' ),
$addon['title']
),
'path' => $addon['path'],
'nonce' => wp_create_nonce( 'wpforms-admin' ),
],
'class' => 'education-modal',
],
$default
);
}
return wp_parse_args(
[
'data' => [
'action' => 'upgrade',
'name' => esc_html__( 'Calculations', 'wpforms-lite' ),
'utm-content' => 'Enable Calculations',
'license' => $addon['license_level'],
],
'class' => 'education-modal',
],
$default
);
}
/**
* Get attributes for address autocomplete field.
*
* @since 1.8.4.1
*
* @param array $field Field data.
* @param array $addon Addon data.
*
* @return array
* @noinspection PhpUnusedParameterInspection
*/
private function get_field_attributes( $field, $addon ) {
$default = [
'slug' => 'calculation_is_enabled',
'value' => '0',
'desc' => esc_html__( 'Enable Calculation', 'wpforms-lite' ),
];
if ( $addon['plugin_allow'] ) {
return $default;
}
return wp_parse_args(
[
'desc' => sprintf(
'%1$s%2$s',
esc_html__( 'Enable Calculation', 'wpforms-lite' ),
Helpers::get_badge( $addon['license_level'], 'sm', 'inline', 'slate' )
),
'attrs' => [
'disabled' => 'disabled',
],
],
$default
);
}
/**
* Determine if we require to display educational items according to the addon status.
*
* @since 1.8.4.1
*
* @param array $addon Addon data.
*
* @return bool
* @noinspection PhpUnusedParameterInspection
*/
private function is_edu_required_by_status( $addon ) {
return ! (
empty( $addon ) ||
empty( $addon['action'] ) ||
empty( $addon['status'] ) || (
$addon['status'] === 'active' && $addon['action'] !== 'upgrade'
)
);
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education\EducationInterface;
/**
* Builder/ReCaptcha Education feature.
*
* @since 1.6.6
*/
class Captcha implements EducationInterface {
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*/
public function allow_load() {
return wp_doing_ajax();
}
/**
* Init.
*
* @since 1.6.6
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
// Define hooks.
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wp_ajax_wpforms_update_field_captcha', [ $this, 'captcha_field_callback' ] );
}
/**
* Targeting on hCaptcha/reCAPTCHA field button in the builder.
*
* @since 1.6.6
*/
public function captcha_field_callback() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
// Check for form ID.
if ( empty( $_POST['id'] ) ) {
wp_send_json_error( esc_html__( 'No form ID found.', 'wpforms-lite' ) );
}
$form_id = absint( $_POST['id'] );
// Check for permissions.
if ( ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
wp_send_json_error( esc_html__( 'You do not have permission.', 'wpforms-lite' ) );
}
// Get an actual form data.
$form_data = wpforms()->get( 'form' )->get( $form_id, [ 'content_only' => true ] );
// Check that CAPTCHA is configured in the settings.
$captcha_settings = wpforms_get_captcha_settings();
$captcha_name = $this->get_captcha_name( $captcha_settings );
if ( empty( $form_data ) || empty( $captcha_name ) ) {
wp_send_json_error( esc_html__( 'Something wrong. Please try again later.', 'wpforms-lite' ) );
}
// Prepare a result array.
$data = $this->get_captcha_result_mockup( $captcha_settings );
if ( empty( $captcha_settings['site_key'] ) || empty( $captcha_settings['secret_key'] ) ) {
// If CAPTCHA is not configured in the WPForms plugin settings.
$data['current'] = 'not_configured';
} elseif ( ! isset( $form_data['settings']['recaptcha'] ) || $form_data['settings']['recaptcha'] !== '1' ) {
// If CAPTCHA is configured in WPForms plugin settings, but wasn't set in form settings.
$data['current'] = 'configured_not_enabled';
} else {
// If CAPTCHA is configured in WPForms plugin and form settings.
$data['current'] = 'configured_enabled';
}
wp_send_json_success( $data );
}
/**
* Retrieve the CAPTCHA name.
*
* @since 1.6.6
*
* @param array $settings The CAPTCHA settings.
*
* @return string
*/
private function get_captcha_name( $settings ) {
if ( empty( $settings['provider'] ) ) {
return '';
}
if ( empty( $settings['site_key'] ) && empty( $settings['secret_key'] ) ) {
return esc_html__( 'CAPTCHA', 'wpforms-lite' );
}
if ( $settings['provider'] === 'hcaptcha' ) {
return esc_html__( 'hCaptcha', 'wpforms-lite' );
}
if ( $settings['provider'] === 'turnstile' ) {
return esc_html__( 'Cloudflare Turnstile', 'wpforms-lite' );
}
$recaptcha_names = [
'v2' => esc_html__( 'Google Checkbox v2 reCAPTCHA', 'wpforms-lite' ),
'invisible' => esc_html__( 'Google Invisible v2 reCAPTCHA', 'wpforms-lite' ),
'v3' => esc_html__( 'Google v3 reCAPTCHA', 'wpforms-lite' ),
];
return isset( $recaptcha_names[ $settings['recaptcha_type'] ] ) ? $recaptcha_names[ $settings['recaptcha_type'] ] : '';
}
/**
* Get CAPTCHA callback result mockup.
*
* @since 1.6.6
*
* @param array $settings The CAPTCHA settings.
*
* @return array
*/
private function get_captcha_result_mockup( $settings ) {
$captcha_name = $this->get_captcha_name( $settings );
if ( empty( $captcha_name ) ) {
wp_send_json_error( esc_html__( 'Something wrong. Please, try again later.', 'wpforms-lite' ) );
}
return [
'current' => false,
'cases' => [
'not_configured' => [
'title' => esc_html__( 'Heads up!', 'wpforms-lite' ),
'content' => sprintf(
wp_kses( /* translators: %1$s - CAPTCHA settings page URL, %2$s - WPForms.com doc URL, %3$s - CAPTCHA name. */
__( 'The %3$s settings have not been configured yet. Please complete the setup in your <a href="%1$s" target="_blank">WPForms Settings</a>, and check out our <a href="%2$s" target="_blank" rel="noopener noreferrer">step by step tutorial</a> for full details.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
'rel' => true,
'target' => true,
],
]
),
esc_url( admin_url( 'admin.php?page=wpforms-settings&view=captcha' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/setup-captcha-wpforms/', 'builder-modal', 'Captcha Documentation' ) ),
$captcha_name
),
],
'configured_not_enabled' => [
'title' => false,
/* translators: %s - CAPTCHA name. */
'content' => sprintf( esc_html__( '%s has been enabled for this form. Don\'t forget to save your form!', 'wpforms-lite' ), $captcha_name ),
],
'configured_enabled' => [
'title' => false,
/* translators: %s - CAPTCHA name. */
'content' => sprintf( esc_html__( 'Are you sure you want to disable %s for this form?', 'wpforms-lite' ), $captcha_name ),
'cancel' => true,
],
],
'provider' => $settings['provider'],
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education\AddonsItemBase;
/**
* Base class for Builder/Fields Education feature.
*
* @since 1.6.6
*/
abstract class Fields extends AddonsItemBase {
/**
* Instance of the Education\Fields class.
*
* @since 1.6.6
*
* @var \WPForms\Admin\Education\Fields
*/
protected $fields;
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
return wp_doing_ajax() || wpforms_is_admin_page( 'builder' );
}
/**
* Init.
*
* @since 1.6.6
*/
public function init() {
parent::init();
// Store the instance of the Education\Fields class.
$this->fields = wpforms()->get( 'education_fields' );
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace WPForms\Admin\Education\Builder;
use WPForms\Admin\Education\AddonsItemBase;
use WPForms\Admin\Education\Helpers;
/**
* Builder/Geolocation Education feature for Lite and Pro.
*
* @since 1.6.6
*/
class Geolocation extends AddonsItemBase {
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
return wpforms_is_admin_page( 'builder' ) || wp_doing_ajax();
}
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wpforms_field_options_bottom_advanced-options', [ $this, 'geolocation_options' ], 10, 2 );
}
/**
* Display geolocation options.
*
* @since 1.6.6
*
* @param array $field Field data.
* @param object $instance Builder instance.
*/
public function geolocation_options( $field, $instance ) {
if ( ! in_array( $field['type'], [ 'text', 'address' ], true ) ) {
return;
}
$addon = $this->addons->get_addon( 'geolocation' );
if (
empty( $addon ) ||
empty( $addon['action'] ) ||
empty( $addon['status'] ) || (
$addon['status'] === 'active' &&
$addon['action'] !== 'upgrade'
)
) {
return;
}
$row_args = $this->get_address_autocomplete_row_attributes( $addon );
$row_args['content'] = $instance->field_element(
'toggle',
$field,
$this->get_address_autocomplete_field_attributes( $field, $addon ),
false
);
$instance->field_element( 'row', $field, $row_args );
}
/**
* Get attributes for address autocomplete row.
*
* @since 1.6.6
*
* @param array $addon Current addon information.
*
* @return array
*/
private function get_address_autocomplete_row_attributes( $addon ) {
$default = [
'slug' => 'enable_address_autocomplete',
];
if ( $addon['plugin_allow'] && $addon['action'] === 'install' ) {
return wp_parse_args(
[
'data' => [
'action' => 'install',
'name' => $addon['modal_name'],
'url' => $addon['url'],
'nonce' => wp_create_nonce( 'wpforms-admin' ),
'license' => 'pro',
],
'class' => 'education-modal',
],
$default
);
}
if ( $addon['plugin_allow'] && $addon['action'] === 'activate' ) {
return wp_parse_args(
[
'data' => [
'action' => 'activate',
'name' => sprintf( /* translators: %s - addon name. */
esc_html__( '%s addon', 'wpforms-lite' ),
$addon['title']
),
'path' => $addon['path'],
'nonce' => wp_create_nonce( 'wpforms-admin' ),
],
'class' => 'education-modal',
],
$default
);
}
return wp_parse_args(
[
'data' => [
'action' => 'upgrade',
'name' => esc_html__( 'Address Autocomplete', 'wpforms-lite' ),
'utm-content' => 'Address Autocomplete',
'licence' => 'pro',
'message' => esc_html__( 'We\'re sorry, Address Autocomplete is part of the Geolocation Addon and not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wpforms-lite' ),
],
'class' => 'education-modal',
],
$default
);
}
/**
* Get attributes for address autocomplete field.
*
* @since 1.6.6
*
* @param array $field Field data.
* @param array $addon Current addon information.
*
* @return array
*/
private function get_address_autocomplete_field_attributes( $field, $addon ) {
$default = [
'slug' => 'enable_address_autocomplete',
'value' => '0',
'desc' => esc_html__( 'Enable Address Autocomplete', 'wpforms-lite' ),
];
if ( $addon['plugin_allow'] ) {
return $default;
}
return wp_parse_args(
[
'desc' => sprintf(
'%1$s%2$s',
esc_html__( 'Enable Address Autocomplete', 'wpforms-lite' ),
Helpers::get_badge( 'Pro', 'sm', 'inline', 'slate' )
),
'attrs' => [
'disabled' => 'disabled',
],
],
$default
);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education\AddonsListBase;
/**
* Base class for Builder/Settings, Builder/Providers, Builder/Payments Education features.
*
* @since 1.6.6
*/
abstract class Panel extends AddonsListBase {
/**
* Panel slug. Should be redefined in the real Builder/Panel class.
*
* @since 1.6.6
*
* @return string
**/
abstract protected function get_name();
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
// Load only in the Form Builder.
return wpforms_is_admin_page( 'builder' ) && ! empty( $this->get_name() );
}
/**
* Get addons for the current panel.
*
* @since 1.6.6
*/
protected function get_addons() {
return $this->addons->get_by_category( $this->get_name() );
}
/**
* Template name for rendering single addon item.
*
* @since 1.6.6
*
* @return string
*/
protected function get_single_addon_template() {
return 'education/builder/' . $this->get_name() . '-item';
}
/**
* Display addons.
*
* @since 1.6.6
*/
public function display_addons() {
$this->single_addon_template = $this->get_single_addon_template();
parent::display_addons();
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education;
/**
* Builder/Payments Education feature.
*
* @since 1.6.6
*/
class Payments extends Education\Builder\Panel {
/**
* Panel slug.
*
* @since 1.6.6
*
* @return string
**/
protected function get_name() {
return 'payments';
}
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wpforms_payments_panel_sidebar', [ $this, 'filter_addons' ], 1 );
add_action( 'wpforms_payments_panel_sidebar', [ $this, 'display_addons' ], 500 );
}
/**
* Get addons for the Payments panel.
*
* @since 1.7.7.2
*
* @return array
*/
protected function get_addons() {
$addons = $this->addons->get_by_category( $this->get_name() );
// Make Stripe at the top of the list.
foreach ( $addons as $key => $addon ) {
if ( $addon['slug'] !== 'wpforms-stripe' ) {
continue;
}
$addon['recommended'] = true;
unset( $addons[ $key ] );
array_unshift( $addons, $addon );
break;
}
return $addons;
}
/**
* Template name for rendering single addon item.
*
* @since 1.6.6
*
* @return string
*/
protected function get_single_addon_template() {
return 'education/builder/providers-item';
}
/**
* Ensure that we do not display activated addon items if those addons are not allowed according to the current license.
*
* @since 1.6.6
*/
public function filter_addons() {
$this->filter_not_allowed_addons( 'wpforms_payments_panel_sidebar' );
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education;
/**
* Builder/Providers Education feature.
*
* @since 1.6.6
*/
class Providers extends Education\Builder\Panel {
/**
* Panel slug.
*
* @since 1.6.6
*
* @return string
**/
protected function get_name() {
return 'providers';
}
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wpforms_providers_panel_sidebar', [ $this, 'filter_addons' ], 1 );
add_action( 'wpforms_providers_panel_sidebar', [ $this, 'display_addons' ], 500 );
}
/**
* Ensure that we do not display activated addon items if those addons are not allowed according to the current license.
*
* @since 1.6.6
*/
public function filter_addons() {
$this->filter_not_allowed_addons( 'wpforms_providers_panel_sidebar' );
}
/**
* Get addons for the Marketing panel.
*
* @since 1.7.7.2
*/
protected function get_addons() {
$addons = parent::get_addons();
/**
* Google Sheets uses Providers API. All providers are automatically
* added to the Marketing tab in the builder. We don't need the addon
* on the Marketing tab because the addon is already added to
* the builder's Settings tab.
*/
foreach ( $addons as $key => $addon ) {
if ( isset( $addon['slug'] ) && $addon['slug'] === 'wpforms-google-sheets' ) {
unset( $addons[ $key ] );
break;
}
}
return $addons;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace WPForms\Admin\Education\Builder;
use \WPForms\Admin\Education;
/**
* Builder/Settings Education feature.
*
* @since 1.6.6
*/
class Settings extends Education\Builder\Panel {
/**
* Panel slug.
*
* @since 1.6.6
*
* @return string
**/
protected function get_name() {
return 'settings';
}
/**
* Hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_filter( 'wpforms_builder_settings_sections', [ $this, 'filter_addons' ], 1 );
add_action( 'wpforms_builder_after_panel_sidebar', [ $this, 'display' ], 100, 2 );
}
/**
* Display settings addons.
*
* @since 1.6.6
*
* @param object $form Current form.
* @param string $panel Panel slug.
*/
public function display( $form, $panel ) {
if ( empty( $form ) || $this->get_name() !== $panel ) {
return;
}
$this->display_addons();
}
/**
* Ensure that we do not display activated addon items if those addons are not allowed according to the current license.
*
* @since 1.6.6
*
* @param array $sections Settings sections.
*
* @return array
*/
public function filter_addons( $sections ) {
$this->filter_not_allowed_addons( 'wpforms_builder_settings_sections' );
return $sections;
}
}

View File

@@ -0,0 +1,275 @@
<?php
namespace WPForms\Admin\Education;
/**
* Education core.
*
* @since 1.6.6
*/
class Core {
/**
* Indicate if Education core is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load() {
return wp_doing_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' );
}
/**
* Init.
*
* @since 1.6.6
*/
public function init() {
// Only proceed if allowed.
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.6
*/
protected function hooks() {
if ( wp_doing_ajax() ) {
add_action( 'wp_ajax_wpforms_education_dismiss', [ $this, 'ajax_dismiss' ] );
return;
}
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
}
/**
* Load enqueues.
*
* @since 1.6.6
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-admin-education-core',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/education/core{$min}.js",
[ 'jquery', 'jquery-confirm' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-admin-education-core',
'wpforms_education',
(array) apply_filters( 'wpforms_admin_education_strings', $this->get_js_strings() )
);
}
/**
* Localize common strings.
*
* @since 1.6.6
*
* @return array
*/
protected function get_js_strings() {
$strings = [];
$strings['ok'] = esc_html__( 'Ok', 'wpforms-lite' );
$strings['cancel'] = esc_html__( 'Cancel', 'wpforms-lite' );
$strings['close'] = esc_html__( 'Close', 'wpforms-lite' );
$strings['ajax_url'] = admin_url( 'admin-ajax.php' );
$strings['nonce'] = wp_create_nonce( 'wpforms-education' );
$strings['activate_prompt'] = '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'The %s is installed but not activated. Would you like to activate it?', 'wpforms-lite' ),
'%name%'
)
) . '</p>';
$strings['activate_confirm'] = esc_html__( 'Yes, activate', 'wpforms-lite' );
$strings['addon_activated'] = esc_html__( 'Addon activated', 'wpforms-lite' );
$strings['plugin_activated'] = esc_html__( 'Plugin activated', 'wpforms-lite' );
$strings['activating'] = esc_html__( 'Activating', 'wpforms-lite' );
$strings['install_prompt'] = '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'The %s is not installed. Would you like to install and activate it?', 'wpforms-lite' ),
'%name%'
)
) . '</p>';
$strings['install_confirm'] = esc_html__( 'Yes, install and activate', 'wpforms-lite' );
$strings['installing'] = esc_html__( 'Installing', 'wpforms-lite' );
$strings['save_prompt'] = esc_html__( 'Almost done! Would you like to save and refresh the form builder?', 'wpforms-lite' );
$strings['save_confirm'] = esc_html__( 'Yes, save and refresh', 'wpforms-lite' );
$strings['saving'] = esc_html__( 'Saving ...', 'wpforms-lite' );
// Check if the user can install addons.
// Includes license check.
$can_install_addons = wpforms_can_install( 'addon' );
// Check if the user can install plugins.
// Only checks if the user has the capability.
// Needed to display the correct message for non-admin users.
$can_install_plugins = current_user_can( 'install_plugins' );
$strings['can_install_addons'] = $can_install_addons && $can_install_plugins;
if ( ! $can_install_addons ) {
$strings['install_prompt'] = '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'The %s is not installed. Please install and activate it to use this feature.', 'wpforms-lite' ),
'%name%'
)
) . '</p>';
}
if ( ! $can_install_plugins ) {
/* translators: %s - addon name. */
$strings['install_prompt'] = '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'The %s is not installed. Please contact the site administrator.', 'wpforms-lite' ),
'%name%'
)
) . '</p>';
}
// Check if the user can activate plugins.
$can_activate_plugins = current_user_can( 'activate_plugins' );
$strings['can_activate_addons'] = $can_activate_plugins;
if ( ! $can_activate_plugins ) {
/* translators: %s - addon name. */
$strings['activate_prompt'] = '<p>' . esc_html( sprintf( __( 'The %s is not activated. Please contact the site administrator.', 'wpforms-lite' ), '%name%' ) ) . '</p>';
}
$upgrade_utm_medium = wpforms_is_admin_page() ? 'Settings - Integration' : 'Builder - Settings';
$strings['upgrade'] = [
'pro' => [
'title' => esc_html__( 'is a PRO Feature', 'wpforms-lite' ),
'title_plural' => esc_html__( 'are a PRO Feature', 'wpforms-lite' ),
'message' => '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'We\'re sorry, the %s is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wpforms-lite' ),
'%name%'
)
) . '</p>',
'doc' => sprintf(
'<a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a>',
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/upgrade-wpforms-lite-paid-license/#installing-wpforms', $upgrade_utm_medium, 'AP - %name%' ) ),
esc_html__( 'Already purchased?', 'wpforms-lite' )
),
'button' => esc_html__( 'Upgrade to PRO', 'wpforms-lite' ),
'url' => wpforms_admin_upgrade_link( $upgrade_utm_medium ),
'url_template' => wpforms_is_admin_page( 'templates' ) ? wpforms_admin_upgrade_link( 'Form Templates Subpage' ) : wpforms_admin_upgrade_link( 'builder-modal-template' ),
'modal' => wpforms_get_upgrade_modal_text( 'pro' ),
],
'elite' => [
'title' => esc_html__( 'is an Elite Feature', 'wpforms-lite' ),
'title_plural' => esc_html__( 'are an Elite Feature', 'wpforms-lite' ),
'message' => '<p>' . esc_html(
sprintf( /* translators: %s - addon name. */
__( 'We\'re sorry, the %s is not available on your plan. Please upgrade to the Elite plan to unlock all these awesome features.', 'wpforms-lite' ),
'%name%'
)
) . '</p>',
'doc' => sprintf(
'<a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a>',
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/upgrade-wpforms-lite-paid-license/#installing-wpforms', $upgrade_utm_medium, 'AP - %name%' ) ),
esc_html__( 'Already purchased?', 'wpforms-lite' )
),
'button' => esc_html__( 'Upgrade to Elite', 'wpforms-lite' ),
'url' => wpforms_admin_upgrade_link( $upgrade_utm_medium ),
'url_template' => wpforms_is_admin_page( 'templates' ) ? wpforms_admin_upgrade_link( 'Form Templates Subpage' ) : wpforms_admin_upgrade_link( 'builder-modal-template' ),
'modal' => wpforms_get_upgrade_modal_text( 'elite' ),
],
];
$strings['upgrade_bonus'] = wpautop(
wp_kses(
__( '<strong>Bonus:</strong> WPForms Lite users get <span>50% off</span> regular price, automatically applied at checkout.', 'wpforms-lite' ),
[
'strong' => [],
'span' => [],
]
)
);
$strings['thanks_for_interest'] = esc_html__( 'Thanks for your interest in WPForms Pro!', 'wpforms-lite' );
return $strings;
}
/**
* Ajax handler for the education dismiss buttons.
*
* @since 1.6.6
*/
public function ajax_dismiss() {
// Run a security check.
check_ajax_referer( 'wpforms-education', 'nonce' );
// Section is the identifier of the education feature.
// For example: in Builder/DidYouKnow feature used 'builder-did-you-know-notifications' and 'builder-did-you-know-confirmations'.
$section = ! empty( $_POST['section'] ) ? sanitize_key( $_POST['section'] ) : '';
if ( empty( $section ) ) {
wp_send_json_error(
[ 'error' => esc_html__( 'Please specify a section.', 'wpforms-lite' ) ]
);
}
// Check for permissions.
if ( ! $this->current_user_can() ) {
wp_send_json_error(
[ 'error' => esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) ]
);
}
$user_id = get_current_user_id();
$dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true );
if ( empty( $dismissed ) ) {
$dismissed = [];
}
$dismissed[ 'edu-' . $section ] = time();
update_user_meta( $user_id, 'wpforms_dismissed', $dismissed );
wp_send_json_success();
}
/**
* Whether the current user can perform an action.
*
* @since 1.8.0
*
* @return bool
*/
private function current_user_can() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$page = ! empty( $_POST['page'] ) ? sanitize_key( $_POST['page'] ) : '';
// key is the same as $current_screen->id and the JS global 'pagenow', value - capability name(s).
$caps = [
'toplevel_page_wpforms-overview' => [ 'view_forms' ],
'wpforms_page_wpforms-builder' => [ 'edit_forms' ],
'wpforms_page_wpforms-entries' => [ 'view_entries' ],
];
return isset( $caps[ $page ] ) ? wpforms_current_user_can( $caps[ $page ] ) : wpforms_current_user_can();
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace WPForms\Admin\Education;
/**
* Interface EducationInterface defines required methods for Education features to work properly.
*
* @since 1.6.6
*/
interface EducationInterface {
/**
* Indicate if current Education feature is allowed to load.
*
* @since 1.6.6
*
* @return bool
*/
public function allow_load();
/**
* Init.
*
* @since 1.6.6
*/
public function init();
}

View File

@@ -0,0 +1,410 @@
<?php
namespace WPForms\Admin\Education;
/**
* Fields data holder.
*
* @since 1.6.6
*/
class Fields {
/**
* All fields data.
*
* @since 1.6.6
*
* @var array
*/
protected $fields;
/**
* All fields data.
*
* @since 1.6.6
*
* @return array All possible fields.
*/
private function get_all() {
if ( ! empty( $this->fields ) ) {
return $this->fields;
}
$this->fields = [
[
'icon' => 'fa-phone',
'name' => esc_html__( 'Phone', 'wpforms-lite' ),
'name_en' => 'Phone',
'type' => 'phone',
'group' => 'fancy',
'order' => '50',
],
[
'icon' => 'fa-map-marker',
'name' => esc_html__( 'Address', 'wpforms-lite' ),
'name_en' => 'Address',
'type' => 'address',
'group' => 'fancy',
'order' => '70',
],
[
'icon' => 'fa-calendar-o',
'name' => esc_html__( 'Date / Time', 'wpforms-lite' ),
'name_en' => 'Date / Time',
'type' => 'date-time',
'group' => 'fancy',
'order' => '80',
],
[
'icon' => 'fa-link',
'name' => esc_html__( 'Website / URL', 'wpforms-lite' ),
'name_en' => 'Website / URL',
'type' => 'url',
'group' => 'fancy',
'order' => '90',
],
[
'icon' => 'fa-upload',
'name' => esc_html__( 'File Upload', 'wpforms-lite' ),
'name_en' => 'File Upload',
'type' => 'file-upload',
'group' => 'fancy',
'order' => '100',
],
[
'icon' => 'fa-lock',
'name' => esc_html__( 'Password', 'wpforms-lite' ),
'name_en' => 'Password',
'type' => 'password',
'group' => 'fancy',
'order' => '130',
],
[
'icon' => 'fa-pencil-square-o',
'name' => esc_html__( 'Rich Text', 'wpforms-lite' ),
'name_en' => 'Rich Text',
'type' => 'richtext',
'group' => 'fancy',
'order' => '140',
],
[
'icon' => 'fa-columns',
'name' => esc_html__( 'Layout', 'wpforms-lite' ),
'name_en' => 'Layout',
'type' => 'layout',
'group' => 'fancy',
'order' => '150',
],
[
'icon' => 'fa-files-o',
'name' => esc_html__( 'Page Break', 'wpforms-lite' ),
'name_en' => 'Page Break',
'type' => 'pagebreak',
'group' => 'fancy',
'order' => '160',
],
[
'icon' => 'fa-arrows-h',
'name' => esc_html__( 'Section Divider', 'wpforms-lite' ),
'name_en' => 'Section Divider',
'type' => 'divider',
'group' => 'fancy',
'order' => '170',
],
[
'icon' => 'fa-code',
'name' => esc_html__( 'HTML', 'wpforms-lite' ),
'name_en' => 'HTML',
'type' => 'html',
'group' => 'fancy',
'order' => '180',
],
[
'icon' => 'fa-file-image-o',
'name' => esc_html__( 'Content', 'wpforms-lite' ),
'name_en' => 'Content',
'type' => 'content',
'group' => 'fancy',
'order' => '181',
],
[
'icon' => 'fa-file-text-o',
'name' => esc_html__( 'Entry Preview', 'wpforms-lite' ),
'name_en' => 'Entry Preview',
'type' => 'entry-preview',
'group' => 'fancy',
'order' => '190',
],
[
'icon' => 'fa-star',
'name' => esc_html__( 'Rating', 'wpforms-lite' ),
'name_en' => 'Rating',
'type' => 'rating',
'group' => 'fancy',
'order' => '200',
],
[
'icon' => 'fa-eye-slash',
'name' => esc_html__( 'Hidden Field', 'wpforms-lite' ),
'name_en' => 'Hidden Field',
'type' => 'hidden',
'group' => 'fancy',
'order' => '210',
],
[
'icon' => 'fa-question-circle',
'name' => esc_html__( 'Custom Captcha', 'wpforms-lite' ),
'keywords' => esc_html__( 'spam, math, maths, question', 'wpforms-lite' ),
'name_en' => 'Custom Captcha',
'type' => 'captcha',
'group' => 'fancy',
'addon' => 'wpforms-captcha',
'order' => '300',
],
[
'icon' => 'fa-pencil',
'name' => esc_html__( 'Signature', 'wpforms-lite' ),
'keywords' => esc_html__( 'user, e-signature', 'wpforms-lite' ),
'name_en' => 'Signature',
'type' => 'signature',
'group' => 'fancy',
'addon' => 'wpforms-signatures',
'order' => '310',
],
[
'icon' => 'fa-ellipsis-h',
'name' => esc_html__( 'Likert Scale', 'wpforms-lite' ),
'keywords' => esc_html__( 'survey, rating scale', 'wpforms-lite' ),
'name_en' => 'Likert Scale',
'type' => 'likert_scale',
'group' => 'fancy',
'addon' => 'wpforms-surveys-polls',
'order' => '400',
],
[
'icon' => 'fa-tachometer',
'name' => esc_html__( 'Net Promoter Score', 'wpforms-lite' ),
'keywords' => esc_html__( 'survey, nps', 'wpforms-lite' ),
'name_en' => 'Net Promoter Score',
'type' => 'net_promoter_score',
'group' => 'fancy',
'addon' => 'wpforms-surveys-polls',
'order' => '410',
],
[
'icon' => 'fa-credit-card',
'name' => esc_html__( 'PayPal Commerce', 'wpforms-lite' ),
'keywords' => esc_html__( 'store, ecommerce, credit card, pay, payment, debit card', 'wpforms-lite' ),
'name_en' => 'PayPal Commerce',
'type' => 'paypal-commerce',
'group' => 'payment',
'addon' => 'wpforms-paypal-commerce',
'order' => '89',
],
[
'icon' => 'fa-credit-card',
'name' => esc_html__( 'Square', 'wpforms-lite' ),
'keywords' => esc_html__( 'store, ecommerce, credit card, pay, payment, debit card', 'wpforms-lite' ),
'name_en' => 'Square',
'type' => 'square',
'group' => 'payment',
'addon' => 'wpforms-square',
'order' => '92',
],
[
'icon' => 'fa-credit-card',
'name' => esc_html__( 'Authorize.Net', 'wpforms-lite' ),
'keywords' => esc_html__( 'store, ecommerce, credit card, pay, payment, debit card', 'wpforms-lite' ),
'name_en' => 'Authorize.Net',
'type' => 'authorize_net',
'group' => 'payment',
'addon' => 'wpforms-authorize-net',
'order' => '95',
],
[
'icon' => 'fa-ticket',
'name' => esc_html__( 'Coupon', 'wpforms-lite' ),
'keywords' => esc_html__( 'discount, sale', 'wpforms-lite' ),
'name_en' => 'Coupon',
'type' => 'payment-coupon',
'group' => 'payment',
'addon' => 'wpforms-coupons',
'order' => '100',
],
];
$captcha = $this->get_captcha();
if ( ! empty( $captcha ) ) {
array_push( $this->fields, $captcha );
}
return $this->fields;
}
/**
* Get Captcha field data.
*
* @since 1.6.6
*
* @return array Captcha field data.
*/
private function get_captcha() {
$captcha_settings = wpforms_get_captcha_settings();
if ( empty( $captcha_settings['provider'] ) ) {
return [];
}
$captcha = [
'hcaptcha' => [
'name' => 'hCaptcha',
'icon' => 'fa-question-circle-o',
],
'recaptcha' => [
'name' => 'reCAPTCHA',
'icon' => 'fa-google',
],
'turnstile' => [
'name' => 'Turnstile',
'icon' => 'fa-question-circle-o',
],
];
if ( ! empty( $captcha_settings['site_key'] ) || ! empty( $captcha_settings['secret_key'] ) ) {
$captcha_name = $captcha[ $captcha_settings['provider'] ]['name'];
$captcha_icon = $captcha[ $captcha_settings['provider'] ]['icon'];
} else {
$captcha_name = 'CAPTCHA';
$captcha_icon = 'fa-question-circle-o';
}
return [
'icon' => $captcha_icon,
'name' => $captcha_name,
'name_en' => $captcha_name,
'keywords' => esc_html__( 'captcha, spam, antispam', 'wpforms-lite' ),
'type' => 'captcha_' . $captcha_settings['provider'],
'group' => 'standard',
'order' => 180,
'class' => 'not-draggable',
];
}
/**
* Get filtered fields data.
*
* Usage:
* get_filtered( [ 'group' => 'payment' ] ) - fields from the 'payment' group.
* get_filtered( [ 'addon' => 'surveys-polls' ] ) - fields of the addon 'surveys-polls'.
* get_filtered( [ 'type' => 'payment-total' ] ) - field 'payment-total'.
*
* @since 1.6.6
*
* @param array $args Arguments array.
*
* @return array Fields data filtered according to given arguments.
*/
private function get_filtered( $args = [] ) {
$default_args = [
'group' => '',
'addon' => '',
'type' => '',
];
$args = array_filter( wp_parse_args( $args, $default_args ) );
$fields = $this->get_all();
$filtered_fields = [];
foreach ( $args as $prop => $prop_val ) {
foreach ( $fields as $field ) {
if ( ! empty( $field[ $prop ] ) && $field[ $prop ] === $prop_val ) {
array_push( $filtered_fields, $field );
}
}
}
return $filtered_fields;
}
/**
* Get fields by group.
*
* @since 1.6.6
*
* @param string $group Fields group (standard, fancy or payment).
*
* @return array.
*/
public function get_by_group( $group ) {
return $this->get_filtered( [ 'group' => $group ] );
}
/**
* Get fields by addon.
*
* @since 1.6.6
*
* @param string $addon Addon slug.
*
* @return array.
*/
public function get_by_addon( $addon ) {
return $this->get_filtered( [ 'addon' => $addon ] );
}
/**
* Get field by type.
*
* @since 1.6.6
*
* @param string $type Field type.
*
* @return array Single field data. Empty array if field is not available.
*/
public function get_field( $type ) {
$fields = $this->get_filtered( [ 'type' => $type ] );
return ! empty( $fields[0] ) ? $fields[0] : [];
}
/**
* Set key value of each field (conditionally).
*
* @since 1.6.6
*
* @param array $fields Fields data.
* @param string $key Key.
* @param string $value Value.
* @param string $condition Condition.
*
* @return array Updated field data.
*/
public function set_values( $fields, $key, $value, $condition ) {
if ( empty( $fields ) || empty( $key ) ) {
return $fields;
}
foreach ( $fields as $f => $field ) {
switch ( $condition ) {
case 'empty':
$fields[ $f ][ $key ] = empty( $field[ $key ] ) ? $value : $field[ $key ];
break;
default:
$fields[ $f ][ $key ] = $value;
}
}
return $fields;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace WPForms\Admin\Education;
/**
* Helpers class.
*
* @since 1.8.5
*/
class Helpers {
/**
* Get badge HTML.
*
* @since 1.8.5
*
* @param string $text Badge text.
* @param string $size Badge size.
* @param string $position Badge position.
* @param string $color Badge color.
* @param string $shape Badge shape.
*
* @return string
*/
public static function get_badge(
string $text,
string $size = 'sm',
string $position = 'inline',
string $color = 'titanium',
string $shape = 'rounded'
): string {
// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
return sprintf(
'<span class="wpforms-badge wpforms-badge-%1$s wpforms-badge-%2$s wpforms-badge-%3$s wpforms-badge-%4$s">%5$s</span>',
esc_attr( $size ),
esc_attr( $position ),
esc_attr( $color ),
esc_attr( $shape ),
esc_html( $text )
);
}
/**
* Print badge HTML.
*
* @since 1.8.5
*
* @param string $text Badge text.
* @param string $size Badge size.
* @param string $position Badge position.
* @param string $color Badge color.
* @param string $shape Badge shape.
*/
public static function print_badge(
string $text,
string $size = 'sm',
string $position = 'inline',
string $color = 'titanium',
string $shape = 'rounded'
) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo self::get_badge( $text, $size, $position, $color, $shape );
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace WPForms\Admin;
/**
* Admin Flyout Menu.
*
* @since 1.5.7
*/
class FlyoutMenu {
/**
* Constructor.
*
* @since 1.5.7
*/
public function __construct() {
if ( ! \wpforms_is_admin_page() || \wpforms_is_admin_page( 'builder' ) ) {
return;
}
if ( ! \apply_filters( 'wpforms_admin_flyoutmenu', true ) ) {
return;
}
// Check if WPForms Challenge can be displayed.
if ( wpforms()->get( 'challenge' )->challenge_can_start() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.5.7
*/
public function hooks() {
add_action( 'admin_footer', [ $this, 'output' ] );
}
/**
* Output menu.
*
* @since 1.5.7
*/
public function output() {
printf(
'<div id="wpforms-flyout">
<div id="wpforms-flyout-items">
%1$s
</div>
<a href="#" class="wpforms-flyout-button wpforms-flyout-head">
<div class="wpforms-flyout-label">%2$s</div>
<img src="%3$s" alt="%2$s" data-active="%4$s" />
</a>
</div>',
$this->get_items_html(), // phpcs:ignore
\esc_attr__( 'See Quick Links', 'wpforms-lite' ),
\esc_url( \WPFORMS_PLUGIN_URL . 'assets/images/admin-flyout-menu/sullie-default.svg' ),
\esc_url( \WPFORMS_PLUGIN_URL . 'assets/images/admin-flyout-menu/sullie-active.svg' )
);
}
/**
* Generate menu items HTML.
*
* @since 1.5.7
*
* @return string Menu items HTML.
*/
private function get_items_html() {
$items = array_reverse( $this->menu_items() );
$items_html = '';
foreach ( $items as $item_key => $item ) {
$items_html .= sprintf(
'<a href="%1$s" target="_blank" rel="noopener noreferrer" class="wpforms-flyout-button wpforms-flyout-item wpforms-flyout-item-%2$d"%5$s%6$s>
<div class="wpforms-flyout-label">%3$s</div>
<i class="fa %4$s"></i>
</a>',
\esc_url( $item['url'] ),
(int) $item_key,
\esc_html( $item['title'] ),
\sanitize_html_class( $item['icon'] ),
! empty( $item['bgcolor'] ) ? ' style="background-color: ' . \esc_attr( $item['bgcolor'] ) . '"' : '',
! empty( $item['hover_bgcolor'] ) ? ' onMouseOver="this.style.backgroundColor=\'' . \esc_attr( $item['hover_bgcolor'] ) . '\'" onMouseOut="this.style.backgroundColor=\'' . \esc_attr( $item['bgcolor'] ) . '\'"' : ''
);
}
return $items_html;
}
/**
* Menu items data.
*
* @since 1.5.7
*/
private function menu_items() {
$is_pro = wpforms()->is_pro();
$utm_campaign = $is_pro ? 'plugin' : 'liteplugin';
$items = [
[
'title' => \esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' ),
'url' => wpforms_admin_upgrade_link( 'Flyout Menu', 'Upgrade to WPForms Pro' ),
'icon' => 'fa-star',
'bgcolor' => '#E1772F',
'hover_bgcolor' => '#ff8931',
],
[
'title' => \esc_html__( 'Support & Docs', 'wpforms-lite' ),
'url' => 'https://wpforms.com/docs/?utm_source=WordPress&utm_medium=Flyout Menu&utm_campaign=' . $utm_campaign . '&utm_content=Support',
'icon' => 'fa-life-ring',
],
[
'title' => \esc_html__( 'Join Our Community', 'wpforms-lite' ),
'url' => 'https://www.facebook.com/groups/wpformsvip/',
'icon' => 'fa-comments',
],
[
'title' => \esc_html__( 'Suggest a Feature', 'wpforms-lite' ),
'url' => 'https://wpforms.com/features/suggest/?utm_source=WordPress&utm_medium=Flyout Menu&utm_campaign=' . $utm_campaign . '&utm_content=Feature',
'icon' => 'fa-lightbulb-o',
],
];
if ( $is_pro ) {
array_shift( $items );
}
return \apply_filters( 'wpforms_admin_flyout_menu_items', $items );
}
}

View File

@@ -0,0 +1,466 @@
<?php
namespace WPForms\Admin;
use WP_Post;
/**
* Embed Form in a Page wizard.
*
* @since 1.6.2
*/
class FormEmbedWizard {
/**
* Max search results count of 'Select Page' dropdown.
*
* @since 1.7.9
*
* @var int
*/
const MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT = 20;
/**
* Post statuses of pages in 'Select Page' dropdown.
*
* @since 1.7.9
*
* @var string[]
*/
const POST_STATUSES_OF_DROPDOWN_PAGES = [ 'publish', 'pending' ];
/**
* Initialize class.
*
* @since 1.6.2
*/
public function init() {
// Form Embed Wizard should load only in the Form Builder and on the Edit/Add Page screen.
if (
! wpforms_is_admin_page( 'builder' ) &&
! wpforms_is_admin_ajax() &&
! $this->is_form_embed_page()
) {
return;
}
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.6.2
* @since 1.7.9 Add hook for searching pages in embed wizard via AJAX.
*/
public function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
add_action( 'admin_footer', [ $this, 'output' ] );
add_filter( 'default_title', [ $this, 'embed_page_title' ], 10, 2 );
add_filter( 'default_content', [ $this, 'embed_page_content' ], 10, 2 );
add_action( 'wp_ajax_wpforms_admin_form_embed_wizard_embed_page_url', [ $this, 'get_embed_page_url_ajax' ] );
add_action( 'wp_ajax_wpforms_admin_form_embed_wizard_search_pages_choicesjs', [ $this, 'get_search_result_pages_ajax' ] );
}
/**
* Enqueue assets.
*
* @since 1.6.2
* @since 1.7.9 Add 'underscore' as dependency.
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
if ( $this->is_form_embed_page() && $this->get_meta() && ! $this->is_challenge_active() ) {
wp_enqueue_style(
'wpforms-admin-form-embed-wizard',
WPFORMS_PLUGIN_URL . "assets/css/form-embed-wizard{$min}.css",
[],
WPFORMS_VERSION
);
wp_enqueue_style(
'tooltipster',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css',
null,
'4.2.6'
);
wp_enqueue_script(
'tooltipster',
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js',
[ 'jquery' ],
'4.2.6',
true
);
}
wp_enqueue_script(
'wpforms-admin-form-embed-wizard',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/form-embed-wizard{$min}.js",
[ 'jquery', 'underscore' ],
WPFORMS_VERSION
);
wp_localize_script(
'wpforms-admin-form-embed-wizard',
'wpforms_admin_form_embed_wizard',
[
'nonce' => wp_create_nonce( 'wpforms_admin_form_embed_wizard_nonce' ),
'is_edit_page' => (int) $this->is_form_embed_page( 'edit' ),
'video_url' => esc_url(
sprintf(
'https://youtube.com/embed/%s?rel=0&showinfo=0',
wpforms_is_gutenberg_active() ? '_29nTiDvmLw' : 'IxGVz3AjEe0'
)
),
]
);
}
/**
* Output HTML.
*
* @since 1.6.2
*/
public function output() {
// We don't need to output tooltip if Challenge is active.
if ( $this->is_form_embed_page() && $this->is_challenge_active() ) {
$this->delete_meta();
return;
}
// We don't need to output tooltip if it's not an embed flow.
if ( $this->is_form_embed_page() && ! $this->get_meta() ) {
return;
}
$template = $this->is_form_embed_page() ? 'admin/form-embed-wizard/tooltip' : 'admin/form-embed-wizard/popup';
$args = [];
if ( ! $this->is_form_embed_page() ) {
$args['user_can_edit_pages'] = current_user_can( 'edit_pages' );
$args['dropdown_pages'] = $this->get_select_dropdown_pages_html();
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render( $template, $args );
$this->delete_meta();
}
/**
* Check if Challenge is active.
*
* @since 1.6.4
*
* @return boolean
*/
public function is_challenge_active() {
static $challenge_active = null;
if ( $challenge_active === null ) {
$challenge = wpforms()->get( 'challenge' );
$challenge_active = method_exists( $challenge, 'challenge_active' ) ? $challenge->challenge_active() : false;
}
return $challenge_active;
}
/**
* Check if the current page is a form embed page.
*
* @since 1.6.2
*
* @param string $type Type of the embed page to check. Can be '', 'add' or 'edit'. By default is empty string.
*
* @return boolean
*/
public function is_form_embed_page( $type = '' ) {
global $pagenow;
$type = $type === 'add' || $type === 'edit' ? $type : '';
if (
$pagenow !== 'post.php' &&
$pagenow !== 'post-new.php'
) {
return false;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$post_id = empty( $_GET['post'] ) ? 0 : (int) $_GET['post'];
$post_type = empty( $_GET['post_type'] ) ? '' : sanitize_key( $_GET['post_type'] );
$action = empty( $_GET['action'] ) ? 'add' : sanitize_key( $_GET['action'] );
// phpcs:enable
if ( $pagenow === 'post-new.php' &&
( empty( $post_type ) || $post_type !== 'page' )
) {
return false;
}
if (
$pagenow === 'post.php' &&
( empty( $post_id ) || get_post_type( $post_id ) !== 'page' )
) {
return false;
}
$meta = $this->get_meta();
$embed_page = ! empty( $meta['embed_page'] ) ? (int) $meta['embed_page'] : 0;
if ( 'add' === $action && 0 === $embed_page && $type !== 'edit' ) {
return true;
}
if ( ! empty( $post_id ) && $embed_page === $post_id && $type !== 'add' ) {
return true;
}
return false;
}
/**
* Set user's embed meta data.
*
* @since 1.6.2
*
* @param array $data Data array to set.
*/
public function set_meta( $data ) {
update_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard', $data );
}
/**
* Get user's embed meta data.
*
* @since 1.6.2
*
* @return array User's embed meta data.
*/
public function get_meta() {
return get_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard', true );
}
/**
* Delete user's embed meta data.
*
* @since 1.6.2
*/
public function delete_meta() {
delete_user_meta( get_current_user_id(), 'wpforms_admin_form_embed_wizard' );
}
/**
* Get embed page URL via AJAX.
*
* @since 1.6.2
*/
public function get_embed_page_url_ajax() {
check_admin_referer( 'wpforms_admin_form_embed_wizard_nonce' );
$page_id = ! empty( $_POST['pageId'] ) ? absint( $_POST['pageId'] ) : 0;
if ( ! empty( $page_id ) ) {
$url = get_edit_post_link( $page_id, '' );
$meta = [
'embed_page' => $page_id,
];
} else {
$url = add_query_arg( 'post_type', 'page', admin_url( 'post-new.php' ) );
$meta = [
'embed_page' => 0,
'embed_page_title' => ! empty( $_POST['pageTitle'] ) ? sanitize_text_field( wp_unslash( $_POST['pageTitle'] ) ) : '',
];
}
$meta['form_id'] = ! empty( $_POST['formId'] ) ? absint( $_POST['formId'] ) : 0;
$this->set_meta( $meta );
// Update challenge option to properly continue challenge on the embed page.
if ( $this->is_challenge_active() ) {
$challenge = wpforms()->get( 'challenge' );
if ( method_exists( $challenge, 'set_challenge_option' ) ) {
$challenge->set_challenge_option( [ 'embed_page' => $meta['embed_page'] ] );
}
}
wp_send_json_success( $url );
}
/**
* Set default title for the new page.
*
* @since 1.6.2
*
* @param string $post_title Default post title.
* @param \WP_Post $post Post object.
*
* @return string New default post title.
*/
public function embed_page_title( $post_title, $post ) {
$meta = $this->get_meta();
$this->delete_meta();
return empty( $meta['embed_page_title'] ) ? $post_title : $meta['embed_page_title'];
}
/**
* Embed the form to the new page.
*
* @since 1.6.2
*
* @param string $post_content Default post content.
* @param \WP_Post $post Post object.
*
* @return string Embedding string (shortcode or GB component code).
*/
public function embed_page_content( $post_content, $post ) {
$meta = $this->get_meta();
$form_id = ! empty( $meta['form_id'] ) ? $meta['form_id'] : 0;
$page_id = ! empty( $meta['embed_page'] ) ? $meta['embed_page'] : 0;
if ( ! empty( $page_id ) || empty( $form_id ) ) {
return $post_content;
}
if ( wpforms_is_gutenberg_active() ) {
$pattern = '<!-- wp:wpforms/form-selector {"formId":"%d"} /-->';
} else {
$pattern = '[wpforms id="%d" title="false" description="false"]';
}
return sprintf( $pattern, absint( $form_id ) );
}
/**
* Generate select with pages which are available to edit for current user.
*
* @since 1.6.6
* @since 1.7.9 Refactor to use ChoicesJS instead of `wp_dropdown_pages()`.
*
* @return string
*/
private function get_select_dropdown_pages_html() {
$dropdown_pages = wpforms_search_posts(
'',
[
'count' => self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT,
'post_status' => self::POST_STATUSES_OF_DROPDOWN_PAGES,
]
);
if ( empty( $dropdown_pages ) ) {
return '';
}
$total_pages = 0;
$wp_count_pages = (array) wp_count_posts( 'page' );
foreach ( $wp_count_pages as $page_status => $pages_count ) {
if ( in_array( $page_status, self::POST_STATUSES_OF_DROPDOWN_PAGES, true ) ) {
$total_pages += $pages_count;
}
}
// Include so we can use `\wpforms_settings_select_callback()`.
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/settings-api.php';
return wpforms_settings_select_callback(
[
'id' => 'form-embed-wizard-choicesjs-select-pages',
'type' => 'select',
'choicesjs' => true,
'options' => wp_list_pluck( $dropdown_pages, 'post_title', 'ID' ),
'data' => [
'use_ajax' => $total_pages > self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT,
],
]
);
}
/**
* Get search result pages for ChoicesJS via AJAX.
*
* @since 1.7.9
*/
public function get_search_result_pages_ajax() {
// Run a security check.
if ( ! check_ajax_referer( 'wpforms_admin_form_embed_wizard_nonce', false, false ) ) {
wp_send_json_error(
[
'msg' => esc_html__( 'Your session expired. Please reload the builder.', 'wpforms-lite' ),
]
);
}
if ( ! array_key_exists( 'search', $_GET ) ) {
wp_send_json_error(
[
'msg' => esc_html__( 'Incorrect usage of this operation.', 'wpforms-lite' ),
]
);
}
$result_pages = wpforms_search_pages_for_dropdown(
sanitize_text_field( wp_unslash( $_GET['search'] ) ),
[
'count' => self::MAX_SEARCH_RESULTS_DROPDOWN_PAGES_COUNT,
'post_status' => self::POST_STATUSES_OF_DROPDOWN_PAGES,
]
);
if ( empty( $result_pages ) ) {
wp_send_json_error( [] );
}
wp_send_json_success( $result_pages );
}
/**
* Excludes pages from dropdown which user can't edit.
*
* @since 1.6.6
* @deprecated 1.7.9
*
* @param WP_Post[] $pages Array of page objects.
*
* @return WP_Post[]|false Array of filtered pages or false.
*/
public function remove_inaccessible_pages( $pages ) {
_deprecated_function( __METHOD__, '1.7.9 of the WPForms plugin' );
if ( ! $pages ) {
return $pages;
}
foreach ( $pages as $key => $page ) {
if ( ! current_user_can( 'edit_page', $page->ID ) ) {
unset( $pages[ $key ] );
}
}
return $pages;
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace WPForms\Admin\Forms\Ajax;
use WPForms_Form_Handler;
/**
* Tags AJAX actions on All Forms page.
*
* @since 1.7.5
*/
class Tags {
/**
* Determine if the new tag was added during processing submitted tags.
*
* @since 1.7.5
*
* @var bool
*/
private $is_new_tag_added;
/**
* Determine if the class is allowed to load.
*
* @since 1.7.5
*
* @return bool
*/
private function allow_load() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';
// Load only in the case of AJAX calls on Forms Overview page.
return wp_doing_ajax() && strpos( $action, 'wpforms_admin_forms_overview_' ) === 0;
}
/**
* Initialize class.
*
* @since 1.7.5
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.5
*/
private function hooks() {
add_action( 'wp_ajax_wpforms_admin_forms_overview_save_tags', [ $this, 'save_tags' ] );
add_action( 'wp_ajax_wpforms_admin_forms_overview_delete_tags', [ $this, 'delete_tags' ] );
}
/**
* Save tags.
*
* @since 1.7.5
*/
public function save_tags() {
$data = $this->get_prepared_data( 'save' );
$tags_ids = $this->get_processed_tags( $data['tags'] );
$tags_labels = wp_list_pluck( $data['tags'], 'label' );
// Set tags to each form.
$this->set_tags_to_forms( $data['forms'], $tags_ids, $tags_labels );
$tags_obj = wpforms()->get( 'forms_tags' );
$terms = get_the_terms( array_pop( $data['forms'] ), WPForms_Form_Handler::TAGS_TAXONOMY );
$tags_data = $tags_obj->get_tags_data( $terms );
if ( ! empty( $this->is_new_tag_added ) ) {
$tags_data['all_tags_choices'] = $tags_obj->get_all_tags_choices();
}
wp_send_json_success( $tags_data );
}
/**
* Delete tags.
*
* @since 1.7.5
*/
public function delete_tags() {
$form_obj = wpforms()->get( 'form' );
$data = $this->get_prepared_data( 'delete' );
$deleted = 0;
$labels = [];
// Get forms marked by the tags.
$args = [
'fields' => 'ids',
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'tax_query' => [
[
'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY,
'field' => 'term_id',
'terms' => array_map( 'absint', $data['tags'] ),
],
],
];
$forms = $form_obj->get( 0, $args );
foreach ( $data['tags'] as $tag_id ) {
$term = get_term_by( 'term_id', $tag_id, WPForms_Form_Handler::TAGS_TAXONOMY, ARRAY_A );
$labels[] = $term['name'];
// Delete tag (term).
if ( wp_delete_term( $tag_id, WPForms_Form_Handler::TAGS_TAXONOMY ) === true ) {
$deleted++;
}
}
// Remove tags from the settings of the forms.
foreach ( $forms as $form_id ) {
$form_data = $form_obj->get( $form_id, [ 'content_only' => true ] );
if (
empty( $form_data['settings']['form_tags'] ) ||
! is_array( $form_data['settings']['form_tags'] )
) {
continue;
}
$form_data['settings']['form_tags'] = array_diff( $form_data['settings']['form_tags'], $labels );
$form_obj->update( $form_id, $form_data );
}
wp_send_json_success(
[
'deleted' => $deleted,
]
);
}
/**
* Get processed tags.
*
* @since 1.7.5
*
* @param array $tags_data Submitted tags data.
*
* @return array Tags IDs list.
*/
public function get_processed_tags( $tags_data ) {
if ( ! is_array( $tags_data ) ) {
return [];
}
$tags_ids = [];
// Process the tags' data.
foreach ( $tags_data as $tag ) {
$term = get_term( $tag['value'], WPForms_Form_Handler::TAGS_TAXONOMY );
// In the case when the term is not found, we should create the new term.
if ( empty( $term ) || is_wp_error( $term ) ) {
$new_term = wp_insert_term( sanitize_text_field( $tag['label'] ), WPForms_Form_Handler::TAGS_TAXONOMY );
$tag['value'] = ! is_wp_error( $new_term ) && isset( $new_term['term_id'] ) ? $new_term['term_id'] : 0;
$this->is_new_tag_added = $this->is_new_tag_added || $tag['value'] > 0;
}
if ( ! empty( $tag['value'] ) ) {
$tags_ids[] = absint( $tag['value'] );
}
}
return $tags_ids;
}
/**
* Get prepared data before perform ajax action.
*
* @since 1.7.5
*
* @param string $action Action: `save` OR `delete`.
*
* @return array
*/
private function get_prepared_data( $action ) {
// Run a security check.
if ( ! check_ajax_referer( 'wpforms-admin-forms-overview-nonce', 'nonce', false ) ) {
wp_send_json_error( esc_html__( 'Most likely, your session expired. Please reload the page.', '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' ) );
}
$data = [
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
'tags' => ! empty( $_POST['tags'] ) ? map_deep( (array) wp_unslash( $_POST['tags'] ), 'sanitize_text_field' ) : [],
];
if ( $action === 'save' ) {
$data['forms'] = $this->get_allowed_forms();
}
return $data;
}
/**
* Get allowed forms.
*
* @since 1.7.5
*
* @return array Allowed form IDs.
*/
private function get_allowed_forms() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( empty( $_POST['forms'] ) ) {
wp_send_json_error( esc_html__( 'No forms selected when trying to add a tag to them.', 'wpforms-lite' ) );
}
$forms_all = array_filter( array_map( 'absint', (array) $_POST['forms'] ) );
$forms_allowed = [];
// phpcs:enable WordPress.Security.NonceVerification.Missing
foreach ( $forms_all as $form_id ) {
if ( wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
$forms_allowed[] = $form_id;
}
}
if ( empty( $forms_allowed ) ) {
wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) );
}
return $forms_allowed;
}
/**
* Set tags to each form in the list.
*
* @since 1.7.5
*
* @param array $forms_ids Forms IDs list.
* @param array $tags_ids Tags IDs list.
* @param array $tags_labels Tags labels list.
*/
private function set_tags_to_forms( $forms_ids, $tags_ids, $tags_labels ) {
$form_obj = wpforms()->get( 'form' );
foreach ( $forms_ids as $form_id ) {
wp_set_post_terms(
$form_id,
$tags_ids,
WPForms_Form_Handler::TAGS_TAXONOMY
);
// Store tags labels in the form settings.
$form_data = $form_obj->get( $form_id, [ 'content_only' => true ] );
$form_data['settings']['form_tags'] = $tags_labels;
$form_obj->update( $form_id, $form_data );
}
}
}

View File

@@ -0,0 +1,407 @@
<?php
namespace WPForms\Admin\Forms;
use WPForms\Admin\Notice;
/**
* Bulk actions on All Forms page.
*
* @since 1.7.3
*/
class BulkActions {
/**
* Allowed actions.
*
* @since 1.7.3
*
* @const array
*/
const ALLOWED_ACTIONS = [
'trash',
'restore',
'delete',
'duplicate',
'empty_trash',
];
/**
* Forms ids.
*
* @since 1.7.3
*
* @var array
*/
private $ids;
/**
* Current action.
*
* @since 1.7.3
*
* @var string
*/
private $action;
/**
* Current view.
*
* @since 1.7.3
*
* @var string
*/
private $view;
/**
* Determine if the class is allowed to load.
*
* @since 1.7.3
*
* @return bool
*/
private function allow_load() {
// Load only on the `All Forms` admin page.
return wpforms_is_admin_page( 'overview' );
}
/**
* Initialize class.
*
* @since 1.7.3
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->view = wpforms()->get( 'forms_views' )->get_current_view();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.3
*/
private function hooks() {
add_action( 'load-toplevel_page_wpforms-overview', [ $this, 'notices' ] );
add_action( 'load-toplevel_page_wpforms-overview', [ $this, 'process' ] );
add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] );
}
/**
* Process the bulk actions.
*
* @since 1.7.3
*/
public function process() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$this->ids = isset( $_GET['form_id'] ) ? array_map( 'absint', (array) $_GET['form_id'] ) : [];
$this->action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false;
if ( $this->action === '-1' ) {
$this->action = ! empty( $_REQUEST['action2'] ) ? sanitize_key( $_REQUEST['action2'] ) : false;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( empty( $this->ids ) || empty( $this->action ) ) {
return;
}
// Check exact action values.
if ( ! in_array( $this->action, self::ALLOWED_ACTIONS, true ) ) {
return;
}
if ( empty( $_GET['_wpnonce'] ) ) {
return;
}
// Check the nonce.
if (
! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bulk-forms' ) &&
! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_' . $this->action . '_form_nonce' )
) {
return;
}
// Finally, we can process the action.
$this->process_action();
}
/**
* Process action.
*
* @since 1.7.3
*
* @uses process_action_trash
* @uses process_action_restore
* @uses process_action_delete
* @uses process_action_duplicate
* @uses process_action_empty_trash
*/
private function process_action() {
$method = "process_action_{$this->action}";
// Check that we have a method for this action.
if ( ! method_exists( $this, $method ) ) {
return;
}
if ( empty( $this->ids ) || ! is_array( $this->ids ) ) {
return;
}
$result = [];
foreach ( $this->ids as $id ) {
$result[ $id ] = $this->$method( $id );
}
$count_result = count( array_keys( array_filter( $result ) ) );
// Empty trash action returns count of deleted forms.
if ( $method === 'process_action_empty_trash' ) {
$count_result = isset( $result[1] ) ? $result[1] : 0;
}
// Unset get vars and perform redirect to avoid action reuse.
wp_safe_redirect(
add_query_arg(
rtrim( $this->action, 'e' ) . 'ed',
$count_result,
remove_query_arg( [ 'action', 'action2', '_wpnonce', 'form_id', 'paged', '_wp_http_referer' ] )
)
);
exit;
}
/**
* Trash the form.
*
* @since 1.7.3
*
* @param int $id Form ID to trash.
*
* @return bool
*/
private function process_action_trash( $id ) {
return wpforms()->get( 'form' )->update_status( $id, 'trash' );
}
/**
* Restore the form.
*
* @since 1.7.3
*
* @param int $id Form ID to restore from trash.
*
* @return bool
*/
private function process_action_restore( $id ) {
return wpforms()->get( 'form' )->update_status( $id, 'publish' );
}
/**
* Delete the form.
*
* @since 1.7.3
*
* @param int $id Form ID to delete.
*
* @return bool
*/
private function process_action_delete( $id ) {
return wpforms()->get( 'form' )->delete( $id );
}
/**
* Duplicate the form.
*
* @since 1.7.3
*
* @param int $id Form ID to duplicate.
*
* @return bool
*/
private function process_action_duplicate( $id ) {
if ( ! wpforms_current_user_can( 'create_forms' ) ) {
return false;
}
if ( ! wpforms_current_user_can( 'view_form_single', $id ) ) {
return false;
}
return wpforms()->get( 'form' )->duplicate( $id );
}
/**
* Empty trash.
*
* @since 1.7.3
*
* @param int $id Form ID. This parameter is not used in this method,
* but we need to keep it here because all the `process_action_*` methods
* should be called with the $id parameter.
*
* @return bool
*/
private function process_action_empty_trash( $id ) {
// Empty trash is actually the "delete all forms in trash" action.
// So, after the execution we should display the same notice as for the `delete` action.
$this->action = 'delete';
return wpforms()->get( 'form' )->empty_trash();
}
/**
* Define bulk actions available for forms overview table.
*
* @since 1.7.3
*
* @return array
*/
public function get_dropdown_items() {
$items = [];
if ( wpforms_current_user_can( 'delete_forms' ) ) {
if ( $this->view === 'trash' ) {
$items = [
'restore' => esc_html__( 'Restore', 'wpforms-lite' ),
'delete' => esc_html__( 'Delete Permanently', 'wpforms-lite' ),
];
} else {
$items = [
'trash' => esc_html__( 'Move to Trash', 'wpforms-lite' ),
];
}
}
// phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
/**
* Filters the Bulk Actions dropdown items.
*
* @since 1.7.5
*
* @param array $items Dropdown items.
*/
$items = apply_filters( 'wpforms_admin_forms_bulk_actions_get_dropdown_items', $items );
// phpcs:enable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
if ( empty( $items ) ) {
// We should have dummy item, otherwise, WP will hide the Bulk Actions Dropdown,
// which is not good from a design point of view.
return [
'' => '&mdash;',
];
}
return $items;
}
/**
* Admin notices.
*
* @since 1.7.3
*/
public function notices() {
// phpcs:disable WordPress.Security.NonceVerification
$results = [
'trashed' => ! empty( $_REQUEST['trashed'] ) ? sanitize_key( $_REQUEST['trashed'] ) : false,
'restored' => ! empty( $_REQUEST['restored'] ) ? sanitize_key( $_REQUEST['restored'] ) : false,
'deleted' => ! empty( $_REQUEST['deleted'] ) ? sanitize_key( $_REQUEST['deleted'] ) : false,
'duplicated' => ! empty( $_REQUEST['duplicated'] ) ? sanitize_key( $_REQUEST['duplicated'] ) : false,
];
// phpcs:enable WordPress.Security.NonceVerification
// Display notice in case of error.
if ( in_array( 'error', $results, true ) ) {
Notice::add(
esc_html__( 'Security check failed. Please try again.', 'wpforms-lite' ),
'error'
);
return;
}
$this->notices_success( $results );
}
/**
* Admin success notices.
*
* @since 1.7.3
*
* @param array $results Action results data.
*/
private function notices_success( $results ) {
if ( ! empty( $results['trashed'] ) ) {
$notice = sprintf( /* translators: %d - trashed forms count. */
_n( '%d form was successfully moved to Trash.', '%d forms were successfully moved to Trash.', (int) $results['trashed'], 'wpforms-lite' ),
(int) $results['trashed']
);
}
if ( ! empty( $results['restored'] ) ) {
$notice = sprintf( /* translators: %d - restored forms count. */
_n( '%d form was successfully restored.', '%d forms were successfully restored.', (int) $results['restored'], 'wpforms-lite' ),
(int) $results['restored']
);
}
if ( ! empty( $results['deleted'] ) ) {
$notice = sprintf( /* translators: %d - deleted forms count. */
_n( '%d form was successfully permanently deleted.', '%d forms were successfully permanently deleted.', (int) $results['deleted'], 'wpforms-lite' ),
(int) $results['deleted']
);
}
if ( ! empty( $results['duplicated'] ) ) {
$notice = sprintf( /* translators: %d - duplicated forms count. */
_n( '%d form was successfully duplicated.', '%d forms were successfully duplicated.', (int) $results['duplicated'], 'wpforms-lite' ),
(int) $results['duplicated']
);
}
if ( ! empty( $notice ) ) {
Notice::add( $notice, 'info' );
}
}
/**
* Remove certain arguments from a query string that WordPress should always hide for users.
*
* @since 1.7.3
*
* @param array $removable_query_args An array of parameters to remove from the URL.
*
* @return array Extended/filtered array of parameters to remove from the URL.
*/
public function removable_query_args( $removable_query_args ) {
$removable_query_args[] = 'trashed';
$removable_query_args[] = 'restored';
$removable_query_args[] = 'deleted';
$removable_query_args[] = 'duplicated';
return $removable_query_args;
}
}

View File

@@ -0,0 +1,286 @@
<?php
namespace WPForms\Admin\Forms;
/**
* Search Forms feature.
*
* @since 1.7.2
*/
class Search {
/**
* Current search term.
*
* @since 1.7.2
*
* @var string
*/
private $term;
/**
* Current search term escaped.
*
* @since 1.7.2
*
* @var string
*/
private $term_escaped;
/**
* Determine if the class is allowed to load.
*
* @since 1.7.2
*
* @return bool
*/
private function allow_load() {
// Load only on the `All Forms` admin page and only if the search should be performed.
return wpforms_is_admin_page( 'overview' ) && $this->is_search();
}
/**
* Initialize class.
*
* @since 1.7.2
*/
public function init() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->term = isset( $_GET['search']['term'] ) ? sanitize_text_field( wp_unslash( $_GET['search']['term'] ) ) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$this->term_escaped = isset( $_GET['search']['term'] ) ? esc_html( wp_unslash( $_GET['search']['term'] ) ) : '';
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.2
*/
private function hooks() {
// Use filter to add the search term to the get forms arguments.
add_filter( 'wpforms_get_multiple_forms_args', [ $this, 'get_forms_args' ] );
// Encapsulate search into posts_where.
add_action( 'wpforms_form_handler_get_multiple_before_get_posts', [ $this, 'before_get_posts' ] );
add_action( 'wpforms_form_handler_get_multiple_after_get_posts', [ $this, 'after_get_posts' ], 10, 2 );
}
/**
* Determine whether a search is performing.
*
* @since 1.7.2
*
* @return bool
*/
private function is_search() {
return ! wpforms_is_empty_string( $this->term_escaped );
}
/**
* Count search results.
*
* @since 1.7.2
* @deprecated 1.7.5
*
* @param array $count Number of forms in different views.
* @param array $args Get forms arguments.
*
* @return array
*/
public function update_count( $count, $args ) {
_deprecated_function( __METHOD__, '1.7.5 of the WPForms plugin', "wpforms()->get( 'forms_views' )->update_count()" );
return wpforms()->get( 'forms_views' )->update_count();
}
/**
* Pass the search term to the arguments array.
*
* @since 1.7.2
*
* @param array $args Get posts arguments.
*
* @return array
*/
public function get_forms_args( $args ) {
if ( is_numeric( $this->term ) ) {
$args['post__in'] = [ absint( $this->term ) ];
} else {
$args['search']['term'] = $this->term;
$args['search']['term_escaped'] = $this->term_escaped;
}
return $args;
}
/**
* Before get_posts() call routine.
*
* @since 1.7.2
*
* @param array $args Arguments of the `get_posts()`.
*/
public function before_get_posts( $args ) {
// The `posts_where` hook is very general and has broad usage across the WP core and tons of plugins.
// Therefore, in order to do not break something,
// we should add this hook right before the call of `get_posts()` inside \WPForms_Form_Handler::get_multiple().
add_filter( 'posts_where', [ $this, 'search_by_term_where' ], 10, 2 );
}
/**
* After get_posts() call routine.
*
* @since 1.7.2
*
* @param array $args Arguments of the get_posts().
* @param array $forms Forms data. Result of getting multiple forms.
*/
public function after_get_posts( $args, $forms ) {
// The `posts_where` hook is very general and has broad usage across the WP core and tons of plugins.
// Therefore, in order to do not break something,
// we should remove this hook right after the call of `get_posts()` inside \WPForms_Form_Handler::get_multiple().
remove_filter( 'posts_where', [ $this, 'search_by_term_where' ] );
}
/**
* Modify the WHERE clause of the SQL query in order to search forms by given term.
*
* @since 1.7.2
*
* @param string $where WHERE clause.
* @param \WP_Query $wp_query The WP_Query instance.
*
* @return string
*/
public function search_by_term_where( $where, $wp_query ) {
if ( is_numeric( $this->term ) ) {
return $where;
}
global $wpdb;
// When user types only HTML tag (<section> for example), the sanitized term we will be empty.
// In this case, it's better to return an empty result set than all the forms. It's not the same as the empty search term.
if ( wpforms_is_empty_string( $this->term ) && ! wpforms_is_empty_string( $this->term_escaped ) ) {
$where .= ' AND 1<>1';
}
if ( wpforms_is_empty_string( $this->term ) ) {
return $where;
}
// Prepare the WHERE clause to search form title and description.
$where .= $wpdb->prepare(
" AND (
$wpdb->posts.post_title LIKE %s OR
$wpdb->posts.post_excerpt LIKE %s
)",
'%' . $wpdb->esc_like( esc_html( $this->term ) ) . '%',
'%' . $wpdb->esc_like( $this->term ) . '%'
);
return $where;
}
/**
* Forms search markup.
*
* @since 1.7.2
*
* @param string $text The 'submit' button label.
* @param string $input_id ID attribute value for the search input field.
*/
public function search_box( $text, $input_id ) {
$search_term = wpforms_is_empty_string( $this->term ) ? $this->term_escaped : $this->term;
// Display search reset block.
$this->search_reset_block( $search_term );
// Display search box.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/forms/search-box',
[
'term_input_id' => $input_id . '-term',
'text' => $text,
'search_term' => $search_term,
],
true
);
}
/**
* Forms search reset block.
*
* @since 1.7.2
*
* @param string $search_term Current search term.
*/
private function search_reset_block( $search_term ) {
if ( wpforms_is_empty_string( $search_term ) ) {
return;
}
$views = wpforms()->get( 'forms_views' );
$count = $views->get_count();
$view = $views->get_current_view();
$count['all'] = ! empty( $count['all'] ) ? $count['all'] : 0;
$message = sprintf(
wp_kses( /* translators: %1$d - number of forms found, %2$s - search term. */
_n(
'Found <strong>%1$d form</strong> containing <em>"%2$s"</em>',
'Found <strong>%1$d forms</strong> containing <em>"%2$s"</em>',
(int) $count['all'],
'wpforms-lite'
),
[
'strong' => [],
'em' => [],
]
),
(int) $count['all'],
esc_html( $search_term )
);
/**
* Filters the message in the search reset block.
*
* @since 1.7.3
*
* @param string $message Message text.
* @param string $search_term Search term.
* @param array $count Count forms in different views.
* @param string $view Current view.
*/
$message = apply_filters( 'wpforms_admin_forms_search_search_reset_block_message', $message, $search_term, $count, $view );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/forms/search-reset',
[
'message' => $message,
],
true
);
}
}

View File

@@ -0,0 +1,614 @@
<?php
namespace WPForms\Admin\Forms;
use WP_Post;
use WPForms_Form_Handler;
use WPForms_Overview_Table;
/**
* Tags on All Forms page.
*
* @since 1.7.5
*/
class Tags {
/**
* Current tags filter.
*
* @since 1.7.5
*
* @var array
*/
private $tags_filter;
/**
* Current view slug.
*
* @since 1.7.5
*
* @var string
*/
private $current_view;
/**
* Base URL.
*
* @since 1.7.5
*
* @var string
*/
private $base_url;
/**
* Determine if the class is allowed to load.
*
* @since 1.7.5
*
* @return bool
*/
private function allow_load() {
// Load only on the `All Forms` admin page.
return wpforms_is_admin_page( 'overview' );
}
/**
* Initialize class.
*
* @since 1.7.5
*/
public function init() {
// In case of AJAX call we need to initialize base URL only.
if ( wp_doing_ajax() ) {
$this->base_url = admin_url( 'admin.php?page=wpforms-overview' );
}
if ( ! $this->allow_load() ) {
return;
}
$this->update_view_vars();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.5
*/
private function hooks() {
add_action( 'init', [ $this, 'update_tags_filter' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
add_action( 'wpforms_admin_overview_before_rows', [ $this, 'bulk_edit_tags' ] );
add_filter( 'wpforms_get_multiple_forms_args', [ $this, 'get_forms_args' ] );
add_filter( 'wpforms_admin_forms_bulk_actions_get_dropdown_items', [ $this, 'add_bulk_action' ], 10, 2 );
add_filter( 'wpforms_overview_table_columns', [ $this, 'filter_columns' ] );
}
/**
* Init view-related variables.
*
* @since 1.7.5
*/
private function update_view_vars() {
$views_object = wpforms()->get( 'forms_views' );
$this->current_view = $views_object->get_current_view();
$view_config = $views_object->get_view_by_slug( $this->current_view );
$this->base_url = remove_query_arg(
[ 'tags', 'search', 'action', 'action2', '_wpnonce', 'form_id', 'paged', '_wp_http_referer' ],
$views_object->get_base_url()
);
// Base URL should contain variable according to the current view.
if (
isset( $view_config['get_var'], $view_config['get_var_value'] ) && $this->current_view !== 'all'
) {
$this->base_url = add_query_arg( $view_config['get_var'], $view_config['get_var_value'], $this->base_url );
}
// Base URL fallback.
$this->base_url = empty( $this->base_url ) ? admin_url( 'admin.php?page=wpforms-overview' ) : $this->base_url;
}
/**
* Update tags filter value.
*
* @since 1.7.5
*/
public function update_tags_filter() {
// Do not need to update this property while doing AJAX.
if ( wp_doing_ajax() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$tags = isset( $_GET['tags'] ) ? sanitize_text_field( wp_unslash( rawurldecode( $_GET['tags'] ) ) ) : '';
$tags_slugs = explode( ',', $tags );
$tags_filter = array_filter(
self::get_all_tags_choices(),
static function( $tag ) use ( $tags_slugs ) {
return in_array( trim( rawurldecode( $tag['slug'] ) ), $tags_slugs, true );
}
);
$this->tags_filter = array_map( 'absint', wp_list_pluck( $tags_filter, 'value' ) );
}
/**
* Enqueue assets.
*
* @since 1.7.5
*/
public function enqueues() {
wp_enqueue_script(
'wpforms-admin-forms-overview-choicesjs',
WPFORMS_PLUGIN_URL . 'assets/lib/choices.min.js',
[],
'9.0.1',
true
);
wp_localize_script(
'wpforms-admin-forms-overview-choicesjs',
'wpforms_admin_forms_overview',
[
'choicesjs_config' => self::get_choicesjs_config(),
'edit_tags_form' => $this->get_column_tags_form(),
'all_tags_choices' => self::get_all_tags_choices(),
'strings' => $this->get_localize_strings(),
]
);
}
/**
* Get Choices.js configuration.
*
* @since 1.7.5
*/
public static function get_choicesjs_config() {
return [
'removeItemButton' => true,
'shouldSort' => false,
'loadingText' => esc_html__( 'Loading...', 'wpforms-lite' ),
'noResultsText' => esc_html__( 'No results found', 'wpforms-lite' ),
'noChoicesText' => esc_html__( 'No tags to choose from', 'wpforms-lite' ),
'itemSelectText' => '',
'searchEnabled' => true,
'searchChoices' => true,
'searchFloor' => 1,
'searchResultLimit' => 100,
'searchFields' => [ 'label' ],
// These `fuseOptions` options enable the search of chars not only from the beginning of the tags.
'fuseOptions' => [
'threshold' => 0.1,
'distance' => 1000,
'location' => 2,
],
];
}
/**
* Get all tags (terms) as items for Choices.js.
*
* @since 1.7.5
*
* @return array
*/
public static function get_all_tags_choices() {
static $choices = null;
if ( is_array( $choices ) ) {
return $choices;
}
$choices = [];
$tags = get_terms(
[
'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY,
'hide_empty' => false,
]
);
foreach ( $tags as $tag ) {
$choices[] = [
'value' => (string) $tag->term_id,
'slug' => $tag->slug,
'label' => sanitize_term_field( 'name', $tag->name, $tag->term_id, WPForms_Form_Handler::TAGS_TAXONOMY, 'display' ),
'count' => (int) $tag->count,
];
}
return $choices;
}
/**
* Determine if the Tags column is hidden.
*
* @since 1.7.5
*
* @return bool
*/
private function is_tags_column_hidden() {
$overview_table = WPForms_Overview_Table::get_instance();
$columns = $overview_table->__call( 'get_column_info', [] );
return isset( $columns[1] ) && in_array( 'tags', $columns[1], true );
}
/**
* Get localize strings.
*
* @since 1.7.5
*/
private function get_localize_strings() {
return [
'nonce' => wp_create_nonce( 'wpforms-admin-forms-overview-nonce' ),
'is_tags_column_hidden' => $this->is_tags_column_hidden(),
'base_url' => admin_url( 'admin.php?' . wp_parse_url( $this->base_url, PHP_URL_QUERY ) ),
'add_new_tag' => esc_html__( 'Press Enter or "," key to add new tag', 'wpforms-lite' ),
'error' => esc_html__( 'Something wrong. Please try again later.', 'wpforms-lite' ),
'all_tags' => esc_html__( 'All Tags', 'wpforms-lite' ),
'bulk_edit_one_form' => wp_kses(
__( '<strong>1 form</strong> selected for Bulk Edit.', 'wpforms-lite' ),
[ 'strong' => [] ]
),
'bulk_edit_n_forms' => wp_kses( /* translators: %d - number of forms selected for Bulk Edit. */
__( '<strong>%d forms</strong> selected for Bulk Edit.', 'wpforms-lite' ),
[ 'strong' => [] ]
),
'manage_tags_title' => esc_html__( 'Manage Tags', 'wpforms-lite' ),
'manage_tags_desc' => esc_html__( 'Delete tags that you\'re no longer using. Deleting a tag will remove it from a form, but will not delete the form itself.', 'wpforms-lite' ),
'manage_tags_save' => esc_html__( 'Delete Tags', 'wpforms-lite' ),
'manage_tags_one_tag' => wp_kses(
__( 'You have <strong>1 tag</strong> selected for deletion.', 'wpforms-lite' ),
[ 'strong' => [] ]
),
'manage_tags_n_tags' => wp_kses( /* translators: %d - number of forms selected for Bulk Edit. */
__( 'You have <strong>%d tags</strong> selected for deletion.', 'wpforms-lite' ),
[ 'strong' => [] ]
),
'manage_tags_no_tags' => wp_kses(
__( 'There are no tags to delete.<br>Please create at least one by adding it to any form.', 'wpforms-lite' ),
[ 'br' => [] ]
),
'manage_tags_one_deleted' => esc_html__( '1 tag was successfully deleted.', 'wpforms-lite' ),
/* translators: %d - number of deleted tags. */
'manage_tags_n_deleted' => esc_html__( '%d tags were successfully deleted.', 'wpforms-lite' ),
'manage_tags_result_title' => esc_html__( 'Almost done!', 'wpforms-lite' ),
'manage_tags_result_text' => esc_html__( 'In order to update the tags in the forms list, please refresh the page.', 'wpforms-lite' ),
'manage_tags_btn_refresh' => esc_html__( 'Refresh', 'wpforms-lite' ),
];
}
/**
* Determine if tags are editable.
*
* @since 1.7.5
*
* @param int|null $form_id Form ID.
*
* @return bool
*/
private function is_editable( $form_id = null ) {
if ( $this->current_view === 'trash' ) {
return false;
}
if ( ! empty( $form_id ) && ! wpforms_current_user_can( 'edit_form_single', $form_id ) ) {
return false;
}
if ( empty( $form_id ) && ! wpforms_current_user_can( 'edit_forms' ) ) {
return false;
}
return true;
}
/**
* Generate Tags column markup.
*
* @since 1.7.5
*
* @param WP_Post $form Form.
*
* @return string
*/
public function column_tags( $form ) {
$terms = get_the_terms( $form->ID, WPForms_Form_Handler::TAGS_TAXONOMY );
$data = $this->get_tags_data( $terms );
return $this->get_column_tags_links( $data['tags_links'], $data['tags_ids'], $form->ID ) . $this->get_column_tags_form( $data['tags_options'] );
}
/**
* Generate tags data.
*
* @since 1.7.5
*
* @param array $terms Tags terms.
*
* @return array
*/
public function get_tags_data( $terms ) {
if ( ! is_array( $terms ) ) {
$taxonomy_object = get_taxonomy( WPForms_Form_Handler::TAGS_TAXONOMY );
return [
'tags_links' => sprintf(
'<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">%s</span>',
esc_html( isset( $taxonomy_object->labels->no_terms ) ? $taxonomy_object->labels->no_terms : '—' )
),
'tags_ids' => '',
'tags_options' => '',
];
}
$tags_links = [];
$tags_ids = [];
$tags_options = [];
$terms = empty( $terms ) ? [] : (array) $terms;
foreach ( $terms as $tag ) {
$filter_url = add_query_arg(
'tags',
rawurlencode( $tag->slug ),
$this->base_url
);
$tags_links[] = sprintf(
'<a href="%1$s">%2$s</a>',
esc_url( $filter_url ),
esc_html( $tag->name )
);
$tags_ids[] = $tag->term_id;
$tags_options[] = sprintf(
'<option value="%1$s" selected>%2$s</option>',
esc_attr( $tag->term_id ),
esc_html( $tag->name )
);
}
return [
/* translators: used between list items, there is a space after the comma. */
'tags_links' => implode( __( ', ', 'wpforms-lite' ), $tags_links ),
'tags_ids' => implode( ',', array_filter( $tags_ids ) ),
'tags_options' => implode( '', $tags_options ),
];
}
/**
* Get form tags links list markup.
*
* @since 1.7.5
*
* @param string $tags_links Tags links.
* @param string $tags_ids Tags IDs.
* @param int $form_id Form ID.
*
* @return string
*/
private function get_column_tags_links( $tags_links = '', $tags_ids = '', $form_id = 0 ) {
$edit_link = '';
if ( $this->is_editable( $form_id ) ) {
$edit_link = sprintf(
'<a href="#" class="wpforms-column-tags-edit">%s</a>',
esc_html__( 'Edit', 'wpforms-lite' )
);
}
return sprintf(
'<div class="wpforms-column-tags-links" data-form-id="%1$d" data-is-editable="%2$s" data-tags="%3$s">
<div class="wpforms-column-tags-links-list">%4$s</div>
%5$s
</div>',
absint( $form_id ),
$this->is_editable( $form_id ) ? '1' : '0',
esc_attr( $tags_ids ),
$tags_links,
$edit_link
);
}
/**
* Get edit tags form markup in the Tags column.
*
* @since 1.7.5
*
* @param string $tags_options Tags options.
*
* @return string
*/
private function get_column_tags_form( $tags_options = '' ) {
return sprintf(
'<div class="wpforms-column-tags-form wpforms-hidden">
<select multiple>%1$s</select>
<i class="dashicons dashicons-dismiss wpforms-column-tags-edit-cancel" title="%2$s"></i>
<i class="dashicons dashicons-yes-alt wpforms-column-tags-edit-save" title="%3$s"></i>
<i class="wpforms-spinner spinner wpforms-hidden"></i>
</div>',
$tags_options,
esc_attr__( 'Cancel', 'wpforms-lite' ),
esc_attr__( 'Save changes', 'wpforms-lite' )
);
}
/**
* Extra controls to be displayed between bulk actions and pagination.
*
* @since 1.7.5
*
* @param string $which The location of the table navigation: 'top' or 'bottom'.
* @param WPForms_Overview_Table $overview_table Instance of the WPForms_Overview_Table class.
*/
public function extra_tablenav( $which, $overview_table ) {
if ( $this->current_view === 'trash' ) {
return;
}
$all_tags = self::get_all_tags_choices();
$is_column_hidden = $this->is_tags_column_hidden();
$is_hidden = $is_column_hidden || empty( $all_tags );
$tags_options = '';
if ( $this->is_filtered() ) {
$tags = get_terms(
[
'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY,
'hide_empty' => false,
'include' => $this->tags_filter,
]
);
foreach ( $tags as $tag ) {
$tags_options .= sprintf(
'<option value="%1$s" selected>%2$s</option>',
esc_attr( $tag->term_id ),
esc_html( $tag->name )
);
}
}
printf(
'<div class="wpforms-tags-filter %1$s">
<select multiple size="1" data-tags-filter="1">
<option placeholder>%2$s</option>
%3$s
</select>
<button type="button" class="button">%4$s</button>
</div>
<button type="button" class="button wpforms-manage-tags %1$s">%5$s</button>',
esc_attr( $is_hidden ? 'wpforms-hidden' : '' ),
esc_html( empty( $tags_options ) ? __( 'All Tags', 'wpforms-lite' ) : '' ),
wp_kses(
$tags_options,
[
'option' => [
'value' => [],
'selected' => [],
],
]
),
esc_attr__( 'Filter', 'wpforms-lite' ),
esc_attr__( 'Manage Tags', 'wpforms-lite' )
);
}
/**
* Render and display Bulk Edit Tags form.
*
* @since 1.7.5
*
* @param WPForms_Overview_Table $list_table Overview lit table object.
*/
public function bulk_edit_tags( $list_table ) {
$columns = $list_table->get_columns();
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'admin/forms/bulk-edit-tags',
[
'columns' => count( $columns ),
],
true
);
}
/**
* Determine whether a filtering is performing.
*
* @since 1.7.5
*
* @return bool
*/
private function is_filtered() {
return ! empty( $this->tags_filter );
}
/**
* Pass the tags to the arguments array.
*
* @since 1.7.5
*
* @param array $args Get posts arguments.
*
* @return array
*/
public function get_forms_args( $args ) {
if ( $args['post_status'] === 'trash' || ! $this->is_filtered() ) {
return $args;
}
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
$args['tax_query'] = [
[
'taxonomy' => WPForms_Form_Handler::TAGS_TAXONOMY,
'field' => 'term_id',
'terms' => $this->tags_filter,
],
];
return $args;
}
/**
* Add item to Bulk Actions dropdown.
*
* @since 1.7.5
*
* @param array $items Dropdown items.
*
* @return array
*/
public function add_bulk_action( $items ) {
if ( $this->is_editable() ) {
$items['edit_tags'] = esc_html__( 'Edit Tags', 'wpforms-lite' );
}
return $items;
}
/**
* Filter list table columns.
*
* @since 1.7.5
*
* @param string[] $columns Array of columns.
*
* @return array
*/
public function filter_columns( $columns ) {
if ( $this->current_view === 'trash' ) {
unset( $columns['tags'] );
}
return $columns;
}
}

View File

@@ -0,0 +1,644 @@
<?php
namespace WPForms\Admin\Forms;
use WP_Post;
/**
* List table views.
*
* @since 1.7.3
*/
class Views {
/**
* Current view slug.
*
* @since 1.7.3
*
* @var string
*/
private $current_view;
/**
* Views settings.
*
* @since 1.7.3
*
* @var array
*/
private $views;
/**
* Count forms in different views.
*
* @since 1.7.3
*
* @var array
*/
private $count;
/**
* Base URL.
*
* @since 1.7.3
*
* @var string
*/
private $base_url;
/**
* Views configuration.
*
* @since 1.7.3
*/
private function configuration() {
if ( ! empty( $this->views ) ) {
return;
}
// Define views.
$views = [
'all' => [
'title' => __( 'All', 'wpforms-lite' ),
'get_var' => '',
'get_var_value' => '',
],
'trash' => [
'title' => __( 'Trash', 'wpforms-lite' ),
'get_var' => 'status',
'get_var_value' => 'trash',
],
];
// phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
/**
* Filters configuration of the Forms Overview table views.
*
* @since 1.7.3
*
* @param array $views {
* Views array.
*
* @param array $view {
* Each view is the array with three elements:
*
* @param string $title View title.
* @param string $get_var URL query variable name.
* @param string $get_var_value URL query variable value.
* }
* ...
* }
*/
$this->views = apply_filters( 'wpforms_admin_forms_views_configuration', $views );
// phpcs:enable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
}
/**
* Determine if the class is allowed to load.
*
* @since 1.7.3
*
* @return bool
*/
private function allow_load() {
// Load only on the `All Forms` admin page.
return wpforms_is_admin_page( 'overview' );
}
/**
* Initialize class.
*
* @since 1.7.3
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->configuration();
$this->update_current_view();
$this->update_base_url();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.3
*/
private function hooks() {
add_filter( 'wpforms_overview_table_update_count', [ $this, 'update_count' ], 10, 2 );
add_filter( 'wpforms_overview_table_update_count_all', [ $this, 'update_count' ], 10, 2 );
add_filter( 'wpforms_overview_table_prepare_items_args', [ $this, 'prepare_items_trash' ], 100 );
add_filter( 'wpforms_overview_row_actions', [ $this, 'row_actions_all' ], 9, 2 );
add_filter( 'wpforms_overview_row_actions', [ $this, 'row_actions_trash' ], PHP_INT_MAX, 2 );
add_filter( 'wpforms_admin_forms_search_search_reset_block_message', [ $this, 'search_reset_message' ], 10, 4 );
}
/**
* Determine and save current view slug.
*
* @since 1.7.3
*/
private function update_current_view() {
if ( ! is_array( $this->views ) ) {
return;
}
$this->current_view = 'all';
foreach ( $this->views as $slug => $view ) {
if (
// phpcs:disable WordPress.Security.NonceVerification.Recommended
isset( $_GET[ $view['get_var'] ] ) &&
$view['get_var_value'] === sanitize_key( $_GET[ $view['get_var'] ] )
// phpcs:enable WordPress.Security.NonceVerification.Recommended
) {
$this->current_view = $slug;
return;
}
}
}
/**
* Update Base URL.
*
* @since 1.7.3
*/
private function update_base_url() {
if ( ! is_array( $this->views ) ) {
return;
}
$get_vars = wp_list_pluck( $this->views, 'get_var' );
$get_vars = array_merge(
$get_vars,
[
'paged',
'trashed',
'restored',
'deleted',
'duplicated',
]
);
$this->base_url = remove_query_arg( $get_vars );
}
/**
* Get current view slug.
*
* @since 1.7.3
*
* @return string
*/
public function get_current_view() {
return $this->current_view;
}
/**
* Get base URL.
*
* @since 1.7.5
*
* @return string
*/
public function get_base_url() {
return $this->base_url;
}
/**
* Get view configuration by slug.
*
* @since 1.7.5
*
* @param string $slug View slug.
*
* @return array
*/
public function get_view_by_slug( $slug ) {
return isset( $this->views[ $slug ] ) ? $this->views[ $slug ] : [];
}
/**
* Update count.
*
* @since 1.7.3
*
* @param array $count Number of forms in different views.
* @param array $args Get forms arguments.
*
* @return array
*/
public function update_count( $count, $args ) {
// Count forms in Trash.
// We should perform the search of the trashed forms without paging and then count the results.
$args = array_merge(
$args,
[
'nopaging' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'fields' => 'ids',
'post_status' => 'trash',
]
);
$forms = wpforms()->get( 'form' )->get( '', $args );
$count['trash'] = is_array( $forms ) ? count( $forms ) : 0;
// Count all published forms.
$args['post_status'] = 'publish';
$forms = wpforms()->get( 'form' )->get( '', $args );
$count['all'] = is_array( $forms ) ? count( $forms ) : 0;
// Store in class property for further use.
$this->count = $count;
return $count;
}
/**
* Get counts of forms in different views.
*
* @since 1.7.3
*
* @return array
*/
public function get_count() {
return $this->count;
}
/**
* Get forms from Trash when preparing items for list table.
*
* @since 1.7.3
*
* @param array $args Get multiple forms arguments.
*
* @return array
*/
public function prepare_items_trash( $args ) {
// Update arguments of \WPForms_Form_Handler::get_multiple() in order to get forms in Trash.
if ( $this->current_view === 'trash' ) {
$args['post_status'] = 'trash';
}
return $args;
}
/**
* Generate views items.
*
* @since 1.7.3
*
* @return array
*/
public function get_views() {
if ( ! is_array( $this->views ) ) {
return [];
}
$views = [];
foreach ( $this->views as $slug => $view ) {
if (
$slug === 'trash' &&
$this->current_view !== 'trash' &&
empty( $this->count[ $slug ] )
) {
continue;
}
$views[ $slug ] = $this->get_view_markup( $slug );
}
/**
* Filters the Forms Overview table views links.
*
* @since 1.7.3
*
* @param array $views Views links.
* @param array $count Count forms in different views.
*/
return apply_filters( 'wpforms_admin_forms_views_get_views', $views, $this->count );
}
/**
* Generate single view item.
*
* @since 1.7.3
*
* @param string $slug View slug.
*
* @return string
*/
private function get_view_markup( $slug ) {
if ( empty( $this->views[ $slug ] ) ) {
return '';
}
$view = $this->views[ $slug ];
return sprintf(
'<a href="%1$s"%2$s>%3$s&nbsp;<span class="count">(%4$d)</span></a>',
$slug === 'all' ? esc_url( $this->base_url ) : esc_url( add_query_arg( $view['get_var'], $view['get_var_value'], $this->base_url ) ),
$this->current_view === $slug ? ' class="current"' : '',
esc_html( $view['title'] ),
empty( $this->count[ $slug ] ) ? 0 : absint( $this->count[ $slug ] )
);
}
/**
* Row actions for view "All".
*
* @since 1.7.3
*
* @param array $row_actions Row actions.
* @param WP_Post $form Form object.
*
* @return array
*/
public function row_actions_all( $row_actions, $form ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
if ( $this->current_view !== 'all' ) {
return $row_actions;
}
$row_actions = [];
// Edit.
if ( wpforms_current_user_can( 'edit_form_single', $form->ID ) ) {
$row_actions['edit'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
add_query_arg(
[
'view' => 'fields',
'form_id' => $form->ID,
],
admin_url( 'admin.php?page=wpforms-builder' )
)
),
esc_attr__( 'Edit This Form', 'wpforms-lite' ),
esc_html__( 'Edit', 'wpforms-lite' )
);
}
// Entries.
if ( wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) {
$row_actions['entries'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
add_query_arg(
[
'view' => 'list',
'form_id' => $form->ID,
],
admin_url( 'admin.php?page=wpforms-entries' )
)
),
esc_attr__( 'View entries', 'wpforms-lite' ),
esc_html__( 'Entries', 'wpforms-lite' )
);
}
// Payments.
if (
wpforms_current_user_can( wpforms_get_capability_manage_options(), $form->ID ) &&
wpforms()->get( 'payment' )->get_by( 'form_id', $form->ID )
) {
$row_actions['payments'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
add_query_arg(
[
'page' => 'wpforms-payments',
'form_id' => $form->ID,
],
admin_url( 'admin.php' )
)
),
esc_attr__( 'View payments', 'wpforms-lite' ),
esc_html__( 'Payments', 'wpforms-lite' )
);
}
// Preview.
if ( wpforms_current_user_can( 'view_form_single', $form->ID ) ) {
$row_actions['preview_'] = sprintf(
'<a href="%s" title="%s" target="_blank" rel="noopener noreferrer">%s</a>',
esc_url( wpforms_get_form_preview_url( $form->ID ) ),
esc_attr__( 'View preview', 'wpforms-lite' ),
esc_html__( 'Preview', 'wpforms-lite' )
);
}
// Duplicate.
if ( wpforms_current_user_can( 'create_forms' ) && wpforms_current_user_can( 'view_form_single', $form->ID ) ) {
$row_actions['duplicate'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
wp_nonce_url(
add_query_arg(
[
'action' => 'duplicate',
'form_id' => $form->ID,
],
$this->base_url
),
'wpforms_duplicate_form_nonce'
)
),
esc_attr__( 'Duplicate this form', 'wpforms-lite' ),
esc_html__( 'Duplicate', 'wpforms-lite' )
);
}
// Trash.
if ( wpforms_current_user_can( 'delete_form_single', $form->ID ) ) {
$row_actions['trash'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
wp_nonce_url(
add_query_arg(
[
'action' => 'trash',
'form_id' => $form->ID,
],
$this->base_url
),
'wpforms_trash_form_nonce'
)
),
esc_attr__( 'Move this form to trash', 'wpforms-lite' ),
esc_html__( 'Trash', 'wpforms-lite' )
);
}
return $row_actions;
}
/**
* Row actions for view "Trash".
*
* @since 1.7.3
*
* @param array $row_actions Row actions.
* @param WP_Post $form Form object.
*
* @return array
*/
public function row_actions_trash( $row_actions, $form ) {
if (
$this->current_view !== 'trash' ||
! wpforms_current_user_can( 'delete_form_single', $form->ID )
) {
return $row_actions;
}
$row_actions = [];
// Restore form.
$row_actions['restore'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
wp_nonce_url(
add_query_arg(
[
'action' => 'restore',
'form_id' => $form->ID,
'status' => 'trash',
],
$this->base_url
),
'wpforms_restore_form_nonce'
)
),
esc_attr__( 'Restore this form', 'wpforms-lite' ),
esc_html__( 'Restore', 'wpforms-lite' )
);
// Delete permanently.
$row_actions['delete'] = sprintf(
'<a href="%s" title="%s">%s</a>',
esc_url(
wp_nonce_url(
add_query_arg(
[
'action' => 'delete',
'form_id' => $form->ID,
'status' => 'trash',
],
$this->base_url
),
'wpforms_delete_form_nonce'
)
),
esc_attr__( 'Delete this form permanently', 'wpforms-lite' ),
esc_html__( 'Delete Permanently', 'wpforms-lite' )
);
return $row_actions;
}
/**
* Search reset message.
*
* @since 1.7.3
*
* @param string $message Search reset block message.
* @param string $search_term Search term.
* @param array $count Count forms in different views.
* @param string $current_view Current view.
*
* @return string
*/
public function search_reset_message( $message, $search_term, $count, $current_view ) {
if ( $current_view !== 'trash' ) {
return $message;
}
$count['trash'] = ! empty( $count['trash'] ) ? $count['trash'] : 0;
return sprintf(
wp_kses( /* translators: %1$d - number of forms found in the trash, %2$s - search term. */
_n(
'Found <strong>%1$d form</strong> in <em>the trash</em> containing <em>"%2$s"</em>',
'Found <strong>%1$d forms</strong> in <em>the trash</em> containing <em>"%2$s"</em>',
(int) $count['trash'],
'wpforms-lite'
),
[
'strong' => [],
'em' => [],
]
),
(int) $count['trash'],
esc_html( $search_term )
);
}
/**
* Extra controls to be displayed between bulk actions and pagination.
*
* @since 1.7.3
*
* @param string $which The location of the table navigation: 'top' or 'bottom'.
*/
public function extra_tablenav( $which ) {
if ( ! wpforms_current_user_can( 'delete_form_single' ) ) {
return;
}
if ( $this->current_view !== 'trash' ) {
return;
}
// Preserve current view after applying bulk action.
echo '<input type="hidden" name="status" value="trash">';
// Display Empty Trash button.
printf(
'<a href="%1$s" class="button delete-all">%2$s</a>',
esc_url(
wp_nonce_url(
add_query_arg(
[
'action' => 'empty_trash',
'form_id' => 1, // Technically, `empty_trash` is one of the bulk actions, therefore we need to provide fake form_id to proceed.
'status' => 'trash',
],
$this->base_url
),
'wpforms_empty_trash_form_nonce'
)
),
esc_html__( 'Empty Trash', 'wpforms-lite' )
);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace WPForms\Admin\Helpers;
use DateInterval;
use DatePeriod;
use DateTimeImmutable;
/**
* Chart dataset processing helper methods.
*
* @since 1.8.2
*/
class Chart {
/**
* Default date format.
*
* @since 1.8.2
*/
const DATE_FORMAT = 'Y-m-d';
/**
* Default date-time format.
*
* @since 1.8.2
*/
const DATETIME_FORMAT = 'Y-m-d H:i:s';
/**
* Processes the provided dataset to make sure the formatting needed for the "Chart.js" instance is provided.
*
* @since 1.8.2
*
* @param array $query Dataset retrieved from the database.
* @param DateTimeImmutable $start_date Start date for the timespan.
* @param DateTimeImmutable $end_date End date for the timespan.
*
* @return array
*/
public static function process_chart_dataset_data( $query, $start_date, $end_date ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// Bail early if the given query contains no records to iterate.
if ( ! is_array( $query ) || empty( $query ) ) {
return [ 0, [] ];
}
$dataset = [];
$timezone = wpforms_get_timezone(); // Retrieve the timezone object for the site.
$mysql_timezone = timezone_open( 'UTC' ); // In the database, all datetime are stored in UTC.
foreach ( $query as $row ) {
$row_day = isset( $row['day'] ) ? sanitize_text_field( $row['day'] ) : '';
$row_count = isset( $row['count'] ) ? abs( (float) $row['count'] ) : 0;
// Skip the rest of the current iteration if the date (day) is unavailable.
if ( empty( $row_day ) ) {
continue;
}
// Since we wont need the initial datetime instances after the query,
// there is no need to create immutable date objects.
$row_datetime = date_create_from_format( self::DATETIME_FORMAT, $row_day, $mysql_timezone );
// Skip the rest of the current iteration if the date creation function fails.
if ( ! $row_datetime ) {
continue;
}
$row_datetime->setTimezone( $timezone );
$row_date_formatted = $row_datetime->format( self::DATE_FORMAT );
// We must take into account entries submitted at different hours of the day,
// because it is possible that more than one entry could be submitted on a given day.
if ( ! isset( $dataset[ $row_date_formatted ] ) ) {
$dataset[ $row_date_formatted ] = $row_count;
continue;
}
$dataset_count = $dataset[ $row_date_formatted ];
$dataset[ $row_date_formatted ] = $dataset_count + $row_count;
}
return self::format_chart_dataset_data( $dataset, $start_date, $end_date );
}
/**
* Format given forms dataset to ensure correct data structure is parsed for serving the "chart.js" instance.
* i.e., [ '2023-02-11' => [ 'day' => '2023-02-11', 'count' => 12 ] ].
*
* @since 1.8.2
*
* @param array $dataset Dataset for the chart.
* @param DateTimeImmutable $start_date Start date for the timespan.
* @param DateTimeImmutable $end_date End date for the timespan.
*
* @return array
*/
private static function format_chart_dataset_data( $dataset, $start_date, $end_date ) {
// In the event that there is no dataset to process, leave early.
if ( empty( $dataset ) ) {
return [ 0, [] ];
}
$interval = new DateInterval( 'P1D' ); // Variable that store the date interval of period 1 day.
$period = new DatePeriod( $start_date, $interval, $end_date ); // Used for iteration between start and end date period.
$data = []; // Placeholder for the actual chart dataset data.
$total_entries = 0;
$has_non_zero_count = false;
// Use loop to store date into array.
foreach ( $period as $date ) {
$date_formatted = $date->format( self::DATE_FORMAT );
$count = isset( $dataset[ $date_formatted ] ) ? (float) $dataset[ $date_formatted ] : 0;
$total_entries += $count;
$data[ $date_formatted ] = [
'day' => $date_formatted,
'count' => $count,
];
// This check helps determine whether there is at least one non-zero count value in the dataset being processed.
// It's used to optimize the function's behavior and to decide whether to include certain data in the returned result.
if ( $count > 0 && ! $has_non_zero_count ) {
$has_non_zero_count = true;
}
}
return [
$total_entries,
$has_non_zero_count ? $data : [], // We will return an empty array to indicate that there is no data to display.
];
}
}

View File

@@ -0,0 +1,454 @@
<?php
namespace WPForms\Admin\Helpers;
use DateTimeImmutable;
/**
* Timespan and popover date-picker helper methods.
*
* @since 1.8.2
*/
class Datepicker {
/**
* Number of timespan days by default.
* "Last 30 Days", by default.
*
* @since 1.8.2
*/
const TIMESPAN_DAYS = '30';
/**
* Timespan (date range) delimiter.
*
* @since 1.8.2
*/
const TIMESPAN_DELIMITER = ' - ';
/**
* Default date format.
*
* @since 1.8.2
*/
const DATE_FORMAT = 'Y-m-d';
/**
* Default date-time format.
*
* @since 1.8.2
*/
const DATETIME_FORMAT = 'Y-m-d H:i:s';
/**
* Sets the timespan (or date range) selected.
*
* Includes:
* 1. Start date object in WP timezone.
* 2. End date object in WP timezone.
* 3. Number of "Last X days", if applicable, otherwise returns "custom".
* 4. Label associated with the selected date filter choice. @see "get_date_filter_choices".
*
* @since 1.8.2
*
* @return array
*/
public static function process_timespan() {
$dates = (string) filter_input( INPUT_GET, 'date', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
// Return default timespan if dates are empty.
if ( empty( $dates ) ) {
return self::get_timespan_dates( self::TIMESPAN_DAYS );
}
$dates = self::maybe_validate_string_timespan( $dates );
list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates );
// Return default timespan if start date is more recent than end date.
if ( strtotime( $start_date ) > strtotime( $end_date ) ) {
return self::get_timespan_dates( self::TIMESPAN_DAYS );
}
$timezone = wpforms_get_timezone(); // Retrieve the timezone string for the site.
$start_date = date_create_immutable( $start_date, $timezone );
$end_date = date_create_immutable( $end_date, $timezone );
// Return default timespan if date creation fails.
if ( ! $start_date || ! $end_date ) {
return self::get_timespan_dates( self::TIMESPAN_DAYS );
}
// Set time to 0:0:0 for start date and 23:59:59 for end date.
$start_date = $start_date->setTime( 0, 0, 0 );
$end_date = $end_date->setTime( 23, 59, 59 );
$days_diff = '';
$current_date = date_create_immutable( 'now', $timezone )->setTime( 23, 59, 59 );
// Calculate days difference only if end date is equal to current date.
if ( ! $current_date->diff( $end_date )->format( '%a' ) ) {
$days_diff = $end_date->diff( $start_date )->format( '%a' );
}
list( $days, $timespan_label ) = self::get_date_filter_choices( $days_diff );
return [
$start_date, // WP timezone.
$end_date, // WP timezone.
$days, // e.g., 22.
$timespan_label, // e.g., Custom.
];
}
/**
* Sets the timespan (or date range) for performing mysql queries.
*
* Includes:
* 1. Start date object in WP timezone.
* 2. End date object in WP timezone.
*
* @param null|array $timespan Given timespan (dates) preferably in WP timezone.
*
* @since 1.8.2
*
* @return array
*/
public static function process_timespan_mysql( $timespan = null ) {
// Retrieve and validate timespan if none is given.
if ( empty( $timespan ) || ! is_array( $timespan ) ) {
$timespan = self::process_timespan();
}
list( $start_date, $end_date ) = $timespan; // Ideally should be in WP timezone.
// If the time period is not a date object, return empty values.
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
return [ '', '' ];
}
// If given timespan is already in UTC timezone, return as it is.
if ( date_timezone_get( $start_date )->getName() === 'UTC' && date_timezone_get( $end_date )->getName() === 'UTC' ) {
return [
$start_date, // UTC timezone.
$end_date, // UTC timezone.
];
}
$mysql_timezone = timezone_open( 'UTC' );
return [
$start_date->setTimezone( $mysql_timezone ), // UTC timezone.
$end_date->setTimezone( $mysql_timezone ), // UTC timezone.
];
}
/**
* Helper method to generate WP and UTC based date-time instances.
*
* Includes:
* 1. Start date object in WP timezone.
* 2. End date object in WP timezone.
* 3. Start date object in UTC timezone.
* 4. End date object in UTC timezone.
*
* @since 1.8.2
*
* @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15".
*
* @return bool|array
*/
public static function process_string_timespan( $dates ) {
$dates = self::maybe_validate_string_timespan( $dates );
list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates );
// Return false if the start date is more recent than the end date.
if ( strtotime( $start_date ) > strtotime( $end_date ) ) {
return false;
}
$timezone = wpforms_get_timezone(); // Retrieve the timezone object for the site.
$start_date = date_create_immutable( $start_date, $timezone );
$end_date = date_create_immutable( $end_date, $timezone );
// Return false if the date creation fails.
if ( ! $start_date || ! $end_date ) {
return false;
}
// Set the time to 0:0:0 for the start date and 23:59:59 for the end date.
$start_date = $start_date->setTime( 0, 0, 0 );
$end_date = $end_date->setTime( 23, 59, 59 );
// Since we will need the initial datetime instances after the query,
// we need to return new objects when modifications made.
// Convert the dates to UTC timezone.
$mysql_timezone = timezone_open( 'UTC' );
$utc_start_date = $start_date->setTimezone( $mysql_timezone );
$utc_end_date = $end_date->setTimezone( $mysql_timezone );
return [
$start_date, // WP timezone.
$end_date, // WP timezone.
$utc_start_date, // UTC timezone.
$utc_end_date, // UTC timezone.
];
}
/**
* Sets the timespan (or date range) for performing mysql queries.
*
* Includes:
* 1. A list of date filter options for the datepicker module.
* 2. Currently selected filter or date range values. Last "X" days, or i.e. Feb 8, 2023 - Mar 9, 2023.
* 3. Assigned timespan dates.
*
* @param null|array $timespan Given timespan (dates) preferably in WP timezone.
*
* @since 1.8.2
*
* @return array
*/
public static function process_datepicker_choices( $timespan = null ) {
// Retrieve and validate timespan if none is given.
if ( empty( $timespan ) || ! is_array( $timespan ) ) {
$timespan = self::process_timespan();
}
list( $start_date, $end_date, $days ) = $timespan;
$filters = self::get_date_filter_choices();
$selected = isset( $filters[ $days ] ) ? $days : 'custom';
$value = self::concat_dates( $start_date, $end_date );
$chosen_filter = $selected === 'custom' ? $value : $filters[ $selected ];
$choices = [];
foreach ( $filters as $choice => $label ) {
$timespan_dates = self::get_timespan_dates( $choice );
$checked = checked( $selected, $choice, false );
$choices[] = sprintf(
'<label class="%s">%s<input type="radio" aria-hidden="true" name="timespan" value="%s" %s></label>',
$checked ? 'is-selected' : '',
esc_html( $label ),
esc_attr( self::concat_dates( ...$timespan_dates ) ),
esc_attr( $checked )
);
}
return [
$choices,
$chosen_filter,
$value,
];
}
/**
* Based on the specified date-time range, calculates the comparable prior time period to estimate trends.
*
* Includes:
* 1. Start date object in the given (original) timezone.
* 2. End date object in the given (original) timezone.
*
* @since 1.8.2
*
* @param DateTimeImmutable $start_date Start date for the timespan.
* @param DateTimeImmutable $end_date End date for the timespan.
*
* @return bool|array
*/
public static function get_prev_timespan_dates( $start_date, $end_date ) {
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
return false;
}
$days_diff = $end_date->diff( $start_date )->format( '%a' );
$days_modifier = (int) $days_diff <= 0 ? '1' : (string) $days_diff;
return [
$start_date->modify( "-{$days_modifier} day" ),
$start_date->modify( '-1 second' ),
];
}
/**
* Get the site's date format from WordPress settings and convert it to a format compatible with Moment.js.
*
* @since 1.8.5.4
*
* @return string
*/
public static function get_wp_date_format_for_momentjs() {
// Get the date format from WordPress settings.
$date_format = get_option( 'date_format', 'F j, Y' );
// Define a mapping of PHP date format characters to Moment.js format characters.
$format_mapping = [
'd' => 'DD',
'D' => 'ddd',
'j' => 'D',
'l' => 'dddd',
'S' => '', // PHP's S (English ordinal suffix) is not directly supported in Moment.js.
'w' => 'd',
'z' => '', // PHP's z (Day of the year) is not directly supported in Moment.js.
'W' => '', // PHP's W (ISO-8601 week number of year) is not directly supported in Moment.js.
'F' => 'MMMM',
'm' => 'MM',
'M' => 'MMM',
'n' => 'M',
't' => '', // PHP's t (Number of days in the given month) is not directly supported in Moment.js.
'L' => '', // PHP's L (Whether it's a leap year) is not directly supported in Moment.js.
'o' => 'YYYY',
'Y' => 'YYYY',
'y' => 'YY',
'a' => 'a',
'A' => 'A',
'B' => '', // PHP's B (Swatch Internet time) is not directly supported in Moment.js.
'g' => 'h',
'G' => 'H',
'h' => 'hh',
'H' => 'HH',
'i' => 'mm',
's' => 'ss',
'u' => '', // PHP's u (Microseconds) is not directly supported in Moment.js.
'e' => '', // PHP's e (Timezone identifier) is not directly supported in Moment.js.
'I' => '', // PHP's I (Whether or not the date is in daylight saving time) is not directly supported in Moment.js.
'O' => '', // PHP's O (Difference to Greenwich time (GMT) without colon) is not directly supported in Moment.js.
'P' => '', // PHP's P (Difference to Greenwich time (GMT) with colon) is not directly supported in Moment.js.
'T' => '', // PHP's T (Timezone abbreviation) is not directly supported in Moment.js.
'Z' => '', // PHP's Z (Timezone offset in seconds) is not directly supported in Moment.js.
'c' => 'YYYY-MM-DD', // PHP's c (ISO 8601 date) is not directly supported in Moment.js.
'r' => 'ddd, DD MMM YYYY', // PHP's r (RFC 2822 formatted date) is not directly supported in Moment.js.
'U' => '', // PHP's U (Seconds since the Unix Epoch) is not directly supported in Moment.js.
];
// Convert PHP format to JavaScript format.
$momentjs_format = strtr( $date_format, $format_mapping );
// Use 'MMM D, YYYY' as a fallback if the conversion is not available.
return empty( $momentjs_format ) ? 'MMM D, YYYY' : $momentjs_format;
}
/**
* The number of days is converted to the start and end date range.
*
* @since 1.8.2
*
* @param string $days Timespan days.
*
* @return array
*/
private static function get_timespan_dates( $days ) {
list( $timespan_key, $timespan_label ) = self::get_date_filter_choices( $days );
// Bail early, if the given number of days is NOT a number nor a numeric string.
if ( ! is_numeric( $days ) ) {
return [ '', '', $timespan_key, $timespan_label ];
}
$end_date = date_create_immutable( 'now', wpforms_get_timezone() );
$start_date = $end_date;
if ( (int) $days > 0 ) {
$start_date = $start_date->modify( "-{$days} day" );
}
$start_date = $start_date->setTime( 0, 0, 0 );
$end_date = $end_date->setTime( 23, 59, 59 );
return [
$start_date, // WP timezone.
$end_date, // WP timezone.
$timespan_key, // i.e. 30.
$timespan_label, // i.e. Last 30 days.
];
}
/**
* Check the delimiter to see if the end date is specified.
* We can assume that the start and end dates are the same if the end date is missing.
*
* @since 1.8.2
*
* @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15" or "2023-01-16".
*
* @return string
*/
private static function maybe_validate_string_timespan( $dates ) {
// "-" (en dash) is used as a delimiter for the datepicker module.
if ( strpos( $dates, self::TIMESPAN_DELIMITER ) !== false ) {
return $dates;
}
return $dates . self::TIMESPAN_DELIMITER . $dates;
}
/**
* Returns a list of date filter options for the datepicker module.
*
* @since 1.8.2
*
* @param string|null $key Optional. Key associated with available filters.
*
* @return array
*/
private static function get_date_filter_choices( $key = null ) {
// Available date filters.
$choices = [
'0' => esc_html__( 'Today', 'wpforms-lite' ),
'1' => esc_html__( 'Yesterday', 'wpforms-lite' ),
'7' => esc_html__( 'Last 7 days', 'wpforms-lite' ),
'30' => esc_html__( 'Last 30 days', 'wpforms-lite' ),
'90' => esc_html__( 'Last 90 days', 'wpforms-lite' ),
'365' => esc_html__( 'Last 1 year', 'wpforms-lite' ),
'custom' => esc_html__( 'Custom', 'wpforms-lite' ),
];
// Bail early, and return the full list of options.
if ( is_null( $key ) ) {
return $choices;
}
// Return the "Custom" filter if the given key is not found.
$key = isset( $choices[ $key ] ) ? $key : 'custom';
return [ $key, $choices[ $key ] ];
}
/**
* Concatenate given dates into a single string. i.e. "2023-01-16 - 2023-02-15".
*
* @since 1.8.2
*
* @param DateTimeImmutable $start_date Start date.
* @param DateTimeImmutable $end_date End date.
* @param int|string $fallback Fallback value if dates are not valid.
*
* @return string
*/
private static function concat_dates( $start_date, $end_date, $fallback = '' ) {
// Bail early, if the given dates are not valid.
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
return $fallback;
}
return implode(
self::TIMESPAN_DELIMITER,
[
$start_date->format( self::DATE_FORMAT ),
$end_date->format( self::DATE_FORMAT ),
]
);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace WPForms\Admin;
/**
* Class Loader gives ability to track/load all admin modules.
*
* @since 1.5.0
*/
class Loader {
/**
* Get the instance of a class and store it in itself.
*
* @since 1.5.0
*/
public static function get_instance() {
static $instance;
if ( ! $instance ) {
$instance = new self();
}
return $instance;
}
/**
* Loader constructor.
*
* @since 1.5.0
*/
public function __construct() {
$core_class_names = [
'Connect',
'FlyoutMenu',
'Builder\LicenseAlert',
'Builder\Builder',
'Pages\Community',
'Pages\SMTP',
'Pages\Analytics',
'Entries\PrintPreview',
];
$class_names = \apply_filters( 'wpforms_admin_classes_available', $core_class_names );
foreach ( $class_names as $class_name ) {
$this->register_class( $class_name );
}
}
/**
* Register a new class.
*
* @since 1.5.0
*
* @param string $class_name Class name to register.
*/
public function register_class( $class_name ) {
$class_name = sanitize_text_field( $class_name );
// Load Lite class if exists.
if ( class_exists( 'WPForms\Lite\Admin\\' . $class_name ) && ! wpforms()->is_pro() ) {
$class_name = 'WPForms\Lite\Admin\\' . $class_name;
new $class_name();
return;
}
// Load Pro class if exists.
if ( class_exists( 'WPForms\Pro\Admin\\' . $class_name ) && wpforms()->is_pro() ) {
$class_name = 'WPForms\Pro\Admin\\' . $class_name;
new $class_name();
return;
}
// Load general class if neither Pro nor Lite class exists.
if ( class_exists( __NAMESPACE__ . '\\' . $class_name ) ) {
$class_name = __NAMESPACE__ . '\\' . $class_name;
new $class_name();
}
}
}

View File

@@ -0,0 +1,365 @@
<?php
namespace WPForms\Admin;
/**
* Dismissible admin notices.
*
* @since 1.6.7.1
*
* @example Dismissible - global:
* \WPForms\Admin\Notice::error(
* 'Fatal error!',
* [
* 'dismiss' => \WPForms\Admin\Notice::DISMISS_GLOBAL,
* 'slug' => 'fatal_error_3678975',
* ]
* );
*
* @example Dismissible - per user:
* \WPForms\Admin\Notice::warning(
* 'Do something please.',
* [
* 'dismiss' => \WPForms\Admin\Notice::DISMISS_USER,
* 'slug' => 'do_something_1238943',
* ]
* );
*
* @example Dismissible - global, add custom class to output and disable auto paragraph in text:
* \WPForms\Admin\Notice::error(
* 'Fatal error!',
* [
* 'dismiss' => \WPForms\Admin\Notice::DISMISS_GLOBAL,
* 'slug' => 'fatal_error_348975',
* 'autop' => false,
* 'class' => 'some-additional-class',
* ]
* );
*
* @example Not dismissible:
* \WPForms\Admin\Notice::success( 'Everything is good!' );
*/
class Notice {
/**
* Not dismissible.
*
* Constant attended to use as the value of the $args['dismiss'] argument.
* DISMISS_NONE means that the notice is not dismissible.
*
* @since 1.6.7.1
*/
const DISMISS_NONE = 0;
/**
* Dismissible global.
*
* Constant attended to use as the value of the $args['dismiss'] argument.
* DISMISS_GLOBAL means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed for all users.
*
* @since 1.6.7.1
*/
const DISMISS_GLOBAL = 1;
/**
* Dismissible per user.
*
* Constant attended to use as the value of the $args['dismiss'] argument.
* DISMISS_USER means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed only for the current user..
*
* @since 1.6.7.1
*/
const DISMISS_USER = 2;
/**
* Added notices.
*
* @since 1.6.7.1
*
* @var array
*/
private $notices = [];
/**
* Init.
*
* @since 1.6.7.1
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.6.7.1
*/
public function hooks() {
add_action( 'admin_notices', [ $this, 'display' ], PHP_INT_MAX );
add_action( 'wp_ajax_wpforms_notice_dismiss', [ $this, 'dismiss_ajax' ] );
}
/**
* Enqueue assets.
*
* @since 1.6.7.1
*/
private function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-admin-notices',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/notices{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-admin-notices',
'wpforms_admin_notices',
[
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wpforms-admin' ),
]
);
}
/**
* Display the notices.
*
* @since 1.6.7.1
*/
public function display() {
$dismissed_notices = get_user_meta( get_current_user_id(), 'wpforms_admin_notices', true );
$dismissed_notices = is_array( $dismissed_notices ) ? $dismissed_notices : [];
$dismissed_notices = array_merge( $dismissed_notices, (array) get_option( 'wpforms_admin_notices', [] ) );
foreach ( $this->notices as $slug => $notice ) {
if ( isset( $dismissed_notices[ $slug ] ) && ! empty( $dismissed_notices[ $slug ]['dismissed'] ) ) {
unset( $this->notices[ $slug ] );
}
}
$output = implode( '', $this->notices );
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// Enqueue script only when it's needed.
if ( strpos( $output, 'is-dismissible' ) !== false ) {
$this->enqueues();
}
}
/**
* Add notice to the registry.
*
* @since 1.6.7.1
*
* @param string $message Message to display.
* @param string $type Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ].
* @param array $args The array of additional arguments. Please see the $defaults array below.
*/
public static function add( $message, $type = '', $args = [] ) {
static $uniq_id = 0;
$defaults = [
'dismiss' => self::DISMISS_NONE, // Dismissible level: one of the self::DISMISS_* const. By default notice is not dismissible.
'slug' => '', // Slug. Should be unique if dismissible is not equal self::DISMISS_NONE.
'autop' => true, // `false` if not needed to pass message through wpautop().
'class' => '', // Additional CSS class.
];
$args = wp_parse_args( $args, $defaults );
$dismissible = (int) $args['dismiss'];
$dismissible = $dismissible > self::DISMISS_USER ? self::DISMISS_USER : $dismissible;
$class = $dismissible > self::DISMISS_NONE ? ' is-dismissible' : '';
$global = ( $dismissible === self::DISMISS_GLOBAL ) ? 'global-' : '';
$slug = sanitize_key( $args['slug'] );
++$uniq_id;
$uniq_id += ( $uniq_id === (int) $slug ) ? 1 : 0;
$id = 'wpforms-notice-' . $global;
$id .= empty( $slug ) ? $uniq_id : $slug;
$type = ! empty( $type ) ? 'notice-' . esc_attr( sanitize_key( $type ) ) : '';
$class = empty( $args['class'] ) ? $class : $class . ' ' . esc_attr( sanitize_key( $args['class'] ) );
$message = $args['autop'] ? wpautop( $message ) : $message;
$notice = sprintf(
'<div class="notice wpforms-notice %s%s" id="%s">%s</div>',
esc_attr( $type ),
esc_attr( $class ),
esc_attr( $id ),
$message
);
if ( empty( $slug ) ) {
wpforms()->get( 'notice' )->notices[] = $notice;
} else {
wpforms()->get( 'notice' )->notices[ $slug ] = $notice;
}
}
/**
* Add info notice.
*
* @since 1.6.7.1
*
* @param string $message Message to display.
* @param array $args Array of additional arguments. Details in the self::add() method.
*/
public static function info( $message, $args = [] ) {
self::add( $message, 'info', $args );
}
/**
* Add error notice.
*
* @since 1.6.7.1
*
* @param string $message Message to display.
* @param array $args Array of additional arguments. Details in the self::add() method.
*/
public static function error( $message, $args = [] ) {
self::add( $message, 'error', $args );
}
/**
* Add success notice.
*
* @since 1.6.7.1
*
* @param string $message Message to display.
* @param array $args Array of additional arguments. Details in the self::add() method.
*/
public static function success( $message, $args = [] ) {
self::add( $message, 'success', $args );
}
/**
* Add warning notice.
*
* @since 1.6.7.1
*
* @param string $message Message to display.
* @param array $args Array of additional arguments. Details in the self::add() method.
*/
public static function warning( $message, $args = [] ) {
self::add( $message, 'warning', $args );
}
/**
* AJAX routine that updates dismissed notices meta data.
*
* @since 1.6.7.1
*/
public function dismiss_ajax() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Sanitize POST data.
$post = array_map( 'sanitize_key', wp_unslash( $_POST ) );
// Update notices meta data.
if ( strpos( $post['id'], 'global-' ) !== false ) {
// Check for permissions.
if ( ! wpforms_current_user_can() ) {
wp_send_json_error();
}
$notices = $this->dismiss_global( $post['id'] );
$level = self::DISMISS_GLOBAL;
} else {
$notices = $this->dismiss_user( $post['id'] );
$level = self::DISMISS_USER;
}
/**
* Allows developers to apply additional logic to the dismissing notice process.
* Executes after updating option or user meta (according to the notice level).
*
* @since 1.6.7.1
*
* @param string $notice_id Notice ID (slug).
* @param integer $level Notice level.
* @param array $notices Dismissed notices.
*/
do_action( 'wpforms_admin_notice_dismiss_ajax', $post['id'], $level, $notices );
if ( ! wpforms_debug() ) {
wp_send_json_success();
}
wp_send_json_success(
[
'id' => $post['id'],
'time' => time(),
'level' => $level,
'notices' => $notices,
]
);
}
/**
* AJAX sub-routine that updates dismissed notices option.
*
* @since 1.6.7.1
*
* @param string $id Notice Id.
*
* @return array Notices.
*/
private function dismiss_global( $id ) {
$id = str_replace( 'global-', '', $id );
$notices = get_option( 'wpforms_admin_notices', [] );
$notices[ $id ] = [
'time' => time(),
'dismissed' => true,
];
update_option( 'wpforms_admin_notices', $notices, true );
return $notices;
}
/**
* AJAX sub-routine that updates dismissed notices user meta.
*
* @since 1.6.7.1
*
* @param string $id Notice Id.
*
* @return array Notices.
*/
private function dismiss_user( $id ) {
$user_id = get_current_user_id();
$notices = get_user_meta( $user_id, 'wpforms_admin_notices', true );
$notices = ! is_array( $notices ) ? [] : $notices;
$notices[ $id ] = [
'time' => time(),
'dismissed' => true,
];
update_user_meta( $user_id, 'wpforms_admin_notices', $notices );
return $notices;
}
}

View File

@@ -0,0 +1,752 @@
<?php
namespace WPForms\Admin\Notifications;
/**
* Class EventDriven.
*
* @since 1.7.5
*/
class EventDriven {
/**
* WPForms version when the Event Driven feature has been introduced.
*
* @since 1.7.5
*
* @var string
*/
const FEATURE_INTRODUCED = '1.7.5';
/**
* Expected date format for notifications.
*
* @since 1.7.5
*
* @var string
*/
const DATE_FORMAT = 'Y-m-d H:i:s';
/**
* Common UTM parameters.
*
* @since 1.7.5
*
* @var array
*/
const UTM_PARAMS = [
'utm_source' => 'WordPress',
'utm_medium' => 'Event Notification',
];
/**
* Common targets for date logic.
*
* Available items:
* - upgraded (upgraded to a latest version)
* - activated
* - forms_first_created
* - X.X.X.X (upgraded to a specific version)
* - pro (activated/installed)
* - lite (activated/installed)
*
* @since 1.7.5
*
* @var array
*/
const DATE_LOGIC = [ 'upgraded', 'activated', 'forms_first_created' ];
/**
* Timestamps.
*
* @since 1.7.5
*
* @var array
*/
private $timestamps = [];
/**
* Initialize class.
*
* @since 1.7.5
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Indicate if this is allowed to load.
*
* @since 1.7.5
*
* @return bool
*/
private function allow_load() {
return wpforms()->get( 'notifications' )->has_access() || wp_doing_cron();
}
/**
* Hooks.
*
* @since 1.7.5
*/
private function hooks() {
add_filter( 'wpforms_admin_notifications_update_data', [ $this, 'update_events' ] );
}
/**
* Add Event Driven notifications before saving them in database.
*
* @since 1.7.5
*
* @param array $data Notification data.
*
* @return array
*/
public function update_events( $data ) {
$updated = [];
/**
* Allow developers to turn on debug mode: store all notifications and then show all of them.
*
* @since 1.7.5
*
* @param bool $is_debug True if it's a debug mode. Default: false.
*/
$is_debug = (bool) apply_filters( 'wpforms_admin_notifications_event_driven_update_events_debug', false );
$wpforms_notifications = wpforms()->get( 'notifications' );
foreach ( $this->get_notifications() as $slug => $notification ) {
$is_processed = ! empty( $data['events'][ $slug ]['start'] );
$is_conditional_ok = ! ( isset( $notification['condition'] ) && $notification['condition'] === false );
// If it's a debug mode OR valid notification has been already processed - skip running logic checks and save it.
if (
$is_debug ||
( $is_processed && $is_conditional_ok && $wpforms_notifications->is_valid( $data['events'][ $slug ] ) )
) {
unset( $notification['date_logic'], $notification['offset'], $notification['condition'] );
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$notification['start'] = $is_debug ? date( self::DATE_FORMAT ) : $data['events'][ $slug ]['start'];
$updated[ $slug ] = $notification;
continue;
}
// Ignore if a condition is not passed conditional checks.
if ( ! $is_conditional_ok ) {
continue;
}
$timestamp = $this->get_timestamp_by_date_logic(
$this->prepare_date_logic( $notification )
);
if ( empty( $timestamp ) ) {
continue;
}
// Probably, notification should be visible after some time.
$offset = empty( $notification['offset'] ) ? 0 : absint( $notification['offset'] );
// Set a start date when notification will be shown.
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$notification['start'] = date( self::DATE_FORMAT, $timestamp + $offset );
// Ignore if notification data is not valid.
if ( ! $wpforms_notifications->is_valid( $notification ) ) {
continue;
}
// Remove unnecessary values, mark notification as active, and save it.
unset( $notification['date_logic'], $notification['offset'], $notification['condition'] );
$updated[ $slug ] = $notification;
}
$data['events'] = $updated;
return $data;
}
/**
* Prepare and retrieve date logic.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return array
*/
private function prepare_date_logic( $notification ) {
$date_logic = empty( $notification['date_logic'] ) || ! is_array( $notification['date_logic'] ) ? self::DATE_LOGIC : $notification['date_logic'];
return array_filter( array_filter( $date_logic, 'is_string' ) );
}
/**
* Retrieve a notification timestamp based on date logic.
*
* @since 1.7.5
*
* @param array $args Date logic.
*
* @return int
*/
private function get_timestamp_by_date_logic( $args ) {
foreach ( $args as $target ) {
if ( ! empty( $this->timestamps[ $target ] ) ) {
return $this->timestamps[ $target ];
}
$timestamp = call_user_func(
$this->get_timestamp_callback( $target ),
$target
);
if ( ! empty( $timestamp ) ) {
$this->timestamps[ $target ] = $timestamp;
return $timestamp;
}
}
return 0;
}
/**
* Retrieve a callback that determines needed timestamp.
*
* @since 1.7.5
*
* @param string $target Date logic target.
*
* @return callable
*/
private function get_timestamp_callback( $target ) {
$raw_target = $target;
// As $target should be a part of name for callback method,
// this regular expression allow lowercase characters, numbers, and underscore.
$target = strtolower( preg_replace( '/[^a-z0-9_]/', '', $target ) );
// Basic callback.
$callback = [ $this, 'get_timestamp_' . $target ];
// Determine if a special version number is passed.
// Uses the regular expression to check a SemVer string.
// @link https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string.
if ( preg_match( '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.([1-9\d*]))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/', $raw_target ) ) {
$callback = [ $this, 'get_timestamp_upgraded' ];
}
// If callback is callable, return it. Otherwise, return fallback.
return is_callable( $callback ) ? $callback : '__return_zero';
}
/**
* Retrieve a timestamp when WPForms was upgraded.
*
* @since 1.7.5
*
* @param string $version WPForms version.
*
* @return int|false Unix timestamp. False on failure.
*/
private function get_timestamp_upgraded( $version ) {
if ( $version === 'upgraded' ) {
$version = WPFORMS_VERSION;
}
$timestamp = wpforms_get_upgraded_timestamp( $version );
if ( $timestamp === false ) {
return false;
}
// Return a current timestamp if no luck to return a migration's timestamp.
return $timestamp <= 0 ? time() : $timestamp;
}
/**
* Retrieve a timestamp when WPForms was first installed/activated.
*
* @since 1.7.5
*
* @return int|false Unix timestamp. False on failure.
*/
private function get_timestamp_activated() {
return wpforms_get_activated_timestamp();
}
/**
* Retrieve a timestamp when Lite was first installed.
*
* @since 1.7.5
*
* @return int|false Unix timestamp. False on failure.
*/
private function get_timestamp_lite() {
$activated = (array) get_option( 'wpforms_activated', [] );
return ! empty( $activated['lite'] ) ? absint( $activated['lite'] ) : false;
}
/**
* Retrieve a timestamp when Pro was first installed.
*
* @since 1.7.5
*
* @return int|false Unix timestamp. False on failure.
*/
private function get_timestamp_pro() {
$activated = (array) get_option( 'wpforms_activated', [] );
return ! empty( $activated['pro'] ) ? absint( $activated['pro'] ) : false;
}
/**
* Retrieve a timestamp when a first form was created.
*
* @since 1.7.5
*
* @return int|false Unix timestamp. False on failure.
*/
private function get_timestamp_forms_first_created() {
$timestamp = get_option( 'wpforms_forms_first_created' );
return ! empty( $timestamp ) ? absint( $timestamp ) : false;
}
/**
* Retrieve a number of entries.
*
* @since 1.7.5
*
* @return int
*/
private function get_entry_count() {
static $count;
if ( is_int( $count ) ) {
return $count;
}
global $wpdb;
$count = 0;
$entry_handler = wpforms()->get( 'entry' );
$entry_meta_handler = wpforms()->get( 'entry_meta' );
if ( ! $entry_handler || ! $entry_meta_handler ) {
return $count;
}
$query = "SELECT COUNT( $entry_handler->primary_key )
FROM $entry_handler->table_name
WHERE $entry_handler->primary_key
NOT IN (
SELECT entry_id
FROM $entry_meta_handler->table_name
WHERE type = 'backup_id'
);";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$count = (int) $wpdb->get_var( $query );
return $count;
}
/**
* Retrieve forms.
*
* @since 1.7.5
*
* @param int $posts_per_page Number of form to return.
*
* @return array
*/
private function get_forms( $posts_per_page ) {
$forms = wpforms()->get( 'form' )->get(
'',
[
'posts_per_page' => (int) $posts_per_page,
'nopaging' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'cap' => false,
]
);
return ! empty( $forms ) ? (array) $forms : [];
}
/**
* Determine if the user has at least 1 form.
*
* @since 1.7.5
*
* @return bool
*/
private function has_form() {
return ! empty( $this->get_forms( 1 ) );
}
/**
* Determine if it is a new user.
*
* @since 1.7.5
*
* @return bool
*/
private function is_new_user() {
// Check if this is an update or first install.
return ! get_option( 'wpforms_version_upgraded_from' );
}
/**
* Determine if it's an English site.
*
* @since 1.7.5
*
* @return bool
*/
private function is_english_site() {
static $result;
if ( is_bool( $result ) ) {
return $result;
}
$locales = array_unique(
array_map(
[ $this, 'language_to_iso' ],
[ get_locale(), get_user_locale() ]
)
);
$result = count( $locales ) === 1 && $locales[0] === 'en';
return $result;
}
/**
* Convert language to ISO.
*
* @since 1.7.5
*
* @param string $lang Language value.
*
* @return string
*/
private function language_to_iso( $lang ) {
return $lang === '' ? $lang : explode( '_', $lang )[0];
}
/**
* Retrieve a modified URL query string.
*
* @since 1.7.5
*
* @param array $args An associative array of query variables.
* @param string $url A URL to act upon.
*
* @return string
*/
private function add_query_arg( $args, $url ) {
return add_query_arg(
array_merge( $this->get_utm_params(), array_map( 'rawurlencode', $args ) ),
$url
);
}
/**
* Retrieve UTM parameters for Event Driven notifications links.
*
* @since 1.7.5
*
* @return array
*/
private function get_utm_params() {
static $utm_params;
if ( ! $utm_params ) {
$utm_params = [
'utm_source' => self::UTM_PARAMS['utm_source'],
'utm_medium' => rawurlencode( self::UTM_PARAMS['utm_medium'] ),
'utm_campaign' => wpforms()->is_pro() ? 'plugin' : 'liteplugin',
];
}
return $utm_params;
}
/**
* Retrieve Event Driven notifications.
*
* @since 1.7.5
*
* @return array
*/
private function get_notifications() {
return [
'welcome-message' => [
'id' => 'welcome-message',
'title' => esc_html__( 'Welcome to WPForms!', 'wpforms-lite' ),
'content' => sprintf( /* translators: %s - number of templates. */
esc_html__( 'Were grateful that you chose WPForms for your website! Now that youve installed the plugin, youre less than 5 minutes away from publishing your first form. To make it easy, weve got %s form templates to get you started!', 'wpforms-lite' ),
'1100+'
),
'btns' => [
'main' => [
'url' => admin_url( 'admin.php?page=wpforms-builder' ),
'text' => esc_html__( 'Start Building', 'wpforms-lite' ),
],
'alt' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Welcome Read the Guide' ],
'https://wpforms.com/docs/creating-first-form/'
),
'text' => esc_html__( 'Read the Guide', 'wpforms-lite' ),
],
],
'type' => [
'lite',
'basic',
'plus',
'pro',
'agency',
'elite',
'ultimate',
],
// Immediately after activation (new users only, not upgrades).
'condition' => $this->is_new_user(),
],
'wp-mail-smtp-education' => [
'id' => 'wp-mail-smtp-education',
'title' => esc_html__( 'Dont Miss Your Form Notification Emails!', 'wpforms-lite' ),
'content' => esc_html__( 'Did you know that many WordPress sites are not properly configured to send emails? With the free WP Mail SMTP plugin, you can easily optimize your site to send emails, avoid the spam folder, and make sure your emails land in the recipients inbox every time.', 'wpforms-lite' ),
'btns' => [
'main' => [
'url' => admin_url( 'admin.php?page=wpforms-smtp' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
'alt' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'WP Mail SMTP Learn More' ],
'https://wpforms.com/docs/how-to-set-up-smtp-using-the-wp-mail-smtp-plugin/'
),
'text' => esc_html__( 'Learn More', 'wpforms-lite' ),
],
],
// 3 days after activation/upgrade.
'offset' => 3 * DAY_IN_SECONDS,
'condition' => ! function_exists( 'wp_mail_smtp' ),
],
'join-vip-circle' => [
'id' => 'join-vip-circle',
'title' => esc_html__( 'Want to Be a VIP? Join Now!', 'wpforms-lite' ),
'content' => esc_html__( 'Running a WordPress site can be challenging. But help is just around the corner! Our Facebook group contains tons of tips and help to get your business growing! When you join our VIP Circle, youll get instant access to tips, tricks, and answers from a community of loyal WPForms users. Best of all, membership is 100% free!', 'wpforms-lite' ),
'btns' => [
'main' => [
'url' => 'https://www.facebook.com/groups/wpformsvip/',
'text' => esc_html__( 'Join Now', 'wpforms-lite' ),
],
],
// 30 days after activation/upgrade.
'offset' => 30 * DAY_IN_SECONDS,
],
'survey-reports' => [
'id' => 'survey-reports',
'title' => esc_html__( 'Want to Know What Your Customers Really Think?', 'wpforms-lite' ),
'content' => esc_html__( 'Nothing beats real feedback from your customers and visitors. Thats why many small businesses love our awesome Surveys and Polls addon. Instantly unlock full survey reporting right in your WordPress dashboard. And dont forget: building a survey is easy with our pre-made templates, so you could get started within a few minutes!', 'wpforms-lite' ),
'btns' => [
'main' => [
'license' => [
'lite' => [
'url' => $this->add_query_arg(
[
'utm_content' => 'Surveys and Polls Upgrade Lite',
'utm_locale' => wpforms_sanitize_key( get_locale() ),
],
'https://wpforms.com/lite-upgrade/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'basic' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Surveys and Polls Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'plus' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Surveys and Polls Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'pro' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
'elite' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
],
],
'alt' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Surveys and Polls Learn More' ],
'https://wpforms.com/docs/how-to-install-and-use-the-surveys-and-polls-addon/'
),
'text' => esc_html__( 'Learn More', 'wpforms-lite' ),
],
],
// 45 days after activation/upgrade.
'offset' => 45 * DAY_IN_SECONDS,
'condition' => ! defined( 'WPFORMS_SURVEYS_POLLS_VERSION' ),
],
'form-abandonment' => [
'id' => 'form-abandonment',
'title' => esc_html__( 'Get More Leads From Your Forms!', 'wpforms-lite' ),
'content' => esc_html__( 'Are your forms converting fewer visitors than you hoped? Often, visitors quit forms partway through. That can prevent you from getting all the leads you deserve to capture. With our Form Abandonment addon, you can capture partial entries even if your visitor didnt hit Submit! From there, its easy to follow up with leads and turn them into loyal customers.', 'wpforms-lite' ),
'btns' => [
'main' => [
'license' => [
'lite' => [
'url' => $this->add_query_arg(
[
'utm_content' => 'Form Abandonment Upgrade Lite',
'utm_locale' => wpforms_sanitize_key( get_locale() ),
],
'https://wpforms.com/lite-upgrade/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'basic' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Form Abandonment Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'plus' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Form Abandonment Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'pro' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
'elite' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
],
],
'alt' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'Form Abandonment Learn More' ],
'https://wpforms.com/docs/how-to-install-and-use-form-abandonment-with-wpforms/'
),
'text' => esc_html__( 'Learn More', 'wpforms-lite' ),
],
],
// 60 days after activation/upgrade.
'offset' => 60 * DAY_IN_SECONDS,
'condition' => ! defined( 'WPFORMS_FORM_ABANDONMENT_VERSION' ),
],
'ideas' => [
'id' => 'ideas',
'title' => esc_html__( 'Whats Your Dream WPForms Feature?', 'wpforms-lite' ),
'content' => esc_html__( 'If you could add just one feature to WPForms, what would it be? We want to know! Our team is busy surveying valued customers like you as we plan the year ahead. Wed love to know which features would take your business to the next level! Do you have a second to share your idea with us?', 'wpforms-lite' ),
'btns' => [
'main' => [
'url' => 'https://wpforms.com/share-your-idea/',
'text' => esc_html__( 'Share Your Idea', 'wpforms-lite' ),
],
],
// 120 days after activation/upgrade.
'offset' => 120 * DAY_IN_SECONDS,
'condition' => $this->has_form(),
],
'user-insights' => [
'id' => 'user-insights',
'title' => esc_html__( 'Congratulations! You Just Got Your 100th Form Entry!', 'wpforms-lite' ),
'content' => esc_html__( 'You just hit 100 entries&hellip; and this is just the beginning! Now its time to dig into the data and figure out what makes your visitors tick. The User Journey addon shows you what your visitors looked at before submitting your form. Now you can easily find which areas of your site are triggering form conversions.', 'wpforms-lite' ),
'btns' => [
'main' => [
'license' => [
'lite' => [
'url' => $this->add_query_arg(
[
'utm_content' => 'User Journey Upgrade Lite',
'utm_locale' => wpforms_sanitize_key( get_locale() ),
],
'https://wpforms.com/lite-upgrade/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'basic' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'User Journey Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'plus' => [
'url' => $this->add_query_arg(
[ 'utm_content' => 'User Journey Upgrade Basic' ],
'https://wpforms.com/account/licenses/'
),
'text' => esc_html__( 'Upgrade Now', 'wpforms-lite' ),
],
'pro' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
'elite' => [
'url' => admin_url( 'admin.php?page=wpforms-addons' ),
'text' => esc_html__( 'Install Now', 'wpforms-lite' ),
],
],
],
],
'condition' => ! defined( 'WPFORMS_USER_JOURNEY_VERSION' ) && $this->get_entry_count() >= 100,
],
];
}
}

View File

@@ -0,0 +1,780 @@
<?php
namespace WPForms\Admin\Notifications;
/**
* Notifications.
*
* @since 1.7.5
*/
class Notifications {
/**
* Source of notifications content.
*
* @since 1.7.5
*
* @var string
*/
const SOURCE_URL = 'https://plugin.wpforms.com/wp-content/notifications.json';
/**
* Array of license types, that are considered being Elite level.
*
* @since 1.7.5
*
* @var array
*/
const LICENSES_ELITE = [ 'agency', 'ultimate', 'elite' ];
/**
* Option value.
*
* @since 1.7.5
*
* @var bool|array
*/
public $option = false;
/**
* Current license type.
*
* @since 1.7.5
*
* @var string
*/
private $license_type;
/**
* Initialize class.
*
* @since 1.7.5
*/
public function init() {
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.7.5
*/
public function hooks() {
add_action( 'wpforms_admin_notifications_update', [ $this, 'update' ] );
if ( ! wpforms_is_admin_ajax() && ! is_admin() ) {
return;
}
add_action( 'wpforms_overview_enqueue', [ $this, 'enqueues' ] );
add_action( 'wpforms_admin_overview_before_table', [ $this, 'output' ] );
add_action( 'deactivate_plugin', [ $this, 'delete' ], 10, 2 );
add_action( 'wp_ajax_wpforms_notification_dismiss', [ $this, 'dismiss' ] );
}
/**
* Check if user has access and is enabled.
*
* @since 1.7.5
* @since 1.8.2 Added AS task support.
*
* @return bool
*/
public function has_access() {
$has_access = ! wpforms_setting( 'hide-announcements' );
if ( ! wp_doing_cron() && ! wpforms_doing_wp_cli() ) {
$has_access = $has_access && wpforms_current_user_can( 'view_forms' );
}
/**
* Allow modifying state if a user has access.
*
* @since 1.6.0
*
* @param bool $access True if user has access.
*/
return (bool) apply_filters( 'wpforms_admin_notifications_has_access', $has_access );
}
/**
* Get option value.
*
* @since 1.7.5
*
* @param bool $cache Reference property cache if available.
*
* @return array
*/
public function get_option( $cache = true ) {
if ( $this->option && $cache ) {
return $this->option;
}
$option = (array) get_option( 'wpforms_notifications', [] );
$this->option = [
'update' => ! empty( $option['update'] ) ? (int) $option['update'] : 0,
'feed' => ! empty( $option['feed'] ) ? (array) $option['feed'] : [],
'events' => ! empty( $option['events'] ) ? (array) $option['events'] : [],
'dismissed' => ! empty( $option['dismissed'] ) ? (array) $option['dismissed'] : [],
];
return $this->option;
}
/**
* Fetch notifications from feed.
*
* @since 1.7.5
*
* @return array
*/
public function fetch_feed() {
$response = wp_remote_get(
self::SOURCE_URL,
[
'timeout' => 10,
'user-agent' => wpforms_get_default_user_agent(),
]
);
if ( is_wp_error( $response ) ) {
return [];
}
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return [];
}
return $this->verify( json_decode( $body, true ) );
}
/**
* Verify notification data before it is saved.
*
* @since 1.7.5
*
* @param array $notifications Array of notifications items to verify.
*
* @return array
*/
public function verify( $notifications ) {
$data = [];
if ( ! is_array( $notifications ) || empty( $notifications ) ) {
return $data;
}
foreach ( $notifications as $notification ) {
// Ignore if one of the conditional checks is true:
//
// 1. notification message is empty.
// 2. license type does not match.
// 3. notification is expired.
// 4. notification has already been dismissed.
// 5. notification existed before installing WPForms.
// (Prevents bombarding the user with notifications after activation).
if (
empty( $notification['content'] ) ||
! $this->is_license_type_match( $notification ) ||
$this->is_expired( $notification ) ||
$this->is_dismissed( $notification ) ||
$this->is_existed( $notification )
) {
continue;
}
$data[] = $notification;
}
return $data;
}
/**
* Verify saved notification data for active notifications.
*
* @since 1.7.5
*
* @param array $notifications Array of notifications items to verify.
*
* @return array
*/
public function verify_active( $notifications ) {
if ( ! is_array( $notifications ) || empty( $notifications ) ) {
return [];
}
$current_timestamp = time();
// Remove notifications that are not active.
foreach ( $notifications as $key => $notification ) {
if (
( ! empty( $notification['start'] ) && $current_timestamp < strtotime( $notification['start'] ) ) ||
( ! empty( $notification['end'] ) && $current_timestamp > strtotime( $notification['end'] ) )
) {
unset( $notifications[ $key ] );
}
}
return $notifications;
}
/**
* Get notification data.
*
* @since 1.7.5
*
* @return array
*/
public function get() {
if ( ! $this->has_access() ) {
return [];
}
$option = $this->get_option();
// Update notifications using async task.
if ( empty( $option['update'] ) || time() > $option['update'] + DAY_IN_SECONDS ) {
$tasks = wpforms()->get( 'tasks' );
if ( ! $tasks->is_scheduled( 'wpforms_admin_notifications_update' ) !== false ) {
$tasks
->create( 'wpforms_admin_notifications_update' )
->async()
->params()
->register();
}
}
$feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : [];
$events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : [];
return array_merge( $feed, $events );
}
/**
* Get notification count.
*
* @since 1.7.5
*
* @return int
*/
public function get_count() {
return count( $this->get() );
}
/**
* Add a new Event Driven notification.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*/
public function add( $notification ) {
if ( ! $this->is_valid( $notification ) ) {
return;
}
$option = $this->get_option();
// Notification ID already exists.
if ( ! empty( $option['events'][ $notification['id'] ] ) ) {
return;
}
update_option(
'wpforms_notifications',
[
'update' => $option['update'],
'feed' => $option['feed'],
'events' => array_merge( $notification, $option['events'] ),
'dismissed' => $option['dismissed'],
]
);
}
/**
* Determine if notification data is valid.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return bool
*/
public function is_valid( $notification ) {
if ( empty( $notification['id'] ) ) {
return false;
}
return ! empty( $this->verify( [ $notification ] ) );
}
/**
* Determine if notification has already been dismissed.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return bool
*/
private function is_dismissed( $notification ) {
$option = $this->get_option();
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
return ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] );
}
/**
* Determine if license type is match.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return bool
*/
private function is_license_type_match( $notification ) {
// A specific license type is not required.
if ( empty( $notification['type'] ) ) {
return true;
}
return in_array( $this->get_license_type(), (array) $notification['type'], true );
}
/**
* Determine if notification is expired.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return bool
*/
private function is_expired( $notification ) {
return ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] );
}
/**
* Determine if notification existed before installing WPForms.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return bool
*/
private function is_existed( $notification ) {
$activated = wpforms_get_activated_timestamp();
return ! empty( $activated ) &&
! empty( $notification['start'] ) &&
$activated > strtotime( $notification['start'] );
}
/**
* Update notification data from feed.
*
* @since 1.7.5
* @since 1.7.8 Added `wp_cache_flush()` call when the option has been updated.
* @since 1.8.2 Don't fire the update action when it disabled or was fired recently.
*/
public function update() {
if ( ! $this->has_access() ) {
return;
}
$option = $this->get_option();
// Double-check the last update time to prevent multiple requests.
if ( ! empty( $option['update'] ) && time() < $option['update'] + DAY_IN_SECONDS ) {
return;
}
$data = [
'feed' => $this->fetch_feed(),
'events' => $option['events'],
'dismissed' => $option['dismissed'],
];
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
/**
* Allow changing notification data before it will be updated in database.
*
* @since 1.7.5
*
* @param array $data New notification data.
*/
$data = (array) apply_filters( 'wpforms_admin_notifications_update_data', $data );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
$data['update'] = time();
// Flush the cache after the option has been updated
// for the case when it earlier returns an old value without the new data from DB.
if ( update_option( 'wpforms_notifications', $data ) ) {
wp_cache_flush();
}
}
/**
* Remove notification data from database before a plugin is deactivated.
*
* @since 1.7.5
*
* @param string $plugin Path to the plugin file relative to the plugins directory.
* @param bool $network_deactivating Whether the plugin is deactivated for all sites in the network
* or just the current site. Multisite only. Default false.
*/
public function delete( $plugin, $network_deactivating ) {
$wpforms_plugins = [
'wpforms-lite/wpforms.php',
'wpforms/wpforms.php',
];
if ( ! in_array( $plugin, $wpforms_plugins, true ) ) {
return;
}
delete_option( 'wpforms_notifications' );
}
/**
* Enqueue assets on Form Overview admin page.
*
* @since 1.7.5
*/
public function enqueues() {
if ( ! $this->get_count() ) {
return;
}
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-admin-notifications',
WPFORMS_PLUGIN_URL . "assets/css/admin-notifications{$min}.css",
[ 'wpforms-lity' ],
WPFORMS_VERSION
);
wp_enqueue_script(
'wpforms-admin-notifications',
WPFORMS_PLUGIN_URL . "assets/js/admin-notifications{$min}.js",
[ 'jquery', 'wpforms-lity' ],
WPFORMS_VERSION,
true
);
// Lity.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
[],
WPFORMS_VERSION
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
WPFORMS_VERSION,
true
);
}
/**
* Output notifications on Form Overview admin area.
*
* @since 1.7.5
*/
public function output() {
$notifications = $this->get();
if ( empty( $notifications ) ) {
return;
}
$notifications_html = '';
$current_class = ' current';
$content_allowed_tags = [
'br' => [],
'em' => [],
'strong' => [],
'span' => [
'style' => [],
],
'p' => [
'id' => [],
'class' => [],
],
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
];
foreach ( $notifications as $notification ) {
// Prepare required arguments.
$notification = wp_parse_args(
$notification,
[
'id' => 0,
'title' => '',
'content' => '',
'video' => '',
]
);
$title = $this->get_component_data( $notification['title'] );
$content = $this->get_component_data( $notification['content'] );
if ( ! $title && ! $content ) {
continue;
}
// Notification HTML.
$notifications_html .= sprintf(
'<div class="wpforms-notifications-message%5$s" data-message-id="%4$s">
<h3 class="wpforms-notifications-title">%1$s%6$s</h3>
<div class="wpforms-notifications-content">%2$s</div>
%3$s
</div>',
esc_html( $title ),
wp_kses( wpautop( $content ), $content_allowed_tags ),
$this->get_notification_buttons_html( $notification ),
esc_attr( $notification['id'] ),
esc_attr( $current_class ),
$this->get_video_badge_html( $this->get_component_data( $notification['video'] ) )
);
// Only first notification is current.
$current_class = '';
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/notifications',
[
'notifications' => [
'count' => count( $notifications ),
'html' => $notifications_html,
],
],
true
);
}
/**
* Retrieve notification's buttons HTML.
*
* @since 1.7.5
*
* @param array $notification Notification data.
*
* @return string
*/
private function get_notification_buttons_html( $notification ) {
$html = '';
if ( empty( $notification['btns'] ) || ! is_array( $notification['btns'] ) ) {
return $html;
}
foreach ( $notification['btns'] as $btn_type => $btn ) {
$btn = $this->get_component_data( $btn );
if ( ! $btn ) {
continue;
}
$url = $this->prepare_btn_url( $btn );
$target = ! empty( $btn['target'] ) ? $btn['target'] : '_blank';
$target = ! empty( $url ) && strpos( $url, home_url() ) === 0 ? '_self' : $target;
$html .= sprintf(
'<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>',
esc_url( $url ),
$btn_type === 'main' ? 'primary' : 'secondary',
$target === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '',
! empty( $btn['text'] ) ? esc_html( $btn['text'] ) : ''
);
}
return ! empty( $html ) ? sprintf( '<div class="wpforms-notifications-buttons">%s</div>', $html ) : '';
}
/**
* Retrieve notification's component data by a license type.
*
* @since 1.7.5
*
* @param mixed $data Component data.
*
* @return false|mixed
*/
private function get_component_data( $data ) {
if ( empty( $data['license'] ) ) {
return $data;
}
$license_type = $this->get_license_type();
if ( in_array( $license_type, self::LICENSES_ELITE, true ) ) {
$license_type = 'elite';
}
return ! empty( $data['license'][ $license_type ] ) ? $data['license'][ $license_type ] : false;
}
/**
* Retrieve the current installation license type (always lowercase).
*
* @since 1.7.5
*
* @return string
*/
private function get_license_type() {
if ( $this->license_type ) {
return $this->license_type;
}
$this->license_type = wpforms_get_license_type();
if ( ! $this->license_type ) {
$this->license_type = 'lite';
}
return $this->license_type;
}
/**
* Dismiss notification via AJAX.
*
* @since 1.7.5
*/
public function dismiss() {
// Check for required param, security and access.
if (
empty( $_POST['id'] ) ||
! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ||
! $this->has_access()
) {
wp_send_json_error();
}
$id = sanitize_key( $_POST['id'] );
$type = is_numeric( $id ) ? 'feed' : 'events';
$option = $this->get_option();
$option['dismissed'][] = $id;
$option['dismissed'] = array_unique( $option['dismissed'] );
// Remove notification.
if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) {
foreach ( $option[ $type ] as $key => $notification ) {
if ( (string) $notification['id'] === (string) $id ) {
unset( $option[ $type ][ $key ] );
break;
}
}
}
update_option( 'wpforms_notifications', $option );
wp_send_json_success();
}
/**
* Prepare button URL.
*
* @since 1.7.5
*
* @param array $btn Button data.
*
* @return string
*/
private function prepare_btn_url( $btn ) {
if ( empty( $btn['url'] ) ) {
return '';
}
$replace_tags = [
'{admin_url}' => admin_url(),
'{license_key}' => wpforms_get_license_key(),
];
return str_replace( array_keys( $replace_tags ), array_values( $replace_tags ), $btn['url'] );
}
/**
* Get the notification's video badge HTML.
*
* @since 1.7.5
*
* @param string $video_url Valid video URL.
*
* @return string
*/
private function get_video_badge_html( $video_url ) {
$video_url = wp_http_validate_url( $video_url );
if ( empty( $video_url ) ) {
return '';
}
$data_attr_lity = wp_is_mobile() ? '' : 'data-lity';
return sprintf(
'<a class="wpforms-notifications-badge" href="%1$s" %2$s>
<svg fill="none" viewBox="0 0 15 13" aria-hidden="true">
<path fill="#fff" d="M4 2.5h7v8H4z"/>
<path fill="#D63638" d="M14.2 10.5v-8c0-.4-.2-.8-.5-1.1-.3-.3-.7-.5-1.1-.5H2.2c-.5 0-.8.2-1.1.5-.4.3-.5.7-.5 1.1v8c0 .4.2.8.5 1.1.3.3.6.5 1 .5h10.5c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1Zm-8.8-.8V3.3l4.8 3.2-4.8 3.2Z"/>
</svg>
%3$s
</a>',
esc_url( $video_url ),
esc_attr( $data_attr_lity ),
esc_html__( 'Watch Video', 'wpforms-lite' )
);
}
}

View File

@@ -0,0 +1,577 @@
<?php
namespace WPForms\Admin\Pages;
/**
* Analytics Sub-page.
*
* @since 1.5.7
*/
class Analytics {
/**
* Admin menu page slug.
*
* @since 1.5.7
*
* @var string
*/
const SLUG = 'wpforms-analytics';
/**
* Configuration.
*
* @since 1.5.7
*
* @var array
*/
private $config = [
'lite_plugin' => 'google-analytics-for-wordpress/googleanalytics.php',
'lite_wporg_url' => 'https://wordpress.org/plugins/google-analytics-for-wordpress/',
'lite_download_url' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip',
'pro_plugin' => 'google-analytics-premium/googleanalytics-premium.php',
'forms_addon' => 'monsterinsights-forms/monsterinsights-forms.php',
'mi_forms_addon_page' => 'https://www.monsterinsights.com/addon/forms/?utm_source=wpformsplugin&utm_medium=link&utm_campaign=analytics-page',
'mi_onboarding' => 'admin.php?page=monsterinsights-onboarding',
'mi_addons' => 'admin.php?page=monsterinsights_settings#/addons',
'mi_forms' => 'admin.php?page=monsterinsights_reports#/forms',
];
/**
* Runtime data used for generating page HTML.
*
* @since 1.5.7
*
* @var array
*/
private $output_data = [];
/**
* Constructor.
*
* @since 1.5.7
*/
public function __construct() {
if ( ! wpforms_current_user_can() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.5.7
*/
public function hooks() {
if ( wp_doing_ajax() ) {
add_action( 'wp_ajax_wpforms_analytics_page_check_plugin_status', [ $this, 'ajax_check_plugin_status' ] );
}
// Check what page we are on.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
// Only load if we are actually on the Analytics page.
if ( $page !== self::SLUG ) {
return;
}
add_action( 'admin_init', [ $this, 'redirect_to_mi_forms' ] );
add_filter( 'wpforms_admin_header', '__return_false' );
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
// Hook for addons.
do_action( 'wpforms_admin_pages_analytics_hooks' );
}
/**
* Enqueue JS and CSS files.
*
* @since 1.5.7
*/
public function enqueue_assets() {
$min = wpforms_get_min_suffix();
// Lity.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
null,
'3.0.0'
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
'3.0.0',
true
);
wp_enqueue_script(
'wpforms-admin-page-analytics',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/pages/mi-analytics{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-admin-page-analytics',
'wpforms_pluginlanding',
$this->get_js_strings()
);
}
/**
* JS Strings.
*
* @since 1.5.7
*
* @return array Array of strings.
*/
protected function get_js_strings() {
$error_could_not_install = sprintf(
wp_kses( /* translators: %s - Lite plugin download URL. */
__( 'Could not install the plugin automatically. Please <a href="%s">download</a> it and install it manually.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
],
]
),
esc_url( $this->config['lite_download_url'] )
);
$error_could_not_activate = sprintf(
wp_kses( /* translators: %s - Lite plugin download URL. */
__( 'Could not activate the plugin. Please activate it on the <a href="%s">Plugins page</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
],
]
),
esc_url( admin_url( 'plugins.php' ) )
);
return [
'installing' => esc_html__( 'Installing...', 'wpforms-lite' ),
'activating' => esc_html__( 'Activating...', 'wpforms-lite' ),
'activated' => esc_html__( 'MonsterInsights Installed & Activated', 'wpforms-lite' ),
'install_now' => esc_html__( 'Install Now', 'wpforms-lite' ),
'activate_now' => esc_html__( 'Activate Now', 'wpforms-lite' ),
'download_now' => esc_html__( 'Download Now', 'wpforms-lite' ),
'plugins_page' => esc_html__( 'Go to Plugins page', 'wpforms-lite' ),
'error_could_not_install' => $error_could_not_install,
'error_could_not_activate' => $error_could_not_activate,
'mi_manual_install_url' => $this->config['lite_download_url'],
'mi_manual_activate_url' => admin_url( 'plugins.php' ),
];
}
/**
* Generate and output page HTML.
*
* @since 1.5.7
*/
public function output() {
echo '<div id="wpforms-admin-analytics" class="wrap wpforms-admin-wrap wpforms-admin-plugin-landing">';
$this->output_section_heading();
$this->output_section_screenshot();
$this->output_section_step_install();
$this->output_section_step_setup();
$this->output_section_step_addon();
echo '</div>';
}
/**
* Generate and output heading section HTML.
*
* @since 1.5.7
*/
public function output_section_heading() {
// Heading section.
printf(
'<section class="top">
<img class="img-top" src="%1$s" srcset="%2$s 2x" alt="%3$s"/>
<h1>%4$s</h1>
<p>%5$s</p>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/analytics/wpforms-monsterinsights.png' ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/analytics/wpforms-monsterinsights@2x.png' ),
esc_attr__( 'WPForms ♥ MonsterInsights', 'wpforms-lite' ),
esc_html__( 'The Best Google Analytics Plugin for WordPress', 'wpforms-lite' ),
esc_html__( 'MonsterInsights connects WPForms to Google Analytics, providing a powerful integration with their Forms addon. MonsterInsights is a sister company of WPForms.', 'wpforms-lite' )
);
}
/**
* Generate and output heading section HTML.
*
* @since 1.5.7
*/
protected function output_section_screenshot() {
// Screenshot section.
printf(
'<section class="screenshot">
<div class="cont">
<img src="%1$s" alt="%2$s"/>
<a href="%3$s" class="hover" data-lity></a>
</div>
<ul>
<li>%4$s</li>
<li>%5$s</li>
<li>%6$s</li>
<li>%7$s</li>
</ul>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/analytics/screenshot-tnail.jpg' ),
esc_attr__( 'Analytics screenshot', 'wpforms-lite' ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/analytics/screenshot-full.jpg' ),
esc_html__( 'Track form impressions and conversions.', 'wpforms-lite' ),
esc_html__( 'View form conversion rates from WordPress.', 'wpforms-lite' ),
esc_html__( 'Complete UTM tracking with form entries.', 'wpforms-lite' ),
esc_html__( 'Automatic integration with WPForms.', 'wpforms-lite' )
);
}
/**
* Generate and output step 'Install' section HTML.
*
* @since 1.5.7
*/
protected function output_section_step_install() {
$step = $this->get_data_step_install();
if ( empty( $step ) ) {
return;
}
$button_format = '<button class="button %3$s" data-plugin="%1$s" data-action="%4$s">%2$s</button>';
$button_allowed_html = [
'button' => [
'class' => true,
'data-plugin' => true,
'data-action' => true,
],
];
if (
! $this->output_data['plugin_installed'] &&
! $this->output_data['pro_plugin_installed'] &&
! wpforms_can_install( 'plugin' )
) {
$button_format = '<a class="link" href="%1$s" target="_blank" rel="nofollow noopener">%2$s <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
$button_allowed_html = [
'a' => [
'class' => true,
'href' => true,
'target' => true,
'rel' => true,
],
'span' => [
'class' => true,
'aria-hidden' => true,
],
];
}
$button = sprintf( $button_format, esc_attr( $step['plugin'] ), esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ) );
printf(
'<section class="step step-install">
<aside class="num">
<img src="%1$s" alt="%2$s" />
<i class="loader hidden"></i>
</aside>
<div>
<h2>%3$s</h2>
<p>%4$s</p>
%5$s
</div>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ),
esc_attr__( 'Step 1', 'wpforms-lite' ),
esc_html( $step['heading'] ),
esc_html( $step['description'] ),
wp_kses( $button, $button_allowed_html )
);
}
/**
* Generate and output step 'Setup' section HTML.
*
* @since 1.5.7
*/
protected function output_section_step_setup() {
$step = $this->get_data_step_setup();
if ( empty( $step ) ) {
return;
}
printf(
'<section class="step step-setup %1$s">
<aside class="num">
<img src="%2$s" alt="%3$s" />
<i class="loader hidden"></i>
</aside>
<div>
<h2>%4$s</h2>
<p>%5$s</p>
<button class="button %6$s" data-url="%7$s">%8$s</button>
</div>
</section>',
esc_attr( $step['section_class'] ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ),
esc_attr__( 'Step 2', 'wpforms-lite' ),
esc_html__( 'Setup MonsterInsights', 'wpforms-lite' ),
esc_html__( 'MonsterInsights has an intuitive setup wizard to guide you through the setup process.', 'wpforms-lite' ),
esc_attr( $step['button_class'] ),
esc_url( admin_url( $this->config['mi_onboarding'] ) ),
esc_html( $step['button_text'] )
);
}
/**
* Generate and output step 'Addon' section HTML.
*
* @since 1.5.7
*/
protected function output_section_step_addon() {
$step = $this->get_data_step_addon();
if ( empty( $step ) ) {
return;
}
printf(
'<section class="step step-addon %1$s">
<aside class="num">
<img src="%2$s" alt="%3$s" />
<i class="loader hidden"></i>
</aside>
<div>
<h2>%4$s</h2>
<p>%5$s</p>
<button class="button %6$s" data-url="%7$s">%8$s</button>
</div>
</section>',
esc_attr( $step['section_class'] ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/step-3.svg' ),
esc_attr__( 'Step 3', 'wpforms-lite' ),
esc_html__( 'Get Form Conversion Tracking', 'wpforms-lite' ),
esc_html__( 'With the MonsterInsights Form addon you can easily track your form views, entries, conversion rates, and more.', 'wpforms-lite' ),
esc_attr( $step['button_class'] ),
esc_url( $step['button_url'] ),
esc_html( $step['button_text'] )
);
}
/**
* Step 'Install' data.
*
* @since 1.5.7
*
* @return array Step data.
*/
protected function get_data_step_install() {
$step = [];
$step['heading'] = esc_html__( 'Install & Activate MonsterInsights', 'wpforms-lite' );
$step['description'] = esc_html__( 'Track form impressions and conversions.', 'wpforms-lite' );
$this->output_data['all_plugins'] = get_plugins();
$this->output_data['plugin_installed'] = array_key_exists( $this->config['lite_plugin'], $this->output_data['all_plugins'] );
$this->output_data['plugin_activated'] = false;
$this->output_data['pro_plugin_installed'] = array_key_exists( $this->config['pro_plugin'], $this->output_data['all_plugins'] );
$this->output_data['pro_plugin_activated'] = false;
if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] ) {
$step['icon'] = 'step-1.svg';
$step['button_text'] = esc_html__( 'Install MonsterInsights', 'wpforms-lite' );
$step['button_class'] = 'button-primary';
$step['button_action'] = 'install';
$step['plugin'] = $this->config['lite_download_url'];
if ( ! wpforms_can_install( 'plugin' ) ) {
$step['heading'] = esc_html__( 'MonsterInsights', 'wpforms-lite' );
$step['description'] = '';
$step['button_text'] = esc_html__( 'MonsterInsights on WordPress.org', 'wpforms-lite' );
$step['plugin'] = $this->config['lite_wporg_url'];
}
} else {
$this->output_data['plugin_activated'] = is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] );
$step['icon'] = $this->output_data['plugin_activated'] ? 'step-complete.svg' : 'step-1.svg';
$step['button_text'] = $this->output_data['plugin_activated'] ? esc_html__( 'MonsterInsights Installed & Activated', 'wpforms-lite' ) : esc_html__( 'Activate MonsterInsights', 'wpforms-lite' );
$step['button_class'] = $this->output_data['plugin_activated'] ? 'grey disabled' : 'button-primary';
$step['button_action'] = $this->output_data['plugin_activated'] ? '' : 'activate';
$step['plugin'] = $this->output_data['pro_plugin_installed'] ? $this->config['pro_plugin'] : $this->config['lite_plugin'];
}
return $step;
}
/**
* Step 'Setup' data.
*
* @since 1.5.7
*
* @return array Step data.
* @noinspection PhpUndefinedFunctionInspection
*/
protected function get_data_step_setup() {
$step = [];
$this->output_data['plugin_setup'] = false;
if ( $this->output_data['plugin_activated'] ) {
$this->output_data['plugin_setup'] = function_exists( 'monsterinsights_get_ua' ) && '' !== (string) monsterinsights_get_ua();
}
$step['icon'] = 'step-2.svg';
$step['section_class'] = $this->output_data['plugin_activated'] ? '' : 'grey';
$step['button_text'] = esc_html__( 'Run Setup Wizard', 'wpforms-lite' );
$step['button_class'] = 'grey disabled';
if ( $this->output_data['plugin_setup'] ) {
$step['icon'] = 'step-complete.svg';
$step['section_class'] = '';
$step['button_text'] = esc_html__( 'Setup Complete', 'wpforms-lite' );
} else {
$step['button_class'] = $this->output_data['plugin_activated'] ? 'button-primary' : 'grey disabled';
}
return $step;
}
/**
* Step 'Addon' data.
*
* @since 1.5.7
*
* @return array Step data.
* @noinspection PhpUndefinedFunctionInspection
*/
protected function get_data_step_addon() {
$step = [];
$step['icon'] = 'step-3.svg';
$step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey';
$step['button_text'] = esc_html__( 'Learn More', 'wpforms-lite' );
$step['button_class'] = 'grey disabled';
$step['button_url'] = '';
$plugin_license_level = false;
if ( $this->output_data['plugin_activated'] ) {
$mi = MonsterInsights();
$plugin_license_level = 'lite';
if ( is_object( $mi->license ) && method_exists( $mi->license, 'license_can' ) ) {
$plugin_license_level = $mi->license->license_can( 'plus' ) ? 'lite' : $plugin_license_level;
$plugin_license_level = $mi->license->license_can( 'pro' ) || $mi->license->license_can( 'agency' ) ? 'pro' : $plugin_license_level;
}
}
switch ( $plugin_license_level ) {
case 'lite':
$step['button_url'] = $this->config['mi_forms_addon_page'];
$step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey';
break;
case 'pro':
$addon_installed = array_key_exists( $this->config['forms_addon'], $this->output_data['all_plugins'] );
$step['button_text'] = $addon_installed ? esc_html__( 'Activate Now', 'wpforms-lite' ) : esc_html__( 'Install Now', 'wpforms-lite' );
$step['button_url'] = admin_url( $this->config['mi_addons'] );
$step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey';
break;
}
return $step;
}
/**
* Ajax endpoint. Check plugin setup status.
* Used to properly init step 2 section after completing step 1.
*
* @since 1.5.7
*
* @noinspection PhpUndefinedFunctionInspection
*/
public function ajax_check_plugin_status() {
// Security checks.
if (
! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ||
! wpforms_current_user_can()
) {
wp_send_json_error(
[
'error' => esc_html__( 'You do not have permission.', 'wpforms-lite' ),
]
);
}
$result = [];
if ( ! function_exists( 'MonsterInsights' ) || ! function_exists( 'monsterinsights_get_ua' ) ) {
wp_send_json_error(
[
'error' => esc_html__( 'Plugin unavailable.', 'wpforms-lite' ),
]
);
}
$result['setup_status'] = (int) ( '' !== (string) monsterinsights_get_ua() );
$mi = MonsterInsights();
$result['license_level'] = 'lite';
$result['step3_button_url'] = $this->config['mi_forms_addon_page'];
if ( is_object( $mi->license ) && method_exists( $mi->license, 'license_can' ) ) {
$result['license_level'] = $mi->license->license_can( 'pro' ) || $mi->license->license_can( 'agency' ) ? 'pro' : $result['license_level'];
$result['step3_button_url'] = admin_url( $this->config['mi_addons'] );
}
$result['addon_installed'] = (int) array_key_exists( $this->config['forms_addon'], get_plugins() );
wp_send_json_success( $result );
}
/**
* Redirect to MI forms reporting page.
* We need this function because `is_plugin_active()` available only after `admin_init` action.
*
* @since 1.5.7
*/
public function redirect_to_mi_forms() {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
// Redirect to MI Forms addon if it is activated.
if ( is_plugin_active( $this->config['forms_addon'] ) ) {
wp_safe_redirect( admin_url( $this->config['mi_forms'] ) );
exit;
}
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace WPForms\Admin\Pages;
/**
* Community Sub-page.
*
* @since 1.5.6
*/
class Community {
/**
* Admin menu page slug.
*
* @since 1.5.6
*
* @var string
*/
const SLUG = 'wpforms-community';
/**
* Constructor.
*
* @since 1.5.6
*/
public function __construct() {
if ( \wpforms_current_user_can() ) {
$this->hooks();
}
}
/**
* Hooks.
*
* @since 1.5.6
*/
public function hooks() {
// Check what page we are on.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.CSRF.NonceVerification
// Only load if we are actually on the Community page.
if ( self::SLUG !== $page ) {
return;
}
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
// Hook for addons.
do_action( 'wpforms_admin_community_init' );
}
/**
* Page data.
*
* @since 1.5.6
*/
public function get_blocks_data() {
$type = wpforms()->is_pro() ? 'plugin' : 'liteplugin';
$data = [];
$data['vip_circle'] = [
'title' => esc_html__( 'WPForms VIP Circle Facebook Group', 'wpforms-lite' ),
'description' => esc_html__( 'Powered by the community, for the community. Anything and everything WPForms: Discussions. Questions. Tutorials. Insights and sneak peaks. Also, exclusive giveaways!', 'wpforms-lite' ),
'button_text' => esc_html__( 'Join WPForms VIP Circle', 'wpforms-lite' ),
'button_link' => 'https://www.facebook.com/groups/wpformsvip/',
'cover_bg_color' => '#E4F0F6',
'cover_img' => 'vip-circle.png',
'cover_img2x' => 'vip-circle@2x.png',
];
$data['youtube'] = [
'title' => esc_html__( 'WPForms YouTube Channel', 'wpforms-lite' ),
'description' => esc_html__( 'Take a visual dive into everything WPForms has to offer. From simple contact forms to advanced payment forms and email marketing integrations, our extensive video collection covers it all.', 'wpforms-lite' ),
'button_text' => esc_html__( 'Visit WPForms YouTube Channel', 'wpforms-lite' ),
'button_link' => 'https://www.youtube.com/c/wpformsplugin',
'cover_bg_color' => '#FFE6E6',
'cover_img' => 'youtube.png',
'cover_img2x' => 'youtube@2x.png',
];
$data['dev_docs'] = [
'title' => esc_html__( 'WPForms Developer Documentation', 'wpforms-lite' ),
'description' => esc_html__( 'Customize and extend WPForms with code. Our comprehensive developer resources include tutorials, snippets, and documentation on core actions, filters, functions, and more.', 'wpforms-lite' ),
'button_text' => esc_html__( 'View WPForms Dev Docs', 'wpforms-lite' ),
'button_link' => 'https://wpforms.com/developers/?utm_source=WordPress&amp;utm_medium=Community&amp;utm_campaign=' . esc_attr( $type ) . '&amp;utm_content=Developers',
'cover_bg_color' => '#EBEBEB',
'cover_img' => 'dev-docs.png',
'cover_img2x' => 'dev-docs@2x.png',
];
$data['wpbeginner'] = [
'title' => esc_html__( 'WPBeginner Engage Facebook Group', 'wpforms-lite' ),
'description' => esc_html__( 'Hang out with other WordPress experts and like minded website owners such as yourself! Hosted by WPBeginner, the largest free WordPress site for beginners.', 'wpforms-lite' ),
'button_text' => esc_html__( 'Join WPBeginner Engage', 'wpforms-lite' ),
'button_link' => 'https://www.facebook.com/groups/wpbeginner/',
'cover_bg_color' => '#FCEBDF',
'cover_img' => 'wpbeginner.png',
'cover_img2x' => 'wpbeginner@2x.png',
];
$data['suggest'] = [
'title' => esc_html__( 'Suggest a Feature', 'wpforms-lite' ),
'description' => esc_html__( 'Do you have an idea or suggestion for WPForms? If you have thoughts on features, integrations, addons, or improvements - we want to hear it! We appreciate all feedback and insight from our users.', 'wpforms-lite' ),
'button_text' => esc_html__( 'Suggest a Feature', 'wpforms-lite' ),
'button_link' => 'https://wpforms.com/features/suggest/?utm_source=WordPress&amp;utm_medium=Community&amp;utm_campaign=' . esc_attr( $type ) . '&amp;utm_content=Feature',
'cover_bg_color' => '#FFF9EF',
'cover_img' => 'suggest.png',
'cover_img2x' => 'suggest@2x.png',
];
return $data;
}
/**
* Generate and output page HTML.
*
* @since 1.5.6
*/
public function output() {
?>
<div id="wpforms-admin-community" class="wrap wpforms-admin-wrap">
<h1 class="page-title"><?php esc_html_e( 'Community', 'wpforms-lite' ); ?></h1>
<div class="items">
<?php
$data = $this->get_blocks_data();
foreach ( $data as $item ) {
printf(
'<div class="item">
<a href="%6$s" target="_blank" rel="noopener noreferrer" class="item-cover" style="background-color: %s;" title="%4$s"><img class="item-img" src="%s" srcset="%s 2x" alt="%4$s"/></a>
<h3 class="item-title">%s</h3>
<p class="item-description">%s</p>
<div class="item-footer">
<a class="wpforms-btn button-primary wpforms-btn-blue" href="%s" target="_blank" rel="noopener noreferrer">%s</a>
</div>
</div>',
esc_attr( $item['cover_bg_color'] ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/community/' . $item['cover_img'] ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/community/' . $item['cover_img2x'] ),
esc_html( $item['title'] ),
esc_html( $item['description'] ),
esc_url( $item['button_link'] ),
esc_html( $item['button_text'] )
);
}
?>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace WPForms\Admin\Pages;
/**
* Constant Contact Sub-page.
*
* @since 1.7.3
*/
class ConstantContact {
/**
* Determine if the class is allowed to be loaded.
*
* @since 1.7.3
*/
private function allow_load() {
return wpforms_is_admin_page( 'page', 'constant-contact' );
}
/**
* Initialize class.
*
* @since 1.7.3
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.7.3
*/
private function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_action( 'wpforms_admin_page', [ $this, 'view' ] );
}
/**
* Enqueue JS and CSS files.
*
* @since 1.7.3
*/
public function enqueue_assets() {
// Lity.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
null,
'3.0.0'
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
'3.0.0',
true
);
}
/**
* Page view.
*
* @since 1.7.3
*/
public function view() {
$sign_up_link = get_option( 'wpforms_constant_contact_signup', 'https://constant-contact.evyy.net/c/11535/341874/3411?sharedid=wpforms' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/pages/constant-contact',
[
'sign_up_link' => is_string( $sign_up_link ) ? $sign_up_link : '',
'wpbeginners_guide_link' => 'https://www.wpbeginner.com/beginners-guide/why-you-should-start-building-your-email-list-right-away',
],
true
);
}
}

View File

@@ -0,0 +1,575 @@
<?php
namespace WPForms\Admin\Pages;
/**
* SMTP Sub-page.
*
* @since 1.5.7
*/
class SMTP {
/**
* Admin menu page slug.
*
* @since 1.5.7
*
* @var string
*/
const SLUG = 'wpforms-smtp';
/**
* Configuration.
*
* @since 1.5.7
*
* @var array
*/
private $config = [
'lite_plugin' => 'wp-mail-smtp/wp_mail_smtp.php',
'lite_wporg_url' => 'https://wordpress.org/plugins/wp-mail-smtp/',
'lite_download_url' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
'pro_plugin' => 'wp-mail-smtp-pro/wp_mail_smtp.php',
'smtp_settings_url' => 'admin.php?page=wp-mail-smtp',
'smtp_wizard_url' => 'admin.php?page=wp-mail-smtp-setup-wizard',
];
/**
* Runtime data used for generating page HTML.
*
* @since 1.5.7
*
* @var array
*/
private $output_data = [];
/**
* Constructor.
*
* @since 1.5.7
*/
public function __construct() {
if ( ! wpforms_current_user_can() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.5.7
*/
public function hooks() {
if ( wp_doing_ajax() ) {
add_action( 'wp_ajax_wpforms_smtp_page_check_plugin_status', [ $this, 'ajax_check_plugin_status' ] );
}
// Check what page we are on.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.CSRF.NonceVerification
// Only load if we are actually on the SMTP page.
if ( $page !== self::SLUG ) {
return;
}
add_action( 'admin_init', [ $this, 'redirect_to_smtp_settings' ] );
add_filter( 'wpforms_admin_header', '__return_false' );
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
// Hook for addons.
do_action( 'wpforms_admin_pages_smtp_hooks' );
}
/**
* Enqueue JS and CSS files.
*
* @since 1.5.7
*/
public function enqueue_assets() {
$min = wpforms_get_min_suffix();
// Lity.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
null,
'3.0.0'
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
'3.0.0',
true
);
wp_enqueue_script(
'wpforms-admin-page-smtp',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/pages/smtp{$min}.js",
[ 'jquery' ],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-admin-page-smtp',
'wpforms_pluginlanding',
$this->get_js_strings()
);
}
/**
* JS Strings.
*
* @since 1.5.7
*
* @return array Array of strings.
*/
protected function get_js_strings() {
$error_could_not_install = sprintf(
wp_kses( /* translators: %s - Lite plugin download URL. */
__( 'Could not install the plugin automatically. Please <a href="%s">download</a> it and install it manually.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
],
]
),
esc_url( $this->config['lite_download_url'] )
);
$error_could_not_activate = sprintf(
wp_kses( /* translators: %s - Lite plugin download URL. */
__( 'Could not activate the plugin. Please activate it on the <a href="%s">Plugins page</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => true,
],
]
),
esc_url( admin_url( 'plugins.php' ) )
);
return [
'installing' => esc_html__( 'Installing...', 'wpforms-lite' ),
'activating' => esc_html__( 'Activating...', 'wpforms-lite' ),
'activated' => esc_html__( 'WP Mail SMTP Installed & Activated', 'wpforms-lite' ),
'install_now' => esc_html__( 'Install Now', 'wpforms-lite' ),
'activate_now' => esc_html__( 'Activate Now', 'wpforms-lite' ),
'download_now' => esc_html__( 'Download Now', 'wpforms-lite' ),
'plugins_page' => esc_html__( 'Go to Plugins page', 'wpforms-lite' ),
'error_could_not_install' => $error_could_not_install,
'error_could_not_activate' => $error_could_not_activate,
'manual_install_url' => $this->config['lite_download_url'],
'manual_activate_url' => admin_url( 'plugins.php' ),
'smtp_settings' => esc_html__( 'Go to SMTP settings', 'wpforms-lite' ),
'smtp_wizard' => esc_html__( 'Open Setup Wizard', 'wpforms-lite' ),
'smtp_settings_url' => esc_url( $this->config['smtp_settings_url'] ),
'smtp_wizard_url' => esc_url( $this->config['smtp_wizard_url'] ),
];
}
/**
* Generate and output page HTML.
*
* @since 1.5.7
*/
public function output() {
echo '<div id="wpforms-admin-smtp" class="wrap wpforms-admin-wrap wpforms-admin-plugin-landing">';
$this->output_section_heading();
$this->output_section_screenshot();
$this->output_section_step_install();
$this->output_section_step_setup();
echo '</div>';
}
/**
* Generate and output heading section HTML.
*
* @since 1.5.7
*/
protected function output_section_heading() {
// Heading section.
printf(
'<section class="top">
<img class="img-top" src="%1$s" srcset="%2$s 2x" alt="%3$s"/>
<h1>%4$s</h1>
<p>%5$s</p>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/smtp/wpforms-wpmailsmtp.png' ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/smtp/wpforms-wpmailsmtp@2x.png' ),
esc_attr__( 'WPForms ♥ WP Mail SMTP', 'wpforms-lite' ),
esc_html__( 'Making Email Deliverability Easy for WordPress', 'wpforms-lite' ),
esc_html__( 'WP Mail SMTP fixes deliverability problems with your WordPress emails and form notifications. It\'s built by the same folks behind WPForms.', 'wpforms-lite' )
);
}
/**
* Generate and output screenshot section HTML.
*
* @since 1.5.7
*/
protected function output_section_screenshot() {
// Screenshot section.
printf(
'<section class="screenshot">
<div class="cont">
<img src="%1$s" alt="%2$s"/>
<a href="%3$s" class="hover" data-lity></a>
</div>
<ul>
<li>%4$s</li>
<li>%5$s</li>
<li>%6$s</li>
<li>%7$s</li>
</ul>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/smtp/screenshot-tnail.png?ver=' . WPFORMS_VERSION ),
esc_attr__( 'WP Mail SMTP screenshot', 'wpforms-lite' ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/smtp/screenshot-full.png?ver=' . WPFORMS_VERSION ),
esc_html__( 'Improves email deliverability in WordPress.', 'wpforms-lite' ),
esc_html__( 'Used by 2+ million websites.', 'wpforms-lite' ),
esc_html__( 'Free mailers: SendLayer, SMTP.com, Brevo, Google Workspace / Gmail, Mailgun, Postmark, SendGrid.', 'wpforms-lite' ),
esc_html__( 'Pro mailers: Amazon SES, Microsoft 365 / Outlook.com, Zoho Mail.', 'wpforms-lite' )
);
}
/**
* Generate and output step 'Install' section HTML.
*
* @since 1.5.7
*/
protected function output_section_step_install() {
$step = $this->get_data_step_install();
if ( empty( $step ) ) {
return;
}
$button_format = '<button class="button %3$s" data-plugin="%1$s" data-action="%4$s">%2$s</button>';
$button_allowed_html = [
'button' => [
'class' => true,
'data-plugin' => true,
'data-action' => true,
],
];
if (
! $this->output_data['plugin_installed'] &&
! $this->output_data['pro_plugin_installed'] &&
! wpforms_can_install( 'plugin' )
) {
$button_format = '<a class="link" href="%1$s" target="_blank" rel="nofollow noopener">%2$s <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
$button_allowed_html = [
'a' => [
'class' => true,
'href' => true,
'target' => true,
'rel' => true,
],
'span' => [
'class' => true,
'aria-hidden' => true,
],
];
}
$button = sprintf( $button_format, esc_attr( $step['plugin'] ), esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ) );
printf(
'<section class="step step-install">
<aside class="num">
<img src="%1$s" alt="%2$s" />
<i class="loader hidden"></i>
</aside>
<div>
<h2>%3$s</h2>
<p>%4$s</p>
%5$s
</div>
</section>',
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ),
esc_attr__( 'Step 1', 'wpforms-lite' ),
esc_html( $step['heading'] ),
esc_html( $step['description'] ),
wp_kses( $button, $button_allowed_html )
);
}
/**
* Generate and output step 'Setup' section HTML.
*
* @since 1.5.7
*/
protected function output_section_step_setup() {
$step = $this->get_data_step_setup();
if ( empty( $step ) ) {
return;
}
printf(
'<section class="step step-setup %1$s">
<aside class="num">
<img src="%2$s" alt="%3$s" />
<i class="loader hidden"></i>
</aside>
<div>
<h2>%4$s</h2>
<p>%5$s</p>
<button class="button %6$s" data-url="%7$s">%8$s</button>
</div>
</section>',
esc_attr( $step['section_class'] ),
esc_url( WPFORMS_PLUGIN_URL . 'assets/images/' . $step['icon'] ),
esc_attr__( 'Step 2', 'wpforms-lite' ),
esc_html__( 'Set Up WP Mail SMTP', 'wpforms-lite' ),
esc_html__( 'Select and configure your mailer.', 'wpforms-lite' ),
esc_attr( $step['button_class'] ),
esc_url( admin_url( $this->config['smtp_wizard_url'] ) ),
esc_html( $step['button_text'] )
);
}
/**
* Step 'Install' data.
*
* @since 1.5.7
*
* @return array Step data.
*/
protected function get_data_step_install() {
$step = [];
$step['heading'] = esc_html__( 'Install and Activate WP Mail SMTP', 'wpforms-lite' );
$step['description'] = esc_html__( 'Install WP Mail SMTP from the WordPress.org plugin repository.', 'wpforms-lite' );
$this->output_data['all_plugins'] = get_plugins();
$this->output_data['plugin_installed'] = array_key_exists( $this->config['lite_plugin'], $this->output_data['all_plugins'] );
$this->output_data['pro_plugin_installed'] = array_key_exists( $this->config['pro_plugin'], $this->output_data['all_plugins'] );
$this->output_data['plugin_activated'] = false;
$this->output_data['plugin_setup'] = false;
if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] ) {
$step['icon'] = 'step-1.svg';
$step['button_text'] = esc_html__( 'Install WP Mail SMTP', 'wpforms-lite' );
$step['button_class'] = 'button-primary';
$step['button_action'] = 'install';
$step['plugin'] = $this->config['lite_download_url'];
if ( ! wpforms_can_install( 'plugin' ) ) {
$step['heading'] = esc_html__( 'WP Mail SMTP', 'wpforms-lite' );
$step['description'] = '';
$step['button_text'] = esc_html__( 'WP Mail SMTP on WordPress.org', 'wpforms-lite' );
$step['plugin'] = $this->config['lite_wporg_url'];
}
} else {
$this->output_data['plugin_activated'] = $this->is_smtp_activated();
$this->output_data['plugin_setup'] = $this->is_smtp_configured();
$step['icon'] = $this->output_data['plugin_activated'] ? 'step-complete.svg' : 'step-1.svg';
$step['button_text'] = $this->output_data['plugin_activated'] ? esc_html__( 'WP Mail SMTP Installed & Activated', 'wpforms-lite' ) : esc_html__( 'Activate WP Mail SMTP', 'wpforms-lite' );
$step['button_class'] = $this->output_data['plugin_activated'] ? 'grey disabled' : 'button-primary';
$step['button_action'] = $this->output_data['plugin_activated'] ? '' : 'activate';
$step['plugin'] = $this->output_data['pro_plugin_installed'] ? $this->config['pro_plugin'] : $this->config['lite_plugin'];
}
return $step;
}
/**
* Step 'Setup' data.
*
* @since 1.5.7
*
* @return array Step data.
*/
protected function get_data_step_setup() {
$step = [
'icon' => 'step-2.svg',
];
if ( $this->output_data['plugin_activated'] ) {
$step['section_class'] = '';
$step['button_class'] = 'button-primary';
$step['button_text'] = esc_html__( 'Open Setup Wizard', 'wpforms-lite' );
} else {
$step['section_class'] = 'grey';
$step['button_class'] = 'grey disabled';
$step['button_text'] = esc_html__( 'Start Setup', 'wpforms-lite' );
}
if ( $this->output_data['plugin_setup'] ) {
$step['icon'] = 'step-complete.svg';
$step['button_text'] = esc_html__( 'Go to SMTP settings', 'wpforms-lite' );
}
return $step;
}
/**
* Ajax endpoint. Check plugin setup status.
* Used to properly init step 'Setup' section after completing step 'Install'.
*
* @since 1.5.7
*/
public function ajax_check_plugin_status() {
// Security checks.
if (
! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ||
! wpforms_current_user_can()
) {
wp_send_json_error(
[
'error' => esc_html__( 'You do not have permission.', 'wpforms-lite' ),
]
);
}
$result = [];
if ( ! $this->is_smtp_activated() ) {
wp_send_json_error(
[
'error' => esc_html__( 'Plugin unavailable.', 'wpforms-lite' ),
]
);
}
$result['setup_status'] = (int) $this->is_smtp_configured();
$result['license_level'] = wp_mail_smtp()->get_license_type();
// Prevent redirect to the WP Mail SMTP Setup Wizard on the fresh installs.
// We need this workaround since WP Mail SMTP doesn't check whether the mailer is already configured when redirecting to the Setup Wizard on the first run.
if ( $result['setup_status'] > 0 ) {
update_option( 'wp_mail_smtp_activation_prevent_redirect', true );
}
wp_send_json_success( $result );
}
/**
* Get $phpmailer instance.
*
* @since 1.5.7
* @since 1.6.1.2 Conditionally returns $phpmailer v5 or v6.
*
* @return \PHPMailer|\PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer.
*/
protected function get_phpmailer() {
if ( version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ) ) {
$phpmailer = $this->get_phpmailer_v5();
} else {
$phpmailer = $this->get_phpmailer_v6();
}
return $phpmailer;
}
/**
* Get $phpmailer v5 instance.
*
* @since 1.6.1.2
*
* @return \PHPMailer Instance of PHPMailer.
*/
private function get_phpmailer_v5() {
global $phpmailer;
if ( ! ( $phpmailer instanceof \PHPMailer ) ) {
require_once ABSPATH . WPINC . '/class-phpmailer.php';
require_once ABSPATH . WPINC . '/class-smtp.php';
$phpmailer = new \PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
return $phpmailer;
}
/**
* Get $phpmailer v6 instance.
*
* @since 1.6.1.2
*
* @return \PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer.
*/
private function get_phpmailer_v6() {
global $phpmailer;
if ( ! ( $phpmailer instanceof \PHPMailer\PHPMailer\PHPMailer ) ) {
require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
$phpmailer = new \PHPMailer\PHPMailer\PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
return $phpmailer;
}
/**
* Whether WP Mail SMTP plugin configured or not.
*
* @since 1.5.7
*
* @return bool True if some mailer is selected and configured properly.
*/
protected function is_smtp_configured() {
if ( ! $this->is_smtp_activated() ) {
return false;
}
$phpmailer = $this->get_phpmailer();
$mailer = \WPMailSMTP\Options::init()->get( 'mail', 'mailer' );
return ! empty( $mailer ) &&
$mailer !== 'mail' &&
wp_mail_smtp()->get_providers()->get_mailer( $mailer, $phpmailer )->is_mailer_complete();
}
/**
* Whether WP Mail SMTP plugin active or not.
*
* @since 1.5.7
*
* @return bool True if SMTP plugin is active.
*/
protected function is_smtp_activated() {
return function_exists( 'wp_mail_smtp' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) );
}
/**
* Redirect to SMTP settings page.
*
* @since 1.5.7
*/
public function redirect_to_smtp_settings() {
// Redirect to SMTP plugin if it is activated.
if ( $this->is_smtp_configured() ) {
wp_safe_redirect( admin_url( $this->config['smtp_settings_url'] ) );
exit;
}
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace WPForms\Admin\Pages;
use WPForms\Admin\Traits\FormTemplates;
/**
* Main Templates page class.
*
* @since 1.7.7
*/
class Templates {
use FormTemplates;
/**
* Page slug.
*
* @since 1.7.7
*
* @var string
*/
const SLUG = 'wpforms-templates';
/**
* Initialize class.
*
* @since 1.7.7
*/
public function init() {
if ( ! wpforms_is_admin_page( 'templates' ) ) {
return;
}
$this->addons_obj = wpforms()->get( 'addons' );
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.7.7
*/
private function hooks() {
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
}
/**
* Enqueue assets.
*
* @since 1.7.7
*/
public function enqueues() {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-form-templates',
WPFORMS_PLUGIN_URL . "assets/css/admin/admin-form-templates{$min}.css",
[],
WPFORMS_VERSION
);
wp_enqueue_script(
'wpforms-admin-form-templates',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/pages/form-templates{$min}.js",
[],
WPFORMS_VERSION,
true
);
wp_localize_script(
'wpforms-admin-form-templates',
'wpforms_admin_form_templates',
[
'nonce' => wp_create_nonce( 'wpforms-builder' ),
]
);
}
/**
* Build the output for the Form Templates admin page.
*
* @since 1.7.7
*/
public function output() {
?>
<div id="wpforms-form-templates" class="wrap wpforms-admin-wrap">
<h1 class="page-title"><?php esc_html_e( 'Form Templates', 'wpforms-lite' ); ?></h1>
<div class="wpforms-form-setup-content" >
<div class="wpforms-setup-title">
<?php esc_html_e( 'Get a Head Start With Our Pre-Made Form Templates', 'wpforms-lite' ); ?>
</div>
<p class="wpforms-setup-desc secondary-text">
<?php
printf(
wp_kses( /* translators: %1$s - create template doc link; %2$s - Contact us page link. */
__( 'Choose a template to speed up the process of creating your form. You can also 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>. <br>Have a suggestion for a new template? <a href="%2$s" target="_blank" rel="noopener noreferrer">Wed love to hear it</a>!', 'wpforms-lite' ),
[
'strong' => [],
'br' => [],
'a' => [
'href' => [],
'class' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-create-a-custom-form-template/', 'Form Templates Subpage', 'Create Your Own Template' ) ),
esc_url( wpforms_utm_link( 'https://wpforms.com/form-template-suggestion/', 'Form Templates Subpage', 'Form Template Suggestion' ) )
);
?>
</p>
<?php $this->output_templates_content(); ?>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,299 @@
<?php
namespace WPForms\Admin\Payments;
use WPForms\Admin\Payments\Views\Coupons\Education;
use WPForms\Admin\Payments\Views\Overview\BulkActions;
use WPForms\Admin\Payments\Views\Single;
use WPForms\Admin\Payments\Views\Overview\Page;
use WPForms\Admin\Payments\Views\Overview\Coupon;
use WPForms\Admin\Payments\Views\Overview\Filters;
use WPForms\Admin\Payments\Views\Overview\Search;
/**
* Payments class.
*
* @since 1.8.2
*/
class Payments {
/**
* Payments page slug.
*
* @since 1.8.2
*
* @var string
*/
const SLUG = 'wpforms-payments';
/**
* Available views (pages).
*
* @since 1.8.2
*
* @var array
*/
private $views = [];
/**
* The current page slug.
*
* @since 1.8.2
*
* @var string
*/
private $active_view_slug;
/**
* The current page view.
*
* @since 1.8.2
*
* @var null|\WPForms\Admin\Payments\Views\PaymentsViewsInterface
*/
private $view;
/**
* Initialize class.
*
* @since 1.8.2
*/
public function init() {
if ( ! wpforms_is_admin_page( 'payments' ) ) {
return;
}
$this->update_request_uri();
( new ScreenOptions() )->init();
( new Coupon() )->init();
( new Filters() )->init();
( new Search() )->init();
( new BulkActions() )->init();
$this->hooks();
}
/**
* Initialize the active view.
*
* @since 1.8.2
*/
public function init_view() {
$view_ids = array_keys( $this->get_views() );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->active_view_slug = isset( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'payments';
// If the user tries to load an invalid view - fallback to the first available.
if ( ! in_array( $this->active_view_slug, $view_ids, true ) ) {
$this->active_view_slug = reset( $view_ids );
}
if ( ! isset( $this->views[ $this->active_view_slug ] ) ) {
return;
}
$this->view = $this->views[ $this->active_view_slug ];
$this->view->init();
}
/**
* Get available views.
*
* @since 1.8.2
*
* @return array
*/
private function get_views() {
if ( ! empty( $this->views ) ) {
return $this->views;
}
$views = [
'coupons' => new Education(),
];
/**
* Allow to extend payment views.
*
* @since 1.8.2
*
* @param array $views Array of views where key is slug.
*/
$this->views = (array) apply_filters( 'wpforms_admin_payments_payments_get_views', $views );
$this->views['payments'] = new Page();
$this->views['payment'] = new Single();
// Payments view should be the first one.
$this->views = array_merge( [ 'payments' => $this->views['payments'] ], $this->views );
$this->views = array_filter(
$this->views,
static function ( $view ) {
return $view->current_user_can();
}
);
return $this->views;
}
/**
* Register hooks.
*
* @since 1.8.2
*/
private function hooks() {
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
add_action( 'current_screen', [ $this, 'init_view' ] );
add_filter( 'wpforms_db_payments_payment_add_secondary_where_conditions_args', [ $this, 'modify_secondary_where_conditions_args' ] );
}
/**
* Output the page.
*
* @since 1.8.2
*/
public function output() {
if ( empty( $this->view ) ) {
return;
}
?>
<div id="wpforms-payments" class="wrap wpforms-admin-wrap wpforms-payments-wrap wpforms-payments-wrap-<?php echo esc_attr( $this->active_view_slug ); ?>">
<h1 class="page-title">
<?php esc_html_e( 'Payments', 'wpforms-lite' ); ?>
<?php $this->view->heading(); ?>
</h1>
<?php if ( ! empty( $this->view->get_tab_label() ) ) : ?>
<div class="wpforms-tabs-wrapper">
<?php $this->display_tabs(); ?>
</div>
<?php endif; ?>
<div class="wpforms-admin-content">
<?php $this->view->display(); ?>
</div>
</div>
<?php
}
/**
* Display tabs.
*
* @since 1.8.2.2
*/
private function display_tabs() {
$views = $this->get_views();
// Remove views that should not be displayed.
$views = array_filter(
$views,
static function ( $view ) {
return ! empty( $view->get_tab_label() );
}
);
// If there is only one view - no need to display tabs.
if ( count( $views ) === 1 ) {
return;
}
?>
<nav class="nav-tab-wrapper">
<?php foreach ( $views as $slug => $view ) : ?>
<a href="<?php echo esc_url( $this->get_tab_url( $slug ) ); ?>" class="nav-tab <?php echo $slug === $this->active_view_slug ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $view->get_tab_label() ); ?>
</a>
<?php endforeach; ?>
</nav>
<?php
}
/**
* Get tab URL.
*
* @since 1.8.2.2
*
* @param string $tab Tab slug.
*
* @return string
*/
private function get_tab_url( $tab ) {
return add_query_arg(
[
'page' => self::SLUG,
'view' => $tab,
],
admin_url( 'admin.php' )
);
}
/**
* Modify arguments of secondary where clauses.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return array
*/
public function modify_secondary_where_conditions_args( $args ) {
// Set a current mode.
if ( ! isset( $args['mode'] ) ) {
$args['mode'] = Page::get_mode();
}
return $args;
}
/**
* Update view param in request URI.
*
* Backward compatibility for old URLs.
*
* @since 1.8.4
*/
private function update_request_uri() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( ! isset( $_GET['view'], $_SERVER['REQUEST_URI'] ) ) {
return;
}
$old_new = [
'single' => 'payment',
'overview' => 'payments',
];
if (
! array_key_exists( $_GET['view'], $old_new )
|| in_array( $_GET['view'], $old_new, true )
) {
return;
}
wp_safe_redirect(
str_replace(
'view=' . $_GET['view'],
'view=' . $old_new[ $_GET['view'] ],
esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
)
);
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
exit;
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace WPForms\Admin\Payments;
use WP_Screen;
/**
* Payments screen options.
*
* @since 1.8.2
*/
class ScreenOptions {
/**
* Screen id.
*
* @since 1.8.2
*/
const SCREEN_ID = 'wpforms_page_wpforms-payments';
/**
* Screen option name.
*
* @since 1.8.2
*/
const PER_PAGE = 'wpforms_payments_per_page';
/**
* Screen option name.
*
* @since 1.8.2
*/
const SINGLE = 'wpforms_payments_single';
/**
* Initialize.
*
* @since 1.8.2
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.2
*/
private function hooks() {
// Setup screen options - this needs to run early.
add_action( 'load-wpforms_page_wpforms-payments', [ $this, 'screen_options' ] );
add_filter( 'screen_settings', [ $this, 'single_screen_settings' ], 10, 2 );
add_filter( 'set-screen-option', [ $this, 'screen_options_set' ], 10, 3 );
add_filter( 'set_screen_option_wpforms_payments_per_page', [ $this, 'screen_options_set' ], 10, 3 );
add_filter( 'set_screen_option_wpforms_payments_single', [ $this, 'screen_options_set' ], 10, 3 );
}
/**
* Add per-page screen option to the Payments table.
*
* @since 1.8.2
*/
public function screen_options() {
$screen = get_current_screen();
if ( ! isset( $screen->id ) || $screen->id !== self::SCREEN_ID ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_GET['view'] ) && $_GET['view'] !== 'payments' ) {
return;
}
/**
* Filter the number of payments per page default value.
*
* Notice, the filter will be applied to default value in Screen Options only and still will be able to provide other value.
* If you want to change the number of payments per page, use the `wpforms_payments_per_page` filter.
*
* @since 1.8.2
*
* @param int $per_page Number of payments per page.
*/
$per_page = (int) apply_filters( 'wpforms_admin_payments_screen_options_per_page_default', 20 );
add_screen_option(
'per_page',
[
'label' => esc_html__( 'Number of payments per page:', 'wpforms-lite' ),
'option' => self::PER_PAGE,
'default' => $per_page,
]
);
}
/**
* Returns the screen options markup for the payment single page.
*
* @since 1.8.2
*
* @param string $status The current screen settings.
* @param WP_Screen $args WP_Screen object.
*
* @return string
*/
public function single_screen_settings( $status, $args ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $args->id !== self::SCREEN_ID || empty( $_GET['view'] ) || $_GET['view'] !== 'payment' ) {
return $status;
}
$screen_options = self::get_single_page_options();
$advanced_options = [
'advanced' => __( 'Advanced details', 'wpforms-lite' ),
'log' => __( 'Log', 'wpforms-lite' ),
];
$output = '<fieldset class="metabox-prefs">';
$output .= '<legend>' . esc_html__( 'Additional information', 'wpforms-lite' ) . '</legend>';
$output .= '<div>';
foreach ( $advanced_options as $key => $label ) {
$output .= sprintf(
'<input name="%1$s" type="checkbox" id="%1$s" value="true" %2$s /><label for="%1$s">%3$s</label>',
esc_attr( $key ),
! empty( $screen_options[ $key ] ) ? 'checked="checked"' : '',
esc_html( $label )
);
}
$output .= '</div></fieldset>';
$output .= '<p class="submit">';
$output .= '<input type="hidden" name="wp_screen_options[option]" value="wpforms_payments_single">';
$output .= '<input type="hidden" name="wp_screen_options[value]" value="true">';
$output .= '<input type="submit" name="screen-options-apply" id="screen-options-apply" class="button button-primary" value="' . esc_html__( 'Apply', 'wpforms-lite' ) . '">';
$output .= wp_nonce_field( 'screen-options-nonce', 'screenoptionnonce', false, false );
$output .= '</p>';
return $output;
}
/**
* Get single page screen options.
*
* @since 1.8.2
*
* @return false|mixed
*/
public static function get_single_page_options() {
return get_user_option( self::SINGLE );
}
/**
* Payments table per-page screen option value.
*
* @since 1.8.2
*
* @param mixed $status The value to save instead of the option value.
* @param string $option Screen option name.
* @param mixed $value Screen option value.
*
* @return mixed
*/
public function screen_options_set( $status, $option, $value ) {
if ( $option === self::PER_PAGE ) {
return $value;
}
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( $option === self::SINGLE ) {
return [
'advanced' => isset( $_POST['advanced'] ) && (bool) $_POST['advanced'],
'log' => isset( $_POST['log'] ) && (bool) $_POST['log'],
];
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
return $status;
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace WPForms\Admin\Payments\Views\Coupons;
use WPForms\Admin\Payments\Views\Overview\Helpers;
use WPForms\Admin\Payments\Views\PaymentsViewsInterface;
/**
* Payments Coupons Education class.
*
* @since 1.8.2.2
*/
class Education implements PaymentsViewsInterface {
/**
* Coupons addon data.
*
* @since 1.8.2.2
*
* @var array
*/
private $addon;
/**
* Initialize class.
*
* @since 1.8.2.2
*/
public function init() {
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.8.2.2
*/
private function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Get the page label.
*
* @since 1.8.2.2
*
* @return string
*/
public function get_tab_label() {
return __( 'Coupons', 'wpforms-lite' );
}
/**
* Enqueue scripts.
*
* @since 1.8.2.2
*/
public function enqueue_scripts() {
// Lity - lightbox for images.
wp_enqueue_style(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
null,
'3.0.0'
);
wp_enqueue_script(
'wpforms-lity',
WPFORMS_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
[ 'jquery' ],
'3.0.0',
true
);
}
/**
* Check if the current user has the capability to view the page.
*
* @since 1.8.2.2
*
* @return bool
*/
public function current_user_can() {
if ( ! wpforms_current_user_can() ) {
return false;
}
$this->addon = wpforms()->get( 'addons' )->get_addon( 'coupons' );
if (
empty( $this->addon ) ||
empty( $this->addon['status'] ) ||
empty( $this->addon['action'] )
) {
return false;
}
return true;
}
/**
* Page heading content.
*
* @since 1.8.2.2
*/
public function heading() {
Helpers::get_default_heading();
}
/**
* Page content.
*
* @since 1.8.2.2
*/
public function display() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render( 'education/admin/payments/coupons', $this->addon, true );
}
}

View File

@@ -0,0 +1,546 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use DateTimeImmutable;
// phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement
use wpdb;
use WPForms\Db\Payments\ValueValidator;
use WPForms\Admin\Helpers\Chart as ChartHelper;
use WPForms\Admin\Helpers\Datepicker;
/**
* "Payments" overview page inside the admin, which lists all payments.
* This page will be accessible via "WPForms" → "Payments".
*
* When requested data is sent via Ajax, this class is responsible for exchanging datasets.
*
* @since 1.8.2
*/
class Ajax {
/**
* Database table name.
*
* @since 1.8.2
*
* @var string
*/
private $table_name;
/**
* Temporary storage for the stat cards.
*
* @since 1.8.4
*
* @var array
*/
private $stat_cards;
/**
* Hooks.
*
* @since 1.8.2
*/
public function hooks() {
add_action( 'wp_ajax_wpforms_payments_overview_refresh_chart_dataset_data', [ $this, 'get_chart_dataset_data' ] );
add_action( 'wp_ajax_wpforms_payments_overview_save_chart_preference_settings', [ $this, 'save_chart_preference_settings' ] );
add_filter( 'wpforms_db_payments_payment_add_secondary_where_conditions_args', [ $this, 'modify_secondary_where_conditions_args' ] );
}
/**
* Generate and return the data for our dataset data.
*
* @since 1.8.2
*/
public function get_chart_dataset_data() {
// Verify the nonce.
check_ajax_referer( 'wpforms_payments_overview_nonce' );
$report = ! empty( $_POST['report'] ) ? sanitize_text_field( wp_unslash( $_POST['report'] ) ) : null;
$dates = ! empty( $_POST['dates'] ) ? sanitize_text_field( wp_unslash( $_POST['dates'] ) ) : null;
$fallback = [
'data' => [],
'reports' => [],
];
// If the report type or dates for the timespan are missing, leave early.
if ( ! $report || ! $dates ) {
wp_send_json_error( $fallback );
}
// Validates and creates date objects of given timespan string.
$timespans = Datepicker::process_string_timespan( $dates );
// If the timespan is not validated, leave early.
if ( ! $timespans ) {
wp_send_json_error( $fallback );
}
// Extract start and end timespans in local (site) and UTC timezones.
list( $start_date, $end_date, $utc_start_date, $utc_end_date ) = $timespans;
// Payment table name.
$this->table_name = wpforms()->get( 'payment' )->table_name;
// Get the stat cards.
$this->stat_cards = Chart::stat_cards();
// Get the payments in the given timespan.
$results = $this->get_payments_in_timespan( $utc_start_date, $utc_end_date, $report );
// In case the database's results were empty, leave early.
if ( empty( $results ) ) {
wp_send_json_error( $fallback );
}
// Process the results and return the data.
// The first element of the array is the total number of entries, the second is the data.
list( , $data ) = ChartHelper::process_chart_dataset_data( $results, $start_date, $end_date );
// Sends the JSON response back to the Ajax request, indicating success.
wp_send_json_success(
[
'data' => $data,
'reports' => $this->get_payments_summary_in_timespan( $start_date, $end_date ),
]
);
}
/**
* Save the user's preferred graph style and color scheme.
*
* @since 1.8.2
*/
public function save_chart_preference_settings() {
// Verify the nonce.
check_ajax_referer( 'wpforms_payments_overview_nonce' );
$graph_style = isset( $_POST['graphStyle'] ) ? absint( $_POST['graphStyle'] ) : 2; // Line.
update_user_meta( get_current_user_id(), 'wpforms_dash_widget_graph_style', $graph_style );
exit();
}
/**
* Retrieve and create payment entries from the database within the specified time frame (timespan).
*
* @global wpdb $wpdb Instantiation of the wpdb class.
*
* @since 1.8.2
*
* @param DateTimeImmutable $start_date Start date for the timespan preferably in UTC.
* @param DateTimeImmutable $end_date End date for the timespan preferably in UTC.
* @param string $report Payment summary stat card name. i.e. "total_payments".
*
* @return array
*/
private function get_payments_in_timespan( $start_date, $end_date, $report ) {
// Ensure given timespan dates are in UTC timezone.
list( $utc_start_date, $utc_end_date ) = Datepicker::process_timespan_mysql( [ $start_date, $end_date ] );
// If the time period is not a date object, leave early.
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
return [];
}
// Get the database instance.
global $wpdb;
// SELECT clause to construct the SQL statement.
$column_clause = $this->get_stats_column_clause( $report );
// JOIN clause to construct the SQL statement for metadata.
$join_by_meta = $this->add_join_by_meta( $report );
// WHERE clauses for items query statement.
$where_clause = $this->get_stats_where_clause( $report );
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_results(
$wpdb->prepare(
"SELECT date_created_gmt AS day, $column_clause AS count FROM $this->table_name AS p {$join_by_meta}
WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s GROUP BY day ORDER BY day ASC",
[
$utc_start_date->format( Datepicker::DATETIME_FORMAT ),
$utc_end_date->format( Datepicker::DATETIME_FORMAT ),
]
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
* Fetch and generate payment summary reports from the database.
*
* @global wpdb $wpdb Instantiation of the wpdb class.
*
* @since 1.8.2
*
* @param DateTimeImmutable $start_date Start date for the timespan preferably in UTC.
* @param DateTimeImmutable $end_date End date for the timespan preferably in UTC.
*
* @return array
*/
private function get_payments_summary_in_timespan( $start_date, $end_date ) {
// Ensure given timespan dates are in UTC timezone.
list( $utc_start_date, $utc_end_date ) = Datepicker::process_timespan_mysql( [ $start_date, $end_date ] );
// If the time period is not a date object, leave early.
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
return [];
}
// Get the database instance.
global $wpdb;
list( $clause, $query ) = $this->prepare_sql_summary_reports( $utc_start_date, $utc_end_date );
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$group_by = Chart::ACTIVE_REPORT;
$results = $wpdb->get_row(
"SELECT $clause FROM (SELECT $query) AS results GROUP BY $group_by",
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $this->maybe_format_amounts( $results );
}
/**
* Generate SQL statements to create a derived (virtual) table for the report stat cards.
*
* @global wpdb $wpdb Instantiation of the wpdb class.
*
* @since 1.8.2
*
* @param DateTimeImmutable $start_date Start date for the timespan.
* @param DateTimeImmutable $end_date End date for the timespan.
*
* @return array
*/
private function prepare_sql_summary_reports( $start_date, $end_date ) {
// In case there are no report stat cards defined, leave early.
if ( empty( $this->stat_cards ) ) {
return [ '', '' ];
}
global $wpdb;
$clause = []; // SELECT clause.
$query = []; // Query statement for the derived table.
// Validates and creates date objects for the previous time spans.
$prev_timespans = Datepicker::get_prev_timespan_dates( $start_date, $end_date );
// If the timespan is not validated, leave early.
if ( ! $prev_timespans ) {
return [ '', '' ];
}
list( $prev_start_date, $prev_end_date ) = $prev_timespans;
// Get the default number of decimals for the payment currency.
$current_currency = wpforms_get_currency();
$currency_decimals = wpforms_get_currency_decimals( $current_currency );
// Loop through the reports and create the SQL statements.
foreach ( $this->stat_cards as $report => $attributes ) {
// Skip stat card, if it's not supposed to be displayed or disabled (upsell).
if (
( isset( $attributes['condition'] ) && ! $attributes['condition'] )
|| in_array( 'disabled', $attributes['button_classes'], true )
) {
continue;
}
// Determine whether the number of rows has to be counted.
$has_count = isset( $attributes['has_count'] ) && $attributes['has_count'];
// SELECT clause to construct the SQL statement.
$column_clause = $this->get_stats_column_clause( $report, $has_count );
// JOIN clause to construct the SQL statement for metadata.
$join_by_meta = $this->add_join_by_meta( $report );
// WHERE clauses for items query statement.
$where_clause = $this->get_stats_where_clause( $report );
// Get the current and previous values for the report.
$current_value = "TRUNCATE($report,$currency_decimals)";
$prev_value = "TRUNCATE({$report}_prev,$currency_decimals)";
// Add the current and previous reports to the SELECT clause.
$clause[] = $report;
$clause[] = "ROUND( ( ( $current_value - $prev_value ) / $current_value ) * 100 ) AS {$report}_delta";
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.MissingReplacements
$query[] = $wpdb->prepare(
"(
SELECT $column_clause
FROM $this->table_name AS p
{$join_by_meta}
WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s
) AS $report,
(
SELECT $column_clause
FROM $this->table_name AS p
{$join_by_meta}
WHERE 1=1 $where_clause AND date_created_gmt BETWEEN %s AND %s
) AS {$report}_prev",
[
$start_date->format( Datepicker::DATETIME_FORMAT ),
$end_date->format( Datepicker::DATETIME_FORMAT ),
$prev_start_date->format( Datepicker::DATETIME_FORMAT ),
$prev_end_date->format( Datepicker::DATETIME_FORMAT ),
]
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.MissingReplacements
}
return [
implode( ',', $clause ),
implode( ',', $query ),
];
}
/**
* Helper method to build where clause used to construct the SQL statement.
*
* @since 1.8.2
*
* @param string $report Payment summary stat card name. i.e. "total_payments".
*
* @return string
*/
private function get_stats_where_clause( $report ) {
// Get the default WHERE clause from the Payments database class.
$clause = wpforms()->get( 'payment' )->add_secondary_where_conditions();
// If the report doesn't have any additional funnel arguments, leave early.
if ( ! isset( $this->stat_cards[ $report ]['funnel'] ) ) {
return $clause;
}
// Get the where arguments for the report.
$where_args = (array) $this->stat_cards[ $report ]['funnel'];
// If the where arguments are empty, leave early.
if ( empty( $where_args ) ) {
return $clause;
}
return $this->prepare_sql_where_clause( $where_args, $clause );
}
/**
* Prepare SQL where clause for the given funnel arguments.
*
* @since 1.8.4
*
* @param array $where_args Array of where arguments.
* @param string $clause SQL where clause.
*
* @return string
*/
private function prepare_sql_where_clause( $where_args, $clause ) {
$allowed_funnels = [ 'in', 'not_in' ];
$filtered_where_args = array_filter(
$where_args,
static function ( $key ) use ( $allowed_funnels ) {
return in_array( $key, $allowed_funnels, true );
},
ARRAY_FILTER_USE_KEY
);
// Leave early if the filtered where arguments are empty.
if ( empty( $filtered_where_args ) ) {
return $clause;
}
// Loop through the where arguments and add them to the clause.
foreach ( $filtered_where_args as $operator => $columns ) {
foreach ( $columns as $column => $values ) {
if ( ! is_array( $values ) ) {
continue;
}
// Skip if the value is not valid.
$valid_values = array_filter(
$values,
static function ( $item ) use ( $column ) {
return ValueValidator::is_valid( $item, $column );
}
);
$placeholders = wpforms_wpdb_prepare_in( $valid_values );
$clause .= $operator === 'in' ? " AND {$column} IN ({$placeholders})" : " AND {$column} NOT IN ({$placeholders})";
}
}
return $clause;
}
/**
* Helper method to build column clause used to construct the SQL statement.
*
* @since 1.8.2
*
* @param string $report Stats card chart type (name). i.e. "total_payments".
* @param bool $with_count Whether to concatenate the count to the clause.
*
* @return string
*/
private function get_stats_column_clause( $report, $with_count = false ) {
// Default column clause.
// Count the number of rows as fast as possible.
$default = 'COUNT(*)';
// If the report has a meta key, then count the number of unique rows for the meta table.
if ( isset( $this->stat_cards[ $report ]['meta_key'] ) ) {
$default = 'COUNT(pm.id)';
}
/**
* Filters the column clauses for the stat cards.
*
* @since 1.8.2
*
* @param array $clauses Array of column clauses.
*/
$clauses = (array) apply_filters(
'wpforms_admin_payments_views_overview_ajax_stats_column_clauses',
[
'total_payments' => "FORMAT({$default},0)",
'total_sales' => 'IFNULL(SUM(total_amount),0)',
'total_refunded' => 'IFNULL(SUM(pm.meta_value),0)',
'total_subscription' => 'IFNULL(SUM(total_amount),0)',
'total_renewal_subscription' => 'IFNULL(SUM(total_amount),0)',
'total_coupons' => "FORMAT({$default},0)",
]
);
$clause = isset( $clauses[ $report ] ) ? $clauses[ $report ] : $default;
// Several stat cards might include the count of payment records.
if ( $with_count ) {
$clause = "CONCAT({$clause}, ' (', {$default}, ')')";
}
return $clause;
}
/**
* Add join by meta table.
*
* @since 1.8.4
*
* @param string $report Stats card chart type (name). i.e. "total_payments".
*
* @return string
*/
private function add_join_by_meta( $report ) {
// Leave early if the meta key is empty.
if ( ! isset( $this->stat_cards[ $report ]['meta_key'] ) ) {
return '';
}
// Retrieve the global database instance.
global $wpdb;
// Retrieve the meta table name.
$meta_table_name = wpforms()->get( 'payment_meta' )->table_name;
return $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"LEFT JOIN {$meta_table_name} AS pm ON p.id = pm.payment_id AND pm.meta_key = %s",
$this->stat_cards[ $report ]['meta_key']
);
}
/**
* Modify arguments of secondary where clauses.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return array
*/
public function modify_secondary_where_conditions_args( $args ) {
// Set a current mode.
if ( ! isset( $args['mode'] ) ) {
$args['mode'] = Page::get_mode();
}
return $args;
}
/**
* Maybe format the amounts for the given stat cards.
*
* @since 1.8.4
*
* @param array $results Query results.
*
* @return array
*/
private function maybe_format_amounts( $results ) {
// If the input is empty, leave early.
if ( empty( $results ) ) {
return [];
}
foreach ( $results as $key => $value ) {
// If the given stat card doesn't have a button class, leave early.
// If the given stat card doesn't have a button class of "is-amount," leave early.
if ( ! isset( $this->stat_cards[ $key ]['button_classes'] ) || ! in_array( 'is-amount', $this->stat_cards[ $key ]['button_classes'], true ) ) {
continue;
}
// Format the given amount and split the input by space.
$results[ $key ] = wpforms_format_amount( $value, true );
// If the given stat card doesn't have a count, leave early.
if ( empty( $this->stat_cards[ $key ]['has_count'] ) ) {
continue;
}
// Split the input by space to look for the count.
$input_arr = (array) explode( ' ', $value );
// If the input array doesn't have a second element, leave early.
if ( ! isset( $input_arr[1] ) ) {
continue;
}
// Format the amount with the concatenation of count in parentheses.
// Example: 2185.52000000 (79).
$results[ $key ] = sprintf(
'%s <span>%s</span>',
esc_html( $results[ $key ] ),
esc_html( $input_arr[1] ) // 1: Would be count of the records.
);
}
return $results;
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use WPForms\Admin\Notice;
/**
* Bulk actions on the Payments Overview page.
*
* @since 1.8.2
*/
class BulkActions {
/**
* Allowed actions.
*
* @since 1.8.2
*
* @const array
*/
const ALLOWED_ACTIONS = [
'trash',
'restore',
'delete',
];
/**
* Payments ids.
*
* @since 1.8.2
*
* @var array
*/
private $ids;
/**
* Current action.
*
* @since 1.8.2
*
* @var string
*/
private $action;
/**
* Init.
*
* @since 1.8.2
*/
public function init() {
$this->process();
}
/**
* Get the current action selected from the bulk actions dropdown.
*
* @since 1.8.2
*
* @return string|false The action name or False if no action was selected
*/
private function current_action() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] !== '-1' ) {
return sanitize_key( $_REQUEST['action'] );
}
if ( isset( $_REQUEST['action2'] ) && $_REQUEST['action2'] !== '-1' ) {
return sanitize_key( $_REQUEST['action2'] );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return false;
}
/**
* Process bulk actions.
*
* @since 1.8.2
*/
private function process() {
if ( empty( $_GET['_wpnonce'] ) || empty( $_GET['payment_id'] ) ) {
return;
}
$error = __( 'Are you sure you want to do this?', 'wpforms-lite' );
if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bulk-wpforms_page_wpforms-payments' ) ) {
wp_die( esc_html( $error ) );
}
$this->ids = array_map( 'absint', (array) $_GET['payment_id'] );
$this->action = $this->current_action();
if ( empty( $this->ids ) || ! $this->action || ! $this->is_allowed_action( $this->action ) ) {
wp_die( esc_html( $error ) );
}
$this->process_action();
}
/**
* Process a bulk action.
*
* @since 1.8.2
*/
private function process_action() {
$method = "process_action_{$this->action}";
// Check that we have a method for this action.
if ( ! method_exists( $this, $method ) ) {
return;
}
$processed = 0;
foreach ( $this->ids as $id ) {
$processed = $this->$method( $id ) ? $processed + 1 : $processed;
}
if ( ! $processed ) {
return;
}
$this->display_bulk_action_message( $processed );
}
/**
* Trash the payment.
*
* @since 1.8.2
*
* @param int $id Payment ID to trash.
*
* @return bool
*/
private function process_action_trash( $id ) {
return wpforms()->get( 'payment' )->update( $id, [ 'is_published' => 0 ] );
}
/**
* Restore the payment.
*
* @since 1.8.2
*
* @param int $id Payment ID to restore from trash.
*
* @return bool
*/
private function process_action_restore( $id ) {
return wpforms()->get( 'payment' )->update( $id, [ 'is_published' => 1 ] );
}
/**
* Delete the payment.
*
* @since 1.8.2
*
* @param int $id Payment ID to delete.
*
* @return bool
*/
private function process_action_delete( $id ) {
return wpforms()->get( 'payment' )->delete( $id );
}
/**
* Display a bulk action message.
*
* @since 1.8.2
*
* @param int $count Count of processed payment IDs.
*/
private function display_bulk_action_message( $count ) {
switch ( $this->action ) {
case 'delete':
/* translators: %d - number of deleted payments. */
$message = sprintf( _n( '%d payment was successfully permanently deleted.', '%d payments were successfully permanently deleted.', $count, 'wpforms-lite' ), number_format_i18n( $count ) );
break;
case 'restore':
/* translators: %d - number of restored payments. */
$message = sprintf( _n( '%d payment was successfully restored.', '%d payments were successfully restored.', $count, 'wpforms-lite' ), number_format_i18n( $count ) );
break;
case 'trash':
/* translators: %d - number of trashed payments. */
$message = sprintf( _n( '%d payment was successfully moved to the Trash.', '%d payments were successfully moved to the Trash.', $count, 'wpforms-lite' ), number_format_i18n( $count ) );
break;
default:
$message = '';
}
if ( empty( $message ) ) {
return;
}
Notice::success( $message );
}
/**
* Determine whether the action is allowed.
*
* @since 1.8.2
*
* @param string $action Action name.
*
* @return bool
*/
private function is_allowed_action( $action ) {
return in_array( $action, self::ALLOWED_ACTIONS, true );
}
}

View File

@@ -0,0 +1,294 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use WPForms\Admin\Helpers\Datepicker;
/**
* Payment Overview Chart class.
*
* @since 1.8.2
*/
class Chart {
/**
* Default payments summary report stat card.
*
* @since 1.8.2
*/
const ACTIVE_REPORT = 'total_payments';
/**
* Whether the chart should be displayed.
*
* @since 1.8.2
*
* @return bool
*/
private function allow_load() {
$disallowed_views = [
's', // Search.
'type', // Payment type.
'status', // Payment status.
'gateway', // Payment gateway.
'subscription_status', // Subscription status.
'form_id', // Form ID.
'coupon_id', // Coupon ID.
];
// Avoid displaying the chart when filtering of payment records is performed.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
return array_reduce(
array_keys( $_GET ),
static function ( $carry, $key ) use ( $disallowed_views ) {
if ( ! $carry ) {
return false;
}
return ! in_array( $key, $disallowed_views, true ) || empty( $_GET[ $key ] );
},
true
);
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Display the chart.
*
* @since 1.8.2
*/
public function display() {
// If the chart should not be displayed, leave early.
if ( ! $this->allow_load() ) {
return;
}
// Output HTML elements on the page.
$this->output_top_bar();
$this->output_test_mode_banner();
$this->output_chart();
}
/**
* Handles output of the overview page top-bar.
*
* Includes:
* 1. Heading.
* 2. Datepicker filter.
* 3. Chart theme customization settings.
*
* @since 1.8.2
*/
private function output_top_bar() {
list( $choices, $chosen_filter, $value ) = Datepicker::process_datepicker_choices();
?>
<div class="wpforms-overview-top-bar">
<div class="wpforms-overview-top-bar-heading">
<h2><?php esc_html_e( 'Payments Summary', 'wpforms-lite' ); ?></h2>
</div>
<div class="wpforms-overview-top-bar-filters">
<?php
// Output "Mode Toggle" template.
( new ModeToggle() )->display();
// Output "Datepicker" form template.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/components/datepicker',
[
'id' => 'payments',
'action' => Page::get_url(),
'chosen_filter' => $chosen_filter,
'choices' => $choices,
'value' => $value,
],
true
);
?>
<div class="wpforms-overview-chart-settings">
<?php
// Output "Settings" template.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/dashboard/widget/settings',
array_merge( $this->get_chart_settings(), [ 'enabled' => true ] ),
true
);
?>
</div>
</div>
</div>
<?php
}
/**
* Display a banner when viewing test data.
*
* @since 1.8.2
*
* @return void
*/
private function output_test_mode_banner() {
// Determine if we are viewing test data.
if ( Page::get_mode() !== 'test' ) {
return;
}
?>
<div class="wpforms-payments-viewing-test-mode">
<p>
<?php esc_html_e( 'Viewing Test Data', 'wpforms-lite' ); ?>
</p>
</div>
<?php
}
/**
* Handles output of the overview page chart (graph).
*
* @since 1.8.2
*/
private function output_chart() {
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="wpforms-payments-overview-stats">';
echo wpforms_render(
'admin/components/chart',
[
'id' => 'payments',
'notice' => [
'heading' => esc_html__( 'No payments for selected period', 'wpforms-lite' ),
'description' => esc_html__( 'Please select a different period or check back later.', 'wpforms-lite' ),
],
],
true
);
echo wpforms_render(
'admin/payments/reports',
[
'current' => self::ACTIVE_REPORT,
'statcards' => self::stat_cards(),
],
true
);
echo '</div>';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Get the users preferences for displaying of the graph.
*
* @since 1.8.2
*
* @return array
*/
public function get_chart_settings() {
$graph_style = get_user_meta( get_current_user_id(), 'wpforms_dash_widget_graph_style', true );
return [
'graph_style' => $graph_style ? absint( $graph_style ) : 2, // Line.
];
}
/**
* Get the stat cards for the payment summary report.
*
* Note that "funnel" is used to filter the payments, and can take the following values:
* - in: payments that match the given criteria.
* - not_in: payments that do not match the given criteria.
*
* @since 1.8.2
*
* @return array
*/
public static function stat_cards() {
return [
'total_payments' => [
'label' => esc_html__( 'Total Payments', 'wpforms-lite' ),
'button_classes' => [
'total-payments',
],
],
'total_sales' => [
'label' => esc_html__( 'Total Sales', 'wpforms-lite' ),
'funnel' => [
'not_in' => [
'status' => [ 'failed' ],
'subscription_status' => [ 'failed' ],
],
],
'button_classes' => [
'total-sales',
'is-amount',
],
],
'total_refunded' => [
'label' => esc_html__( 'Total Refunded', 'wpforms-lite' ),
'has_count' => true,
'meta_key' => 'refunded_amount', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'button_classes' => [
'total-refunded',
'is-amount',
],
],
'total_subscription' => [
'label' => esc_html__( 'New Subscriptions', 'wpforms-lite' ),
'condition' => wpforms()->get( 'payment_queries' )->has_subscription(),
'has_count' => true,
'funnel' => [
'in' => [
'type' => [ 'subscription' ],
],
'not_in' => [
'subscription_status' => [ 'failed' ],
],
],
'button_classes' => [
'total-subscription',
'is-amount',
],
],
'total_renewal_subscription' => [
'label' => esc_html__( 'Subscription Renewals', 'wpforms-lite' ),
'condition' => wpforms()->get( 'payment_queries' )->has_subscription(),
'has_count' => true,
'funnel' => [
'in' => [
'type' => [ 'renewal' ],
],
'not_in' => [
'subscription_status' => [ 'failed' ],
],
],
'button_classes' => [
'total-renewal-subscription',
'is-amount',
],
],
'total_coupons' => [
'label' => esc_html__( 'Coupons Redeemed', 'wpforms-lite' ),
'meta_key' => 'coupon_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'funnel' => [
'not_in' => [
'status' => [ 'failed' ],
'subscription_status' => [ 'failed' ],
],
],
'button_classes' => [
'total-coupons',
],
],
];
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use WPForms\Admin\Payments\Payments;
/**
* Generic functionality for interacting with the Coupons data.
*
* @since 1.8.4
*/
class Coupon {
/**
* Initialize the Coupon class.
*
* @since 1.8.4
*/
public function init() {
$this->hooks();
}
/**
* Attach hooks for filtering payments by coupon ID.
*
* @since 1.8.4
*/
private function hooks() {
// This filter has been added for backward compatibility with older versions of the Coupons addon.
add_filter( 'wpforms_admin_payments_views_overview_table_get_columns', [ $this, 'remove_legacy_coupon_column' ], 99, 1 );
// Bail early if the current page is not the Payments page
// or if no coupon ID is given in the URL.
if ( ! self::is_coupon() ) {
return;
}
add_filter( 'wpforms_db_payments_payment_get_payments_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 );
add_filter( 'wpforms_db_payments_queries_count_all_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 );
add_filter( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_after_where', [ $this, 'filter_by_coupon_id' ], 10, 2 );
add_filter( 'wpforms_admin_payments_views_overview_search_inner_join_query', [ $this, 'join_search_by_coupon_id' ], 10, 2 );
}
/**
* Remove the legacy coupon column from the Payments page.
*
* This function has been added for backward compatibility with older versions of the Coupons addon.
* The legacy coupon column is no longer used by the Coupons addon.
*
* @since 1.8.4
*
* @param array $columns List of columns to be displayed on the Payments page.
*
* @return array
*/
public function remove_legacy_coupon_column( $columns ) {
// Bail early if the Coupons addon is not active.
if ( ! $this->is_addon_active() ) {
return $columns;
}
// Remove the legacy coupon column from the Payments page.
unset( $columns['coupon_id'] );
return $columns;
}
/**
* Retrieve payment entries based on a given coupon ID.
*
* @since 1.8.4
*
* @param string $after_where SQL query after the WHERE clause.
* @param array $args Query arguments.
*
* @return string
*/
public function filter_by_coupon_id( $after_where, $args ) {
// Check if the query is for the Payments Overview table.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $args['table_query'] ) ) {
return $after_where;
}
// Retrieve the coupon ID from the URL.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended
$coupon_id = absint( $_GET['coupon_id'] );
global $wpdb;
$table_name = wpforms()->get( 'payment_meta' )->table_name;
// Prepare and return the modified SQL query.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->prepare(
" AND EXISTS (
SELECT 1 FROM {$table_name} AS pm_coupon
WHERE pm_coupon.payment_id = p.id AND pm_coupon.meta_key = 'coupon_id' AND pm_coupon.meta_value = %d
)",
$coupon_id
);
}
/**
* Further filter down the search results by coupon ID.
*
* @since 1.8.4
*
* @param string $query The SQL JOIN clause.
* @param int $n The number of the JOIN clause.
*
* @return string
*/
public function join_search_by_coupon_id( $query, $n ) {
// Retrieve the coupon ID from the URL.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended
$coupon_id = absint( $_GET['coupon_id'] );
// Retrieve the global database instance.
global $wpdb;
$n = absint( $n );
$table_name = wpforms()->get( 'payment_meta' )->table_name;
// Build the derived query using a prepared statement.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$derived_query = $wpdb->prepare(
"RIGHT JOIN (
SELECT payment_id, meta_key, meta_value FROM {$table_name}
WHERE meta_key = 'coupon_id' AND meta_value = %d
) AS pm_coupon{$n} ON p.id = pm_coupon{$n}.payment_id",
$coupon_id
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// Combine the original query and the derived query.
return "$query $derived_query";
}
/**
* Determine if the overview page is being viewed, and coupon ID is given.
*
* @since 1.8.4
*
* @return bool
*/
public static function is_coupon() {
// Check if the URL parameters contain a coupon ID and if the current page is the Payments page.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return ! empty( $_GET['coupon_id'] ) && ! empty( $_GET['page'] ) && $_GET['page'] === Payments::SLUG;
}
/**
* Determine whether the addon is activated.
*
* @since 1.8.4
*
* @return bool
*/
private function is_addon_active() {
return function_exists( 'wpforms_coupons' );
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
/**
* Class for extending SQL queries for filtering payments by multicheckbox fields.
*
* @since 1.8.4
*/
class Filters {
/**
* Initialize the Filters class.
*
* @since 1.8.4
*/
public function init() {
$this->hooks();
}
/**
* Attach hooks for filtering payments by multicheckbox fields.
*
* @since 1.8.4
*/
private function hooks() {
add_filter( 'wpforms_db_payments_payment_get_payments_query_after_where', [ $this, 'add_renewals_by_subscription_id' ], 10, 2 );
add_filter( 'wpforms_db_payments_queries_count_all_query_after_where', [ $this, 'count_renewals_by_subscription_id' ], 10, 2 );
add_filter( 'wpforms_db_payments_queries_count_if_exists_after_where', [ $this, 'exists_renewals_by_subscription_id' ], 10, 2 );
}
/**
* Add renewals to the query.
*
* @since 1.8.4
*
* @param string $after_where SQL query.
* @param array $args Query arguments.
*
* @return string
*/
public function add_renewals_by_subscription_id( $after_where, $args ) {
$query = $this->query_renewals_by_subscription_id( $args );
if ( empty( $query ) ) {
return $after_where; // Return early if $query is empty.
}
return "{$after_where} UNION {$query}";
}
/**
* Add renewals to the count query.
*
* @since 1.8.4
*
* @param string $after_where SQL query.
* @param array $args Query arguments.
*
* @return string
*/
public function count_renewals_by_subscription_id( $after_where, $args ) {
$query = $this->query_renewals_by_subscription_id( $args, 'COUNT(*)' );
if ( empty( $query ) ) {
return $after_where; // Return early if $query is empty.
}
return "{$after_where} UNION ALL {$query}";
}
/**
* Add renewals to the exists query.
*
* @since 1.8.4
*
* @param string $after_where SQL query.
* @param array $args Query arguments.
*
* @return string
*/
public function exists_renewals_by_subscription_id( $after_where, $args ) {
$query = $this->query_renewals_by_subscription_id( $args, '1' );
if ( empty( $query ) ) {
return $after_where; // Return early if $query is empty.
}
return "{$after_where} UNION ALL {$query}";
}
/**
* Query renewals by subscription ID.
*
* @since 1.8.4
*
* @param array $args Query arguments.
* @param string $selector SQL selector.
*
* @return string
*/
private function query_renewals_by_subscription_id( $args, $selector = 'p.*' ) {
// Check if essential arguments are missing.
if ( empty( $args['table_query'] ) || empty( $args['subscription_status'] ) ) {
return '';
}
// Check if the query type is not 'renewal'.
if ( ! empty( $args['type'] ) && ! in_array( 'renewal', explode( '|', $args['type'] ), true ) ) {
return '';
}
$payment_handle = wpforms()->get( 'payment' );
$subscription_statuses = explode( '|', $args['subscription_status'] );
$placeholders = wpforms_wpdb_prepare_in( $subscription_statuses );
// This is needed to avoid the count_all method from adding the WHERE clause for the other types.
$args['type'] = 'renewal';
// Remove the subscription_status argument from the query.
// The primary reason for this is that the subscription_status has to be checked in the subquery.
unset( $args['subscription_status'] );
// Prepare the query.
$query[] = "SELECT {$selector} FROM {$payment_handle->table_name} as p";
/**
* Append custom query parts before the WHERE clause.
*
* This hook allows external code to extend the SQL query by adding custom conditions
* immediately before the WHERE clause.
*
* @since 1.8.4
*
* @param string $where Before the WHERE clause in the database query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_before_where', '', $args );
// Add the WHERE clause.
$query[] = 'WHERE 1=1';
$query[] = $payment_handle->add_columns_where_conditions( $args );
$query[] = $payment_handle->add_secondary_where_conditions( $args );
$query[] = "AND EXISTS (
SELECT 1 FROM {$payment_handle->table_name} as subquery_p
WHERE subquery_p.subscription_id = p.subscription_id
AND subquery_p.subscription_status IN ({$placeholders})
)";
/**
* Append custom query parts after the WHERE clause.
*
* This hook allows external code to extend the SQL query by adding custom conditions
* immediately after the WHERE clause.
*
* @since 1.8.4
*
* @param string $where After the WHERE clause in the database query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_after_where', '', $args );
return implode( ' ', $query );
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use WPForms\Db\Payments\ValueValidator;
/**
* Helper methods for the Overview page.
*
* @since 1.8.2
*/
class Helpers {
/**
* Get subscription description.
*
* @since 1.8.2
*
* @param string $payment_id Payment id.
* @param string $amount Payment amount.
*
* @return string
*/
public static function get_subscription_description( $payment_id, $amount ) {
// Get the subscription period for the payment.
$period = wpforms()->get( 'payment_meta' )->get_single( $payment_id, 'subscription_period' );
$intervals = ValueValidator::get_allowed_subscription_intervals();
// If the subscription period is not set or not allowed, return the amount only.
if ( ! isset( $intervals[ $period ] ) ) {
return $amount;
}
// Use "/" as a separator between the amount and the subscription period.
return $amount . ' / ' . $intervals[ $period ];
}
/**
* Return a placeholder text "N/A" when there is no actual data to display.
*
* @since 1.8.2
*
* @param string $with_wrapper Wrap the text within a span tag for styling purposes. Default: true.
*
* @return string
*/
public static function get_placeholder_na_text( $with_wrapper = true ) {
$text = __( 'N/A', 'wpforms-lite' );
// Check if the text should be wrapped within a span tag.
if ( $with_wrapper ) {
return sprintf( '<span class="payment-placeholder-text-none">%s</span>', $text );
}
return $text;
}
/**
* Get the default heading for the Payments pages.
*
* @since 1.8.2.2
*
* @param string $help_link Help link.
*/
public static function get_default_heading( $help_link = '' ) {
if ( ! $help_link ) {
$help_link = 'https://wpforms.com/docs/viewing-and-managing-payments/';
}
echo '<span class="wpforms-payments-overview-help">';
printf(
'<a href="%s" target="_blank"><i class="fa fa-question-circle-o"></i>%s</a>',
esc_url(
wpforms_utm_link(
$help_link,
'Payments Dashboard',
'Manage Payments Documentation'
)
),
esc_html__( 'Help', 'wpforms-lite' )
);
echo '</span>';
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
/**
* Payments Overview Mode Toggle class.
*
* @since 1.8.2
*/
class ModeToggle {
/**
* Determine if the toggle should be displayed and render it.
*
* @since 1.8.2
*/
public function display() {
// Bail early if no payments are found in test mode.
if ( ! $this->should_display() ) {
return;
}
$this->render();
}
/**
* Look for at least one payment in test mode.
*
* @since 1.8.2
*
* @return bool
*/
private function should_display() {
return wpforms()->get( 'payment' )->get_payments(
[
'mode' => 'test',
'number' => 1,
]
);
}
/**
* Display the toggle button.
*
* @since 1.8.2
*/
private function render() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/payments/mode-toggle',
[
'mode' => Page::get_mode(),
],
true
);
}
}

View File

@@ -0,0 +1,457 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
use WPForms\Admin\Helpers\Datepicker;
use WPForms\Db\Payments\ValueValidator;
use WPForms\Admin\Payments\Payments;
use WPForms\Admin\Payments\Views\PaymentsViewsInterface;
use WPForms\Integrations\Stripe\Helpers as StripeHelpers;
/**
* Payments Overview Page class.
*
* @since 1.8.2
*/
class Page implements PaymentsViewsInterface {
/**
* Payments table.
*
* @since 1.8.2
*
* @var Table
*/
private $table;
/**
* Payments chart.
*
* @since 1.8.2
*
* @var Chart
*/
private $chart;
/**
* Initialize class.
*
* @since 1.8.2
*/
public function init() {
if ( ! $this->has_any_mode_payment() ) {
return;
}
$this->chart = new Chart();
$this->table = new Table();
$this->table->prepare_items();
$this->clean_request_uri();
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.8.2
*/
private function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
}
/**
* Get the tab label.
*
* @since 1.8.2.2
*
* @return string
*/
public function get_tab_label() {
return __( 'Overview', 'wpforms-lite' );
}
/**
* Enqueue scripts and styles.
*
* @since 1.8.2
*/
public function enqueue_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-flatpickr',
WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.css',
[],
'4.6.9'
);
wp_enqueue_script(
'wpforms-flatpickr',
WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.js',
[ 'jquery' ],
'4.6.9',
true
);
wp_enqueue_style(
'wpforms-multiselect-checkboxes',
WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.css',
[],
'1.0.0'
);
wp_enqueue_script(
'wpforms-multiselect-checkboxes',
WPFORMS_PLUGIN_URL . 'assets/lib/wpforms-multiselect/wpforms-multiselect-checkboxes.min.js',
[],
'1.0.0',
true
);
wp_enqueue_script(
'wpforms-chart',
WPFORMS_PLUGIN_URL . 'assets/lib/chart.min.js',
[ 'moment' ],
'2.9.4',
true
);
wp_enqueue_script(
'wpforms-admin-payments-overview',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/payments/overview{$min}.js",
[ 'jquery', 'wpforms-flatpickr', 'wpforms-chart' ],
WPFORMS_VERSION,
true
);
$admin_l10n = [
'settings' => $this->chart->get_chart_settings(),
'locale' => sanitize_key( wpforms_get_language_code() ),
'nonce' => wp_create_nonce( 'wpforms_payments_overview_nonce' ),
'date_format' => sanitize_text_field( Datepicker::get_wp_date_format_for_momentjs() ),
'delimiter' => Datepicker::TIMESPAN_DELIMITER,
'report' => Chart::ACTIVE_REPORT,
'currency' => sanitize_text_field( wpforms_get_currency() ),
'decimals' => absint( wpforms_get_currency_decimals( wpforms_get_currency() ) ),
'i18n' => [
'label' => esc_html__( 'Payments', 'wpforms-lite' ),
'delete_button' => esc_html__( 'Delete', 'wpforms-lite' ),
'subscription_delete_confirm' => $this->get_subscription_delete_confirmation_message(),
'no_dataset' => [
'total_payments' => esc_html__( 'No payments for selected period', 'wpforms-lite' ),
'total_sales' => esc_html__( 'No sales for selected period', 'wpforms-lite' ),
'total_refunded' => esc_html__( 'No refunds for selected period', 'wpforms-lite' ),
'total_subscription' => esc_html__( 'No new subscriptions for selected period', 'wpforms-lite' ),
'total_renewal_subscription' => esc_html__( 'No subscription renewals for the selected period', 'wpforms-lite' ),
'total_coupons' => esc_html__( 'No coupons applied during the selected period', 'wpforms-lite' ),
],
],
'page_uri' => $this->get_current_uri(),
];
wp_localize_script(
'wpforms-admin-payments-overview', // Script handle the data will be attached to.
'wpforms_admin_payments_overview', // Name for the JavaScript object.
$admin_l10n
);
}
/**
* Retrieve a Payment Overview URI.
*
* @since 1.8.2
*
* @return string
*/
private function get_current_uri() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$query = $_GET;
unset( $query['mode'], $query['paged'] );
return add_query_arg( $query, self::get_url() );
}
/**
* Determine whether the current user has the capability to view the page.
*
* @since 1.8.2
*
* @return bool
*/
public function current_user_can() {
return wpforms_current_user_can();
}
/**
* Page heading.
*
* @since 1.8.2
*/
public function heading() {
Helpers::get_default_heading();
}
/**
* Page content.
*
* @since 1.8.2
*/
public function display() {
// If there are no payments at all, display an empty state.
if ( ! $this->has_any_mode_payment() ) {
$this->display_empty_state();
return;
}
// Display the page content, including the chart and the table.
$this->chart->display();
$this->table->display();
}
/**
* Get the URL of the page.
*
* @since 1.8.2
*
* @return string
*/
public static function get_url() {
static $url;
if ( $url ) {
return $url;
}
$url = add_query_arg(
[
'page' => Payments::SLUG,
],
admin_url( 'admin.php' )
);
return $url;
}
/**
* Get payment mode.
*
* Use only for logged-in users. Returns mode from user meta data or from the $_GET['mode'] parameter.
*
* @since 1.8.2
*
* @return string
*/
public static function get_mode() {
static $mode;
$default_mode = 'live';
if ( ! wpforms_is_admin_ajax() && ! wpforms_is_admin_page( 'payments' ) && ! wpforms_is_admin_page( 'entries' ) ) {
return $default_mode;
}
if ( $mode ) {
return $mode;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$mode = isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : '';
$user_id = get_current_user_id();
$meta_key = 'wpforms-payments-mode';
if ( ValueValidator::is_valid( $mode, 'mode' ) ) {
update_user_meta( $user_id, $meta_key, $mode );
return $mode;
}
$mode = get_user_meta( $user_id, $meta_key, true );
return ! empty( $mode ) ? $mode : $default_mode;
}
/**
* Display one of the empty states.
*
* @since 1.8.2
*/
private function display_empty_state() {
// If a payment gateway is configured, output no payments state.
if ( $this->is_gateway_configured() ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/empty-states/payments/no-payments',
[
'cta_url' => add_query_arg(
[
'page' => 'wpforms-overview',
],
'admin.php'
),
],
true
);
return;
}
// Otherwise, output get started state.
$is_upgraded = StripeHelpers::is_allowed_license_type();
$message = __( "First you need to set up a payment gateway. We've partnered with <strong>Stripe</strong> to bring easy payment forms to everyone.&nbsp;", 'wpforms-lite' );
$message .= $is_upgraded
? sprintf( /* translators: %s - WPForms Addons admin page URL. */
__( 'Other payment gateways such as <strong>PayPal</strong> and <strong>Square</strong> can be installed from the <a href="%s">Addons screen</a>.', 'wpforms-lite' ),
esc_url(
add_query_arg(
[
'page' => 'wpforms-addons',
],
admin_url( 'admin.php' )
)
)
)
: sprintf( /* translators: %s - WPForms.com Upgrade page URL. */
__( "If you'd like to use another payment gateway, please consider <a href='%s'>upgrading to WPForms Pro</a>.", 'wpforms-lite' ),
esc_url( wpforms_admin_upgrade_link( 'Payments Dashboard', 'Splash - Upgrade to Pro Text' ) )
);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/empty-states/payments/get-started',
[
'message' => $message,
'version' => $is_upgraded ? 'pro' : 'lite',
'cta_url' => add_query_arg(
[
'page' => 'wpforms-settings',
'view' => 'payments',
],
admin_url( 'admin.php' )
),
],
true
);
}
/**
* Determine whether a payment gateway is configured.
*
* @since 1.8.2
*
* @return bool
*/
private function is_gateway_configured() {
/**
* Allow to modify a status whether a payment gateway is configured.
*
* @since 1.8.2
*
* @param bool $is_configured True if a payment gateway is configured.
*/
return (bool) apply_filters( 'wpforms_admin_payments_views_overview_page_gateway_is_configured', StripeHelpers::has_stripe_keys() );
}
/**
* Determine whether there are payments of any modes.
*
* @since 1.8.2
*
* @return bool
*/
private function has_any_mode_payment() {
static $has_any_mode_payment;
if ( $has_any_mode_payment !== null ) {
return $has_any_mode_payment;
}
$has_any_mode_payment = count(
wpforms()->get( 'payment' )->get_payments(
[
'mode' => 'any',
'number' => 1,
]
)
) > 0;
// Check on trashed payments.
if ( ! $has_any_mode_payment ) {
$has_any_mode_payment = count(
wpforms()->get( 'payment' )->get_payments(
[
'mode' => 'any',
'number' => 1,
'is_published' => 0,
]
)
) > 0;
}
return $has_any_mode_payment;
}
/**
* To avoid recursively, remove the previous variables from the REQUEST_URI.
*
* @since 1.8.2
*/
private function clean_request_uri() {
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
$_SERVER['REQUEST_URI'] = remove_query_arg( [ '_wpnonce', '_wp_http_referer', 'action', 'action2', 'payment_id' ], wp_unslash( $_SERVER['REQUEST_URI'] ) );
if ( empty( $_GET['s'] ) ) {
$_SERVER['REQUEST_URI'] = remove_query_arg( [ 'search_where', 'search_mode', 's' ], wp_unslash( $_SERVER['REQUEST_URI'] ) );
}
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
}
}
/**
* Get the subscription delete confirmation message.
* The returned message is used in the JavaScript file and shown in a "Heads up!" modal.
*
* @since 1.8.4
*
* @return string
*/
private function get_subscription_delete_confirmation_message() {
$help_link = wpforms_utm_link(
'https://wpforms.com/docs/viewing-and-managing-payments/#deleting-parent-subscription',
'Delete Payment',
'Learn More'
);
return sprintf(
wp_kses( /* translators: WPForms.com docs page URL. */
__( 'Deleting one or more selected payments may prevent processing of future subscription renewals. Payment filtering may also be affected. <a href="%1$s" rel="noopener" target="_blank">Learn More</a>', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'rel' => [],
'target' => [],
],
]
),
esc_url( $help_link )
);
}
}

View File

@@ -0,0 +1,378 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview;
/**
* Search related methods for Payment and Payment Meta.
*
* @since 1.8.2
*/
class Search {
/**
* Credit card meta key.
*
* @since 1.8.2
*
* @var string
*/
const CREDIT_CARD = 'credit_card_last4';
/**
* Customer email meta key.
*
* @since 1.8.2
*
* @var string
*/
const EMAIL = 'customer_email';
/**
* Payment title column name.
*
* @since 1.8.2
*
* @var string
*/
const TITLE = 'title';
/**
* Transaction ID column name.
*
* @since 1.8.2
*
* @var string
*/
const TRANSACTION_ID = 'transaction_id';
/**
* Subscription ID column name.
*
* @since 1.8.2
*
* @var string
*/
const SUBSCRIPTION_ID = 'subscription_id';
/**
* Any column indicator key.
*
* @since 1.8.2
*
* @var string
*/
const ANY = 'any';
/**
* Equals mode.
*
* @since 1.8.2
*
* @var string
*/
const MODE_EQUALS = 'equals';
/**
* Starts with mode.
*
* @since 1.8.2
*
* @var string
*/
const MODE_STARTS = 'starts';
/**
* Contains mode.
*
* @since 1.8.2
*
* @var string
*/
const MODE_CONTAINS = 'contains';
/**
* Init.
*
* @since 1.8.2
*/
public function init() {
if ( ! self::is_search() ) {
return;
}
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.2
*/
private function hooks() {
add_filter( 'wpforms_db_payments_queries_count_all_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 );
add_filter( 'wpforms_db_payments_payment_get_payments_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 );
add_filter( 'wpforms_admin_payments_views_overview_filters_renewals_by_subscription_id_query_before_where', [ $this, 'add_search_where_conditions' ], 10, 2 );
}
/**
* Check if search query.
*
* @since 1.8.2
*
* @return bool
*/
public static function is_search() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return ! empty( $_GET['s'] );
}
/**
* Add search where conditions.
*
* @since 1.8.2
*
* @param string $where Query where string.
* @param array $args Query arguments.
*
* @return string
*/
public function add_search_where_conditions( $where, $args ) {
if ( empty( $args['search'] ) ) {
return $where;
}
if ( ! empty( $args['search_conditions']['search_mode'] ) && $args['search_conditions']['search_mode'] === self::MODE_CONTAINS ) {
$to_search = explode( ' ', $args['search'] );
} else {
$to_search = [ $args['search'] ];
}
$query = [];
foreach ( $to_search as $counter => $single ) {
$query[] = $this->add_single_search_condition( $single, $args, $counter );
}
return implode( ' ', $query );
}
/**
* Add single search condition.
*
* @since 1.8.2
*
* @param string $word Single searched part.
* @param array $args Query arguments.
* @param int $n Word counter.
*
* @return string
*/
private function add_single_search_condition( $word, $args, $n ) {
if ( empty( $word ) ) {
return '';
}
$mode = $this->prepare_mode( $args );
$where = $this->prepare_where( $args );
list( $operator, $word ) = $this->prepare_operator_and_word( $word, $mode );
$column = $this->prepare_column( $where );
if ( in_array( $column, [ self::EMAIL, self::CREDIT_CARD ], true ) ) {
return $this->select_from_meta_table( $column, $operator, $word, $n );
}
if ( $column === self::ANY ) {
return $this->select_from_any( $operator, $word, $n );
}
$payment_table = wpforms()->get( 'payment' )->table_name;
$query = "SELECT id FROM {$payment_table}
WHERE {$payment_table}.{$column} {$operator} {$word}";
return $this->wrap_in_inner_join( $query, $n );
}
/**
* Prepare search mode part.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return string Mode part for search.
*/
private function prepare_mode( $args ) {
return isset( $args['search_conditions']['search_mode'] ) ? $args['search_conditions']['search_mode'] : self::MODE_EQUALS;
}
/**
* Prepare search where part.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return string Where part for search.
*/
private function prepare_where( $args ) {
return isset( $args['search_conditions']['search_where'] ) ? $args['search_conditions']['search_where'] : self::TITLE;
}
/**
* Prepare operator and word parts.
*
* @since 1.8.2
*
* @param string $word Single word.
* @param string $mode Search mode.
*
* @return array Array with operator and word parts for search.
*/
private function prepare_operator_and_word( $word, $mode ) {
global $wpdb;
if ( $mode === self::MODE_CONTAINS ) {
return [
'LIKE',
$wpdb->prepare( '%s', '%' . $wpdb->esc_like( $word ) . '%' ),
];
}
if ( $mode === self::MODE_STARTS ) {
return [
'LIKE',
$wpdb->prepare( '%s', $wpdb->esc_like( $word ) . '%' ),
];
}
return [
'=',
$wpdb->prepare( '%s', $word ),
];
}
/**
* Prepare column to search in.
*
* @since 1.8.2
*
* @param string $where Search where.
*
* @return string Column to search in.
*/
private function prepare_column( $where ) {
if ( in_array( $where, [ self::TRANSACTION_ID, self::SUBSCRIPTION_ID, self::EMAIL, self::CREDIT_CARD, self::ANY ], true ) ) {
return $where;
}
return self::TITLE;
}
/**
* Prepare select part to select from payments meta table.
*
* @since 1.8.2
*
* @param string $meta_key Meta key.
* @param string $operator Comparison operator.
* @param string $word Word to search.
* @param int $n Word count.
*
* @return string
* @noinspection CallableParameterUseCaseInTypeContextInspection
*/
private function select_from_meta_table( $meta_key, $operator, $word, $n ) {
global $wpdb;
$payment_table = wpforms()->get( 'payment' )->table_name;
$meta_table = wpforms()->get( 'payment_meta' )->table_name;
$meta_key = $wpdb->prepare( '%s', $meta_key );
$query = "SELECT id FROM $payment_table
WHERE id IN (
SELECT DISTINCT payment_id FROM $meta_table
WHERE meta_value $operator $word
AND meta_key = $meta_key
)";
return $this->wrap_in_inner_join( $query, $n );
}
/**
* Prepare select part to select from places from both tables.
*
* @since 1.8.2
*
* @param string $operator Comparison operator.
* @param string $word Word to search.
* @param int $n Word count.
*
* @return string
*/
private function select_from_any( $operator, $word, $n ) {
$payment_table = wpforms()->get( 'payment' )->table_name;
$meta_table = wpforms()->get( 'payment_meta' )->table_name;
$query = sprintf(
"SELECT id FROM {$payment_table}
WHERE (
{$payment_table}.%s {$operator} {$word}
OR {$payment_table}.%s {$operator} {$word}
OR {$payment_table}.%s {$operator} {$word}
OR id IN (
SELECT DISTINCT payment_id
FROM {$meta_table}
WHERE meta_value {$operator} {$word}
AND meta_key IN ( '%s', '%s' )
)
)",
self::TITLE,
self::TRANSACTION_ID,
self::SUBSCRIPTION_ID,
self::CREDIT_CARD,
self::EMAIL
);
return $this->wrap_in_inner_join( $query, $n );
}
/**
* Wrap the query in INNER JOIN part.
*
* @since 1.8.2
*
* @param string $query Partial query.
* @param int $n Word count.
*
* @return string
*/
private function wrap_in_inner_join( $query, $n ) {
/**
* Filter to modify the inner join query.
*
* @since 1.8.4
*
* @param string $query Partial query.
* @param int $n The number of the JOIN clause.
*/
return apply_filters(
'wpforms_admin_payments_views_overview_search_inner_join_query',
sprintf( 'INNER JOIN ( %1$s ) AS p%2$d ON p.id = p%2$d.id', $query, $n ),
$n
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
<?php
namespace WPForms\Admin\Payments\Views\Overview\Traits;
use WPForms\Admin\Payments\Views\Overview\Coupon;
use WPForms\Admin\Payments\Views\Overview\Search;
use WPForms\Db\Payments\ValueValidator;
/**
* This file is part of the Table class and contains methods responsible for
* displaying notices on the Payments Overview page.
*
* @since 1.8.4
*/
trait ResetNotices {
/**
* Show reset filter box.
*
* @since 1.8.4
*/
private function show_reset_filter() {
$applied_filters = [
$this->get_search_reset_filter(),
$this->get_status_reset_filter(),
$this->get_coupon_reset_filter(),
$this->get_form_reset_filter(),
$this->get_type_reset_filter(),
$this->get_gateway_reset_filter(),
$this->get_subscription_status_reset_filter(),
];
$applied_filters = array_filter( $applied_filters );
// Let's not show the reset filter notice if there are no applied filters.
if ( empty( $applied_filters ) ) {
return;
}
// Output the reset filter notice.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'admin/payments/reset-filter-notice',
[
'total' => $this->get_valid_status_count_from_request(),
'applied_filters' => $applied_filters,
],
true
);
}
/**
* Show search reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_search_reset_filter() {
// Do not show the reset filter notice on the search results page.
if ( ! Search::is_search() ) {
return [];
}
$search_where = $this->get_search_where( $this->get_search_where_key() );
$search_mode = $this->get_search_mode( $this->get_search_mode_key() );
return [
'reset_url' => remove_query_arg( [ 's', 'search_where', 'search_mode', 'paged' ] ),
'results' => sprintf(
' %s <em>%s</em> %s "<em>%s</em>"',
__( 'where', 'wpforms-lite' ),
esc_html( $search_where ),
esc_html( $search_mode ),
// It's important to escape the search term here for security.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
esc_html( isset( $_GET['s'] ) ? wp_unslash( $_GET['s'] ) : '' )
),
];
}
/**
* Show status reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_status_reset_filter() {
// Do not show the reset filter notice on the status results page.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $this->get_valid_status_from_request() ) || $this->is_trash_view() ) {
return [];
}
$statuses = ValueValidator::get_allowed_one_time_statuses();
// Leave early if the status is not found.
if ( ! isset( $statuses[ $this->get_valid_status_from_request() ] ) ) {
return [];
}
return [
'reset_url' => remove_query_arg( [ 'status' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
__( 'with the status', 'wpforms-lite' ),
$statuses[ $this->get_valid_status_from_request() ]
),
];
}
/**
* Show coupon reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_coupon_reset_filter() {
// Do not show the reset filter notice on the coupon results page.
if ( ! Coupon::is_coupon() ) {
return [];
}
// Get the payment meta with the specified coupon ID.
$payment_meta = wpforms()->get( 'payment_meta' )->get_all_by_meta(
'coupon_id',
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Recommended
absint( $_GET['coupon_id'] )
);
// If the coupon info is empty, exit the function.
if ( empty( $payment_meta['coupon_info'] ) ) {
return [];
}
return [
'reset_url' => remove_query_arg( [ 'coupon_id', 'paged' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
__( 'with the coupon', 'wpforms-lite' ),
$this->get_coupon_name_by_info( $payment_meta['coupon_info']->value )
),
];
}
/**
* Show form reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_form_reset_filter() {
// Do not show the reset filter notice on the form results page.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $_GET['form_id'] ) ) {
return [];
}
// Retrieve the form with the specified ID.
$form = wpforms()->get( 'form' )->get( absint( $_GET['form_id'] ) );
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
// If the form is not found or not published, exit the function.
if ( ! $form || $form->post_status !== 'publish' ) {
return [];
}
return [
'reset_url' => remove_query_arg( [ 'form_id', 'paged' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
__( 'with the form titled', 'wpforms-lite' ),
! empty( $form->post_title ) ? $form->post_title : $form->post_name
),
];
}
/**
* Show type reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_type_reset_filter() {
// Do not show the reset filter notice on the type results page.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $_GET['type'] ) ) {
return [];
}
$allowed_types = ValueValidator::get_allowed_types();
$type = explode( '|', sanitize_text_field( wp_unslash( $_GET['type'] ) ) );
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return [
'reset_url' => remove_query_arg( [ 'type', 'paged' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
_n( 'with the type', 'with the types', count( $type ), 'wpforms-lite' ),
implode( ', ', array_intersect_key( $allowed_types, array_flip( $type ) ) )
),
];
}
/**
* Show gateway reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_gateway_reset_filter() {
// Do not show the reset filter notice on the gateway results page.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $_GET['gateway'] ) ) {
return [];
}
$allowed_gateways = ValueValidator::get_allowed_gateways();
$gateway = explode( '|', sanitize_text_field( wp_unslash( $_GET['gateway'] ) ) );
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return [
'reset_url' => remove_query_arg( [ 'gateway', 'paged' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
_n( 'with the gateway', 'with the gateways', count( $gateway ), 'wpforms-lite' ),
implode( ', ', array_intersect_key( $allowed_gateways, array_flip( $gateway ) ) )
),
];
}
/**
* Show subscription status reset filter.
*
* @since 1.8.4
*
* @return array
*/
private function get_subscription_status_reset_filter() {
// Do not show the reset filter notice on the subscription status results page.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( empty( $_GET['subscription_status'] ) ) {
return [];
}
$allowed_subscription_statuses = ValueValidator::get_allowed_subscription_statuses();
$subscription_status = explode( '|', sanitize_text_field( wp_unslash( $_GET['subscription_status'] ) ) );
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return [
'reset_url' => remove_query_arg( [ 'subscription_status', 'paged' ] ),
'results' => sprintf(
' %s "<em>%s</em>"',
_n( 'with the subscription status', 'with the subscription statuses', count( $subscription_status ), 'wpforms-lite' ),
implode( ', ', array_intersect_key( $allowed_subscription_statuses, array_flip( $subscription_status ) ) )
),
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace WPForms\Admin\Payments\Views;
interface PaymentsViewsInterface {
/**
* Initialize class.
*
* @since 1.8.2
*/
public function init();
/**
* Check if the current user has the capability to view the page.
*
* @since 1.8.2
*
* @return bool
*/
public function current_user_can();
/**
* Page heading content.
*
* @since 1.8.2
*/
public function heading();
/**
* Page content.
*
* @since 1.8.2
*/
public function display();
/**
* Get the Tab label.
*
* @since 1.8.2.2
*/
public function get_tab_label();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
<?php
namespace WPForms\Admin;
use WP_Post;
/**
* Form Revisions.
*
* @since 1.7.3
*/
class Revisions {
/**
* Current Form Builder panel view.
*
* @since 1.7.3
*
* @var string
*/
private $view = 'revisions';
/**
* Current Form ID.
*
* @since 1.7.3
*
* @var int|false
*/
private $form_id = false;
/**
* Current Form.
*
* @since 1.7.3
*
* @var WP_Post|null
*/
private $form;
/**
* Current Form Revision ID.
*
* @since 1.7.3
*
* @var int|false
*/
private $revision_id = false;
/**
* Current Form Revision.
*
* @since 1.7.3
*
* @var WP_Post|null
*/
private $revision;
/**
* Whether revisions panel was already viewed by the user at least once.
*
* @since 1.7.3
*
* @var bool
*/
private $viewed;
/**
* Initialize the class if preconditions are met.
*
* @since 1.7.3
*
* @return void
*/
public function init() {
if ( ! $this->allow_load() ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['view'] ) ) {
$this->view = sanitize_key( $_REQUEST['view'] );
}
if ( isset( $_REQUEST['revision_id'] ) ) {
$this->revision_id = absint( $_REQUEST['revision_id'] );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// Fetch revision, if needed.
if ( $this->revision_id && wp_revisions_enabled( $this->form ) ) {
$this->revision = wp_get_post_revision( $this->revision_id );
}
// Bail if we don't have a valid revision.
if ( $this->revision_id && ! $this->revision instanceof WP_Post ) {
return;
}
$this->hooks();
}
/**
* Whether it is allowed to load under certain conditions.
*
* - numeric, non-zero form ID provided,
* - the form with this ID exists and was successfully fetched,
* - we're in the Form Builder or processing an ajax request.
*
* @since 1.7.3
*
* @return bool
*/
private function allow_load() {
if ( ! ( wpforms_is_admin_page( 'builder' ) || wp_doing_ajax() ) ) {
return false;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$id = wp_doing_ajax() && isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : false;
$id = isset( $_REQUEST['form_id'] ) && ! is_array( $_REQUEST['form_id'] ) ? absint( $_REQUEST['form_id'] ) : $id;
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$this->form_id = $id;
$form_handler = wpforms()->get( 'form' );
if ( ! $form_handler ) {
return false;
}
$this->form = $form_handler->get( $this->form_id );
return $this->form_id && $this->form instanceof WP_Post;
}
/**
* Hook into WordPress lifecycle.
*
* @since 1.7.3
*/
private function hooks() {
// Restore a revision. The `admin_init` action has already fired, `current_screen` fires before headers are sent.
add_action( 'current_screen', [ $this, 'process_restore' ] );
// Refresh a rendered list of revisions on the frontend.
add_action( 'wp_ajax_wpforms_get_form_revisions', [ $this, 'fetch_revisions_list' ] );
// Mark Revisions panel as viewed when viewed for the first time. Hides the error badge.
add_action( 'wp_ajax_wpforms_mark_panel_viewed', [ $this, 'mark_panel_viewed' ] );
// Back-compat for forms created with revisions disabled.
add_action( 'wpforms_builder_init', [ $this, 'maybe_create_initial_revision' ] );
// Pass localized strings to frontend.
add_filter( 'wpforms_builder_strings', [ $this, 'get_localized_strings' ], 10, 2 );
}
/**
* Get current revision, if available.
*
* @since 1.7.3
*
* @return WP_Post|null
*/
public function get_revision() {
return $this->revision;
}
/**
* Get formatted date or time.
*
* @since 1.7.3
*
* @param string $datetime UTC datetime from the post object.
* @param string $part What to return - date or time, defaults to date.
*
* @return string
*/
public function get_formatted_datetime( $datetime, $part = 'date' ) {
if ( $part === 'time' ) {
return wpforms_time_format( $datetime, '', true );
}
// M j format needs to keep one-line date.
return wpforms_date_format( $datetime, 'M j', true );
}
/**
* Get admin (Form Builder) base URL with additional query args.
*
* @since 1.7.3
*
* @param array $query_args Additional query args to append to the base URL.
*
* @return string
*/
public function get_url( $query_args = [] ) {
$defaults = [
'page' => 'wpforms-builder',
'view' => $this->view,
'form_id' => $this->form_id,
];
return add_query_arg(
wp_parse_args( $query_args, $defaults ),
admin_url( 'admin.php' )
);
}
/**
* Determine if Revisions panel was previously viewed by current user.
*
* @since 1.7.3
*
* @return bool
*/
public function panel_viewed() {
if ( $this->viewed === null ) {
$this->viewed = (bool) get_user_meta( get_current_user_id(), 'wpforms_revisions_disabled_notice_dismissed', true );
}
return $this->viewed;
}
/**
* Mark Revisions panel as viewed by current user.
*
* @since 1.7.3
*/
public function mark_panel_viewed() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
if ( ! $this->panel_viewed() ) {
$this->viewed = update_user_meta( get_current_user_id(), 'wpforms_revisions_disabled_notice_dismissed', true );
}
wp_send_json_success( [ 'updated' => $this->viewed ] );
}
/**
* Get a rendered list of all revisions.
*
* @since 1.7.3
*
* @return string
*/
public function render_revisions_list() {
return wpforms_render(
'builder/revisions/list',
$this->prepare_template_render_arguments(),
true
);
}
/**
* Prepare all arguments for the template to be rendered.
*
* Note: All data is escaped in the template.
*
* @since 1.7.3
*
* @return array
*/
private function prepare_template_render_arguments() {
$args = [
'active_class' => $this->revision ? '' : ' active',
'current_version_url' => $this->get_url(),
'author_id' => $this->form->post_author,
'revisions' => [],
'show_avatars' => get_option( 'show_avatars' ),
];
$revisions = wp_get_post_revisions( $this->form_id );
if ( empty( $revisions ) ) {
return $args;
}
// WordPress always orders entries by `post_date` column, which contains a date and time in site's timezone configured in settings.
// This setting is per site, not per user, and it's not expected to be changed. However, if it was changed for whatever reason,
// the order of revisions will be incorrect. This is definitely an edge case, but we can prevent this from ever happening
// by sorting the results using `post_date_gmt` or `post_modified_gmt`, which contains UTC date and never changes.
uasort(
$revisions,
static function ( $a, $b ) {
return strtotime( $a->post_modified_gmt ) > strtotime( $b->post_modified_gmt ) ? -1 : 1;
}
);
// The first revision is always identical to the current version and should not be displayed in the list.
$current_revision = array_shift( $revisions );
// Display the author of current version instead of a form author.
$args['author_id'] = $current_revision->post_author;
foreach ( $revisions as $revision ) {
$time_diff = sprintf( /* translators: %s - relative time difference, e.g. "5 minutes", "12 days". */
__( '%s ago', 'wpforms-lite' ),
human_time_diff( strtotime( $revision->post_modified_gmt . ' +0000' ) )
);
$date_time = sprintf( /* translators: %1$s - date, %2$s - time when item was created, e.g. "Oct 22 at 11:11am". */
__( '%1$s at %2$s', 'wpforms-lite' ),
$this->get_formatted_datetime( $revision->post_modified_gmt ),
$this->get_formatted_datetime( $revision->post_modified_gmt, 'time' )
);
$args['revisions'][] = [
'active_class' => $this->revision && $this->revision->ID === $revision->ID ? ' active' : '',
'url' => $this->get_url(
[
'revision_id' => $revision->ID,
]
),
'author_id' => $revision->post_author,
'time_diff' => $time_diff,
'date_time' => $date_time,
];
}
return $args;
}
/**
* Fetch a list of revisions via ajax.
*
* @since 1.7.3
*/
public function fetch_revisions_list() {
// Run a security check.
check_ajax_referer( 'wpforms-builder', 'nonce' );
wp_send_json_success(
[
'html' => $this->render_revisions_list(),
]
);
}
/**
* Restore the revision (if needed) and reload the Form Builder.
*
* @since 1.7.3
*
* @return void
*/
public function process_restore() {
$is_restore_request = isset( $_GET['action'] ) && $_GET['action'] === 'restore_revision';
// Bail early.
if (
! $is_restore_request ||
! $this->form_id ||
! $this->form ||
! $this->revision_id ||
! $this->revision ||
! check_admin_referer( 'restore_revision', 'wpforms_nonce' )
) {
return;
}
$restored_id = wp_restore_post_revision( $this->revision );
if ( $restored_id ) {
wp_safe_redirect(
wpforms()->get( 'revisions' )->get_url(
[
'form_id' => $restored_id,
]
)
);
exit;
}
}
/**
* Create initial revision for existing form.
*
* When a new form is created with revisions enabled, WordPress immediately creates first revision which is identical to the form. But when
* a form was created with revisions disabled, this initial revision does not exist. Revisions are saved after post update, so modifying
* a form that have no initial revision will update the post first, then a revision of this updated post will be saved. The version of
* the form that existed before this update is now gone. To avoid losing this pre-revisions state, we create this initial revision
* when the Form Builder loads, if needed.
*
* @since 1.7.3
*
* @return void
*/
public function maybe_create_initial_revision() {
// On new form creation there's no revisions yet, bail. Also, when revisions are disabled.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['newform'] ) || ! wp_revisions_enabled( $this->form ) ) {
return;
}
$revisions = wp_get_post_revisions(
$this->form_id,
[
'fields' => 'ids',
'numberposts' => 1,
]
);
if ( $revisions ) {
return;
}
$initial_revision_id = wp_save_post_revision( $this->form_id );
$initial_revision = wp_get_post_revision( $initial_revision_id );
// Initial revision should belong to the author of the original form.
if ( $initial_revision->post_author !== $this->form->post_author ) {
wp_update_post(
[
'ID' => $initial_revision_id,
'post_author' => $this->form->post_author,
]
);
}
}
/**
* Pass localized strings to frontend.
*
* @since 1.7.3
*
* @param array $strings All strings that will be passed to frontend.
* @param WP_Post $form Current form object.
*
* @return array
*/
public function get_localized_strings( $strings, $form ) {
$strings['revision_update_confirm'] = esc_html__( 'Youre about to save a form revision. Continuing will make this the current version.', 'wpforms-lite' );
return $strings;
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace WPForms\Admin\Settings;
use WPForms\Admin\Notice;
use WPForms\Admin\Settings\Captcha\Page;
/**
* CAPTCHA setting page.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
class Captcha {
/**
* Slug identifier for admin page view.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @var string
*/
const VIEW = 'captcha';
/**
* The hCaptcha javascript URL-resource.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
const HCAPTCHA_API_URL = 'https://hcaptcha.com/1/api.js';
/**
* The reCAPTCHA javascript URL-resource.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
const RECAPTCHA_API_URL = 'https://www.google.com/recaptcha/api.js';
/**
* Saved CAPTCHA settings.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @var array
*/
private $settings;
/**
* Initialize class.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function init() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::init()' );
( new Page() )->init();
}
/**
* Init CAPTCHA settings.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function init_settings() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::init_settings()' );
( new Page() )->init_settings();
}
/**
* Hooks.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function hooks() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::hooks()' );
( new Page() )->hooks();
}
/**
* Register CAPTCHA settings tab.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @param array $tabs Admin area tabs list.
*
* @return array
*/
public function register_settings_tabs( $tabs ) {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::register_settings_tabs()' );
return ( new Page() )->register_settings_tabs( $tabs );
}
/**
* Register CAPTCHA settings fields.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @param array $settings Admin area settings list.
*
* @return array
*/
public function register_settings_fields( $settings ) {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::register_settings_fields()' );
return ( new Page() )->register_settings_fields( $settings );
}
/**
* Re-init CAPTCHA settings when plugin settings were updated.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function updated() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::updated()' );
( new Page() )->updated();
}
/**
* Display notice about the CAPTCHA preview.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
protected function notice() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin' );
if (
! wpforms_is_admin_page( 'settings', self::VIEW ) ||
! $this->is_captcha_preview_ready()
) {
return;
}
Notice::info( esc_html__( 'A preview of your CAPTCHA is displayed below. Please view to verify the CAPTCHA settings are correct.', 'wpforms-lite' ) );
}
/**
* Enqueue assets for the CAPTCHA settings page.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function enqueues() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::enqueues()' );
( new Page() )->enqueues();
}
/**
* Use the CAPTCHA no-conflict mode.
*
* When enabled in the WPForms settings, forcefully remove all other
* CAPTCHA enqueues to prevent conflicts. Filter can be used to target
* specific pages, etc.
*
* @since 1.6.4
* @deprecated 1.8.0
*/
public function apply_noconflict() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin', 'WPForms\Admin\Settings\Captcha\Page::apply_noconflict()' );
( new Page() )->apply_noconflict();
}
/**
* Check if CAPTCHA config is ready to display a preview.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @return bool
*/
protected function is_captcha_preview_ready() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin' );
return (
( 'hcaptcha' === $this->settings['provider'] || ( 'recaptcha' === $this->settings['provider'] && 'v2' === $this->settings['recaptcha_type'] ) ) &&
! empty( $this->settings['site_key'] ) &&
! empty( $this->settings['secret_key'] )
);
}
/**
* Retrieve the CAPTCHA provider API URL.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @return string
*/
protected function get_api_url() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin' );
$api_url = '';
if ( $this->settings['provider'] === 'hcaptcha' ) {
$api_url = self::HCAPTCHA_API_URL;
}
if ( $this->settings['provider'] === 'recaptcha' ) {
$api_url = self::RECAPTCHA_API_URL;
}
if ( ! empty( $api_url ) ) {
$api_url = add_query_arg( $this->get_api_url_query_arg(), $api_url );
}
return apply_filters( 'wpforms_admin_settings_captcha_get_api_url', $api_url, $this->settings );
}
/**
* Retrieve query arguments for the CAPTCHA API URL.
*
* @since 1.6.4
* @deprecated 1.8.0
*
* @return array
*/
protected function get_api_url_query_arg() {
_deprecated_function( __METHOD__, '1.8.0 of the WPForms plugin' );
return (array) apply_filters(
'wpforms_admin_settings_captcha_get_api_url_query_arg', // phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
[
'onload' => 'wpformsSettingsCaptchaLoad',
'render' => 'explicit',
],
$this->settings
);
}
}

View File

@@ -0,0 +1,196 @@
<?php
namespace WPForms\Admin\Settings\Captcha;
/**
* Base captcha settings class.
*
* @since 1.8.0
*/
abstract class Captcha {
/**
* Saved CAPTCHA settings.
*
* @since 1.8.0
*
* @var array
*/
protected $settings;
/**
* List of required static properties.
*
* @since 1.8.0
*
* @var array
*/
private $required_static_properties = [
'api_var',
'slug',
'url',
];
/**
* Initialize class.
*
* @since 1.8.0
*/
public function init() {
$this->settings = wp_parse_args( wpforms_get_captcha_settings(), [ 'provider' => 'none' ] );
foreach ( $this->required_static_properties as $property ) {
if ( empty( static::${$property} ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error(
sprintf(
'The $%s static property is required for a %s class',
esc_html( $property ),
__CLASS__
),
E_USER_ERROR
);
}
}
}
/**
* Array of captcha settings fields.
*
* @since 1.8.0
*
* @return array[]
*/
abstract public function get_settings_fields();
/**
* Get API request url for the captcha preview.
*
* @since 1.8.0
*
* @return string
*/
public function get_api_url() {
$url = static::$url;
if ( ! empty( $url ) ) {
$url = add_query_arg( $this->get_api_url_query_arg(), $url );
}
/**
* Filter API URL.
*
* @since 1.6.4
*
* @param string $url API URL.
* @param array $settings Captcha settings array.
*/
return apply_filters( 'wpforms_admin_settings_captcha_get_api_url', $url, $this->settings );
}
/**
* Enqueue assets for the CAPTCHA settings page.
*
* @since 1.8.0
*/
public function enqueues() {
/**
* Allow/disallow to enquire captcha settings.
*
* @since 1.6.4
*
* @param boolean $allow True/false. Default: false.
*/
$disable_enqueues = apply_filters( 'wpforms_admin_settings_captcha_enqueues_disable', false );
if ( $disable_enqueues || ! $this->is_captcha_preview_ready() ) {
return;
}
$api_url = $this->get_api_url();
$provider_name = $this->settings['provider'];
$handle = "wpforms-settings-{$provider_name}";
wp_enqueue_script( $handle, $api_url, [ 'jquery' ], null, true );
wp_add_inline_script( $handle, $this->get_inline_script() );
}
/**
* Inline script for initialize captcha JS code.
*
* @since 1.8.0
*
* @return string
*/
protected function get_inline_script() {
return /** @lang JavaScript */
'var wpformsSettingsCaptchaLoad = function() {
jQuery( ".wpforms-captcha" ).each( function( index, el ) {
var widgetID = ' . static::$api_var . '.render( el );
jQuery( el ).attr( "data-captcha-id", widgetID );
} );
jQuery( document ).trigger( "wpformsSettingsCaptchaLoaded" );
};';
}
/**
* Check if CAPTCHA config is ready to display a preview.
*
* @since 1.8.0
*
* @return bool
*/
public function is_captcha_preview_ready() {
return (
( $this->settings['provider'] === static::$slug || ( $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v2' ) ) &&
! empty( $this->settings['site_key'] ) &&
! empty( $this->settings['secret_key'] )
);
}
/**
* Retrieve query arguments for the CAPTCHA API URL.
*
* @since 1.8.0
*
* @return array
*/
protected function get_api_url_query_arg() {
/**
* Modify captcha api url parameters.
*
* @since 1.8.0
*
* @param array $params Array of parameters.
* @param array $params Saved CAPTCHA settings.
*/
return (array) apply_filters(
'wpforms_admin_settings_captcha_get_api_url_query_arg',
[
'onload' => 'wpformsSettingsCaptchaLoad',
'render' => 'explicit',
],
$this->settings
);
}
/**
* Heading description.
*
* @since 1.8.0
*
* @return string
*/
public function get_field_desc() {
$content = wpforms_render( 'admin/settings/' . static::$slug . '-description' );
return wpforms_render( 'admin/settings/specific-note', [ 'content' => $content ], true );
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace WPForms\Admin\Settings\Captcha;
/**
* HCaptcha settings class.
*
* @since 1.8.0
*/
class HCaptcha extends Captcha {
/**
* Captcha variable used for JS invoking.
*
* @since 1.8.0
*
* @var string
*/
protected static $api_var = 'hcaptcha';
/**
* Get captcha key name.
*
* @since 1.8.0
*
* @var string
*/
protected static $slug = 'hcaptcha';
/**
* The hCaptcha Javascript URL-resource.
*
* @since 1.8.0
*
* @var string
*/
protected static $url = 'https://hcaptcha.com/1/api.js';
/**
* Array of captcha settings fields.
*
* @since 1.8.0
*
* @return array[]
*/
public function get_settings_fields() {
return [
'hcaptcha-heading' => [
'id' => 'hcaptcha-heading',
'content' => $this->get_field_desc(),
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'specific-note' ],
],
'hcaptcha-site-key' => [
'id' => 'hcaptcha-site-key',
'name' => esc_html__( 'Site Key', 'wpforms-lite' ),
'type' => 'text',
],
'hcaptcha-secret-key' => [
'id' => 'hcaptcha-secret-key',
'name' => esc_html__( 'Secret Key', 'wpforms-lite' ),
'type' => 'text',
],
'hcaptcha-fail-msg' => [
'id' => 'hcaptcha-fail-msg',
'name' => esc_html__( 'Fail Message', 'wpforms-lite' ),
'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'hCaptcha verification failed, please try again later.', 'wpforms-lite' ),
],
];
}
}

View File

@@ -0,0 +1,372 @@
<?php
namespace WPForms\Admin\Settings\Captcha;
use WPForms\Admin\Notice;
/**
* CAPTCHA setting page.
*
* @since 1.8.0
*/
class Page {
/**
* Slug identifier for admin page view.
*
* @since 1.8.0
*
* @var string
*/
const VIEW = 'captcha';
/**
* Saved CAPTCHA settings.
*
* @since 1.8.0
*
* @var array
*/
private $settings;
/**
* All available captcha types.
*
* @since 1.8.0
*
* @var array
*/
private $captchas;
/**
* Initialize class.
*
* @since 1.8.0
*/
public function init() {
// Only load if we are actually on the settings page.
if ( ! wpforms_is_admin_page( 'settings' ) ) {
return;
}
// Listen the previous reCAPTCHA page and safely redirect from it.
if ( wpforms_is_admin_page( 'settings', 'recaptcha' ) ) {
wp_safe_redirect( add_query_arg( 'view', self::VIEW, admin_url( 'admin.php?page=wpforms-settings' ) ) );
exit;
}
$this->init_settings();
$this->hooks();
}
/**
* Init CAPTCHA settings.
*
* @since 1.8.0
*/
public function init_settings() {
$this->settings = wp_parse_args( wpforms_get_captcha_settings(), [ 'provider' => 'none' ] );
/**
* Filter available captcha for the settings page.
*
* @since 1.8.0
*
* @param array $captcha Array where key is captcha name and value is captcha class instance.
* @param array $settings Array of settings.
*/
$this->captchas = apply_filters(
'wpforms_admin_settings_captcha_page_init_settings_available_captcha',
[
'hcaptcha' => new HCaptcha(),
'recaptcha' => new ReCaptcha(),
'turnstile' => new Turnstile(),
],
$this->settings
);
foreach ( $this->captchas as $captcha ) {
$captcha->init();
}
}
/**
* Hooks.
*
* @since 1.8.0
*/
public function hooks() {
add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5, 1 );
add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5, 1 );
add_action( 'wpforms_settings_updated', [ $this, 'updated' ] );
add_action( 'wpforms_settings_enqueue', [ $this, 'enqueues' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'apply_noconflict' ], 9999 );
}
/**
* Register CAPTCHA settings tab.
*
* @since 1.8.0
*
* @param array $tabs Admin area tabs list.
*
* @return array
*/
public function register_settings_tabs( $tabs ) {
$captcha = [
self::VIEW => [
'name' => esc_html__( 'CAPTCHA', 'wpforms-lite' ),
'form' => true,
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
];
return wpforms_array_insert( $tabs, $captcha, 'email' );
}
/**
* Register CAPTCHA settings fields.
*
* @since 1.8.0
*
* @param array $settings Admin area settings list.
*
* @return array
*/
public function register_settings_fields( $settings ) {
$settings[ self::VIEW ] = [
self::VIEW . '-heading' => [
'id' => self::VIEW . '-heading',
'content' => '<h4>' . esc_html__( 'CAPTCHA', 'wpforms-lite' ) . '</h4><p>' . esc_html__( 'A CAPTCHA is an anti-spam technique which helps to protect your website from spam and abuse while letting real people pass through with ease.', 'wpforms-lite' ) . '</p>',
'type' => 'content',
'no_label' => true,
'class' => [ 'wpforms-setting-captcha-heading', 'section-heading' ],
],
self::VIEW . '-provider' => [
'id' => self::VIEW . '-provider',
'type' => 'radio',
'default' => 'none',
'options' => [
'hcaptcha' => 'hCaptcha',
'recaptcha' => 'reCAPTCHA',
'turnstile' => 'Turnstile',
'none' => esc_html__( 'None', 'wpforms-lite' ),
],
'desc' => sprintf(
wp_kses( /* translators: %s - WPForms.com CAPTCHA comparison page URL. */
__( 'Not sure which service is right for you? <a href="%s" target="_blank" rel="noopener noreferrer">Check out our comparison</a> for more details.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/setup-captcha-wpforms/', 'Settings - Captcha', 'Captcha Comparison Documentation' ) )
),
],
];
// Add settings fields for each of available captcha types.
foreach ( $this->captchas as $captcha ) {
$settings[ self::VIEW ] = array_merge( $settings[ self::VIEW ], $captcha->get_settings_fields() );
}
$settings[ self::VIEW ] = array_merge(
$settings[ self::VIEW ],
[
'recaptcha-noconflict' => [
'id' => 'recaptcha-noconflict',
'name' => esc_html__( 'No-Conflict Mode', 'wpforms-lite' ),
'desc' => esc_html__( 'Forcefully remove other CAPTCHA occurrences in order to prevent conflicts. Only enable this option if your site is having compatibility issues or instructed by support.', 'wpforms-lite' ),
'type' => 'toggle',
'status' => true,
],
self::VIEW . '-preview' =>
[
'id' => self::VIEW . '-preview',
'name' => esc_html__( 'Preview', 'wpforms-lite' ),
'content' => '<p class="desc wpforms-captcha-preview-desc">' . esc_html__( 'Please save settings to generate a preview of your CAPTCHA here.', 'wpforms-lite' ) . '</p>',
'type' => 'content',
'class' => [ 'wpforms-hidden' ],
],
]
);
if (
$this->settings['provider'] === 'hcaptcha' ||
$this->settings['provider'] === 'turnstile' ||
( $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v2' )
) {
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
/**
* Modify captcha settings data.
*
* @since 1.6.4
*
* @param array $data Array of settings.
*/
$data = apply_filters(
'wpforms_admin_pages_settings_captcha_data',
[
'sitekey' => $this->settings['site_key'],
'theme' => $this->settings['theme'],
]
);
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
// Prepare HTML for CAPTCHA preview.
$placeholder_description = $settings[ self::VIEW ][ self::VIEW . '-preview' ]['content'];
$captcha_description = esc_html__( 'This CAPTCHA is generated using your site and secret keys. If an error is displayed, please double-check your keys.', 'wpforms-lite' );
$captcha_preview = sprintf(
'<div class="wpforms-captcha-container" style="pointer-events:none!important;cursor:default!important;">
<div %s></div>
<input type="text" name="wpforms-captcha-hidden" class="wpforms-recaptcha-hidden" style="position:absolute!important;clip:rect(0,0,0,0)!important;height:1px!important;width:1px!important;border:0!important;overflow:hidden!important;padding:0!important;margin:0!important;">
</div>',
wpforms_html_attributes( '', [ 'wpforms-captcha', 'wpforms-captcha-' . $this->settings['provider'] ], $data )
);
$settings[ self::VIEW ][ self::VIEW . '-preview' ]['content'] = sprintf(
'<div class="wpforms-captcha-preview">
%1$s <p class="desc">%2$s</p>
</div>
<div class="wpforms-captcha-placeholder wpforms-hidden">%3$s</div>',
$captcha_preview,
$captcha_description,
$placeholder_description
);
$settings[ self::VIEW ][ self::VIEW . '-preview' ]['class'] = [];
}
return $settings;
}
/**
* Re-init CAPTCHA settings when plugin settings were updated.
*
* @since 1.8.0
*/
public function updated() {
$this->init_settings();
$this->notice();
}
/**
* Display notice about the CAPTCHA preview.
*
* @since 1.8.0
*/
private function notice() {
if ( ! wpforms_is_admin_page( 'settings', self::VIEW ) || ! $this->is_captcha_preview_ready() ) {
return;
}
Notice::info( esc_html__( 'A preview of your CAPTCHA is displayed below. Please view to verify the CAPTCHA settings are correct.', 'wpforms-lite' ) );
}
/**
* Check if CAPTCHA config is ready to display a preview.
*
* @since 1.8.0
*
* @return bool
*/
private function is_captcha_preview_ready() {
$current_captcha = $this->get_current_captcha();
if ( ! $current_captcha ) {
return false;
}
return $current_captcha->is_captcha_preview_ready();
}
/**
* Enqueue assets for the CAPTCHA settings page.
*
* @since 1.8.0
*/
public function enqueues() {
$current_captcha = $this->get_current_captcha();
if ( ! $current_captcha ) {
return;
}
$current_captcha->enqueues();
}
/**
* Get current active captcha object.
*
* @since 1.8.0
*
* @return object|string
*/
private function get_current_captcha() {
return ! empty( $this->captchas[ $this->settings['provider'] ] ) ? $this->captchas[ $this->settings['provider'] ] : '';
}
/**
* Use the CAPTCHA no-conflict mode.
*
* When enabled in the WPForms settings, forcefully remove all other
* CAPTCHA enqueues to prevent conflicts. Filter can be used to target
* specific pages, etc.
*
* @since 1.6.4
*/
public function apply_noconflict() {
if (
! wpforms_is_admin_page( 'settings', self::VIEW ) ||
empty( wpforms_setting( 'recaptcha-noconflict' ) ) ||
/**
* Allow/disallow applying non-conflict mode for captcha scripts.
*
* @since 1.6.4
*
* @param boolean $allow True/false. Default: true.
*/
! apply_filters( 'wpforms_admin_settings_captcha_apply_noconflict', true ) // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
) {
return;
}
$scripts = wp_scripts();
$urls = [ 'google.com/recaptcha', 'gstatic.com/recaptcha', 'hcaptcha.com/1', 'challenges.cloudflare.com/turnstile' ];
foreach ( $scripts->queue as $handle ) {
// Skip the WPForms JavaScript assets.
if (
! isset( $scripts->registered[ $handle ] ) ||
false !== strpos( $scripts->registered[ $handle ]->handle, 'wpforms' )
) {
return;
}
foreach ( $urls as $url ) {
if ( false !== strpos( $scripts->registered[ $handle ]->src, $url ) ) {
wp_dequeue_script( $handle );
wp_deregister_script( $handle );
break;
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace WPForms\Admin\Settings\Captcha;
/**
* ReCaptcha settings class.
*
* @since 1.8.0
*/
class ReCaptcha extends Captcha {
/**
* Captcha variable used for JS invoking.
*
* @since 1.8.0
*
* @var string
*/
protected static $api_var = 'grecaptcha';
/**
* Get captcha key name.
*
* @since 1.8.0
*
* @var string
*/
protected static $slug = 'recaptcha';
/**
* The ReCAPTCHA Javascript URL-resource.
*
* @since 1.8.0
*
* @var string
*/
protected static $url = 'https://www.google.com/recaptcha/api.js';
/**
* Array of captcha settings fields.
*
* @since 1.8.0
*
* @return array[]
*/
public function get_settings_fields() {
return [
'recaptcha-heading' => [
'id' => 'recaptcha-heading',
'content' => $this->get_field_desc(),
'type' => 'content',
'no_label' => true,
'class' => [ 'wpforms-setting-recaptcha', 'section-heading', 'specific-note' ],
],
'recaptcha-type' => [
'id' => 'recaptcha-type',
'name' => esc_html__( 'Type', 'wpforms-lite' ),
'type' => 'radio',
'default' => 'v2',
'options' => [
'v2' => esc_html__( 'Checkbox reCAPTCHA v2', 'wpforms-lite' ),
'invisible' => esc_html__( 'Invisible reCAPTCHA v2', 'wpforms-lite' ),
'v3' => esc_html__( 'reCAPTCHA v3', 'wpforms-lite' ),
],
'class' => [ 'wpforms-setting-recaptcha' ],
],
'recaptcha-site-key' => [
'id' => 'recaptcha-site-key',
'name' => esc_html__( 'Site Key', 'wpforms-lite' ),
'type' => 'text',
],
'recaptcha-secret-key' => [
'id' => 'recaptcha-secret-key',
'name' => esc_html__( 'Secret Key', 'wpforms-lite' ),
'type' => 'text',
],
'recaptcha-fail-msg' => [
'id' => 'recaptcha-fail-msg',
'name' => esc_html__( 'Fail Message', 'wpforms-lite' ),
'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Google reCAPTCHA verification failed, please try again later.', 'wpforms-lite' ),
],
'recaptcha-v3-threshold' => [
'id' => 'recaptcha-v3-threshold',
'name' => esc_html__( 'Score Threshold', 'wpforms-lite' ),
'desc' => esc_html__( 'reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot). If the score less than or equal to this threshold, the form submission will be blocked and the message above will be displayed.', 'wpforms-lite' ),
'type' => 'number',
'attr' => [
'step' => '0.1',
'min' => '0.0',
'max' => '1.0',
],
'default' => esc_html__( '0.4', 'wpforms-lite' ),
'class' => $this->settings['provider'] === 'recaptcha' && $this->settings['recaptcha_type'] === 'v3' ? [ 'wpforms-setting-recaptcha' ] : [ 'wpforms-setting-recaptcha', 'wpforms-hidden' ],
],
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace WPForms\Admin\Settings\Captcha;
/**
* Cloudflare Turnstile settings class.
*
* @since 1.8.0
*/
class Turnstile extends Captcha {
/**
* Captcha variable used for JS invoking.
*
* @since 1.8.0
*
* @var string
*/
protected static $api_var = 'turnstile';
/**
* Captcha key name.
*
* @since 1.8.0
*
* @var string
*/
protected static $slug = 'turnstile';
/**
* The Turnstile Javascript URL-resource.
*
* @since 1.8.0
*
* @var string
*/
protected static $url = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
/**
* Inline script for captcha initialization JS code.
*
* @since 1.8.0
*
* @return string
*/
protected function get_inline_script() {
return /** @lang JavaScript */
'const wpformsCaptcha = jQuery( ".wpforms-captcha" );
if ( wpformsCaptcha.length > 0 ) {
var widgetID = ' . static::$api_var . '.render( ".wpforms-captcha", {
"refresh-expired": "auto"
} );
wpformsCaptcha.attr( "data-captcha-id", widgetID);
jQuery( document ).trigger( "wpformsSettingsCaptchaLoaded" );
}';
}
/**
* Array of captcha settings fields.
*
* @since 1.8.0
*
* @return array[]
*/
public function get_settings_fields() {
return [
'turnstile-heading' => [
'id' => 'turnstile-heading',
'content' => $this->get_field_desc(),
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'specific-note' ],
],
'turnstile-site-key' => [
'id' => 'turnstile-site-key',
'name' => esc_html__( 'Site Key', 'wpforms-lite' ),
'type' => 'text',
],
'turnstile-secret-key' => [
'id' => 'turnstile-secret-key',
'name' => esc_html__( 'Secret Key', 'wpforms-lite' ),
'type' => 'text',
],
'turnstile-fail-msg' => [
'id' => 'turnstile-fail-msg',
'name' => esc_html__( 'Fail Message', 'wpforms-lite' ),
'desc' => esc_html__( 'Displays to users who fail the verification process.', 'wpforms-lite' ),
'type' => 'text',
'default' => esc_html__( 'Cloudflare Turnstile verification failed, please try again later.', 'wpforms-lite' ),
],
'turnstile-theme' => [
'id' => 'turnstile-theme',
'name' => esc_html__( 'Type', 'wpforms-lite' ),
'type' => 'select',
'default' => 'auto',
'options' => [
'auto' => esc_html__( 'Auto', 'wpforms-lite' ),
'light' => esc_html__( 'Light', 'wpforms-lite' ),
'dark' => esc_html__( 'Dark', 'wpforms-lite' ),
],
],
];
}
}

View File

@@ -0,0 +1,487 @@
<?php
namespace WPForms\Admin\Settings;
use WPForms\Emails\Helpers;
use WPForms\Emails\Notifications;
use WPForms\Admin\Education\Helpers as EducationHelpers;
/**
* Email setting page.
* Settings will be accessible via “WPForms” → “Settings” → “Email”.
*
* @since 1.8.5
*/
class Email {
/**
* Content is plain text type.
*
* @since 1.8.5
*
* @var bool
*/
private $plain_text;
/**
* Initialize class.
*
* @since 1.8.5
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.5
*/
private function hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_filter( 'wpforms_update_settings', [ $this, 'maybe_update_settings' ] );
add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5 );
add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5 );
}
/**
* Enqueue scripts and styles.
* Static resources are enqueued only on the "Email" settings page.
*
* @since 1.8.5
*/
public function enqueue_assets() {
// Leave if the current page is not the "Email" settings page.
if ( ! $this->is_settings_page() ) {
return;
}
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-admin-email-settings',
WPFORMS_PLUGIN_URL . "assets/js/components/admin/email/settings{$min}.js",
[ 'jquery', 'wpforms-admin', 'choicesjs' ],
WPFORMS_VERSION,
true
);
}
/**
* Maybe update settings.
*
* @since 1.8.5
*
* @param array $settings Admin area settings list.
*
* @return array
*/
public function maybe_update_settings( $settings ) {
// Leave if the current page is not the "Email" settings page.
if ( ! $this->is_settings_page() ) {
return $settings;
}
// Backup the Pro version background color setting to the free version.
// This is needed to keep the background color when the Pro version is deactivated.
if ( wpforms()->is_pro() && ! Helpers::is_legacy_html_template() ) {
$settings['email-background-color'] = sanitize_hex_color( $settings['email-color-scheme']['email_background_color'] );
return $settings;
}
// Backup the free version background color setting to the Pro version.
// This is needed to keep the background color when the Pro version is activated.
$settings['email-color-scheme']['email_background_color'] = sanitize_hex_color( $settings['email-background-color'] );
return $settings;
}
/**
* Register "Email" settings tab.
*
* @since 1.8.5
*
* @param array $tabs Admin area tabs list.
*
* @return array
*/
public function register_settings_tabs( $tabs ) {
$payments = [
'email' => [
'form' => true,
'name' => esc_html__( 'Email', 'wpforms-lite' ),
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
];
return wpforms_array_insert( $tabs, $payments, 'general' );
}
/**
* Register "Email" settings fields.
*
* @since 1.8.5
*
* @param array $settings Admin area settings list.
*
* @return array
*/
public function register_settings_fields( $settings ) {
$education_args = [ 'action' => 'upgrade' ];
$has_eduction = ! wpforms()->is_pro() ? 'education-modal' : '';
$style_overrides = Helpers::get_current_template_style_overrides();
$preview_link = $this->get_current_template_preview_link();
$this->plain_text = Helpers::is_plain_text_template();
$has_legacy = Helpers::is_legacy_html_template() ? 'legacy-template' : '';
// After initializing the color picker, the helper icon from 1Password and LastPass appears inside the input field.
// These data attributes disable the extension form from appearing.
$color_scheme_data = [
'1p-ignore' => 'true', // 1Password ignore.
'lp-ignore' => 'true', // LastPass ignore.
];
// Add Email settings.
$settings['email'] = [
'email-heading' => [
'id' => 'email-heading',
'content' => $this->get_heading_content(),
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'email-template' => [
'id' => 'email-template',
'name' => esc_html__( 'Template', 'wpforms-lite' ),
'class' => [ 'wpforms-email-template', 'wpforms-card-image-group' ],
'type' => 'email_template',
'default' => Notifications::DEFAULT_TEMPLATE,
'options' => Helpers::get_email_template_choices(),
'value' => Helpers::get_current_template_name(),
],
'email-header-image' => [
'id' => 'email-header-image',
'name' => esc_html__( 'Header Image', 'wpforms-lite' ),
'desc' => esc_html__( 'Upload or choose a logo to be displayed at the top of email notifications.', 'wpforms-lite' ),
'class' => [ 'wpforms-email-header-image', 'hide-for-template-none', $this->get_external_header_image_class() ],
'type' => 'image',
'is_hidden' => $this->plain_text,
'show_remove' => true,
],
'email-header-image-size' => [
'id' => 'email-header-image-size',
'no_label' => true,
'type' => 'select',
'class' => 'wpforms-email-header-image-size',
'is_hidden' => true,
'choicesjs' => false,
'default' => 'medium',
'options' => [
'small' => esc_html__( 'Small', 'wpforms-lite' ),
'medium' => esc_html__( 'Medium', 'wpforms-lite' ),
'large' => esc_html__( 'Large', 'wpforms-lite' ),
],
],
'email-color-scheme' => [
'id' => 'email-color-scheme',
'name' => esc_html__( 'Color Scheme', 'wpforms-lite' ),
'class' => [ 'hide-for-template-none', $has_eduction, $has_legacy ],
'type' => 'color_scheme',
'is_hidden' => $this->plain_text,
'education_badge' => $has_eduction ? EducationHelpers::get_badge( 'Pro' ) : '',
'data_attributes' => $has_eduction ? array_merge( [ 'name' => esc_html__( 'Color Scheme', 'wpforms-lite' ) ], $education_args ) : [],
'colors' => [
'email_background_color' => [
'name' => esc_html__( 'Background', 'wpforms-lite' ),
'data' => array_merge(
[
'fallback-color' => $style_overrides['email_background_color'],
],
$color_scheme_data
),
],
'email_body_color' => [
'name' => esc_html__( 'Body', 'wpforms-lite' ),
'data' => array_merge(
[
'fallback-color' => $style_overrides['email_body_color'],
],
$color_scheme_data
),
],
'email_text_color' => [
'name' => esc_html__( 'Text', 'wpforms-lite' ),
'data' => array_merge(
[
'fallback-color' => $style_overrides['email_text_color'],
],
$color_scheme_data
),
],
'email_links_color' => [
'name' => esc_html__( 'Links', 'wpforms-lite' ),
'data' => array_merge(
[
'fallback-color' => $style_overrides['email_links_color'],
],
$color_scheme_data
),
],
],
],
'email-typography' => [
'id' => 'email-typography',
'name' => esc_html__( 'Typography', 'wpforms-lite' ),
'desc' => esc_html__( 'Choose the style thats applied to all text in email notifications.', 'wpforms-lite' ),
'class' => [ 'hide-for-template-none', $has_eduction, $has_legacy ],
'education_badge' => $has_eduction ? EducationHelpers::get_badge( 'Pro' ) : '',
'data_attributes' => $has_eduction ? array_merge( [ 'name' => esc_html__( 'Typography', 'wpforms-lite' ) ], $education_args ) : [],
'type' => 'select',
'is_hidden' => $this->plain_text,
'choicesjs' => true,
'default' => 'sans-serif',
'options' => [
'sans-serif' => esc_html__( 'Sans Serif', 'wpforms-lite' ),
'serif' => esc_html__( 'Serif', 'wpforms-lite' ),
],
],
'email-preview' => [
'id' => 'email-preview',
'type' => 'content',
'is_hidden' => empty( $preview_link ),
'content' => $preview_link,
],
'sending-heading' => [
'id' => 'sending-heading',
'content' => '<h4>' . esc_html__( 'Sending', 'wpforms-lite' ) . '</h4>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'email-async' => [
'id' => 'email-async',
'name' => esc_html__( 'Optimize Email Sending', 'wpforms-lite' ),
'desc' => sprintf(
wp_kses( /* translators: %1$s - WPForms.com Email settings documentation URL. */
__( 'Send emails asynchronously, which can make processing faster but may delay email delivery by a minute or two. <a href="%1$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/a-complete-guide-to-wpforms-settings/#email', 'Settings - Email', 'Optimize Email Sending Documentation' ) )
),
'type' => 'toggle',
'status' => true,
],
'email-carbon-copy' => [
'id' => 'email-carbon-copy',
'name' => esc_html__( 'Carbon Copy', 'wpforms-lite' ),
'desc' => esc_html__( 'Enable the ability to CC: email addresses in the form notification settings.', 'wpforms-lite' ),
'type' => 'toggle',
'status' => true,
],
];
// Add background color control if the Pro version is not active or Legacy template is selected.
$settings['email'] = $this->maybe_add_background_color_control( $settings['email'], $style_overrides['email_background_color'] );
// Maybe add the Legacy template notice.
$settings['email'] = $this->maybe_add_legacy_notice( $settings['email'] );
return $settings;
}
/**
* Maybe add the legacy template notice.
*
* @since 1.8.5
*
* @param array $settings Email settings.
*
* @return array
*/
private function maybe_add_legacy_notice( $settings ) {
if ( ! $this->is_settings_page() || ! Helpers::is_legacy_html_template() ) {
return $settings;
}
$content = '<div class="notice-info"><p>';
$content .= sprintf(
wp_kses( /* translators: %1$s - WPForms.com Email settings legacy template documentation URL. */
__( 'Some style settings are not available when using the Legacy template. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn More</a>', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/customizing-form-notification-emails/#legacy-template', 'Settings - Email', 'Legacy Template' ) )
);
$content .= '</p></div>';
// Add the background color control after the header image.
return wpforms_array_insert(
$settings,
[
'email-legacy-notice' => [
'id' => 'email-legacy-notice',
'content' => $content,
'type' => 'content',
'class' => 'wpforms-email-legacy-notice',
],
],
'email-template'
);
}
/**
* Get Email settings heading content.
*
* @since 1.8.5
*
* @return string
*/
private function get_heading_content() {
return wpforms_render( 'admin/settings/email-heading' );
}
/**
* Get current email template hyperlink.
*
* @since 1.8.5
*
* @return string
*/
private function get_current_template_preview_link() {
// Leave if the user has the legacy template is set or the user doesn't have the capability.
if ( ! wpforms_current_user_can() || Helpers::is_legacy_html_template() ) {
return '';
}
$template_name = Helpers::get_current_template_name();
$current_template = Notifications::get_available_templates( $template_name );
// Return empty string if the current template is not found.
// Leave early if the preview link is empty.
if ( ! isset( $current_template['path'] ) || ! class_exists( $current_template['path'] ) || empty( $current_template['preview'] ) ) {
return '';
}
return sprintf(
wp_kses( /* translators: %1$s - Email template preview URL. */
__( '<a href="%1$s" target="_blank" rel="noopener">Preview Email Template</a>', 'wpforms-lite' ),
[
'a' => [
'href' => true,
'target' => true,
'rel' => true,
],
]
),
esc_url( $current_template['preview'] )
);
}
/**
* Maybe add the background color control to the email settings.
* This is only available in the free version.
*
* @since 1.8.5
*
* @param array $settings Email settings.
* @param string $fallback_color Fallback color.
*
* @return array
*/
private function maybe_add_background_color_control( $settings, $fallback_color = '#e9eaec' ) {
// Leave as is if the Pro version is active and no legacy template available.
if ( ! Helpers::is_legacy_html_template() && wpforms()->is_pro() ) {
return $settings;
}
// Add the background color control after the header image.
return wpforms_array_insert(
$settings,
[
'email-background-color' => [
'id' => 'email-background-color',
'name' => esc_html__( 'Background Color', 'wpforms-lite' ),
'desc' => esc_html__( 'Customize the background color of the email template.', 'wpforms-lite' ),
'class' => 'email-background-color',
'type' => 'color',
'is_hidden' => $this->plain_text,
'default' => '#e9eaec',
'data' => [
'fallback-color' => $fallback_color,
'1p-ignore' => 'true', // 1Password ignore.
'lp-ignore' => 'true', // LastPass ignore.
],
],
],
'email-header-image'
);
}
/**
* Gets the class for the header image control.
*
* This is used to determine if the header image is external.
* Legacy header image control was allowing external URLs.
*
* @since 1.8.5
*
* @return string
*/
private function get_external_header_image_class() {
$header_image_url = wpforms_setting( 'email-header-image', '' );
// If the header image URL is empty, return an empty string.
if ( empty( $header_image_url ) ) {
return '';
}
$site_url = home_url(); // Get the current site's URL.
// Get the hosts of the site URL and the header image URL.
$site_url_host = wp_parse_url( $site_url, PHP_URL_HOST );
$header_image_url_host = wp_parse_url( $header_image_url, PHP_URL_HOST );
// Check if the header image URL host is different from the site URL host.
if ( $header_image_url_host && $site_url_host && $header_image_url_host !== $site_url_host ) {
return 'has-external-image-url';
}
return ''; // If none of the conditions match, return an empty string.
}
/**
* Determine if the current page is the "Email" settings page.
*
* @since 1.8.5
*
* @return bool
*/
private function is_settings_page() {
return wpforms_is_admin_page( 'settings', 'email' );
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace WPForms\Admin\Settings;
use WPForms\Helpers\Transient;
/**
* Modern Markup setting element.
*
* @since 1.8.1
*/
class ModernMarkup {
/**
* Settings array.
*
* @since 1.8.1
*
* @var array
*/
private $settings;
/**
* Initialize class.
*
* @since 1.8.1
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
public function hooks() {
add_action( 'wpforms_create_form', [ $this, 'clear_transient' ] );
add_action( 'wpforms_save_form', [ $this, 'clear_transient' ] );
add_action( 'wpforms_delete_form', [ $this, 'clear_transient' ] );
add_action( 'wpforms_form_handler_update_status', [ $this, 'clear_transient' ] );
// Only continue if we are actually on the settings page.
if ( ! wpforms_is_admin_page( 'settings' ) ) {
return;
}
add_filter( 'wpforms_settings_defaults', [ $this, 'register_field' ] );
}
/**
* Register setting field.
*
* @since 1.8.1
*
* @param array $settings Settings data.
*
* @return array
*/
public function register_field( $settings ) {
/**
* Allows to show/hide the Modern Markup setting field on the Settings page.
*
* @since 1.8.1
*
* @param mixed $is_disabled Whether the setting must be hidden.
*/
$is_hidden = apply_filters(
'wpforms_admin_settings_modern_markup_register_field_is_hidden',
wpforms_setting( 'modern-markup-hide-setting' )
);
if ( ! empty( $is_hidden ) ) {
return $settings;
}
$modern_markup = [
'id' => 'modern-markup',
'name' => esc_html__( 'Use Modern Markup', 'wpforms-lite' ),
'desc' => sprintf(
wp_kses( /* translators: %s - WPForms.com form markup setting URL. */
__( 'Use modern markup, which has increased accessibility and allows you to easily customize your forms in the block editor. <a href="%s" target="_blank" rel="noopener noreferrer" class="wpforms-learn-more">Learn More</a>', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
'class' => [],
],
]
),
wpforms_utm_link( 'https://wpforms.com/docs/styling-your-forms/', 'settings-license', 'Form Markup Documentation' )
),
'type' => 'toggle',
'status' => true,
];
$is_disabled_transient = Transient::get( 'modern_markup_setting_disabled' );
// Transient doesn't set or expired.
if ( $is_disabled_transient === false ) {
$forms = wpforms()->get( 'form' )->get( '', [ 'post_status' => 'publish' ] );
$is_disabled_transient = wpforms_has_field_type( 'credit-card', $forms, true ) ? '1' : '0';
// Re-check all the forms for the CC field once per day.
Transient::set( 'modern_markup_setting_disabled', $is_disabled_transient, DAY_IN_SECONDS );
}
/**
* Allows to enable/disable the Modern Markup setting field on the Settings page.
*
* @since 1.8.1
*
* @param mixed $is_disabled Whether the Modern Markup setting must be disabled.
*/
$is_disabled = (bool) apply_filters(
'wpforms_admin_settings_modern_markup_register_field_is_disabled',
! empty( $is_disabled_transient )
);
$current_value = wpforms_setting( 'modern-markup' );
// In the case it is disabled because of the legacy CC field, add corresponding description.
if ( $is_disabled && ! empty( $is_disabled_transient ) && empty( $current_value ) ) {
$modern_markup['disabled'] = true;
$modern_markup['disabled_desc'] = sprintf(
wp_kses( /* translators: %s - WPForms Stripe addon URL. */
__( '<strong>You cannot use modern markup because youre using the deprecated Credit Card field.</strong> If youd like to use modern markup, replace your credit card field with a payment gateway like <a href="%s" target="_blank" rel="noopener noreferrer">Stripe</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
'strong' => [],
]
),
'https://wpforms.com/docs/how-to-install-and-use-the-stripe-addon-with-wpforms'
);
}
$modern_markup = [
'modern-markup' => $modern_markup,
];
$settings['general'] = wpforms_list_insert_after( $settings['general'], 'disable-css', $modern_markup );
return $settings;
}
/**
* Clear transient in the case when the form is created/saved/deleted.
* So, next time when the user will open the Settings page,
* the Modern Markup setting will check for the legacy Credit Card field in all the forms again.
*
* @since 1.8.1
*/
public function clear_transient() {
Transient::delete( 'modern_markup_setting_disabled' );
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace WPForms\Admin\Settings;
/**
* Payments setting page.
* Settings will be accessible via “WPForms” → “Settings” → “Payments”.
*
* @since 1.8.2
*/
class Payments {
/**
* Initialize class.
*
* @since 1.8.2
*/
public function init() {
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.2
*/
private function hooks() {
add_filter( 'wpforms_settings_tabs', [ $this, 'register_settings_tabs' ], 5 );
add_filter( 'wpforms_settings_defaults', [ $this, 'register_settings_fields' ], 5 );
}
/**
* Register "Payments" settings tab.
*
* @since 1.8.2
*
* @param array $tabs Admin area tabs list.
*
* @return array
*/
public function register_settings_tabs( $tabs ) {
$payments = [
'payments' => [
'name' => esc_html__( 'Payments', 'wpforms-lite' ),
'form' => true,
'submit' => esc_html__( 'Save Settings', 'wpforms-lite' ),
],
];
return wpforms_array_insert( $tabs, $payments, 'validation' );
}
/**
* Register "Payments" settings fields.
*
* @since 1.8.2
*
* @param array $settings Admin area settings list.
*
* @return array
*/
public function register_settings_fields( $settings ) {
$currency_option = [];
$currencies = wpforms_get_currencies();
// Format currencies for select element.
foreach ( $currencies as $code => $currency ) {
$currency_option[ $code ] = sprintf( '%s (%s %s)', $currency['name'], $code, $currency['symbol'] );
}
$settings['payments'] = [
'heading' => [
'id' => 'payments-heading',
'content' => '<h4>' . esc_html__( 'Payments', 'wpforms-lite' ) . '</h4>',
'type' => 'content',
'no_label' => true,
'class' => [ 'section-heading', 'no-desc' ],
],
'currency' => [
'id' => 'currency',
'name' => esc_html__( 'Currency', 'wpforms-lite' ),
'type' => 'select',
'choicesjs' => true,
'search' => true,
'default' => 'USD',
'options' => $currency_option,
],
];
return $settings;
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace WPForms\Admin;
/**
* Site Health WPForms Info.
*
* @since 1.5.5
*/
class SiteHealth {
/**
* Indicate if Site Health is allowed to load.
*
* @since 1.5.5
*
* @return bool
*/
private function allow_load() {
global $wp_version;
return version_compare( $wp_version, '5.2', '>=' );
}
/**
* Init Site Health.
*
* @since 1.5.5
*/
final public function init() {
if ( ! $this->allow_load() ) {
return;
}
$this->hooks();
}
/**
* Integration hooks.
*
* @since 1.5.5
*/
protected function hooks() {
add_filter( 'debug_information', [ $this, 'add_info_section' ] );
}
/**
* Add WPForms section to Info tab.
*
* @since 1.5.5
*
* @param array $debug_info Array of all information.
*
* @return array Array with added WPForms info section.
*/
public function add_info_section( $debug_info ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$wpforms = [
'label' => 'WPForms',
'fields' => [
'version' => [
'label' => esc_html__( 'Version', 'wpforms-lite' ),
'value' => WPFORMS_VERSION,
],
],
];
// Install date.
$activated = get_option( 'wpforms_activated', [] );
if ( ! empty( $activated['lite'] ) ) {
$wpforms['fields']['lite'] = [
'label' => esc_html__( 'Lite install date', 'wpforms-lite' ),
'value' => wpforms_datetime_format( $activated['lite'], '', true ),
];
}
if ( ! empty( $activated['pro'] ) ) {
$wpforms['fields']['pro'] = [
'label' => esc_html__( 'Pro install date', 'wpforms-lite' ),
'value' => wpforms_datetime_format( $activated['pro'], '', true ),
];
}
// Permissions for the upload directory.
$upload_dir = wpforms_upload_dir();
$wpforms['fields']['upload_dir'] = [
'label' => esc_html__( 'Uploads directory', 'wpforms-lite' ),
'value' => empty( $upload_dir['error'] ) && ! empty( $upload_dir['path'] ) && wp_is_writable( $upload_dir['path'] ) ? esc_html__( 'Writable', 'wpforms-lite' ) : esc_html__( 'Not writable', 'wpforms-lite' ),
];
// DB tables.
$db_tables = wpforms()->get_existing_custom_tables();
if ( $db_tables ) {
$db_tables_str = empty( $db_tables ) ? esc_html__( 'Not found', 'wpforms-lite' ) : implode( ', ', $db_tables );
$wpforms['fields']['db_tables'] = [
'label' => esc_html__( 'DB tables', 'wpforms-lite' ),
'value' => $db_tables_str,
'private' => true,
];
}
// Total forms.
$wpforms['fields']['total_forms'] = [
'label' => esc_html__( 'Total forms', 'wpforms-lite' ),
'value' => wp_count_posts( 'wpforms' )->publish,
];
if ( ! wpforms()->is_pro() ) {
$forms = wpforms()->get( 'form' )->get( '', [ 'fields' => 'ids' ] );
if ( empty( $forms ) || ! is_array( $forms ) ) {
$forms = [];
}
$count = 0;
foreach ( $forms as $form_id ) {
$count += (int) get_post_meta( $form_id, 'wpforms_entries_count', true );
}
$wpforms['fields']['total_submissions'] = [
'label' => esc_html__( 'Total submissions (since v1.5.0)', 'wpforms-lite' ),
'value' => $count,
];
}
$debug_info['wpforms'] = $wpforms;
return $debug_info;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace WPForms\Admin\Tools;
use WPForms\Admin\Tools\Importers\ContactForm7;
use WPForms\Admin\Tools\Importers\NinjaForms;
use WPForms\Admin\Tools\Importers\PirateForms;
/**
* Load the different form importers.
*
* @since 1.6.6
*/
class Importers {
/**
* Available importers.
*
* @since 1.6.6
*
* @var array
*/
private $importers = [];
/**
* Load default form importers.
*
* @since 1.6.6
*/
public function load() {
if ( empty( $this->importers ) ) {
$this->importers = [
'contact-form-7' => new ContactForm7(),
'ninja-forms' => new NinjaForms(),
'pirate-forms' => new PirateForms(),
];
}
}
/**
* Load default form importers.
*
* @since 1.6.6
*
* @return array
*/
public function get_importers() {
$this->load();
$importers = [];
foreach ( $this->importers as $importer ) {
$importers = $importer->register( $importers );
}
return apply_filters( 'wpforms_importers', $importers );
}
/**
* Get a importer forms.
*
* @since 1.6.6
*
* @param string $provider Provider.
*
* @return array
*/
public function get_importer_forms( $provider ) {
if ( isset( $this->importers[ $provider ] ) ) {
return apply_filters( "wpforms_importer_forms_{$provider}", $this->importers[ $provider ]->get_forms() );
}
return [];
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace WPForms\Admin\Tools\Importers;
/**
* Base Importer class.
*
* @since 1.6.6
*/
abstract class Base implements ImporterInterface {
/**
* Importer name.
*
* @since 1.6.6
*
* @var string
*/
public $name;
/**
* Importer name in slug format.
*
* @since 1.6.6
*
* @var string
*/
public $slug;
/**
* Importer plugin path.
*
* @since 1.6.6
*
* @var string
*/
public $path;
/**
* Primary class constructor.
*
* @since 1.6.6
*/
public function __construct() {
$this->init();
// Import a specific form with AJAX.
add_action( "wp_ajax_wpforms_import_form_{$this->slug}", [ $this, 'import_form' ] );
}
/**
* Add to list of registered importers.
*
* @since 1.6.6
*
* @param array $importers List of supported importers.
*
* @return array
*/
public function register( $importers = [] ) {
$importers[ $this->slug ] = [
'name' => $this->name,
'slug' => $this->slug,
'path' => $this->path,
'installed' => file_exists( trailingslashit( WP_PLUGIN_DIR ) . $this->path ),
'active' => $this->is_active(),
];
return $importers;
}
/**
* If the importer source is available.
*
* @since 1.6.6
*
* @return bool
*/
protected function is_active() {
return is_plugin_active( $this->path );
}
/**
* Add the new form to the database and return AJAX data.
*
* @since 1.6.6
*
* @param array $form Form to import.
* @param array $unsupported List of unsupported fields.
* @param array $upgrade_plain List of fields, that are supported inside the paid WPForms, but not in Lite.
* @param array $upgrade_omit No field alternative in WPForms.
*/
public function add_form( $form, $unsupported = [], $upgrade_plain = [], $upgrade_omit = [] ) {
// Create empty form so we have an ID to work with.
$form_id = wp_insert_post(
[
'post_status' => 'publish',
'post_type' => 'wpforms',
]
);
if ( empty( $form_id ) || is_wp_error( $form_id ) ) {
wp_send_json_success(
[
'error' => true,
'name' => sanitize_text_field( $form['settings']['form_title'] ),
'msg' => esc_html__( 'There was an error while creating a new form.', 'wpforms-lite' ),
]
);
}
$form['id'] = $form_id;
$form['field_id'] = count( $form['fields'] ) + 1;
// Update the form with all our compiled data.
wpforms()->form->update( $form_id, $form );
// Make note that this form has been imported.
$this->track_import( $form['settings']['import_form_id'], $form_id );
// Build and send final AJAX response!
wp_send_json_success(
[
'name' => $form['settings']['form_title'],
'edit' => esc_url_raw( admin_url( 'admin.php?page=wpforms-builder&view=fields&form_id=' . $form_id ) ),
'preview' => wpforms_get_form_preview_url( $form_id ),
'unsupported' => $unsupported,
'upgrade_plain' => $upgrade_plain,
'upgrade_omit' => $upgrade_omit,
]
);
}
/**
* After a form has been successfully imported we track it, so that in the
* future we can alert users if they try to import a form that has already
* been imported.
*
* @since 1.6.6
*
* @param int $source_id Imported plugin form ID.
* @param int $wpforms_id WPForms form ID.
*/
public function track_import( $source_id, $wpforms_id ) {
$imported = get_option( 'wpforms_imported', [] );
$imported[ $this->slug ][ $wpforms_id ] = $source_id;
update_option( 'wpforms_imported', $imported, false );
}
}

View File

@@ -0,0 +1,686 @@
<?php
namespace WPForms\Admin\Tools\Importers;
/**
* Contact Form 7 Importer class.
*
* @since 1.6.6
*/
class ContactForm7 extends Base {
/**
* Define required properties.
*
* @since 1.6.6
*/
public function init() {
$this->name = 'Contact Form 7';
$this->slug = 'contact-form-7';
$this->path = 'contact-form-7/wp-contact-form-7.php';
}
/**
* Get ALL THE FORMS.
*
* @since 1.6.6
*/
public function get_forms() {
$forms_final = [];
if ( ! $this->is_active() ) {
return $forms_final;
}
$forms = \WPCF7_ContactForm::find( [ 'posts_per_page' => - 1 ] );
if ( empty( $forms ) ) {
return $forms_final;
}
foreach ( $forms as $form ) {
if ( ! empty( $form ) && ( $form instanceof \WPCF7_ContactForm ) ) {
$forms_final[ $form->id() ] = $form->title();
}
}
return $forms_final;
}
/**
* Get a single form.
*
* @since 1.6.6
*
* @param int $id Form ID.
*
* @return \WPCF7_ContactForm|bool
*/
public function get_form( $id ) {
$form = \WPCF7_ContactForm::find(
[
'posts_per_page' => 1,
'p' => $id,
]
);
if ( ! empty( $form[0] ) && ( $form[0] instanceof \WPCF7_ContactForm ) ) {
return $form[0];
}
return false;
}
/**
* Import a single form using AJAX.
*
* @since 1.6.6
*/
public function import_form() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'create_forms' ) ) {
wp_send_json_error();
}
// Define some basic information.
$analyze = isset( $_POST['analyze'] );
$cf7_id = ! empty( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0;
$cf7_form = $this->get_form( $cf7_id );
if ( ! $cf7_form ) {
wp_send_json_error(
[
'error' => true,
'name' => esc_html__( 'Unknown Form', 'wpforms-lite' ),
'msg' => esc_html__( 'The form you are trying to import does not exist.', 'wpforms-lite' ),
]
);
}
$cf7_form_name = $cf7_form->title();
$cf7_fields = $cf7_form->scan_form_tags();
$cf7_properties = $cf7_form->get_properties();
$cf7_recaptcha = false;
$fields_pro_plain = [ 'url', 'tel', 'date' ];
$fields_pro_omit = [ 'file' ];
$fields_unsupported = [ 'quiz', 'hidden' ];
$upgrade_plain = [];
$upgrade_omit = [];
$unsupported = [];
$form = [
'id' => '',
'field_id' => '',
'fields' => [],
'settings' => [
'form_title' => $cf7_form_name,
'form_desc' => '',
'submit_text' => esc_html__( 'Submit', 'wpforms-lite' ),
'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ),
'antispam' => '1',
'notification_enable' => '1',
'notifications' => [
1 => [
'notification_name' => esc_html__( 'Notification 1', 'wpforms-lite' ),
'email' => '{admin_email}',
'subject' => sprintf( /* translators: %s - form name. */
esc_html__( 'New Entry: %s', 'wpforms-lite' ),
$cf7_form_name
),
'sender_name' => get_bloginfo( 'name' ),
'sender_address' => '{admin_email}',
'replyto' => '',
'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',
],
],
'import_form_id' => $cf7_id,
],
];
// If form does not contain fields, bail.
if ( empty( $cf7_fields ) ) {
wp_send_json_success(
[
'error' => true,
'name' => sanitize_text_field( $cf7_form_name ),
'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ),
]
);
}
// Convert fields.
foreach ( $cf7_fields as $cf7_field ) {
if ( ! $cf7_field instanceof \WPCF7_FormTag ) {
continue;
}
// Try to determine field label to use.
$label = $this->get_field_label( $cf7_properties['form'], $cf7_field->type, $cf7_field->name );
// Next, check if field is unsupported. If supported make note and
// then continue to the next field.
if ( in_array( $cf7_field->basetype, $fields_unsupported, true ) ) {
$unsupported[] = $label;
continue;
}
// Now check if this install is Lite. If it is Lite and it's a
// field type not included, make a note then continue to the next
// field.
if ( ! wpforms()->is_pro() && in_array( $cf7_field->basetype, $fields_pro_plain, true ) ) {
$upgrade_plain[] = $label;
}
if ( ! wpforms()->is_pro() && in_array( $cf7_field->basetype, $fields_pro_omit, true ) ) {
$upgrade_omit[] = $label;
continue;
}
// Determine next field ID to assign.
if ( empty( $form['fields'] ) ) {
$field_id = 1;
} else {
$field_id = (int) max( array_keys( $form['fields'] ) ) + 1;
}
switch ( $cf7_field->basetype ) {
// Plain text, email, URL, number, and textarea fields.
case 'text':
case 'email':
case 'url':
case 'number':
case 'textarea':
$type = $cf7_field->basetype;
if ( $type === 'url' && ! wpforms()->is_pro() ) {
$type = 'text';
}
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'size' => 'medium',
'required' => $cf7_field->is_required() ? '1' : '',
'placeholder' => $this->get_field_placeholder_default( $cf7_field ),
'default_value' => $this->get_field_placeholder_default( $cf7_field, 'default' ),
'cf7_name' => $cf7_field->name,
];
break;
// Phone number field.
case 'tel':
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'phone',
'label' => $label,
'format' => 'international',
'size' => 'medium',
'required' => $cf7_field->is_required() ? '1' : '',
'placeholder' => $this->get_field_placeholder_default( $cf7_field ),
'default_value' => $this->get_field_placeholder_default( $cf7_field, 'default' ),
'cf7_name' => $cf7_field->name,
];
break;
// Date field.
case 'date':
$type = wpforms()->is_pro() ? 'date-time' : 'text';
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'format' => 'date',
'size' => 'medium',
'required' => $cf7_field->is_required() ? '1' : '',
'date_placeholder' => '',
'date_format' => 'm/d/Y',
'date_type' => 'datepicker',
'time_format' => 'g:i A',
'time_interval' => 30,
'cf7_name' => $cf7_field->name,
];
break;
// Select, radio, and checkbox fields.
case 'select':
case 'radio':
case 'checkbox':
$choices = [];
$options = (array) $cf7_field->labels;
foreach ( $options as $option ) {
$choices[] = [
'label' => $option,
'value' => '',
];
}
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $cf7_field->basetype,
'label' => $label,
'choices' => $choices,
'size' => 'medium',
'required' => $cf7_field->is_required() ? '1' : '',
'cf7_name' => $cf7_field->name,
];
if (
$cf7_field->basetype === 'select' &&
$cf7_field->has_option( 'include_blank' )
) {
$form['fields'][ $field_id ]['placeholder'] = '---';
}
break;
// File upload field.
case 'file':
$extensions = '';
$max_size = '';
$file_types = $cf7_field->get_option( 'filetypes' );
$limit = $cf7_field->get_option( 'limit' );
if ( ! empty( $file_types[0] ) ) {
$extensions = implode( ',', explode( '|', strtolower( preg_replace( '/[^A-Za-z0-9|]/', '', strtolower( $file_types[0] ) ) ) ) );
}
if ( ! empty( $limit[0] ) ) {
$limit = $limit[0];
$mb = ( strpos( $limit, 'm' ) !== false );
$kb = ( strpos( $limit, 'kb' ) !== false );
$limit = (int) preg_replace( '/[^0-9]/', '', $limit );
if ( $mb ) {
$max_size = $limit;
} elseif ( $kb ) {
$max_size = round( $limit / 1024, 1 );
} else {
$max_size = round( $limit / 1048576, 1 );
}
}
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'file-upload',
'label' => $label,
'size' => 'medium',
'extensions' => $extensions,
'max_size' => $max_size,
'required' => $cf7_field->is_required() ? '1' : '',
'cf7_name' => $cf7_field->name,
];
break;
// Acceptance field.
case 'acceptance':
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'checkbox',
'label' => esc_html__( 'Acceptance Field', 'wpforms-lite' ),
'choices' => [
1 => [
'label' => $label,
'value' => '',
],
],
'size' => 'medium',
'required' => '1',
'label_hide' => '1',
'cf7_name' => $cf7_field->name,
];
break;
// ReCAPTCHA field.
case 'recaptcha':
$cf7_recaptcha = true;
}
}
// If we are only analyzing the form, we can stop here and return the
// details about this form.
if ( $analyze ) {
wp_send_json_success(
[
'name' => $cf7_form_name,
'upgrade_plain' => $upgrade_plain,
'upgrade_omit' => $upgrade_omit,
]
);
}
// Settings.
// Confirmation message.
if ( ! empty( $cf7_properties['messages']['mail_sent_ok'] ) ) {
$form['settings']['confirmation_message'] = $cf7_properties['messages']['mail_sent_ok'];
}
// ReCAPTCHA.
if ( $cf7_recaptcha ) {
// If the user has already defined v2 reCAPTCHA keys in the WPForms
// settings, use those.
$site_key = wpforms_setting( 'recaptcha-site-key', '' );
$secret_key = wpforms_setting( 'recaptcha-secret-key', '' );
$type = wpforms_setting( 'recaptcha-type', 'v2' );
// Try to abstract keys from CF7.
if ( empty( $site_key ) || empty( $secret_key ) ) {
$cf7_settings = get_option( 'wpcf7' );
if (
! empty( $cf7_settings['recaptcha'] ) &&
is_array( $cf7_settings['recaptcha'] )
) {
foreach ( $cf7_settings['recaptcha'] as $key => $val ) {
if ( ! empty( $key ) && ! empty( $val ) ) {
$site_key = $key;
$secret_key = $val;
}
}
$wpforms_settings = get_option( 'wpforms_settings', [] );
$wpforms_settings['recaptcha-site-key'] = $site_key;
$wpforms_settings['recaptcha-secret-key'] = $secret_key;
update_option( 'wpforms_settings', $wpforms_settings );
}
}
// Don't enable reCAPTCHA if user had configured invisible reCAPTCHA.
if (
$type === 'v2' &&
! empty( $site_key ) &&
! empty( $secret_key )
) {
$form['settings']['recaptcha'] = '1';
}
}
// Setup email notifications.
if ( ! empty( $cf7_properties['mail']['subject'] ) ) {
$form['settings']['notifications'][1]['subject'] = $this->get_smarttags( $cf7_properties['mail']['subject'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail']['recipient'] ) ) {
$form['settings']['notifications'][1]['email'] = $this->get_smarttags( $cf7_properties['mail']['recipient'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail']['body'] ) ) {
$form['settings']['notifications'][1]['message'] = $this->get_smarttags( $cf7_properties['mail']['body'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail']['additional_headers'] ) ) {
$form['settings']['notifications'][1]['replyto'] = $this->get_replyto( $cf7_properties['mail']['additional_headers'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail']['sender'] ) ) {
$sender = $this->get_sender_details( $cf7_properties['mail']['sender'], $form['fields'] );
if ( $sender ) {
$form['settings']['notifications'][1]['sender_name'] = $sender['name'];
$form['settings']['notifications'][1]['sender_address'] = $sender['address'];
}
}
if ( ! empty( $cf7_properties['mail_2'] ) && (int) $cf7_properties['mail_2']['active'] === 1 ) {
// Check if a secondary notification is enabled, if so set defaults
// and set it up.
$form['settings']['notifications'][2] = [
'notification_name' => esc_html__( 'Notification 2', 'wpforms-lite' ),
'email' => '{admin_email}',
'subject' => sprintf( /* translators: %s - form name. */
esc_html__( 'New Entry: %s', 'wpforms-lite' ),
$cf7_form_name
),
'sender_name' => get_bloginfo( 'name' ),
'sender_address' => '{admin_email}',
'replyto' => '',
'message' => '{all_fields}',
];
if ( ! empty( $cf7_properties['mail_2']['subject'] ) ) {
$form['settings']['notifications'][2]['subject'] = $this->get_smarttags( $cf7_properties['mail_2']['subject'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail_2']['recipient'] ) ) {
$form['settings']['notifications'][2]['email'] = $this->get_smarttags( $cf7_properties['mail_2']['recipient'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail_2']['body'] ) ) {
$form['settings']['notifications'][2]['message'] = $this->get_smarttags( $cf7_properties['mail_2']['body'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail_2']['additional_headers'] ) ) {
$form['settings']['notifications'][2]['replyto'] = $this->get_replyto( $cf7_properties['mail_2']['additional_headers'], $form['fields'] );
}
if ( ! empty( $cf7_properties['mail_2']['sender'] ) ) {
$sender = $this->get_sender_details( $cf7_properties['mail_2']['sender'], $form['fields'] );
if ( $sender ) {
$form['settings']['notifications'][2]['sender_name'] = $sender['name'];
$form['settings']['notifications'][2]['sender_address'] = $sender['address'];
}
}
}
$this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit );
}
/**
* Lookup and return the placeholder or default value.
*
* @since 1.6.6
*
* @param object $field Field object.
* @param string $type Type of the field.
*
* @return string
*/
public function get_field_placeholder_default( $field, $type = 'placeholder' ) {
$placeholder = '';
$default_value = (string) reset( $field->values );
if ( $field->has_option( 'placeholder' ) || $field->has_option( 'watermark' ) ) {
$placeholder = $default_value;
$default_value = '';
}
if ( $type === 'placeholder' ) {
return $placeholder;
}
return $default_value;
}
/**
* Get the field label.
*
* @since 1.6.6
*
* @param string $form Form data and settings.
* @param string $type Field type.
* @param string $name Field name.
*
* @return string
*/
public function get_field_label( $form, $type, $name = '' ) {
preg_match_all( '/<label>([ \w\S\r\n\t]+?)<\/label>/', $form, $matches );
foreach ( $matches[1] as $match ) {
$match = trim( str_replace( "\n", '', $match ) );
preg_match( '/\[(?:' . preg_quote( $type ) . ') ' . $name . '(?:[ ](.*?))?(?:[\r\n\t ](\/))?\]/', $match, $input_match );
if ( ! empty( $input_match[0] ) ) {
return strip_shortcodes( sanitize_text_field( str_replace( $input_match[0], '', $match ) ) );
}
}
$label = sprintf( /* translators: %1$s - field type, %2$s - field name if available. */
esc_html__( '%1$s Field %2$s', 'wpforms-lite' ),
ucfirst( $type ),
! empty( $name ) ? "($name)" : ''
);
return trim( $label );
}
/**
* Replace 3rd-party form provider tags/shortcodes with our own Smart Tags.
*
* @since 1.6.6
*
* @param string $string Text to look for Smart Tags in.
* @param array $fields List of fields to process Smart Tags in.
*
* @return string
*/
public function get_smarttags( $string, $fields ) {
preg_match_all( '/\[(.+?)\]/', $string, $tags );
if ( empty( $tags[1] ) ) {
return $string;
}
// Process form-tags and mail-tags.
foreach ( $tags[1] as $tag ) {
foreach ( $fields as $field ) {
if ( ! empty( $field['cf7_name'] ) && $field['cf7_name'] === $tag ) {
$string = str_replace( '[' . $tag . ']', '{field_id="' . $field['id'] . '"}', $string );
}
}
}
// Process CF7 tags that we can map with WPForms alternatives.
$string = str_replace(
[
'[_remote_ip]',
'[_date]',
'[_serial_number]',
'[_post_id]',
'[_post_title]',
'[_post_url]',
'[_url]',
'[_post_author]',
'[_post_author_email]',
'[_site_admin_email]',
'[_user_login]',
'[_user_email]',
'[_user_first_name]',
'[_user_last_name]',
'[_user_nickname]',
'[_user_display_name]',
],
[
'{user_ip}',
'{date format="m/d/Y"}',
'{entry_id}',
'{page_id}',
'{page_title}',
'{page_url}',
'{page_url}',
'{author_display}',
'{author_email}',
'{admin_email}',
'{user_display}',
'{user_email}',
'{user_first_name}',
'{user_last_name}',
'{user_display}',
'{user_full_name}',
],
$string
);
// Replace those CF7 that are used in Notifications by default and that we can't leave empty.
$string = str_replace(
[
'[_site_title]',
'[_site_description]',
'[_site_url]',
],
[
get_bloginfo( 'name' ),
get_bloginfo( 'description' ),
get_bloginfo( 'url' ),
],
$string
);
/*
* We are not replacing certain special CF7 tags: [_user_url], [_post_name], [_time], [_user_agent].
* Without them some logic may be broken and for user it will be harder to stop missing strings.
* With them - they can see strange text and will be able to understand, based on the tag name, which value is expected there.
*/
return $string;
}
/**
* Find Reply-To in headers if provided.
*
* @since 1.6.6
*
* @param string $headers CF7 email headers.
* @param array $fields List of fields.
*
* @return string
*/
public function get_replyto( $headers, $fields ) {
if ( strpos( $headers, 'Reply-To:' ) !== false ) {
preg_match( '/Reply-To: \[(.+?)\]/', $headers, $tag );
if ( ! empty( $tag[1] ) ) {
foreach ( $fields as $field ) {
if ( ! empty( $field['cf7_name'] ) && $field['cf7_name'] === $tag[1] ) {
return '{field_id="' . $field['id'] . '"}';
}
}
}
}
return '';
}
/**
* Sender information.
*
* @since 1.6.6
*
* @param string $sender Sender strings in "Name <email@example.com>" format.
* @param array $fields List of fields.
*
* @return bool|array
*/
public function get_sender_details( $sender, $fields ) {
preg_match( '/(.+?)\<(.+?)\>/', $sender, $tag );
if ( ! empty( $tag[1] ) && ! empty( $tag[2] ) ) {
return [
'name' => $this->get_smarttags( $tag[1], $fields ),
'address' => $this->get_smarttags( $tag[2], $fields ),
];
}
return false;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace WPForms\Admin\Tools\Importers;
/**
* Interface WPForms_Importer_Interface to handle common methods for all importers.
*
* @since 1.6.6
*/
interface ImporterInterface {
/**
* Define required properties.
*
* @since 1.6.6
*/
public function init();
/**
* Get ALL THE FORMS.
*
* @since 1.6.6
*/
public function get_forms();
/**
* Get a single form.
*
* @since 1.6.6
*
* @param int $id Form ID.
*/
public function get_form( $id );
/**
* Import a single form using AJAX.
*
* @since 1.6.6
*/
public function import_form();
/**
* Replace 3rd-party form provider tags/shortcodes with our own Smart Tags.
*
* @since 1.6.6
*
* @param string $string Text to look for Smart Tags in.
* @param array $fields List of fields to process Smart Tags in.
*
* @return string
*/
public function get_smarttags( $string, $fields );
}

View File

@@ -0,0 +1,541 @@
<?php
namespace WPForms\Admin\Tools\Importers;
/**
* Ninja Forms Importer class.
*
* @since 1.6.6
*/
class NinjaForms extends Base {
/**
* Define required properties.
*
* @since 1.6.6
*/
public function init() {
$this->name = 'Ninja Forms';
$this->slug = 'ninja-forms';
$this->path = 'ninja-forms/ninja-forms.php';
}
/**
* Get ALL THE FORMS.
*
* @since 1.6.6
*
* @return \NF_Database_Models_Form[]
*/
public function get_forms() {
$forms_final = [];
if ( ! $this->is_active() ) {
return $forms_final;
}
$forms = \Ninja_Forms()->form()->get_forms();
if ( ! empty( $forms ) ) {
foreach ( $forms as $form ) {
if ( ! $form instanceof \NF_Database_Models_Form ) {
continue;
}
$forms_final[ $form->get_id() ] = $form->get_setting( 'title' );
}
}
return $forms_final;
}
/**
* Get a single form.
*
* @since 1.6.6
*
* @param int $id Form ID.
*
* @return array
*/
public function get_form( $id ) {
$form = [];
$form['settings'] = \Ninja_Forms()->form( $id )->get()->get_settings();
$fields = \Ninja_Forms()->form( $id )->get_fields();
$actions = \Ninja_Forms()->form( $id )->get_actions();
foreach ( $fields as $field ) {
if ( ! $field instanceof \NF_Database_Models_Field ) {
continue;
}
$form['fields'][] = array_merge(
[
'id' => $field->get_id(),
],
$field->get_settings()
);
}
foreach ( $actions as $action ) {
if ( ! $action instanceof \NF_Database_Models_Action ) {
continue;
}
$form['actions'][] = $action->get_settings();
}
return $form;
}
/**
* Import a single form using AJAX.
*
* @since 1.6.6
*/
public function import_form() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'create_forms' ) ) {
wp_send_json_error();
}
// Define some basic information.
$analyze = isset( $_POST['analyze'] );
$nf_id = ! empty( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0;
$nf_form = $this->get_form( $nf_id );
$nf_form_name = $nf_form['settings']['title'];
$nf_recaptcha = false;
$nf_recaptcha_type = 'v2';
$fields_pro_plain = [ 'phone', 'date' ];
$fields_pro_omit = [ 'html', 'divider' ];
$fields_unsupported = [ 'spam', 'starrating', 'listmultiselect', 'hidden', 'total', 'shipping', 'quantity', 'product' ];
$upgrade_plain = [];
$upgrade_omit = [];
$unsupported = [];
$form = [
'id' => '',
'field_id' => '',
'fields' => [],
'settings' => [
'form_title' => $nf_form_name,
'form_desc' => '',
'submit_text' => esc_html__( 'Submit', 'wpforms-lite' ),
'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ),
'antispam' => '1',
'notification_enable' => '1',
'notifications' => [
1 => [
'notification_name' => esc_html__( 'Notification 1', 'wpforms-lite' ),
'email' => '{admin_email}',
'subject' => sprintf( /* translators: %s - form name. */
esc_html__( 'New Entry: %s', 'wpforms-lite' ),
$nf_form_name
),
'sender_name' => get_bloginfo( 'name' ),
'sender_address' => '{admin_email}',
'replyto' => '',
'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',
],
],
'import_form_id' => $nf_id,
],
];
// If form does not contain fields, bail.
if ( empty( $nf_form['fields'] ) ) {
wp_send_json_success(
[
'error' => true,
'name' => sanitize_text_field( $nf_form_name ),
'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ),
]
);
}
// Convert fields.
foreach ( $nf_form['fields'] as $nf_field ) {
// Try to determine field label to use.
$label = $this->get_field_label( $nf_field );
// Next, check if field is unsupported. If unsupported make note and
// then continue to the next field.
if ( in_array( $nf_field['type'], $fields_unsupported, true ) ) {
$unsupported[] = $label;
continue;
}
// Now check if this install is Lite. If it is Lite and it's a
// field type not included, make a note then continue to the next
// field.
if ( ! wpforms()->is_pro() && in_array( $nf_field['type'], $fields_pro_plain, true ) ) {
$upgrade_plain[] = $label;
}
if ( ! wpforms()->is_pro() && in_array( $nf_field['type'], $fields_pro_omit, true ) ) {
$upgrade_omit[] = $label;
continue;
}
// Determine next field ID to assign.
if ( empty( $form['fields'] ) ) {
$field_id = 1;
} else {
$field_id = (int) max( array_keys( $form['fields'] ) ) + 1;
}
switch ( $nf_field['type'] ) {
// Single line text, address, city, first name, last name,
// zipcode, email, number, textarea fields.
case 'textbox':
case 'address':
case 'city':
case 'firstname':
case 'lastname':
case 'zip':
case 'email':
case 'number':
case 'textarea':
$type = 'text';
if ( $nf_field['type'] === 'email' ) {
$type = 'email';
} elseif ( $nf_field['type'] === 'number' ) {
$type = 'number';
} elseif ( $nf_field['type'] === 'textarea' ) {
$type = 'textarea';
}
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '',
'size' => 'medium',
'required' => ! empty( $nf_field['required'] ) ? '1' : '',
'placeholder' => ! empty( $nf_field['placeholder'] ) ? $nf_field['placeholder'] : '',
'default_value' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '',
'nf_key' => $nf_field['key'],
];
break;
// Single checkbox field.
case 'checkbox':
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'checkbox',
'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ),
'choices' => [
1 => [
'label' => $label,
'value' => '',
],
],
'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '',
'size' => 'medium',
'required' => ! empty( $nf_field['required'] ) ? '1' : '',
'label_hide' => '1',
'nf_key' => $nf_field['key'],
];
break;
// Multi-check field, radio, select, state, and country fields.
case 'listcheckbox':
case 'listradio':
case 'listselect':
case 'liststate':
case 'listcountry':
$type = 'select';
if ( $nf_field['type'] === 'listcheckbox' ) {
$type = 'checkbox';
} elseif ( $nf_field['type'] === 'listradio' ) {
$type = 'radio';
}
$choices = [];
if ( $nf_field['type'] === 'listcountry' ) {
$countries = wpforms_countries();
foreach ( $countries as $key => $country ) {
$choices[] = [
'label' => $country,
'value' => $key,
'default' => isset( $nf_field['default'] ) && $nf_field['default'] === $key ? '1' : '',
];
}
} else {
foreach ( $nf_field['options'] as $option ) {
$choices[] = [
'label' => $option['label'],
'value' => $option['value'],
];
}
}
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'choices' => $choices,
'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '',
'size' => 'medium',
'required' => ! empty( $nf_field['required'] ) ? '1' : '',
'nf_key' => $nf_field['key'],
];
break;
// HTML field.
case 'html':
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'html',
'code' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '',
'label_disable' => '1',
'nf_key' => $nf_field['key'],
];
break;
// Divider field.
case 'hr':
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => 'divider',
'label' => '',
'description' => '',
'label_disable' => '1',
'nf_key' => $nf_field['key'],
];
break;
// Phone number field.
case 'phone':
$type = wpforms()->is_pro() ? 'phone' : 'text';
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'format' => ! empty( $nf_field['mask'] ) && '(999) 999-9999' === $nf_field['mask'] ? 'us' : 'international',
'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '',
'size' => 'medium',
'required' => ! empty( $nf_field['required'] ) ? '1' : '',
'placeholder' => ! empty( $nf_field['placeholder'] ) ? $nf_field['placeholder'] : '',
'default_value' => ! empty( $nf_field['default'] ) ? $nf_field['default'] : '',
'nf_key' => $nf_field['key'],
];
break;
// Date field.
case 'date':
$type = wpforms()->is_pro() ? 'date-time' : 'text';
$form['fields'][ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'description' => ! empty( $nf_field['desc_text'] ) ? $nf_field['desc_text'] : '',
'format' => 'date',
'size' => 'medium',
'required' => ! empty( $nf_field['required'] ) ? '1' : '',
'date_placeholder' => '',
'date_format' => 'm/d/Y',
'date_type' => 'datepicker',
'time_format' => 'g:i A',
'time_interval' => 30,
'nf_key' => $nf_field['key'],
];
break;
// ReCAPTCHA field.
case 'recaptcha':
$nf_recaptcha = true;
if ( $nf_field['size'] === 'invisible' ) {
$nf_recaptcha_type = 'invisible';
}
}
}
// If we are only analyzing the form, we can stop here and return the
// details about this form.
if ( $analyze ) {
wp_send_json_success(
[
'name' => $nf_form_name,
'upgrade_plain' => $upgrade_plain,
'upgrade_omit' => $upgrade_omit,
]
);
}
// Settings.
// Confirmation message.
foreach ( $nf_form['actions'] as $action ) {
if ( $action['type'] === 'successmessage' ) {
$form['settings']['confirmations'][1]['message'] = $this->get_smarttags( $action['message'], $form['fields'] );
}
}
// ReCAPTCHA.
if ( $nf_recaptcha ) {
// If the user has already defined v2 reCAPTCHA keys in the WPForms
// settings, use those.
$site_key = wpforms_setting( 'recaptcha-site-key', '' );
$secret_key = wpforms_setting( 'recaptcha-secret-key', '' );
// Try to abstract keys from NF.
if ( empty( $site_key ) || empty( $secret_key ) ) {
$nf_settings = get_option( 'ninja_forms_settings' );
if ( ! empty( $nf_settings['recaptcha_site_key'] ) && ! empty( $nf_settings['recaptcha_secret_key'] ) ) {
$wpforms_settings = get_option( 'wpforms_settings', [] );
$wpforms_settings['recaptcha-site-key'] = $nf_settings['recaptcha_site_key'];
$wpforms_settings['recaptcha-secret-key'] = $nf_settings['recaptcha_secret_key'];
$wpforms_settings['recaptcha-type'] = $nf_recaptcha_type;
update_option( 'wpforms_settings', $wpforms_settings );
}
}
if ( ! empty( $site_key ) && ! empty( $secret_key ) ) {
$form['settings']['recaptcha'] = '1';
}
}
// Setup email notifications.
$action_count = 1;
$action_defaults = [
'notification_name' => esc_html__( 'Notification', 'wpforms-lite' ) . " $action_count",
'email' => '{admin_email}',
'subject' => sprintf( /* translators: %s - form name. */
esc_html__( 'New Entry: %s', 'wpforms-lite' ),
$nf_form_name
),
'sender_name' => get_bloginfo( 'name' ),
'sender_address' => '{admin_email}',
'replyto' => '',
'message' => '{all_fields}',
];
foreach ( $nf_form['actions'] as $action ) {
if ( $action['type'] !== 'email' ) {
continue;
}
$action_defaults['notification_name'] = esc_html__( 'Notification', 'wpforms-lite' ) . " $action_count";
$form['settings']['notifications'][ $action_count ] = $action_defaults;
if ( ! empty( $action['label'] ) ) {
$form['settings']['notifications'][ $action_count ]['notification_name'] = $action['label'];
}
if ( ! empty( $action['to'] ) ) {
$form['settings']['notifications'][ $action_count ]['email'] = $this->get_smarttags( $action['to'], $form['fields'] );
}
if ( ! empty( $action['reply_to'] ) ) {
$form['settings']['notifications'][ $action_count ]['replyto'] = $this->get_smarttags( $action['reply_to'], $form['fields'] );
}
if ( ! empty( $action['email_subject'] ) ) {
$form['settings']['notifications'][ $action_count ]['subject'] = $this->get_smarttags( $action['email_subject'], $form['fields'] );
}
if ( ! empty( $action['email_message'] ) ) {
$form['settings']['notifications'][ $action_count ]['message'] = $this->get_smarttags( $action['email_message'], $form['fields'] );
}
if ( ! empty( $action['from_name'] ) ) {
$form['settings']['notifications'][ $action_count ]['sender_name'] = $this->get_smarttags( $action['from_name'], $form['fields'] );
}
if ( ! empty( $action['from_address'] ) ) {
$form['settings']['notifications'][ $action_count ]['sender_address'] = $this->get_smarttags( $action['from_address'], $form['fields'] );
}
$action_count ++;
}
$this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit );
}
/**
* Get the field label.
*
* @since 1.6.6
*
* @param array $field Field data.
*
* @return string
*/
public function get_field_label( $field ) {
if ( ! empty( $field['label'] ) ) {
$label = sanitize_text_field( $field['label'] );
} else {
$label = sprintf( /* translators: %s - field type. */
esc_html__( '%s Field', 'wpforms-lite' ),
ucfirst( $field['type'] )
);
}
return trim( $label );
}
/**
* Replace 3rd-party form provider tags/shortcodes with our own Smart Tags.
*
* @since 1.6.6
*
* @param string $string Text to look for Smart Tags in.
* @param array $fields List of fields to process Smart Tags in.
*
* @return string
*/
public function get_smarttags( $string, $fields ) {
preg_match_all( '/\{(.+?)\}/', $string, $tags );
if ( empty( $tags[1] ) ) {
return $string;
}
foreach ( $tags[1] as $tag ) {
$tag_formatted = str_replace( 'field:', '', $tag );
foreach ( $fields as $field ) {
if ( ! empty( $field['nf_key'] ) && $field['nf_key'] === $tag_formatted ) {
$string = str_replace( '{' . $tag . '}', '{field_id="' . $field['id'] . '"}', $string );
}
}
if ( in_array( $tag, [ 'wp:admin_email', 'system:admin_email' ], true ) ) {
$string = str_replace( [ '{wp:admin_email}', '{system:admin_email}' ], '{admin_email}', $string );
}
if ( $tag === 'all_fields_table' || $tag === 'fields_table' ) {
$string = str_replace( '{' . $tag . '}', '{all_fields}', $string );
}
}
return $string;
}
}

View File

@@ -0,0 +1,684 @@
<?php
namespace WPForms\Admin\Tools\Importers;
use WPForms\Helpers\PluginSilentUpgrader;
use WPForms\Helpers\PluginSilentUpgraderSkin;
/**
* Pirate Forms Importer class.
*
* @since 1.6.6
*/
class PirateForms extends Base {
/**
* Direct URL to download the latest version of WP Mail SMTP plugin from WP.org repo.
*
* @since 1.6.6
*
* @var string
*/
const URL_SMTP_ZIP = 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip';
/**
* WP Mail SMTP plugin basename.
*
* @since 1.6.6
*
* @var string
*/
const SLUG_SMTP_PLUGIN = 'wp-mail-smtp/wp_mail_smtp.php';
/**
* Default PirateForms smart tags.
*
* @since 1.6.6
*
* @var array
*/
public static $tags = [
'[email]',
];
/**
* Define required properties.
*
* @since 1.6.6
*/
public function init() {
$this->name = 'Pirate Forms';
$this->slug = 'pirate-forms';
$this->path = 'pirate-forms/pirate-forms.php';
}
/**
* Get ALL THE FORMS.
* We need only ID's and names here.
*
* @since 1.6.6
*
* @return array
*/
public function get_forms() {
// Union those arrays, as array_merge() does keys reindexing.
$forms = $this->get_default_forms() + $this->get_pro_forms();
// Sort by IDs ASC.
ksort( $forms );
return $forms;
}
/**
* Pirate Forms has a default form, which doesn't have an ID.
*
* @since 1.6.6
*
* @return array
*/
protected function get_default_forms() {
$form = \PirateForms_Util::get_form_options();
// Just make sure that it's there and not broken.
if ( empty( $form ) ) {
return [];
}
return [ 0 => esc_html__( 'Default Form', 'wpforms-lite' ) ];
}
/**
* Copy-paste from Pro plugin code, it doesn't have API to get this data easily.
*
* @since 1.6.6
*
* @return array
*/
protected function get_pro_forms() {
$forms = [];
$query = new \WP_Query(
[
'post_type' => 'pf_form',
'post_status' => 'publish',
'posts_per_page' => - 1,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]
);
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$forms[ get_the_ID() ] = get_the_title();
}
}
return $forms;
}
/**
* Get a single form options.
*
* @since 1.6.6
*
* @param int $id Form ID.
*
* @return array
*/
public function get_form( $id ) {
return \PirateForms_Util::get_form_options( (int) $id );
}
/**
* Import a single form using AJAX.
*
* @since 1.6.6
*/
public function import_form() {
// Run a security check.
check_ajax_referer( 'wpforms-admin', 'nonce' );
// Check for permissions.
if ( ! wpforms_current_user_can( 'create_forms' ) ) {
wp_send_json_error();
}
$analyze = isset( $_POST['analyze'] );
$pf_form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0;
$pf_form = $this->get_form( $pf_form_id );
$pf_fields_custom = \PirateForms_Util::get_post_meta( $pf_form_id, 'custom' );
$pf_fields_default = [
'name',
'email',
'subject',
'message',
'attachment',
'checkbox',
'recaptcha',
];
$fields_pro_plain = [ 'tel' ]; // Convert them in Lite to the closest Standard alternatives.
$fields_pro_omit = [ 'label', 'file', 'attachment' ]; // Strict PRO fields with no Lite alternatives.
$upgrade_plain = [];
$upgrade_omit = [];
$unsupported = [];
$fields = [];
if ( ! empty( $pf_fields_custom[0] ) ) {
$pf_fields_custom = $pf_fields_custom[0];
} else {
$pf_fields_custom = [];
}
if ( empty( $pf_form_id ) ) {
$pf_form_name = esc_html__( 'Default Form', 'wpforms-lite' );
} else {
$pf_form_name = get_post_field( 'post_title', $pf_form_id );
}
$pf_form_name = wpforms_decode_string( apply_filters( 'the_title', $pf_form_name, $pf_form_id ) );
// Prepare all DEFAULT fields.
foreach ( $pf_fields_default as $field ) {
// Ignore fields that are not displayed or not added at all.
if ( empty( $pf_form[ 'pirateformsopt_' . $field . '_field' ] ) ) {
continue;
}
// Ignore certain fields as they are dealt with later.
if ( $field === 'recaptcha' ) {
continue;
}
$required = $pf_form[ 'pirateformsopt_' . $field . '_field' ] === 'req' ? '1' : '';
$label = ! empty( $pf_form[ 'pirateformsopt_label_' . $field ] ) ? $pf_form[ 'pirateformsopt_label_' . $field ] : ucwords( $field );
// If it is Lite and it's a field type not included, make a note then continue to the next field.
if ( ! wpforms()->is_pro() && in_array( $field, $fields_pro_plain, true ) ) {
$upgrade_plain[] = $label;
}
if ( ! wpforms()->is_pro() && in_array( $field, $fields_pro_omit, true ) ) {
$upgrade_omit[] = $label;
continue;
}
// Determine next field ID to assign.
if ( empty( $fields ) ) {
$field_id = 1;
} else {
$field_id = (int) max( array_keys( $fields ) ) + 1;
}
// Separately process certain fields.
switch ( $field ) {
case 'name':
case 'email':
case 'subject':
case 'message':
$type = $field;
if ( $field === 'subject' ) {
$type = 'text';
} elseif ( $field === 'message' ) {
$type = 'textarea';
}
$fields[ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'required' => $required,
'size' => 'medium',
];
if ( $field === 'name' ) {
$fields[ $field_id ]['format'] = 'simple';
}
break;
case 'checkbox':
$fields[ $field_id ] = [
'id' => $field_id,
'type' => 'checkbox',
'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ),
'choices' => [
1 => [
'label' => $label,
'value' => '',
],
],
'size' => 'medium',
'required' => $required,
'label_hide' => true,
];
break;
case 'attachment':
case 'file':
$fields[ $field_id ] = [
'id' => $field_id,
'type' => 'file-upload',
'label' => $label,
'required' => $required,
'label_hide' => true,
];
// If PF attachments were saved into FS, we need to save them in WP Media.
// That will allow admins to easily delete if needed.
if (
! empty( $pf_form['pirateformsopt_save_attachment'] ) &&
$pf_form['pirateformsopt_save_attachment'] === 'yes'
) {
$fields[ $field_id ]['media_library'] = true;
}
break;
}
}
// Prepare all CUSTOM fields.
foreach ( $pf_fields_custom as $id => $field ) {
// Ignore fields that are not displayed.
if ( empty( $field['display'] ) ) {
continue;
}
$required = $field['display'] === 'req' ? '1' : ''; // Possible values in PF: 'yes', 'req'.
$label = sanitize_text_field( $field['label'] );
// If it is Lite and it's a field type not included, make a note then continue to the next field.
if ( ! wpforms()->is_pro() && in_array( $field['type'], $fields_pro_plain, true ) ) {
$upgrade_plain[] = $label;
}
if ( ! wpforms()->is_pro() && in_array( $field['type'], $fields_pro_omit, true ) ) {
$upgrade_omit[] = $label;
continue;
}
// Determine next field ID to assign.
if ( empty( $fields ) ) {
$field_id = 1;
} else {
$field_id = (int) max( array_keys( $fields ) ) + 1;
}
switch ( $field['type'] ) {
case 'text':
case 'textarea':
case 'number':
case 'tel':
$type = $field['type'];
if ( $field['type'] === 'textarea' ) {
$type = 'textarea';
}
if ( $field['type'] === 'tel' ) {
$type = 'phone';
}
$fields[ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'required' => $required,
'size' => 'medium',
];
if ( $field['type'] === 'tel' ) {
$fields[ $field_id ]['format'] = 'international';
}
break;
case 'checkbox':
$fields[ $field_id ] = [
'id' => $field_id,
'type' => 'checkbox',
'label' => esc_html__( 'Single Checkbox Field', 'wpforms-lite' ),
'choices' => [
1 => [
'label' => $label,
'value' => '',
],
],
'size' => 'medium',
'required' => $required,
'label_hide' => true,
];
break;
case 'select':
case 'multiselect':
$options = [];
$i = 1;
$type = 'select';
if ( $field['type'] === 'multiselect' ) {
$type = 'checkbox';
}
foreach ( explode( PHP_EOL, $field['options'] ) as $option ) {
$options[ $i ] = [
'label' => $option,
'value' => '',
'image' => '',
];
$i ++;
}
$fields[ $field_id ] = [
'id' => $field_id,
'type' => $type,
'label' => $label,
'required' => $required,
'size' => 'medium',
'choices' => $options,
];
break;
case 'label':
$fields[ $field_id ] = [
'id' => $field_id,
'type' => 'html',
'code' => $field['label'],
'label_disable' => true,
];
break;
case 'file':
$fields[ $field_id ] = [
'id' => $field_id,
'type' => 'file-upload',
'label' => $label,
'required' => $required,
'label_hide' => true,
];
// If PF attachments were saved into FS, we need to save them in WP Media.
// That will allow admins to easily delete if needed.
if (
! empty( $pf_form['pirateformsopt_save_attachment'] ) &&
$pf_form['pirateformsopt_save_attachment'] === 'yes'
) {
$fields[ $field_id ]['media_library'] = true;
}
break;
}
}
// If we are analyzing the form (in Lite only),
// we can stop here and return the details about this form.
if ( $analyze ) {
wp_send_json_success(
[
'name' => $pf_form_name,
'upgrade_plain' => $upgrade_plain,
'upgrade_omit' => $upgrade_omit,
]
);
}
// Make sure we have imported some fields.
if ( empty( $fields ) ) {
wp_send_json_success(
[
'error' => true,
'name' => $pf_form_name,
'msg' => esc_html__( 'No form fields found.', 'wpforms-lite' ),
]
);
}
// Create a form array, that holds all the data.
$form = [
'id' => '',
'field_id' => '',
'fields' => $fields,
'settings' => [
'form_title' => $pf_form_name,
'form_desc' => '',
'submit_text' => stripslashes( $pf_form['pirateformsopt_label_submit_btn'] ),
'submit_text_processing' => esc_html__( 'Sending', 'wpforms-lite' ),
'notification_enable' => '1',
'notifications' => [
1 => [
'notification_name' => esc_html__( 'Default Notification', 'wpforms-lite' ),
'email' => $pf_form['pirateformsopt_email_recipients'],
'subject' => sprintf( /* translators: %s - form name. */
esc_html__( 'New Entry: %s', 'wpforms-lite' ),
$pf_form_name
),
'sender_name' => get_bloginfo( 'name' ),
'sender_address' => $this->get_smarttags( $pf_form['pirateformsopt_email'], $fields ),
'replyto' => '',
'message' => '{all_fields}',
],
],
'confirmations' => [
1 => [
'type' => empty( $pf_form['pirateformsopt_thank_you_url'] ) ? 'message' : 'page',
'page' => (int) $pf_form['pirateformsopt_thank_you_url'],
'message' => ! empty( $pf_form['pirateformsopt_label_submit'] ) ? $pf_form['pirateformsopt_label_submit'] : esc_html__( 'Thanks for contacting us! We will be in touch with you shortly.', 'wpforms-lite' ),
'message_scroll' => '1',
],
],
'disable_entries' => $pf_form['pirateformsopt_store'] === 'yes' ? '0' : '1',
'import_form_id' => $pf_form_id,
],
];
// Do not save user IP address and UA.
if ( empty( $pf_form['pirateformsopt_store_ip'] ) || $pf_form['pirateformsopt_store_ip'] !== 'yes' ) {
$wpforms_settings = get_option( 'wpforms_settings', [] );
$wpforms_settings['gdpr'] = true;
update_option( 'wpforms_settings', $wpforms_settings );
$form['settings']['disable_ip'] = true;
}
// Save recaptcha keys.
if (
! empty( $pf_form['pirateformsopt_recaptcha_field'] ) &&
$pf_form['pirateformsopt_recaptcha_field'] === 'yes'
) {
// If the user has already defined v2 reCAPTCHA keys, use those.
$site_key = wpforms_setting( 'recaptcha-site-key', '' );
$secret_key = wpforms_setting( 'recaptcha-secret-key', '' );
// Try to abstract keys from PF.
if ( empty( $site_key ) || empty( $secret_key ) ) {
if ( ! empty( $pf_form['pirateformsopt_recaptcha_sitekey'] ) && ! empty( $pf_form['pirateformsopt_recaptcha_secretkey'] ) ) {
$wpforms_settings = get_option( 'wpforms_settings', [] );
$wpforms_settings['recaptcha-site-key'] = $pf_form['pirateformsopt_recaptcha_sitekey'];
$wpforms_settings['recaptcha-secret-key'] = $pf_form['pirateformsopt_recaptcha_secretkey'];
$wpforms_settings['recaptcha-type'] = 'v2';
update_option( 'wpforms_settings', $wpforms_settings );
}
}
if (
( ! empty( $site_key ) && ! empty( $secret_key ) ) ||
( ! empty( $wpforms_settings['recaptcha-site-key'] ) && ! empty( $wpforms_settings['recaptcha-secret-key'] ) )
) {
$form['settings']['recaptcha'] = '1';
}
}
$this->import_smtp( $pf_form_id, $form );
$this->add_form( $form, $unsupported, $upgrade_plain, $upgrade_omit );
}
/**
* Replace 3rd-party form provider tags/shortcodes with our own Smart Tags.
* See: PirateForms_Util::get_magic_tags() for all PF tags.
*
* @since 1.6.6
*
* @param string $string String to process the smart tag in.
* @param array $fields List of fields for the form.
*
* @return string
*/
public function get_smarttags( $string, $fields ) {
foreach ( self::$tags as $tag ) {
$wpf_tag = '';
if ( $tag === '[email]' ) {
foreach ( $fields as $field ) {
if ( $field['type'] === 'email' ) {
$wpf_tag = '{field_id="' . $field['id'] . '"}';
break;
}
}
}
$string = str_replace( $tag, $wpf_tag, $string );
}
return $string;
}
/**
* Import SMTP settings from Default form only.
*
* @since 1.6.6
*
* @param int $pf_form_id PirateForms form ID.
* @param array $form WPForms form array.
*/
protected function import_smtp( $pf_form_id, $form ) {
// At this point we import only default form SMTP settings.
if ( $pf_form_id !== 0 ) {
return;
}
$pf_form = $this->get_form( 0 );
// Use only if enabled.
if ( empty( $pf_form['pirateformsopt_use_smtp'] ) || $pf_form['pirateformsopt_use_smtp'] !== 'yes' ) {
return;
}
// If user has WP Mail SMTP already activated - do nothing as it's most likely already configured.
if ( is_plugin_active( self::SLUG_SMTP_PLUGIN ) ) {
return;
}
// Check that we successfully installed and activated the plugin.
if ( ! $this->install_activate_smtp() ) {
return;
}
/*
* Finally, start the settings importing.
*/
// WP Mail SMTP 1.x and PHP 5.3+ are allowed. Older WPMS versions are ignored.
if ( ! function_exists( 'wp_mail_smtp' ) ) {
return;
}
// TODO: change to \WPMailSMTP\Options in future.
$options = get_option( 'wp_mail_smtp', [] );
$options['mail']['from_email'] = $this->get_smarttags( $pf_form['pirateformsopt_email'], $form['fields'] );
$options['mail']['mailer'] = 'smtp';
$options['smtp']['host'] = $pf_form['pirateformsopt_smtp_host'];
$options['smtp']['port'] = $pf_form['pirateformsopt_smtp_port'];
$options['smtp']['encryption'] = empty( $pf_form['pirateformsopt_use_secure'] ) ? 'none' : $pf_form['pirateformsopt_use_secure'];
$options['smtp']['auth'] = ! empty( $pf_form['pirateformsopt_use_smtp_authentication'] ) && $pf_form['pirateformsopt_use_smtp_authentication'] === 'yes';
$options['smtp']['user'] = $pf_form['pirateformsopt_smtp_username'];
$options['smtp']['pass'] = $pf_form['pirateformsopt_smtp_password'];
update_option( 'wp_mail_smtp', $options );
}
/**
* Do all the voodoo to install and activate the WP Mail SMTP plugin behind the scene.
* No user interaction is needed.
*
* @since 1.6.6
*
* @return bool
*/
protected function install_activate_smtp() {
/*
* Check installation.
* If installed but not activated - bail.
* We don't want to break current site email deliverability.
*/
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// FALSE will bail the import.
if ( array_key_exists( self::SLUG_SMTP_PLUGIN, get_plugins() ) ) {
return false;
}
/*
* Let's try to install.
*/
$url = add_query_arg(
[
'provider' => $this->slug,
'page' => 'wpforms-tools',
'view' => 'importer',
],
admin_url( 'admin.php' )
);
$creds = request_filesystem_credentials( esc_url_raw( $url ), '', false, false, null );
// Check for file system permissions.
if ( $creds === false ) {
return false;
}
if ( ! WP_Filesystem( $creds ) ) {
return false;
}
// 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 PluginSilentUpgrader( new PluginSilentUpgraderSkin() );
// Error check.
if ( ! method_exists( $installer, 'install' ) ) {
return false;
}
$installer->install( self::URL_SMTP_ZIP );
// Flush the cache and return the newly installed plugin basename.
wp_cache_flush();
if ( $installer->plugin_info() ) {
$plugin_basename = $installer->plugin_info();
// Activate, do not redirect, run the plugin activation routine.
$activated = activate_plugin( $plugin_basename );
if ( ! is_wp_error( $activated ) ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,195 @@
<?php
namespace WPForms\Admin\Tools;
/**
* Main Tools class.
*
* @since 1.6.6
*/
class Tools {
/**
* Tools page slug.
*
* @since 1.6.6
*/
const SLUG = 'wpforms-tools';
/**
* Available pages.
*
* @since 1.6.6
*
* @var array
*/
private $views = [];
/**
* The current view.
*
* @since 1.6.6
*
* @var null|\WPForms\Admin\Tools\Views\View
*/
private $view;
/**
* The active view slug.
*
* @since 1.6.6
*
* @var string
*/
private $active_view_slug;
/**
* Initialize class.
*
* @since 1.6.6
*/
public function init() {
if ( ! $this->is_tools_page() ) {
return;
}
$this->init_view();
$this->hooks();
}
/**
* Check if we're on tools page.
*
* @since 1.6.6
*
* @return bool
*/
private function is_tools_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : '';
// Only load if we are actually on the settings page.
return $page === self::SLUG;
}
/**
* Init current view.
*
* @since 1.6.6
*/
private function init_view() {
$view_ids = array_keys( $this->get_views() );
// Determine the current active settings tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->active_view_slug = ! empty( $_GET['view'] ) ? sanitize_key( $_GET['view'] ) : 'import';
// If the user tries to load an invalid view - fallback to the first available.
if (
! in_array( $this->active_view_slug, $view_ids, true ) &&
! has_action( 'wpforms_tools_display_tab_' . $this->active_view_slug )
) {
$this->active_view_slug = reset( $view_ids );
}
if ( isset( $this->views[ $this->active_view_slug ] ) ) {
$this->view = $this->views[ $this->active_view_slug ];
$this->view->init();
}
}
/**
* Get Views.
*
* @since 1.6.6
*
* @return array
*/
public function get_views() {
if ( empty( $this->views ) ) {
$this->views = [
'import' => new Views\Import(),
'importer' => new Views\Importer(),
'export' => new Views\Export(),
'system' => new Views\System(),
'action-scheduler' => new Views\ActionScheduler(),
'logs' => new Views\Logs(),
'wpcode' => new Views\CodeSnippets(),
];
}
$this->views = apply_filters( 'wpforms_tools_views', $this->views );
return array_filter(
$this->views,
static function ( $view ) {
return $view->check_capability();
}
);
}
/**
* Register hooks.
*
* @since 1.6.6
*/
public function hooks() {
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
// Hook for addons.
do_action( 'wpforms_tools_init' );
}
/**
* Build the output for the Tools admin page.
*
* @since 1.6.6
*/
public function output() {
?>
<div id="wpforms-tools" class="wrap wpforms-admin-wrap wpforms-tools-tab-<?php echo esc_attr( $this->active_view_slug ); ?>">
<?php
if ( $this->view && $this->view->show_nav() ) {
echo '<ul class="wpforms-admin-tabs">';
foreach ( $this->views as $slug => $view ) {
if ( $view->hide_from_nav() || ! $view->check_capability() ) {
continue;
}
echo '<li>';
printf(
'<a href="%1$s" class="%2$s">%3$s</a>',
esc_url( $view->get_link() ),
sanitize_html_class( $this->active_view_slug === $slug ? 'active' : '' ),
esc_html( $view->get_label() )
);
echo '</li>';
}
echo '</ul>';
}
?>
<h1 class="wpforms-h1-placeholder"></h1>
<div class="wpforms-admin-content wpforms-admin-settings">
<?php
if ( $this->view ) {
$this->view->display();
} else {
do_action( 'wpforms_tools_display_tab_' . $this->active_view_slug );
}
?>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace WPForms\Admin\Tools\Views;
use ActionScheduler_AdminView;
/**
* Class ActionScheduler view.
*
* @since 1.6.6
*/
class ActionScheduler extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'action-scheduler';
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {
if ( $this->admin_view_exists() ) {
ActionScheduler_AdminView::instance()->process_admin_ui();
}
}
/**
* Get link to the view.
*
* @since 1.6.9
*
* @return string
*/
public function get_link() {
return add_query_arg(
[
's' => 'wpforms',
],
parent::get_link()
);
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return esc_html__( 'Scheduled Actions', 'wpforms-lite' );
}
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can();
}
/**
* Display view content.
*
* @since 1.6.6
*/
public function display() {
if ( ! $this->admin_view_exists() ) {
return;
}
( new ActionSchedulerList() )->display_page();
}
/**
* Check if ActionScheduler_AdminView class exists.
*
* @since 1.6.6
*
* @return bool
*/
private function admin_view_exists() {
return class_exists( 'ActionScheduler_AdminView' );
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace WPForms\Admin\Tools\Views;
use ActionScheduler as Scheduler;
use ActionScheduler_ListTable;
/**
* Action Scheduler list table.
*
* @since 1.7.6
*/
class ActionSchedulerList extends ActionScheduler_ListTable {
/**
* ActionSchedulerList constructor.
*
* @since 1.7.6
*/
public function __construct() {
parent::__construct(
Scheduler::store(),
Scheduler::logger(),
Scheduler::runner()
);
$this->process_actions();
}
/**
* Display the table heading.
*
* @since 1.7.6
*/
protected function display_header() {
?>
<h1><?php echo esc_html__( 'Scheduled Actions', 'wpforms-lite' ); ?></h1>
<p>
<?php
echo sprintf(
wp_kses( /* translators: %s - Action Scheduler website URL. */
__( 'WPForms is using the <a href="%s" target="_blank" rel="noopener noreferrer">Action Scheduler</a> library, which allows it to queue and process bigger tasks in the background without making your site slower for your visitors. Below you can see the list of all tasks and their status. This table can be very useful when debugging certain issues.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'rel' => [],
'target' => [],
],
]
),
'https://actionscheduler.org/'
);
?>
</p>
<p>
<?php echo esc_html__( 'Action Scheduler library is also used by other plugins, like WP Mail SMTP and WooCommerce, so you might see tasks that are not related to our plugin in the table below.', 'wpforms-lite' ); ?>
</p>
<?php
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_GET['s'] ) ) {
?>
<div id="wpforms-reset-filter">
<?php
echo wp_kses(
sprintf( /* translators: %s - search term. */
__( 'Search results for <strong>%s</strong>', 'wpforms-lite' ),
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
sanitize_text_field( wp_unslash( $_GET['s'] ) )
),
[
'strong' => [],
]
);
?>
<a href="<?php echo esc_url( remove_query_arg( 's' ) ); ?>">
<span class="reset fa fa-times-circle"></span>
</a>
</div>
<?php
}
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace WPForms\Admin\Tools\Views;
use WPForms\Integrations\WPCode\WPCode;
/**
* Class WPCode view.
*
* @since 1.8.5
*/
class CodeSnippets extends View {
/**
* View slug.
*
* @since 1.8.5
*
* @var string
*/
protected $slug = 'wpcode';
/**
* WPCode action required.
*
* @since 1.8.5
*
* @var string
*/
private $action;
/**
* WPCode snippets.
*
* @since 1.8.5
*
* @var array
*/
private $snippets;
/**
* WPCode plugin slug or download URL.
*
* @since 1.8.5
*
* @var string
*/
private $plugin;
/**
* Whether WPCode action is required.
*
* @since 1.8.5
*
* @var bool
*/
private $action_required;
/**
* Init view.
*
* @since 1.8.5
*/
public function init() {
$wpcode = new WPCode();
$this->snippets = $wpcode->load_wpforms_snippets();
$plugin_slug = $wpcode->is_pro_installed() ? $wpcode->pro_plugin_slug : $wpcode->lite_plugin_slug;
$update_required = $wpcode->is_plugin_installed() && version_compare( $wpcode->plugin_version(), '2.0.10', '<' );
$installed_action = $update_required ? 'update' : 'activate';
$this->action_required = $update_required || ! $wpcode->is_plugin_installed() || ! $wpcode->is_plugin_active();
$this->action = $wpcode->is_plugin_installed() ? $installed_action : 'install';
$this->plugin = $this->action === 'activate' ? $plugin_slug : $wpcode->lite_download_url;
$this->hooks();
}
/**
* Add hooks.
*
* @since 1.8.5
*
* @return void
*/
private function hooks() {
if ( $this->action !== 'update' ) {
return;
}
add_filter(
'upgrader_package_options',
static function ( $options ) {
$options['clear_destination'] = true;
return $options;
}
);
}
/**
* Get view label.
*
* @since 1.8.5
*
* @return string
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function get_label() {
return esc_html__( 'Code Snippets', 'wpforms-lite' );
}
/**
* Checking user capability to view.
*
* @since 1.8.5
*
* @return bool
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function check_capability() {
return wpforms_current_user_can();
}
/**
* Display view content.
*
* @since 1.8.5
*
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function display() {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render(
'integrations/wpcode/code-snippets',
[
'snippets' => $this->snippets,
'action_required' => $this->action_required,
'action' => $this->action,
'plugin' => $this->plugin,
],
true
);
}
}

View File

@@ -0,0 +1,350 @@
<?php
namespace WPForms\Admin\Tools\Views;
/**
* Class Export.
*
* @since 1.6.6
*/
class Export extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'export';
/**
* Template code if generated.
*
* @since 1.6.6
*
* @var string
*/
private $template = '';
/**
* Existed forms.
*
* @since 1.6.6
*
* @var []
*/
private $forms = [];
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {
add_action( 'wpforms_tools_init', [ $this, 'process' ] );
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return esc_html__( 'Export', 'wpforms-lite' );
}
/**
* Export process.
*
* @since 1.6.6
*/
public function process() {
if (
empty( $_POST['action'] ) || //phpcs:ignore WordPress.Security.NonceVerification
! isset( $_POST['submit-export'] ) || //phpcs:ignore WordPress.Security.NonceVerification
! $this->verify_nonce()
) {
return;
}
if ( $_POST['action'] === 'export_form' && ! empty( $_POST['forms'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
$this->process_form();
}
if ( $_POST['action'] === 'export_template' && ! empty( $_POST['form'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
$this->process_template();
}
}
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can( [ 'edit_forms', 'view_entries' ] );
}
/**
* Get available forms.
*
* @since 1.6.6
*
* @return array
*/
public function get_forms() {
$forms = wpforms()->form->get( '', [ 'orderby' => 'title' ] );
return ! empty( $forms ) ? $forms : [];
}
/**
* Export view content.
*
* @since 1.6.6
*/
public function display() {
$this->forms = $this->get_forms();
if ( empty( $this->forms ) ) {
echo wpforms_render( 'admin/empty-states/no-forms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
do_action( 'wpforms_admin_tools_export_top' );
$this->forms_export_block();
$this->form_template_export_block();
do_action( 'wpforms_admin_tools_export_bottom' );
}
/**
* Forms export block.
*
* @since 1.6.6
*/
private function forms_export_block() {
?>
<div class="wpforms-setting-row tools wpforms-settings-row-divider">
<h4 id="form-export"><?php esc_html_e( 'Export Forms', 'wpforms-lite' ); ?></h4>
<p><?php esc_html_e( 'Use form export files to create a backup of your forms or to import forms to another site.', 'wpforms-lite' ); ?></p>
<?php if ( ! empty( $this->forms ) ) { ?>
<form method="post" action="<?php echo esc_attr( $this->get_link() ); ?>">
<?php $this->forms_select_html( 'wpforms-tools-form-export', 'forms[]', esc_html__( 'Select Form(s)', 'wpforms-lite' ) ); ?>
<input type="hidden" name="action" value="export_form">
<?php $this->nonce_field(); ?>
<button name="submit-export" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-export-form" aria-disabled="true">
<?php esc_html_e( 'Export', 'wpforms-lite' ); ?>
</button>
</form>
<?php } else { ?>
<p><?php esc_html_e( 'You need to create a form before you can use form export.', 'wpforms-lite' ); ?></p>
<?php } ?>
</div>
<?php
}
/**
* Forms export block.
*
* @since 1.6.6
*/
private function form_template_export_block() {
?>
<div class="wpforms-setting-row tools">
<h4 id="template-export"><?php esc_html_e( 'Export a Form Template', 'wpforms-lite' ); ?></h4>
<?php
if ( $this->template ) {
$doc_link = sprintf(
wp_kses( /* translators: %s - WPForms.com docs URL. */
__( 'For more information <a href="%s" target="_blank" rel="noopener noreferrer">see our documentation</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
'https://wpforms.com/docs/how-to-create-a-custom-form-template/'
);
?>
<p><?php esc_html_e( 'The following code can be used to register your custom form template. Copy and paste the following code to your theme\'s functions.php file or include it within an external file.', 'wpforms-lite' ); ?><p>
<p><?php echo $doc_link; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><p>
<textarea class="info-area" readonly><?php echo esc_textarea( $this->template ); ?></textarea>
<?php
}
?>
<p><?php esc_html_e( 'Select a form to generate PHP code that can be used to register a custom form template.', 'wpforms-lite' ); ?></p>
<?php if ( ! empty( $this->forms ) ) { ?>
<form method="post" action="<?php echo esc_attr( $this->get_link() ); ?>">
<?php $this->forms_select_html( 'wpforms-tools-form-template', 'form', esc_html__( 'Select a Template', 'wpforms-lite' ), false ); ?>
<input type="hidden" name="action" value="export_template">
<?php $this->nonce_field(); ?>
<button name="submit-export" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-export-template" aria-disabled="true">
<?php esc_html_e( 'Export Template', 'wpforms-lite' ); ?>
</button>
</form>
<?php } else { ?>
<p><?php esc_html_e( 'You need to create a form before you can generate a template.', 'wpforms-lite' ); ?></p>
<?php } ?>
</div>
<?php
}
/**
* Forms selector.
*
* @since 1.6.6
*
* @param string $select_id Select id.
* @param string $select_name Select name.
* @param string $placeholder Placeholder.
* @param bool $multiple Is multiple select.
*/
private function forms_select_html( $select_id, $select_name, $placeholder, $multiple = true ) {
?>
<span class="choicesjs-select-wrap">
<select id="<?php echo esc_attr( $select_id ); ?>" class="choicesjs-select" name="<?php echo esc_attr( $select_name ); ?>" <?php if ( $multiple ) { //phpcs:ignore ?> multiple size="1" <?php } ?> data-search="<?php echo esc_attr( wpforms_choices_js_is_search_enabled( $this->forms ) ); ?>">
<option value=""><?php echo esc_attr( $placeholder ); ?></option>
<?php foreach ( $this->forms as $form ) { ?>
<option value="<?php echo absint( $form->ID ); ?>"><?php echo esc_html( $form->post_title ); ?></option>
<?php } ?>
</select>
</span>
<?php
}
/**
* Export processing.
*
* @since 1.6.6
*/
private function process_form() {
$export = [];
$forms = get_posts(
[
'post_type' => 'wpforms',
'nopaging' => true,
'post__in' => isset( $_POST['forms'] ) ? array_map( 'intval', $_POST['forms'] ) : [], //phpcs:ignore WordPress.Security.NonceVerification
]
);
foreach ( $forms as $form ) {
$export[] = wpforms_decode( $form->post_content );
}
ignore_user_abort( true );
wpforms_set_time_limit();
nocache_headers();
header( 'Content-Type: application/json; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=wpforms-form-export-' . current_time( 'm-d-Y' ) . '.json' );
header( 'Expires: 0' );
echo wp_json_encode( $export );
exit;
}
/**
* Export template processing.
*
* @since 1.6.6
*/
private function process_template() {
$form_data = false;
if ( isset( $_POST['form'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification
$form_data = wpforms()->form->get(
absint( $_POST['form'] ), //phpcs:ignore WordPress.Security.NonceVerification
[ 'content_only' => true ]
);
}
if ( ! $form_data ) {
return;
}
// Define basic data.
$name = sanitize_text_field( $form_data['settings']['form_title'] );
$desc = sanitize_text_field( $form_data['settings']['form_desc'] );
$slug = sanitize_key( str_replace( [ ' ', '-' ], '_', $form_data['settings']['form_title'] ) );
$class = 'WPForms_Template_' . $slug;
// Format template field and settings data.
$data = $form_data;
$data['meta']['template'] = $slug;
$data['fields'] = isset( $data['fields'] ) ? wpforms_array_remove_empty_strings( $data['fields'] ) : [];
$data['settings'] = wpforms_array_remove_empty_strings( $data['settings'] );
unset( $data['id'] );
$data = var_export( $data, true ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
$data = str_replace( ' ', "\t", $data );
$data = preg_replace( '/([\t\r\n]+?)array/', 'array', $data );
// Build the final template string.
$this->template = <<<EOT
if ( class_exists( 'WPForms_Template', false ) ) :
/**
* {$name}
* Template for WPForms.
*/
class {$class} extends WPForms_Template {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Template name
\$this->name = '{$name}';
// Template slug
\$this->slug = '{$slug}';
// Template description
\$this->description = '{$desc}';
// Template field and settings
\$this->data = {$data};
}
}
new {$class}();
endif;
EOT;
}
}

View File

@@ -0,0 +1,359 @@
<?php
namespace WPForms\Admin\Tools\Views;
use WPForms\Helpers\File;
use WPForms\Admin\Tools\Importers;
use WPForms\Admin\Tools\Tools;
use WPForms_Form_Handler;
use WPForms\Admin\Notice;
/**
* Class Import.
*
* @since 1.6.6
*/
class Import extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'import';
/**
* Registered importers.
*
* @since 1.6.6
*
* @var array
*/
public $importers = [];
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can( 'create_forms' );
}
/**
* Determine whether user has the "unfiltered_html" capability.
*
* By default, the "unfiltered_html" permission is only given to
* Super Admins, Administrators and Editors.
*
* @since 1.7.9
*
* @return bool
*/
private function check_unfiltered_html_capability() {
return current_user_can( 'unfiltered_html' );
}
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {
// Bail early, in case the current user lacks the `unfiltered_html` capability.
if ( ! $this->check_unfiltered_html_capability() ) {
$this->error_unfiltered_html_import_message();
return;
}
$this->hooks();
$this->importers = ( new Importers() )->get_importers();
}
/**
* Register hooks.
*
* @since 1.7.9
*/
private function hooks() {
add_action( 'wpforms_tools_init', [ $this, 'import_process' ] );
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return esc_html__( 'Import', 'wpforms-lite' );
}
/**
* Import process.
*
* @since 1.6.6
*/
public function import_process() {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if (
empty( $_POST['action'] ) ||
$_POST['action'] !== 'import_form' ||
empty( $_FILES['file']['tmp_name'] ) ||
! isset( $_POST['submit-import'] ) ||
! $this->verify_nonce()
) {
return;
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
$this->process();
}
/**
* Import view content.
*
* @since 1.6.6
*/
public function display() {
// Bail early, in case the current user lacks the `unfiltered_html` capability.
if ( ! $this->check_unfiltered_html_capability() ) {
return;
}
$this->success_import_message();
$this->wpforms_block();
$this->other_forms_block();
}
/**
* Success import message.
*
* @since 1.6.6
*/
private function success_import_message() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['wpforms_notice'] ) && $_GET['wpforms_notice'] === 'forms-imported' ) {
?>
<div class="updated notice is-dismissible">
<p>
<?php esc_html_e( 'Import was successfully finished.', 'wpforms-lite' ); ?>
<?php
if ( wpforms_current_user_can( 'view_forms' ) ) {
printf(
wp_kses( /* translators: %s - forms list page URL. */
__( 'You can go and <a href="%s">check your forms</a>.', 'wpforms-lite' ),
[ 'a' => [ 'href' => [] ] ]
),
esc_url( admin_url( 'admin.php?page=wpforms-overview' ) )
);
}
?>
</p>
</div>
<?php
}
}
/**
* Error message for users with no `unfiltered_html` permission.
*
* @since 1.7.9
*/
private function error_unfiltered_html_import_message() {
Notice::error(
sprintf(
wp_kses( /* translators: %s - WPForms contact page URL. */
__( 'You cant import forms because you dont have unfiltered HTML permissions. Please contact your site administrator or <a href="%s" target="_blank" rel="noopener noreferrer">reach out to our support team</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
'https://wpforms.com/contact/'
)
);
}
/**
* WPForms section.
*
* @since 1.6.6
*/
private function wpforms_block() {
?>
<div class="wpforms-setting-row tools wpforms-settings-row-divider">
<h4><?php esc_html_e( 'WPForms Import', 'wpforms-lite' ); ?></h4>
<p><?php esc_html_e( 'Select a WPForms export file.', 'wpforms-lite' ); ?></p>
<form method="post" enctype="multipart/form-data" action="<?php echo esc_attr( $this->get_link() ); ?>">
<div class="wpforms-file-upload">
<input type="file" name="file" id="wpforms-tools-form-import" class="inputfile"
data-multiple-caption="{count} <?php esc_attr_e( 'files selected', 'wpforms-lite' ); ?>"
accept=".json" />
<label for="wpforms-tools-form-import">
<span class="fld"><span class="placeholder"><?php esc_html_e( 'No file chosen', 'wpforms-lite' ); ?></span></span>
<strong class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey">
<i class="fa fa-cloud-upload"></i><?php esc_html_e( 'Choose a File', 'wpforms-lite' ); ?>
</strong>
</label>
</div>
<input type="hidden" name="action" value="import_form">
<button name="submit-import" class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-import" aria-disabled="true">
<?php esc_html_e( 'Import', 'wpforms-lite' ); ?>
</button>
<?php $this->nonce_field(); ?>
</form>
</div>
<?php
}
/**
* WPForms section.
*
* @since 1.6.6
*/
private function other_forms_block() {
?>
<div class="wpforms-setting-row tools" id="wpforms-importers">
<h4><?php esc_html_e( 'Import from Other Form Plugins', 'wpforms-lite' ); ?></h4>
<p><?php esc_html_e( 'Not happy with other WordPress contact form plugins?', 'wpforms-lite' ); ?></p>
<p><?php esc_html_e( 'WPForms makes it easy for you to switch by allowing you import your third-party forms with a single click.', 'wpforms-lite' ); ?></p>
<div class="wpforms-importers-wrap">
<?php if ( empty( $this->importers ) ) { ?>
<p><?php esc_html_e( 'No form importers are currently enabled.', 'wpforms-lite' ); ?> </p>
<?php } else { ?>
<form action="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>">
<span class="choicesjs-select-wrap">
<select id="wpforms-tools-form-other-import" class="choicesjs-select" name="provider" data-search="<?php echo esc_attr( wpforms_choices_js_is_search_enabled( $this->importers ) ); ?>" required>
<option value=""><?php esc_html_e( 'Select previous contact form plugin...', 'wpforms-lite' ); ?></option>
<?php
foreach ( $this->importers as $importer ) {
$status = '';
if ( empty( $importer['installed'] ) ) {
$status = esc_html__( 'Not Installed', 'wpforms-lite' );
} elseif ( empty( $importer['active'] ) ) {
$status = esc_html__( 'Not Active', 'wpforms-lite' );
}
printf(
'<option value="%s" %s>%s %s</option>',
esc_attr( $importer['slug'] ),
! empty( $status ) ? 'disabled' : '',
esc_html( $importer['name'] ),
! empty( $status ) ? '(' . esc_html( $status ) . ')' : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
?>
</select>
</span>
<input type="hidden" name="page" value="<?php echo esc_attr( Tools::SLUG ); ?>">
<input type="hidden" name="view" value="importer">
<button class="wpforms-btn wpforms-btn-md wpforms-btn-orange" id="wpforms-import-other" aria-disabled="true">
<?php esc_html_e( 'Import', 'wpforms-lite' ); ?>
</button>
</form>
<?php } ?>
</div>
</div>
<?php
}
/**
* Import processing.
*
* @since 1.6.6
*/
private function process() {
// Add filter of the link rel attr to avoid JSON damage.
add_filter( 'wp_targeted_link_rel', '__return_empty_string', 50, 1 );
$ext = '';
if ( isset( $_FILES['file']['name'] ) ) {
$ext = strtolower( pathinfo( sanitize_text_field( wp_unslash( $_FILES['file']['name'] ) ), PATHINFO_EXTENSION ) );
}
if ( $ext !== 'json' ) {
wp_die(
esc_html__( 'Please upload a valid .json form export file.', 'wpforms-lite' ),
esc_html__( 'Error', 'wpforms-lite' ),
[
'response' => 400,
]
);
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- wp_unslash() breaks upload on Windows.
$tmp_name = isset( $_FILES['file']['tmp_name'] ) ? sanitize_text_field( $_FILES['file']['tmp_name'] ) : '';
$forms = json_decode( File::remove_utf8_bom( file_get_contents( $tmp_name ) ), true );
if ( empty( $forms ) || ! is_array( $forms ) ) {
wp_die(
esc_html__( 'Form data cannot be imported.', 'wpforms-lite' ),
esc_html__( 'Error', 'wpforms-lite' ),
[
'response' => 400,
]
);
}
foreach ( $forms as $form ) {
$title = ! empty( $form['settings']['form_title'] ) ? $form['settings']['form_title'] : '';
$desc = ! empty( $form['settings']['form_desc'] ) ? $form['settings']['form_desc'] : '';
$new_id = wp_insert_post(
[
'post_title' => $title,
'post_status' => 'publish',
'post_type' => 'wpforms',
'post_excerpt' => $desc,
]
);
if ( $new_id ) {
$form['id'] = $new_id;
wp_update_post(
[
'ID' => $new_id,
'post_content' => wpforms_encode( $form ),
]
);
}
if ( ! empty( $form['settings']['form_tags'] ) ) {
wp_set_post_terms(
$new_id,
implode( ',', (array) $form['settings']['form_tags'] ),
WPForms_Form_Handler::TAGS_TAXONOMY
);
}
}
wp_safe_redirect( add_query_arg( [ 'wpforms_notice' => 'forms-imported' ] ) );
exit;
}
}

View File

@@ -0,0 +1,372 @@
<?php
namespace WPForms\Admin\Tools\Views;
use WPForms\Admin\Tools\Importers;
/**
* Class Importer.
*
* @since 1.6.6
*/
class Importer extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'import';
/**
* Registered importers.
*
* @since 1.6.6
*
* @var array
*/
public $importers = [];
/**
* Available forms for a specific importer.
*
* @since 1.6.6
*
* @var array
*/
public $importer_forms = [];
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {
$importers = new Importers();
$this->importers = $importers->get_importers();
if ( ! empty( $_GET['provider'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->importer_forms = $importers->get_importer_forms( sanitize_key( $_GET['provider'] ) );//phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
// Load the Underscores templates for importers.
add_action( 'admin_print_scripts', [ $this, 'importer_templates' ] );
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return '';
}
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can( 'create_forms' );
}
/**
* Checking if needs display in navigation.
*
* @since 1.6.6
*
* @return bool
*/
public function hide_from_nav() {
return true;
}
/**
* Importer view content.
*
* @since 1.6.6
*/
public function display() {
$this->heading_block();
$this->forms_block();
$this->analyze_block();
$this->process_block();
}
/**
* Get provider.
*
* @since 1.6.6
*
* @return string
*/
private function get_provider_name() {
//phpcs:ignore WordPress.Security.NonceVerification.Recommended
$slug = ! empty( $_GET['provider'] ) ? sanitize_key( $_GET['provider'] ) : '';
return isset( $this->importers[ $slug ] ) ? $this->importers[ $slug ]['name'] : '';
}
/**
* Heading block.
*
* @since 1.6.6
*/
private function heading_block() {
?>
<div class="wpforms-setting-row tools wpforms-clear section-heading no-desc">
<div class="wpforms-setting-field">
<h4><?php esc_html_e( 'Form Import', 'wpforms-lite' ); ?></h4>
</div>
</div>
<?php
}
/**
* Forms block.
*
* @since 1.6.6
*/
private function forms_block() {
?>
<div id="wpforms-importer-forms">
<div class="wpforms-setting-row tools">
<p><?php esc_html_e( 'Select the forms you would like to import.', 'wpforms-lite' ); ?></p>
<div class="checkbox-multiselect-columns">
<div class="first-column">
<h5 class="header"><?php esc_html_e( 'Available Forms', 'wpforms-lite' ); ?></h5>
<ul>
<?php
if ( empty( $this->importer_forms ) ) {
echo '<li>' . esc_html__( 'No forms found.', 'wpforms-lite' ) . '</li>';
} else {
foreach ( $this->importer_forms as $id => $form ) {
printf(
'<li><label><input type="checkbox" name="forms[]" value="%s">%s</label></li>',
esc_attr( $id ),
esc_attr( sanitize_text_field( $form ) )
);
}
}
?>
</ul>
<?php if ( ! empty( $this->importer_forms ) ) : ?>
<a href="#" class="all"><?php esc_html_e( 'Select All', 'wpforms-lite' ); ?></a>
<?php endif; ?>
</div>
<div class="second-column">
<h5 class="header"><?php esc_html_e( 'Forms to Import', 'wpforms-lite' ); ?></h5>
<ul></ul>
</div>
</div>
</div>
<?php if ( ! empty( $this->importer_forms ) ) : ?>
<p class="submit">
<button class="wpforms-btn wpforms-btn-md wpforms-btn-orange"
id="wpforms-importer-forms-submit"><?php esc_html_e( 'Import', 'wpforms-lite' ); ?></button>
</p>
<?php endif; ?>
</div>
<?php
}
/**
* Analyze block.
*
* @since 1.6.6
*/
private function analyze_block() {
?>
<div id="wpforms-importer-analyze">
<p class="process-analyze">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<?php
printf(
wp_kses( /* translators: %s - provider name. */
__( 'Analyzing <span class="form-current">1</span> of <span class="form-total">0</span> forms from %s.', 'wpforms-lite' ),
[
'span' => [
'class' => [],
],
]
),
esc_attr( sanitize_text_field( $this->get_provider_name() ) )
);
?>
</p>
<div class="upgrade">
<h5><?php esc_html_e( 'Heads up!', 'wpforms-lite' ); ?></h5>
<p><?php esc_html_e( 'One or more of your forms contain fields that are not available in WPForms Lite. To properly import these fields, we recommend upgrading to WPForms Pro.', 'wpforms-lite' ); ?></p>
<p><?php esc_html_e( 'You can continue with the import without upgrading, and we will do our best to match the fields. However, some of them will be omitted due to compatibility issues.', 'wpforms-lite' ); ?></p>
<p>
<a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'tools-import' ) ); ?>" target="_blank"
rel="noopener noreferrer"
class="wpforms-btn wpforms-btn-md wpforms-btn-orange wpforms-upgrade-modal"><?php esc_html_e( 'Upgrade to WPForms Pro', 'wpforms-lite' ); ?></a>
<a href="#" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey"
id="wpforms-importer-continue-submit"><?php esc_html_e( 'Continue Import without Upgrading', 'wpforms-lite' ); ?></a>
</p>
<hr>
<p><?php esc_html_e( 'Below is the list of form fields that may be impacted:', 'wpforms-lite' ); ?></p>
</div>
</div>
<?php
}
/**
* Process block.
*
* @since 1.6.6
*/
private function process_block() {
?>
<div id="wpforms-importer-process">
<p class="process-count">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<?php
printf(
wp_kses( /* translators: %s - provider name. */
__( 'Importing <span class="form-current">1</span> of <span class="form-total">0</span> forms from %s.', 'wpforms-lite' ),
[
'span' => [
'class' => [],
],
]
),
esc_attr( sanitize_text_field( $this->get_provider_name() ) )
);
?>
</p>
<p class="process-completed">
<?php
echo wp_kses(
__( 'Congrats, the import process has finished! We have successfully imported <span class="forms-completed"></span> forms. You can review the results below.', 'wpforms-lite' ),
[
'span' => [
'class' => [],
],
]
);
?>
</p>
<div class="status"></div>
</div>
<?php
}
/**
* Various Underscores templates for form importing.
*
* @since 1.6.6
*/
public function importer_templates() {
?>
<script type="text/html" id="tmpl-wpforms-importer-upgrade">
<# _.each( data, function( item, key ) { #>
<ul>
<li class="form">{{ item.name }}</li>
<# _.each( item.fields, function( val, key ) { #>
<li>{{ val }}</li>
<# }) #>
</ul>
<# }) #>
</script>
<script type="text/html" id="tmpl-wpforms-importer-status-error">
<div class="item">
<div class="wpforms-clear">
<span class="name">
<i class="status-icon fa fa-times" aria-hidden="true"></i>
{{ data.name }}
</span>
</div>
<p>{{ data.msg }}</p>
</div>
</script>
<script type="text/html" id="tmpl-wpforms-importer-status-update">
<div class="item">
<div class="wpforms-clear">
<span class="name">
<# if ( ! _.isEmpty( data.upgrade_omit ) ) { #>
<i class="status-icon fa fa-exclamation-circle" aria-hidden="true"></i>
<# } else if ( ! _.isEmpty( data.upgrade_plain ) ) { #>
<i class="status-icon fa fa-exclamation-triangle" aria-hidden="true"></i>
<# } else if ( ! _.isEmpty( data.unsupported ) ) { #>
<i class="status-icon fa fa-info-circle" aria-hidden="true"></i>
<# } else { #>
<i class="status-icon fa fa-check" aria-hidden="true"></i>
<# } #>
{{ data.name }}
</span>
<span class="actions">
<a href="{{ data.edit }}" target="_blank"><?php esc_html_e( 'Edit', 'wpforms-lite' ); ?></a>
<span class="sep">|</span>
<a href="{{ data.preview }}" target="_blank"><?php esc_html_e( 'Preview', 'wpforms-lite' ); ?></a>
</span>
</div>
<# if ( ! _.isEmpty( data.upgrade_omit ) ) { #>
<p><?php esc_html_e( 'The following fields are available in PRO and were not imported:', 'wpforms-lite' ); ?></p>
<ul>
<# _.each( data.upgrade_omit, function( val, key ) { #>
<li>{{ val }}</li>
<# }) #>
</ul>
<# } #>
<# if ( ! _.isEmpty( data.upgrade_plain ) ) { #>
<p><?php esc_html_e( 'The following fields are available in PRO and were imported as text fields:', 'wpforms-lite' ); ?></p>
<ul>
<# _.each( data.upgrade_plain, function( val, key ) { #>
<li>{{ val }}</li>
<# }) #>
</ul>
<# } #>
<# if ( ! _.isEmpty( data.unsupported ) ) { #>
<p><?php esc_html_e( 'The following fields are not supported and were not imported:', 'wpforms-lite' ); ?></p>
<ul>
<# _.each( data.unsupported, function( val, key ) { #>
<li>{{ val }}</li>
<# }) #>
</ul>
<# } #>
<# if ( ! _.isEmpty( data.upgrade_plain ) || ! _.isEmpty( data.upgrade_omit ) ) { #>
<p>
<?php esc_html_e( 'Upgrade to the PRO plan to import these fields.', 'wpforms-lite' ); ?><br><br>
<a href="<?php echo esc_url( wpforms_admin_upgrade_link( 'tools-import' ) ); ?>" class="wpforms-btn wpforms-btn-orange wpforms-btn-md wpforms-upgrade-modal" target="_blank" rel="noopener noreferrer">
<?php esc_html_e( 'Upgrade Now', 'wpforms-lite' ); ?>
</a>
</p>
<# } #>
</div>
</script>
<?php
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace WPForms\Admin\Tools\Views;
use WPForms\Logger\Log;
/**
* Class Logs.
*
* @since 1.6.6
*/
class Logs extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'logs';
/**
* ListTable instance.
*
* @since 1.6.6
*
* @var \WPForms\Logger\ListTable
*/
private $list_table = [];
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {
$this->logs_controller();
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return esc_html__( 'Logs', 'wpforms-lite' );
}
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can();
}
/**
* Get ListTable instance.
*
* @since 1.6.6
*
* @return \WPForms\Logger\ListTable
*/
private function get_list_table() {
if ( empty( $this->list_table ) ) {
$this->list_table = wpforms()->get( 'log' )->get_list_table();
}
return $this->list_table;
}
/**
* Display view content.
*
* @since 1.6.6
*/
public function display() {
?>
<form action="<?php echo esc_url( $this->get_link() ); ?>" method="POST">
<?php $this->nonce_field(); ?>
<div class="wpforms-setting-row tools">
<h4><?php esc_html_e( 'Log Settings', 'wpforms-lite' ); ?></h4>
<p><?php esc_html_e( 'Enable and configure the logging functionality while debugging behavior of various parts of the plugin, including form and entry processing.', 'wpforms-lite' ); ?></p>
</div>
<div class="wpforms-setting-row tools wpforms-setting-row-toggle wpforms-clear" id="wpforms-setting-row-logs-enable">
<div class="wpforms-setting-label">
<label for="wpforms-setting-logs-enable"><?php esc_html_e( 'Enable Logs', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-setting-field">
<span class="wpforms-toggle-control">
<input type="checkbox" id="wpforms-setting-logs-enable" name="logs-enable" value="1" <?php checked( wpforms_setting( 'logs-enable' ) ); ?>>
<label class="wpforms-toggle-control-icon" for="wpforms-setting-logs-enable"></label>
<label for="wpforms-setting-logs-enable" class="wpforms-toggle-control-status" data-on="On" data-off="Off">
<?php wpforms_setting( 'logs-enable' ) ? esc_html_e( 'On', 'wpforms-lite' ) : esc_html_e( 'Off', 'wpforms-lite' ); ?>
</label>
</span>
<p class="desc">
<?php esc_html_e( 'Start logging WPForms-related events. This is recommended only while debugging.', 'wpforms-lite' ); ?>
</p>
</div>
</div>
<?php
if ( wpforms_setting( 'logs-enable' ) ) {
$this->types_block();
$this->user_roles_block();
$this->users_block();
}
?>
<p class="submit">
<button class="wpforms-btn wpforms-btn-md wpforms-btn-orange" name="wpforms-settings-submit">
<?php esc_html_e( 'Save Settings', 'wpforms-lite' ); ?>
</button>
</p>
</form>
<?php
$logs_list_table = $this->get_list_table();
if ( wpforms_setting( 'logs-enable' ) || $logs_list_table->get_total() ) {
$logs_list_table->display_page();
}
}
/**
* Types block.
*
* @since 1.6.6
*/
private function types_block() {
?>
<div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear"
id="wpforms-setting-row-log-types">
<div class="wpforms-setting-label">
<label for="wpforms-setting-logs-types"><?php esc_html_e( 'Log Types', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-setting-field">
<span class="choicesjs-select-wrap">
<select id="wpforms-setting-logs-types" class="choicesjs-select" name="logs-types[]" multiple size="1">
<?php
$log_types = wpforms_setting( 'logs-types', [] );
foreach ( Log::get_log_types() as $slug => $name ) {
?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $log_types, true ) ); ?> >
<?php echo esc_html( $name ); ?>
</option>
<?php } ?>
</select>
</span>
<p class="desc"><?php esc_html_e( 'Select the types of events you want to log. Everything is logged by default.', 'wpforms-lite' ); ?></p>
</div>
</div>
<?php
}
/**
* User roles block.
*
* @since 1.6.6
*/
private function user_roles_block() {
?>
<div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear"
id="wpforms-setting-row-log-user-roles">
<div class="wpforms-setting-label">
<label for="wpforms-setting-logs-user-roles"><?php esc_html_e( 'User Roles', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-setting-field">
<span class="choicesjs-select-wrap">
<?php
$logs_user_roles = wpforms_setting( 'logs-user-roles', [] );
$roles = wp_list_pluck( get_editable_roles(), 'name' );
?>
<select id="wpforms-setting-logs-user-roles" class="choicesjs-select" name="logs-user-roles[]" multiple size="1">
<?php foreach ( $roles as $slug => $name ) { ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $logs_user_roles, true ) ); ?> >
<?php echo esc_html( $name ); ?>
</option>
<?php } ?>
</select>
<span class="hidden" id="wpforms-setting-logs-user-roles-selectform-spinner">
<i class="fa fa-cog fa-spin fa-lg"></i>
</span>
</span>
<p class="desc">
<?php esc_html_e( 'Select the user roles you want to log. All roles are logged by default.', 'wpforms-lite' ); ?>
</p>
</div>
</div>
<?php
}
/**
* Users block.
*
* @since 1.6.6
*/
private function users_block() {
?>
<div class="wpforms-setting-row tools wpforms-setting-row-select wpforms-clear"
id="wpforms-setting-row-log-users">
<div class="wpforms-setting-label">
<label for="wpforms-setting-logs-users"><?php esc_html_e( 'Users', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-setting-field">
<span class="choicesjs-select-wrap">
<select id="wpforms-setting-logs-users" class="choicesjs-select" name="logs-users[]" multiple size="1">
<?php
$users = get_users( [ 'fields' => [ 'ID', 'display_name' ] ] );
$users = wp_list_pluck( $users, 'display_name', 'ID' );
$logs_users = wpforms_setting( 'logs-users', [] );
foreach ( $users as $slug => $name ) {
?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $logs_users, true ) ); ?> >
<?php echo esc_html( $name ); ?>
</option>
<?php } ?>
</select>
<span class="hidden" id="wpforms-setting-logs-users-selectform-spinner">
<i class="fa fa-cog fa-spin fa-lg"></i>
</span>
</span>
<p class="desc">
<?php esc_html_e( 'Log events for specific users only. All users are logged by default.', 'wpforms-lite' ); ?>
</p>
</div>
</div>
<?php
}
/**
* Controller.
*
* @since 1.6.6
*/
private function logs_controller() {
$log = wpforms()->get( 'log' );
$log->create_table();
if ( $this->verify_nonce() ) {
$settings = get_option( 'wpforms_settings' );
$was_enabled = ! empty( $settings['logs-enable'] ) ? $settings['logs-enable'] : 0;
$settings['logs-enable'] = filter_input( INPUT_POST, 'logs-enable', FILTER_VALIDATE_BOOLEAN );
$logs_types = filter_input( INPUT_POST, 'logs-types', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
$logs_user_roles = filter_input( INPUT_POST, 'logs-user-roles', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
$logs_users = filter_input( INPUT_POST, 'logs-users', FILTER_SANITIZE_NUMBER_INT, FILTER_REQUIRE_ARRAY );
if ( $was_enabled ) {
$settings['logs-types'] = $logs_types ? $logs_types : [];
$settings['logs-user-roles'] = $logs_user_roles ? $logs_user_roles : [];
$settings['logs-users'] = $logs_users ? array_map( 'absint', $logs_users ) : [];
}
wpforms_update_settings( $settings );
}
$logs_list_table = $this->get_list_table();
$logs_list_table->process_admin_ui();
}
}

View File

@@ -0,0 +1,422 @@
<?php
namespace WPForms\Admin\Tools\Views;
/**
* Class System.
*
* @since 1.6.6
*/
class System extends View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug = 'system';
/**
* Init view.
*
* @since 1.6.6
*/
public function init() {}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
public function get_label() {
return esc_html__( 'System Info', 'wpforms-lite' );
}
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
public function check_capability() {
return wpforms_current_user_can();
}
/**
* System view content.
*
* @since 1.6.6
*/
public function display() {
?>
<div class="wpforms-setting-row tools wpforms-settings-row-system-information">
<h4 id="form-export"><?php esc_html_e( 'System Information', 'wpforms-lite' ); ?></h4>
<textarea id="wpforms-system-information" class="info-area" readonly><?php echo esc_textarea( $this->get_system_info() ); ?></textarea>
<button type="button" id="wpforms-system-information-copy" class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey">
<?php esc_html_e( 'Copy System Information', 'wpforms-lite' ); ?>
</button>
</div>
<div class="wpforms-setting-row tools wpforms-settings-row-test-ssl">
<h4 id="ssl-verify"><?php esc_html_e( 'Test SSL Connections', 'wpforms-lite' ); ?></h4>
<p class="desc"><?php esc_html_e( 'Click the button below to verify your web server can perform SSL connections successfully.', 'wpforms-lite' ); ?></p>
<button type="button" id="wpforms-ssl-verify" class="wpforms-btn wpforms-btn-md wpforms-btn-orange">
<?php esc_html_e( 'Test Connection', 'wpforms-lite' ); ?>
</button>
</div>
<?php
}
/**
* Get system information.
*
* Based on a function from Easy Digital Downloads by Pippin Williamson.
*
* @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/admin/tools.php#L470
*
* @since 1.6.6
*
* @return string
*/
public function get_system_info() {
$data = '### Begin System Info ###' . "\n\n";
$data .= $this->wpforms_info();
$data .= $this->site_info();
$data .= $this->wp_info();
$data .= $this->uploads_info();
$data .= $this->plugins_info();
$data .= $this->server_info();
$data .= "\n" . '### End System Info ###';
return $data;
}
/**
* Get WPForms info.
*
* @since 1.6.6
*
* @return string
*/
private function wpforms_info() {
$activated = get_option( 'wpforms_activated', [] );
$data = '-- WPForms Info' . "\n\n";
if ( ! empty( $activated['pro'] ) ) {
$data .= 'Pro: ' . $this->get_formatted_datetime( $activated['pro'] ) . "\n";
}
if ( ! empty( $activated['lite'] ) ) {
$data .= 'Lite: ' . $this->get_formatted_datetime( $activated['lite'] ) . "\n";
}
$data .= 'Lite Connect: ' . $this->get_lite_connect_info() . "\n";
return $data;
}
/**
* Get Site info.
*
* @since 1.6.6
*
* @return string
*/
private function site_info() {
$data = "\n" . '-- Site Info' . "\n\n";
$data .= 'Site URL: ' . site_url() . "\n";
$data .= 'Home URL: ' . home_url() . "\n";
$data .= 'Multisite: ' . ( is_multisite() ? 'Yes' : 'No' ) . "\n";
return $data;
}
/**
* Get WordPress Configuration info.
*
* @since 1.6.6
*
* @return string
*/
private function wp_info() {
global $wpdb;
$theme_data = wp_get_theme();
$theme = $theme_data->name . ' ' . $theme_data->version;
$data = "\n" . '-- WordPress Configuration' . "\n\n";
$data .= 'Version: ' . get_bloginfo( 'version' ) . "\n";
$data .= 'Language: ' . get_locale() . "\n";
$data .= 'User Language: ' . get_user_locale() . "\n";
$data .= 'Permalink Structure: ' . ( get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default' ) . "\n";
$data .= 'Active Theme: ' . $theme . "\n";
$data .= 'Show On Front: ' . get_option( 'show_on_front' ) . "\n";
// Only show page specs if front page is set to 'page'.
if ( get_option( 'show_on_front' ) === 'page' ) {
$front_page_id = get_option( 'page_on_front' );
$blog_page_id = get_option( 'page_for_posts' );
$data .= 'Page On Front: ' . ( $front_page_id ? get_the_title( $front_page_id ) . ' (#' . $front_page_id . ')' : 'Unset' ) . "\n";
$data .= 'Page For Posts: ' . ( $blog_page_id ? get_the_title( $blog_page_id ) . ' (#' . $blog_page_id . ')' : 'Unset' ) . "\n";
}
$data .= 'ABSPATH: ' . ABSPATH . "\n";
$data .= 'Table Prefix: ' . 'Length: ' . strlen( $wpdb->prefix ) . ' Status: ' . ( strlen( $wpdb->prefix ) > 16 ? 'ERROR: Too long' : 'Acceptable' ) . "\n"; //phpcs:ignore
$data .= 'WP_DEBUG: ' . ( defined( 'WP_DEBUG' ) ? WP_DEBUG ? 'Enabled' : 'Disabled' : 'Not set' ) . "\n";
$data .= 'WPFORMS_DEBUG: ' . ( defined( 'WPFORMS_DEBUG' ) ? WPFORMS_DEBUG ? 'Enabled' : 'Disabled' : 'Not set' ) . "\n";
$data .= 'Memory Limit: ' . WP_MEMORY_LIMIT . "\n";
$data .= 'Registered Post Stati: ' . implode( ', ', get_post_stati() ) . "\n";
$data .= 'Revisions: ' . ( WP_POST_REVISIONS ? WP_POST_REVISIONS > 1 ? 'Limited to ' . WP_POST_REVISIONS : 'Enabled' : 'Disabled' ) . "\n";
return $data;
}
/**
* Get Uploads/Constants info.
*
* @since 1.6.6
*
* @return string
*/
private function uploads_info() {
// @todo WPForms configuration/specific details.
$data = "\n" . '-- WordPress Uploads/Constants' . "\n\n";
$data .= 'WP_CONTENT_DIR: ' . ( defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR ? WP_CONTENT_DIR : 'Disabled' : 'Not set' ) . "\n";
$data .= 'WP_CONTENT_URL: ' . ( defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL ? WP_CONTENT_URL : 'Disabled' : 'Not set' ) . "\n";
$data .= 'UPLOADS: ' . ( defined( 'UPLOADS' ) ? UPLOADS ? UPLOADS : 'Disabled' : 'Not set' ) . "\n";
$uploads_dir = wp_upload_dir();
$data .= 'wp_uploads_dir() path: ' . $uploads_dir['path'] . "\n";
$data .= 'wp_uploads_dir() url: ' . $uploads_dir['url'] . "\n";
$data .= 'wp_uploads_dir() basedir: ' . $uploads_dir['basedir'] . "\n";
$data .= 'wp_uploads_dir() baseurl: ' . $uploads_dir['baseurl'] . "\n";
return $data;
}
/**
* Get Plugins info.
*
* @since 1.6.6
*
* @return string
*/
private function plugins_info() {
// Get plugins that have an update.
$data = $this->mu_plugins();
$data .= $this->installed_plugins();
$data .= $this->multisite_plugins();
return $data;
}
/**
* Get MU Plugins info.
*
* @since 1.6.6
*
* @return string
*/
private function mu_plugins() {
$data = '';
// Must-use plugins.
// NOTE: MU plugins can't show updates!
$muplugins = get_mu_plugins();
if ( ! empty( $muplugins ) && count( $muplugins ) > 0 ) {
$data = "\n" . '-- Must-Use Plugins' . "\n\n";
foreach ( $muplugins as $plugin => $plugin_data ) {
$data .= $plugin_data['Name'] . ': ' . $plugin_data['Version'] . "\n";
}
}
return $data;
}
/**
* Get Installed Plugins info.
*
* @since 1.6.6
*
* @return string
*/
private function installed_plugins() {
$updates = get_plugin_updates();
// WordPress active plugins.
$data = "\n" . '-- WordPress Active Plugins' . "\n\n";
$plugins = get_plugins();
$active_plugins = get_option( 'active_plugins', [] );
foreach ( $plugins as $plugin_path => $plugin ) {
if ( ! in_array( $plugin_path, $active_plugins, true ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
// WordPress inactive plugins.
$data .= "\n" . '-- WordPress Inactive Plugins' . "\n\n";
foreach ( $plugins as $plugin_path => $plugin ) {
if ( in_array( $plugin_path, $active_plugins, true ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
return $data;
}
/**
* Get Multisite Plugins info.
*
* @since 1.6.6
*
* @return string
*/
private function multisite_plugins() {
$data = '';
if ( ! is_multisite() ) {
return $data;
}
$updates = get_plugin_updates();
// WordPress Multisite active plugins.
$data = "\n" . '-- Network Active Plugins' . "\n\n";
$plugins = wp_get_active_network_plugins();
$active_plugins = get_site_option( 'active_sitewide_plugins', [] );
foreach ( $plugins as $plugin_path ) {
$plugin_base = plugin_basename( $plugin_path );
if ( ! array_key_exists( $plugin_base, $active_plugins ) ) {
continue;
}
$update = ( array_key_exists( $plugin_path, $updates ) ) ? ' (needs update - ' . $updates[ $plugin_path ]->update->new_version . ')' : '';
$plugin = get_plugin_data( $plugin_path );
$data .= $plugin['Name'] . ': ' . $plugin['Version'] . $update . "\n";
}
return $data;
}
/**
* Get Server info.
*
* @since 1.6.6
*
* @return string
*/
private function server_info() {
global $wpdb;
// Server configuration (really just versions).
$data = "\n" . '-- Webserver Configuration' . "\n\n";
$data .= 'PHP Version: ' . PHP_VERSION . "\n";
$data .= 'MySQL Version: ' . $wpdb->db_version() . "\n";
$data .= 'Webserver Info: ' . ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '' ) . "\n";
// PHP configs... now we're getting to the important stuff.
$data .= "\n" . '-- PHP Configuration' . "\n\n";
$data .= 'Memory Limit: ' . ini_get( 'memory_limit' ) . "\n";
$data .= 'Upload Max Size: ' . ini_get( 'upload_max_filesize' ) . "\n";
$data .= 'Post Max Size: ' . ini_get( 'post_max_size' ) . "\n";
$data .= 'Upload Max Filesize: ' . ini_get( 'upload_max_filesize' ) . "\n";
$data .= 'Time Limit: ' . ini_get( 'max_execution_time' ) . "\n";
$data .= 'Max Input Vars: ' . ini_get( 'max_input_vars' ) . "\n";
$data .= 'Display Errors: ' . ( ini_get( 'display_errors' ) ? 'On (' . ini_get( 'display_errors' ) . ')' : 'N/A' ) . "\n";
// PHP extensions and such.
$data .= "\n" . '-- PHP Extensions' . "\n\n";
$data .= 'cURL: ' . ( function_exists( 'curl_init' ) ? 'Supported' : 'Not Supported' ) . "\n";
$data .= 'fsockopen: ' . ( function_exists( 'fsockopen' ) ? 'Supported' : 'Not Supported' ) . "\n";
$data .= 'SOAP Client: ' . ( class_exists( 'SoapClient', false ) ? 'Installed' : 'Not Installed' ) . "\n";
$data .= 'Suhosin: ' . ( extension_loaded( 'suhosin' ) ? 'Installed' : 'Not Installed' ) . "\n";
// Session stuff.
$data .= "\n" . '-- Session Configuration' . "\n\n";
$data .= 'Session: ' . ( isset( $_SESSION ) ? 'Enabled' : 'Disabled' ) . "\n";
// The rest of this is only relevant if session is enabled.
if ( isset( $_SESSION ) ) {
$data .= 'Session Name: ' . esc_html( ini_get( 'session.name' ) ) . "\n";
$data .= 'Cookie Path: ' . esc_html( ini_get( 'session.cookie_path' ) ) . "\n";
$data .= 'Save Path: ' . esc_html( ini_get( 'session.save_path' ) ) . "\n";
$data .= 'Use Cookies: ' . ( ini_get( 'session.use_cookies' ) ? 'On' : 'Off' ) . "\n";
$data .= 'Use Only Cookies: ' . ( ini_get( 'session.use_only_cookies' ) ? 'On' : 'Off' ) . "\n";
}
return $data;
}
/**
* Get Lite Connect status info string.
*
* @since 1.7.5
*
* @return string
*/
private function get_lite_connect_info() {
$lc_enabled = wpforms_setting( 'lite-connect-enabled' );
$lc_enabled_since = wpforms_setting( 'lite-connect-enabled-since' );
$date = $this->get_formatted_datetime( $lc_enabled_since );
if ( $lc_enabled ) {
$string = $lc_enabled_since ? 'Backup is enabled since ' . $date : 'Backup is enabled';
} else {
$string = $lc_enabled_since ? 'Backup is not enabled. Previously was enabled since ' . $date : 'Backup is not enabled';
}
return $string;
}
/**
* Get formatted datetime.
*
* @since 1.8.5
*
* @param int|string $date Date.
*
* @return string
*/
private function get_formatted_datetime( $date ) {
return sprintf(
'%1$s at %2$s (GMT)',
gmdate( 'M j, Y', $date ),
gmdate( 'g:ia', $date )
);
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace WPForms\Admin\Tools\Views;
use WPForms\Admin\Tools\Tools;
/**
* Single View class.
*
* @since 1.6.6
*/
abstract class View {
/**
* View slug.
*
* @since 1.6.6
*
* @var string
*/
protected $slug;
/**
* Init.
*
* @since 1.6.6
*/
abstract public function init();
/**
* Get link to the view page.
*
* @since 1.6.6
*
* @return string
*/
public function get_link() {
return add_query_arg(
[
'page' => Tools::SLUG,
'view' => $this->slug,
],
admin_url( 'admin.php' )
);
}
/**
* Get view label.
*
* @since 1.6.6
*
* @return string
*/
abstract public function get_label();
/**
* Checking user capability to view.
*
* @since 1.6.6
*
* @return bool
*/
abstract public function check_capability();
/**
* Checking if needs display in navigation.
*
* @since 1.6.6
*
* @return bool
*/
public function hide_from_nav() {
return false;
}
/**
* Checking if navigation needs display.
*
* @since 1.6.6
*
* @return bool
*/
public function show_nav() {
return true;
}
/**
* Display nonce field.
*
* @since 1.6.6
*/
public function nonce_field() {
wp_nonce_field( 'wpforms_' . $this->slug . '_nonce', 'wpforms-tools-' . $this->slug . '-nonce' );
}
/**
* Verify nonce field.
*
* @since 1.6.6
*/
public function verify_nonce() {
return ! empty( $_POST[ 'wpforms-tools-' . $this->slug . '-nonce' ] ) && wp_verify_nonce( sanitize_key( $_POST[ 'wpforms-tools-' . $this->slug . '-nonce' ] ), 'wpforms_' . $this->slug . '_nonce' );
}
/**
* Display view content.
*
* @since 1.6.6
*/
abstract public function display();
}

View File

@@ -0,0 +1,505 @@
<?php
namespace WPForms\Admin\Traits;
/**
* Form Templates trait.
*
* @since 1.7.7
*/
trait FormTemplates {
// phpcs:disable WPForms.PHP.BackSlash.UseShortSyntax
/**
* Addons data handler class instance.
*
* @since 1.7.7
*
* @var \WPForms\Admin\Addons\Addons
*/
private $addons_obj;
// phpcs:enable WPForms.PHP.BackSlash.UseShortSyntax
/**
* Is addon templates available?
*
* @since 1.7.7
*
* @var bool
*/
private $is_addon_templates_available = false;
/**
* Is custom templates available?
*
* @since 1.7.7
*
* @var bool
*/
private $is_custom_templates_available = false;
/**
* Prepared templates list.
*
* @since 1.7.7
*
* @var array
*/
private $prepared_templates = [];
/**
* Output templates content section.
*
* @since 1.7.7
*/
private function output_templates_content() {
$this->prepare_templates_data();
?>
<div class="wpforms-setup-templates">
<div class="wpforms-setup-templates-sidebar">
<div class="wpforms-setup-templates-search-wrap">
<i class="fa fa-search"></i>
<label>
<input type="text" id="wpforms-setup-template-search" value="" placeholder="<?php esc_attr_e( 'Search Templates', 'wpforms-lite' ); ?>">
</label>
</div>
<ul class="wpforms-setup-templates-categories">
<?php $this->template_categories(); ?>
</ul>
</div>
<div id="wpforms-setup-templates-list">
<div class="list">
<?php $this->template_select_options(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="wpforms-templates-no-results">
<p>
<?php esc_html_e( 'Sorry, we didn\'t find any templates that match your criteria.', 'wpforms-lite' ); ?>
</p>
</div>
</div>
</div>
<?php
}
/**
* Prepare templates data for output.
*
* @since 1.7.7
*/
private function prepare_templates_data() {
$templates = wpforms()->get( 'builder_templates' )->get_templates();
if ( empty( $templates ) ) {
return;
}
// Loop through each available template.
foreach ( $templates as $id => $template ) {
$this->prepared_templates[ $id ] = $this->prepare_template_render_arguments( $template );
}
}
/**
* Generate and display categories menu.
*
* @since 1.7.7
*/
private function template_categories() {
$templates_count = $this->get_count_in_categories();
$generic_categories = [
'all' => esc_html__( 'All Templates', 'wpforms-lite' ),
];
if ( isset( $templates_count['all'], $templates_count['available'] ) && $templates_count['all'] !== $templates_count['available'] ) {
$generic_categories['available'] = esc_html__( 'Available Templates', 'wpforms-lite' );
}
$generic_categories['favorites'] = esc_html__( 'Favorite Templates', 'wpforms-lite' );
$generic_categories['new'] = esc_html__( 'New Templates', 'wpforms-lite' );
$this->output_categories( $generic_categories, $templates_count );
printf( '<li class="divider"></li>' );
$common_categories = [];
if ( $this->is_custom_templates_available ) {
$common_categories['custom'] = esc_html__( 'Custom Templates', 'wpforms-lite' );
}
if ( $this->is_addon_templates_available ) {
$common_categories['addons'] = esc_html__( 'Addon Templates', 'wpforms-lite' );
}
$categories = array_merge(
$common_categories,
wpforms()->get( 'builder_templates' )->get_categories()
);
$this->output_categories( $categories, $templates_count );
}
/**
* Output categories list.
*
* @since 1.7.7
*
* @param array $categories Categories list.
* @param array $templates_count Templates count by categories.
*/
private function output_categories( $categories, $templates_count ) {
$all_subcategories = wpforms()->get( 'builder_templates' )->get_subcategories();
foreach ( $categories as $slug => $name ) {
$class = '';
if ( $slug === 'all' ) {
$class = ' class="active"';
} elseif ( empty( $templates_count[ $slug ] ) ) {
$class = ' class="wpforms-hidden"';
}
$count = isset( $templates_count[ $slug ] ) ? $templates_count[ $slug ] : '0';
printf(
'<li data-category="%1$s"%2$s><div>%3$s<span>%4$s</span></div>%5$s</li>',
esc_attr( $slug ),
$class, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html( $name ),
esc_html( $count ),
$this->output_subcategories( $all_subcategories, $slug ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
}
/**
* Output subcategories list.
*
* @since 1.8.4
*
* @param array $all_subcategories Subcategories list.
* @param array $parent_slug Parent category slug.
*/
private function output_subcategories( $all_subcategories, $parent_slug ) {
$subcategories = [];
$output = '';
foreach ( $all_subcategories as $subcategory_slug => $subcategory ) {
if ( $subcategory['parent'] === $parent_slug ) {
$subcategories[ $subcategory_slug ] = $subcategory;
}
}
if ( ! empty( $subcategories ) ) {
$output .= '<ul class="wpforms-setup-templates-subcategories">';
foreach ( $subcategories as $slug => $subcategory ) {
$output .= sprintf(
'<li data-subcategory="%1$s"><i class="fa fa-angle-right"></i><span>%2$s</span></li>',
esc_attr( $slug ),
esc_html( $subcategory['name'] )
);
}
$output .= '</ul>';
}
return $output;
}
/**
* Generate a block of templates to choose from.
*
* @since 1.7.7
*
* @param array $templates Deprecated.
* @param string $slug Deprecated.
*/
public function template_select_options( $templates = [], $slug = '' ) {
foreach ( $this->prepared_templates as $template ) {
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'builder/templates-item',
$template,
true
);
}
}
/**
* Prepare arguments for rendering template item.
*
* @since 1.7.7
*
* @param array $template Template data.
*
* @return array Arguments.
*/
private function prepare_template_render_arguments( $template ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
$template['plugin_dir'] = isset( $template['plugin_dir'] ) ? $template['plugin_dir'] : '';
$template['source'] = $this->get_template_source( $template );
$template['url'] = ! empty( $template['url'] ) ? $template['url'] : '';
$template['has_access'] = ! empty( $template['license'] ) ? $template['has_access'] : true;
$template['favorite'] = isset( $template['favorite'] ) ? $template['favorite'] : wpforms()->get( 'builder_templates' )->is_favorite( $template['slug'] );
$args = [];
$args['template_id'] = ! empty( $template['id'] ) ? $template['id'] : $template['slug'];
$args['categories'] = $this->get_template_categories( $template );
$args['subcategories'] = $this->get_template_subcategories( $template );
$args['demo_url'] = '';
if ( ! empty( $template['url'] ) ) {
$medium = wpforms_is_admin_page( 'templates' ) ? 'Form Templates Subpage' : 'builder-templates';
$args['demo_url'] = wpforms_utm_link( $template['url'], $medium, $template['name'] );
}
$template_license = ! empty( $template['license'] ) ? $template['license'] : '';
$template_name = sprintf( /* translators: %s - form template name. */
esc_html__( '%s template', 'wpforms-lite' ),
esc_html( $template['name'] )
);
$args['badge_text'] = '';
$args['license_class'] = '';
$args['education_class'] = '';
$args['education_attributes'] = '';
if ( $template['source'] === 'wpforms-addon' ) {
$args['badge_text'] = esc_html__( 'Addon', 'wpforms-lite' );
// At least one addon template available.
$this->is_addon_templates_available = true;
}
if ( $template['source'] === 'wpforms-custom' ) {
$args['badge_text'] = esc_html__( 'Custom', 'wpforms-lite' );
// At least one custom template available.
$this->is_custom_templates_available = true;
}
$args['action_text'] = $this->get_action_button_text( $template );
if ( empty( $template['has_access'] ) ) {
$args['license_class'] = ' pro';
$args['badge_text'] = $template_license;
$args['education_class'] = ' education-modal';
$args['education_attributes'] = sprintf(
' data-name="%1$s" data-license="%2$s" data-action="upgrade"',
esc_attr( $template_name ),
esc_attr( $template_license )
);
}
$args['addons_attributes'] = $this->prepare_addons_attributes( $template );
$args['selected'] = ! empty( $this->form_data['meta']['template'] ) && $this->form_data['meta']['template'] === $args['template_id'];
$args['selected_class'] = $args['selected'] ? ' selected' : '';
$args['badge_text'] = $args['selected'] ? esc_html__( 'Selected', 'wpforms-lite' ) : $args['badge_text'];
$args['badge_class'] = ! empty( $args['badge_text'] ) ? ' badge' : '';
$args['template'] = $template;
return $args;
}
/**
* Get action button text.
*
* @since 1.7.7
*
* @param array $template Template data.
*
* @return string
*/
private function get_action_button_text( $template ) {
if ( $template['slug'] === 'blank' ) {
return __( 'Create Blank Form', 'wpforms-lite' );
}
if ( wpforms_is_admin_page( 'templates' ) ) {
return __( 'Create Form', 'wpforms-lite' );
}
return __( 'Use Template', 'wpforms-lite' );
}
/**
* Generate addon attributes.
*
* @since 1.7.7
*
* @param array $template Template data.
*
* @return string Addon attributes.
*/
private function prepare_addons_attributes( $template ) {
$addons_attributes = '';
$required_addons = false;
if ( ! empty( $template['addons'] ) && is_array( $template['addons'] ) ) {
$required_addons = $this->addons_obj->get_by_slugs( $template['addons'] );
foreach ( $required_addons as $i => $addon ) {
if (
! isset( $addon['action'], $addon['title'], $addon['slug'] ) ||
! in_array( $addon['action'], [ 'install', 'activate' ], true )
) {
unset( $required_addons[ $i ] );
}
}
}
if ( ! empty( $required_addons ) ) {
$addons_names = implode( ', ', wp_list_pluck( $required_addons, 'title' ) );
$addons_slugs = implode( ',', wp_list_pluck( $required_addons, 'slug' ) );
$addons_attributes = sprintf(
' data-addons-names="%1$s" data-addons="%2$s"',
esc_attr( $addons_names ),
esc_attr( $addons_slugs )
);
}
return $addons_attributes;
}
/**
* Determine a template source.
*
* @since 1.7.7
*
* @param array $template Template data.
*
* @return string Template source.
*/
private function get_template_source( $template ) {
if ( ! empty( $template['source'] ) ) {
return $template['source'];
}
$source = 'wpforms-addon';
static $addons = null;
if ( $addons === null ) {
$addons = array_keys( $this->addons_obj->get_all() );
}
if ( $template['plugin_dir'] === 'wpforms' || $template['plugin_dir'] === 'wpforms-lite' ) {
$source = 'wpforms-core';
}
if ( $source !== 'wpforms-core' && ! in_array( $template['plugin_dir'], $addons, true ) ) {
$source = 'wpforms-custom';
}
return $source;
}
/**
* Determine template categories.
*
* @since 1.7.7
*
* @param array $template Template data.
*
* @return string Template categories coma separated.
*/
private function get_template_categories( $template ) {
$categories = ! empty( $template['categories'] ) ? (array) $template['categories'] : [];
$source = $this->get_template_source( $template );
if ( $source === 'wpforms-addon' ) {
$categories[] = 'addons';
}
if ( $source === 'wpforms-custom' ) {
$categories[] = 'custom';
}
if ( isset( $template['created_at'] ) && strtotime( $template['created_at'] ) > strtotime( '-3 Months' ) ) {
$categories[] = 'new';
}
return implode( ',', $categories );
}
/**
* Determine template subcategories.
*
* @since 1.8.4
*
* @param array $template Template data.
*
* @return string Template subcategories coma separated.
*/
private function get_template_subcategories( $template ) {
$subcategories = ! empty( $template['subcategories'] ) ? (array) $template['subcategories'] : [];
$subcategories = array_keys( $subcategories );
return implode( ',', $subcategories );
}
/**
* Get categories templates count.
*
* @since 1.7.7
*
* @return array
*/
private function get_count_in_categories() {
$all_categories = [];
$available_templates_count = 0;
$favorites_templates_count = 0;
foreach ( $this->prepared_templates as $template_data ) {
$template = $template_data['template'];
$categories = explode( ',', $template_data['categories'] );
if ( $template['has_access'] ) {
$available_templates_count ++;
}
if ( $template['favorite'] ) {
$favorites_templates_count++;
}
if ( is_array( $categories ) ) {
array_push( $all_categories, ...$categories );
continue;
}
$all_categories[] = $categories;
}
$categories_count = array_count_values( $all_categories );
$categories_count['all'] = count( $this->prepared_templates );
$categories_count['available'] = $available_templates_count;
$categories_count['favorites'] = $favorites_templates_count;
return $categories_count;
}
}

View File

@@ -0,0 +1,418 @@
<?php
namespace WPForms\Db\Payments;
use WPForms_DB;
/**
* Class for the Payment Meta database table.
*
* @since 1.8.2
*/
class Meta extends WPForms_DB {
/**
* Primary class constructor.
*
* @since 1.8.2
*/
public function __construct() {
$this->table_name = self::get_table_name();
$this->primary_key = 'id';
$this->type = 'payment_meta';
}
/**
* Get the table name.
*
* @since 1.8.2
*
* @return string
*/
public static function get_table_name() {
global $wpdb;
return $wpdb->prefix . 'wpforms_payment_meta';
}
/**
* Get table columns.
*
* @since 1.8.2
*
* @return array
*/
public function get_columns() {
return [
'id' => '%d',
'payment_id' => '%d',
'meta_key' => '%s', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => '%s', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
];
}
/**
* Default column values.
*
* @since 1.8.2
*
* @return array
*/
public function get_column_defaults() {
return [
'payment_id' => 0,
'meta_key' => '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
];
}
/**
* Create the table.
*
* @since 1.8.2
*/
public function create_table() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$max_index_length = self::MAX_INDEX_LENGTH;
/**
* Note: there must be two spaces between the words PRIMARY KEY and the definition of primary key.
*
* @link https://codex.wordpress.org/Creating_Tables_with_Plugins#Creating_or_Updating_the_Table
*/
$query = "CREATE TABLE $this->table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
payment_id bigint(20) NOT NULL,
meta_key varchar(255),
meta_value longtext,
PRIMARY KEY (id),
KEY payment_id (payment_id),
KEY meta_key (meta_key($max_index_length)),
KEY meta_value (meta_value($max_index_length))
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $query );
}
/**
* Insert payment meta's.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $meta Payment meta to be inserted.
*/
public function bulk_add( $payment_id, $meta ) {
global $wpdb;
$values = [];
foreach ( $meta as $meta_key => $meta_value ) {
// Empty strings are skipped.
if ( $meta_value === '' ) {
continue;
}
$values[] = $wpdb->prepare(
'( %d, %s, %s )',
$payment_id,
$meta_key,
maybe_serialize( $meta_value )
);
}
if ( ! $values ) {
return;
}
$values = implode( ', ', $values );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->query(
"INSERT INTO $this->table_name
( payment_id, meta_key, meta_value )
VALUES $values"
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
}
/**
* Update or add payment meta.
*
* If the meta key already exists for given payment id, update the meta value. Otherwise, add the meta key and value.
*
* @since 1.8.4
*
* @param int $payment_id Payment ID.
* @param string $meta_key Payment meta key.
* @param mixed $meta_value Payment meta value.
*
* @return bool
*/
public function update_or_add( $payment_id, $meta_key, $meta_value ) {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
$row = $this->get_last_by( $meta_key, $payment_id );
if ( $row ) {
return $this->update( $row->id, [ 'meta_value' => maybe_serialize( $meta_value ) ], '', $this->type );
}
return (bool) $this->add(
[
'payment_id' => $payment_id,
'meta_key' => $meta_key,
'meta_value' => maybe_serialize( $meta_value ),
],
$this->type
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
}
/**
* Add payment log.
*
* @since 1.8.4
*
* @param int $payment_id Payment ID.
* @param string $content Log content.
*
* @return bool
*/
public function add_log( $payment_id, $content ) {
return (bool) $this->add(
[
'payment_id' => $payment_id,
'meta_key' => 'log', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => wp_json_encode( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
[
'value' => wp_kses_post( $content ),
'date' => gmdate( 'Y-m-d H:i:s' ),
]
),
],
$this->type
);
}
/**
* Get single payment meta.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param string|null $meta_key Payment meta to be retrieved.
*
* @return mixed Meta value.
*/
public function get_single( $payment_id, $meta_key ) {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
$meta_value = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value FROM $this->table_name
WHERE payment_id = %d AND meta_key = %s ORDER BY id DESC LIMIT 1",
$payment_id,
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
return maybe_unserialize( $meta_value );
}
/**
* Get all payment meta.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
*
* @return array|null
*/
public function get_all( $payment_id ) {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_key, meta_value as value FROM $this->table_name
WHERE payment_id = %d ORDER BY id DESC",
$payment_id
),
OBJECT_K
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
}
/**
* Retrieve all rows based on meta_key value.
*
* @since 1.8.2
*
* @param string $meta_key Meta key value.
* @param int $payment_id Payment ID.
*
* @return object|null
*/
public function get_all_by( $meta_key, $payment_id ) {
global $wpdb;
if ( empty( $meta_key ) ) {
return null;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_value as value FROM $this->table_name WHERE payment_id = %d AND meta_key = %s ORDER BY id DESC", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$payment_id,
$meta_key
),
ARRAY_A
);
}
/**
* Check if there are valid entries with a specific meta key.
*
* @since 1.8.4
*
* @param string $meta_key The meta key to check.
*
* @return bool
*/
public function is_valid_meta_by_meta_key( $meta_key ) {
// Check if the meta key is empty and return false.
if ( empty( $meta_key ) ) {
return false;
}
// Retrieve the global database instance.
global $wpdb;
$payment_handler = wpforms()->get( 'payment' );
$payment_table_name = $payment_handler->table_name;
$secondary_where_clause = $payment_handler->add_secondary_where_conditions();
// Prepare and execute the SQL query to check if there are valid entries with the given meta key.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $wpdb->get_var(
$wpdb->prepare(
"SELECT 1 FROM {$this->table_name} AS pm
WHERE meta_key = %s AND meta_value IS NOT NULL
AND EXISTS (SELECT 1 FROM {$payment_table_name} AS p WHERE p.id = pm.payment_id {$secondary_where_clause})
LIMIT 1",
$meta_key
)
);
}
/**
* Check if the given meta key and value exist in the payment meta table.
*
* @since 1.8.4
*
* @param string $meta_key Meta key value.
* @param string $meta_value Meta value.
*
* @return bool
*/
public function is_valid_meta( $meta_key, $meta_value ) {
// Check if the meta key or value is empty and return false.
if ( empty( $meta_key ) || empty( $meta_value ) ) {
return false;
}
// Retrieve the global database instance.
global $wpdb;
// Prepare and execute the SQL query to check if the given meta key and value exist in the payment meta table.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $wpdb->get_var(
$wpdb->prepare(
"SELECT EXISTS( SELECT 1 FROM {$this->table_name} WHERE meta_key = %s AND meta_value = %s )",
$meta_key,
$meta_value
)
);
}
/**
* Retrieve payment meta data by given meta key and value.
*
* @since 1.8.4
*
* @param string $meta_key Meta key value.
* @param string $meta_value Meta value.
*
* @return array
*/
public function get_all_by_meta( $meta_key, $meta_value ) {
// Check if the meta key or value is empty and return null.
if ( empty( $meta_key ) || empty( $meta_value ) ) {
return [];
}
// Retrieve the global database instance.
global $wpdb;
// Prepare and execute the SQL query to retrieve payment meta data based on the given meta key and value.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_key, meta_value AS value FROM {$this->table_name}
WHERE payment_id = ( SELECT payment_id FROM {$this->table_name}
WHERE meta_key = %s AND meta_value = %s LIMIT 1 )",
$meta_key,
$meta_value
),
OBJECT_K
);
}
/**
* Get row from the payment meta table for given payment id and meta key.
*
* @since 1.8.4
*
* @param string $meta_key Meta key value.
* @param int $payment_id Payment ID.
*
* @return object|null
*/
public function get_last_by( $meta_key, $payment_id ) {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key
return $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $this->table_name
WHERE payment_id = %d AND meta_key = %s
ORDER BY id DESC LIMIT 1",
$payment_id,
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key
}
}

View File

@@ -0,0 +1,483 @@
<?php
namespace WPForms\Db\Payments;
use WPForms_DB;
/**
* Class for the Payments database table.
*
* @since 1.8.2
*/
class Payment extends WPForms_DB {
/**
* Primary class constructor.
*
* @since 1.8.2
*/
public function __construct() {
$this->table_name = self::get_table_name();
$this->primary_key = 'id';
$this->type = 'payment';
}
/**
* Get the table name.
*
* @since 1.8.2
*
* @return string
*/
public static function get_table_name() {
global $wpdb;
return $wpdb->prefix . 'wpforms_payments';
}
/**
* Get table columns.
*
* @since 1.8.2
*
* @return array
*/
public function get_columns() {
return [
'id' => '%d',
'form_id' => '%d',
'status' => '%s',
'subtotal_amount' => '%f',
'discount_amount' => '%f',
'total_amount' => '%f',
'currency' => '%s',
'entry_id' => '%d',
'gateway' => '%s',
'type' => '%s',
'mode' => '%s',
'transaction_id' => '%s',
'customer_id' => '%s',
'subscription_id' => '%s',
'subscription_status' => '%s',
'title' => '%s',
'date_created_gmt' => '%s',
'date_updated_gmt' => '%s',
'is_published' => '%d',
];
}
/**
* Default column values.
*
* @since 1.8.2
*
* @return array
*/
public function get_column_defaults() {
$date = gmdate( 'Y-m-d H:i:s' );
return [
'form_id' => 0,
'status' => '',
'subtotal_amount' => 0,
'discount_amount' => 0,
'total_amount' => 0,
'currency' => '',
'entry_id' => 0,
'gateway' => '',
'type' => '',
'mode' => '',
'transaction_id' => '',
'customer_id' => '',
'subscription_id' => '',
'subscription_status' => '',
'title' => '',
'date_created_gmt' => $date,
'date_updated_gmt' => $date,
'is_published' => 1,
];
}
/**
* Insert a new payment into the database.
*
* @since 1.8.2
*
* @param array $data Column data.
* @param string $type Optional. Data type context.
*
* @return int ID for the newly inserted payment. 0 otherwise.
*/
public function add( $data, $type = '' ) {
// Return early if the status is not allowed.
// TODO: consider validating other properties as well or get rid of it.
if ( isset( $data['status'] ) && ! ValueValidator::is_valid( $data['status'], 'status' ) ) {
return 0;
}
// Use database type identifier if a context is empty.
$type = empty( $type ) ? $this->type : $type;
return parent::add( $data, $type );
}
/**
* Retrieve a payment from the database based on a given payment ID.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Additional arguments.
*
* @return object|null
*/
public function get( $payment_id, $args = [] ) {
if ( ! $this->current_user_can( $payment_id, $args ) && wpforms()->get( 'access' )->init_allowed() ) {
return null;
}
return parent::get( $payment_id );
}
/**
* Update an existing payment in the database.
*
* @since 1.8.2
*
* @param string $payment_id Payment ID.
* @param array $data Array of columns and associated data to update.
* @param string $where Column to match against in the WHERE clause. If empty, $primary_key will be used.
* @param string $type Data type context.
* @param array $args Additional arguments.
*
* @return bool
*/
public function update( $payment_id, $data = [], $where = '', $type = '', $args = [] ) {
if ( ! $this->current_user_can( $payment_id, $args ) ) {
return false;
}
// TODO: consider validating other properties as well or get rid of it.
if ( isset( $data['status'] ) && ! ValueValidator::is_valid( $data['status'], 'status' ) ) {
return false;
}
// Use database type identifier if a context is empty.
$type = empty( $type ) ? $this->type : $type;
return parent::update( $payment_id, $data, $where, $type );
}
/**
* Delete a payment from the database, also removes payment meta.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Additional arguments.
*
* @return bool False if the payment and meta could not be deleted, true otherwise.
*/
public function delete( $payment_id = 0, $args = [] ) {
if ( ! $this->current_user_can( $payment_id, $args ) ) {
return false;
}
$is_payment_deleted = parent::delete( $payment_id );
$is_meta_deleted = wpforms()->get( 'payment_meta' )->delete_by( 'payment_id', $payment_id );
return $is_payment_deleted && $is_meta_deleted;
}
/**
* Retrieve a list of payments.
*
* @since 1.8.2
*
* @param array $args Arguments.
*
* @return array
*/
public function get_payments( $args = [] ) {
global $wpdb;
$args = $this->sanitize_get_payments_args( $args );
if ( ! $this->current_user_can( 0, $args ) ) {
return [];
}
// Prepare query.
$query[] = "SELECT p.* FROM {$this->table_name} as p";
/**
* Filter the query for get_payments method before the WHERE clause.
*
* @since 1.8.2
*
* @param string $where Before the WHERE clause in DB query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_payment_get_payments_query_before_where', '', $args );
$query[] = 'WHERE 1=1';
$query[] = $this->add_columns_where_conditions( $args );
$query[] = $this->add_secondary_where_conditions( $args );
/**
* Extend the query for the get_payments method after the WHERE clause.
*
* This hook provides the flexibility to modify the SQL query by appending custom conditions
* right after the WHERE clause.
*
* @since 1.8.4
*
* @param string $where After the WHERE clause in the database query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_payment_get_payments_query_after_where', '', $args );
// Order.
$query[] = sprintf( 'ORDER BY %s', sanitize_sql_orderby( "{$args['orderby']} {$args['order']}" ) );
// Limit.
$query[] = $wpdb->prepare( 'LIMIT %d, %d', $args['offset'], $args['number'] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$result = $wpdb->get_results( implode( ' ', $query ), ARRAY_A );
// Get results.
return ! $result ? [] : $result;
}
/**
* Create the table.
*
* @since 1.8.2
*/
public function create_table() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
/**
* To avoid any possible issues during migration from entries to payments table,
* all data types are preserved.
*
* Note: there must be two spaces between the words PRIMARY KEY and the definition of primary key.
*
* @link https://codex.wordpress.org/Creating_Tables_with_Plugins#Creating_or_Updating_the_Table
*/
$query = "CREATE TABLE $this->table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
form_id bigint(20) NOT NULL,
status varchar(10) NOT NULL DEFAULT '',
subtotal_amount decimal(26,8) NOT NULL DEFAULT 0,
discount_amount decimal(26,8) NOT NULL DEFAULT 0,
total_amount decimal(26,8) NOT NULL DEFAULT 0,
currency varchar(3) NOT NULL DEFAULT '',
entry_id bigint(20) NOT NULL DEFAULT 0,
gateway varchar(20) NOT NULL DEFAULT '',
type varchar(12) NOT NULL DEFAULT '',
mode varchar(4) NOT NULL DEFAULT '',
transaction_id varchar(40) NOT NULL DEFAULT '',
customer_id varchar(40) NOT NULL DEFAULT '',
subscription_id varchar(40) NOT NULL DEFAULT '',
subscription_status varchar(10) NOT NULL DEFAULT '',
title varchar(255) NOT NULL DEFAULT '',
date_created_gmt datetime NOT NULL,
date_updated_gmt datetime NOT NULL,
is_published tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id),
KEY form_id (form_id),
KEY status (status(8)),
KEY total_amount (total_amount),
KEY type (type(8)),
KEY transaction_id (transaction_id(32)),
KEY customer_id (customer_id(32)),
KEY subscription_id (subscription_id(32)),
KEY subscription_status (subscription_status(8)),
KEY title (title(64))
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $query );
}
/**
* Check if current user has capabilities to manage payments.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Additional arguments.
*
* @return bool
*/
private function current_user_can( $payment_id, $args = [] ) {
$manage_cap = wpforms_get_capability_manage_options();
if ( ! isset( $args['cap'] ) ) {
$args['cap'] = $manage_cap;
}
if ( ! empty( $args['cap'] ) && ! wpforms_current_user_can( $args['cap'], $payment_id ) ) {
return false;
}
return true;
}
/**
* Construct where clauses for selected columns.
*
* @since 1.8.4
*
* @param array $args Query arguments.
*
* @return string
*/
public function add_columns_where_conditions( $args = [] ) {
// Allowed columns for filtering.
$allowed_cols = [
'form_id',
'entry_id',
'status',
'subscription_status',
'type',
'gateway',
];
$where = '';
// Determine if this is a table query.
$is_table_query = ! empty( $args['table_query'] );
$keys_to_validate = [ 'status', 'subscription_status', 'type', 'gateway' ];
foreach ( $args as $key => $value ) {
if ( empty( $value ) || ! in_array( $key, $allowed_cols, true ) ) {
continue;
}
// Explode values if needed.
$values = explode( '|', $value );
// Run some keys through the "ValueValidator" class to make sure they are valid.
if ( in_array( $key, $keys_to_validate, true ) ) {
$values = array_filter(
$values,
static function ( $v ) use ( $key ) {
return ValueValidator::is_valid( $v, $key );
}
);
}
// Skip if no valid values found.
if ( empty( $values ) ) {
continue;
}
// Merge "Partially Refunded" status with "Refunded" status.
if ( $is_table_query && $key === 'status' && in_array( 'refunded', $values, true ) ) {
$values[] = 'partrefund';
}
$placeholders = wpforms_wpdb_prepare_in( $values );
// Prepare and add to WHERE clause.
$where .= " AND {$key} IN ({$placeholders})";
}
return $where;
}
/**
* Construct secondary where clauses.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return string
*/
public function add_secondary_where_conditions( $args = [] ) {
global $wpdb;
/**
* Filter arguments needed for all query.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*/
$args = (array) apply_filters( 'wpforms_db_payments_payment_add_secondary_where_conditions_args', $args );
$args = wp_parse_args(
(array) $args,
[
'currency' => wpforms_get_currency(),
'mode' => 'live',
'is_published' => 1,
]
);
$where = '';
// If it's a valid mode, add it to a WHERE clause.
if ( ValueValidator::is_valid( $args['mode'], 'mode' ) ) {
$where .= $wpdb->prepare( ' AND mode = %s', $args['mode'] );
}
$where .= $wpdb->prepare( ' AND currency = %s', $args['currency'] );
$where .= $wpdb->prepare( ' AND is_published = %d', $args['is_published'] );
return $where;
}
/**
* Sanitize query arguments for get_payments() method.
*
* @since 1.8.2
*
* @param array $args Query arguments.
*
* @return array
*/
private function sanitize_get_payments_args( $args ) {
$defaults = [
'number' => 20,
'offset' => 0,
'orderby' => 'id',
'order' => 'DESC',
];
$args = wp_parse_args( (array) $args, $defaults );
// Sanitize.
$args['number'] = absint( $args['number'] );
$args['offset'] = absint( $args['offset'] );
if ( $args['number'] === 0 ) {
$args['number'] = $defaults['number'];
}
return $args;
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace WPForms\Db\Payments;
/**
* Class for the Payments database queries.
*
* @since 1.8.2
*/
class Queries extends Payment {
/**
* Check if given payment table column has different values.
*
* @since 1.8.2
*
* @param string $column Column name.
*
* @return bool
*/
public function has_different_values( $column ) {
global $wpdb;
$subquery[] = "SELECT $column FROM $this->table_name WHERE 1=1";
$subquery[] = $this->add_secondary_where_conditions();
$subquery[] = 'LIMIT 1';
$subquery = implode( ' ', $subquery );
$query[] = "SELECT $column FROM $this->table_name WHERE 1=1";
$query[] = $this->add_secondary_where_conditions();
$query[] = "AND $column != ( $subquery )";
$query[] = 'LIMIT 1';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$result = $wpdb->get_var( implode( ' ', $query ) );
return ! empty( $result );
}
/**
* Check if there is a subscription payment.
*
* @since 1.8.2
*
* @return bool
*/
public function has_subscription() {
return $this->if_exists(
[
'type' => implode( '|', array_keys( ValueValidator::get_allowed_subscription_types() ) ),
]
);
}
/**
* Retrieve the number of all payments.
*
* @since 1.8.2
*
* @param array $args Redefine query parameters by providing own arguments.
*
* @return int Number of payments or count of payments.
*/
public function count_all( $args = [] ) {
// Retrieve the global database instance.
global $wpdb;
$query[] = 'SELECT SUM(count) AS total_count FROM (';
$query[] = "SELECT COUNT(*) AS count FROM {$this->table_name} as p";
/**
* Add parts to the query for count_all method before the WHERE clause.
*
* @since 1.8.2
*
* @param string $where Before the WHERE clause in DB query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_queries_count_all_query_before_where', '', $args );
$query[] = 'WHERE 1=1';
$query[] = $this->add_columns_where_conditions( $args );
$query[] = $this->add_secondary_where_conditions( $args );
/**
* Append custom query parts after the WHERE clause for the count_all method.
*
* This hook allows external code to extend the SQL query by adding custom conditions
* immediately after the WHERE clause.
*
* @since 1.8.4
*
* @param string $where After the WHERE clause in the database query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_queries_count_all_query_after_where', '', $args );
// Close the subquery.
$query[] = ') AS counts;';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return (int) $wpdb->get_var( implode( ' ', $query ) );
}
/**
* Whether at least one payment exists with the given arguments.
*
* @since 1.8.4
*
* @param array $args Optionally, you can redefine query parameters by providing custom arguments.
*
* @return bool False if no results found.
*/
public function if_exists( $args = [] ) {
// Retrieve the global database instance.
global $wpdb;
$query[] = "SELECT 1 FROM {$this->table_name}";
/**
* Add parts to the query for if_exists method before the WHERE clause.
*
* @since 1.8.4
*
* @param string $where Before the WHERE clause in DB query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_queries_count_if_exists_before_where', '', $args );
$query[] = 'WHERE 1=1';
$query[] = $this->add_columns_where_conditions( $args );
$query[] = $this->add_secondary_where_conditions( $args );
/**
* Append custom query parts after the WHERE clause for the if_exists method.
*
* This hook allows external code to extend the SQL query by adding custom conditions
* immediately after the WHERE clause.
*
* @since 1.8.4
*
* @param string $where After the WHERE clause in the database query.
* @param array $args Query arguments.
*
* @return string
*/
$query[] = apply_filters( 'wpforms_db_payments_queries_count_if_exists_after_where', '', $args );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return (bool) $wpdb->get_var( implode( ' ', $query ) );
}
/**
* Get next payment.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Where conditions.
*
* @return object|null Object from DB values or null.
*/
public function get_next( $payment_id, $args = [] ) {
global $wpdb;
if ( empty( $payment_id ) ) {
return null;
}
$query[] = "SELECT * FROM {$this->table_name}";
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query[] = $wpdb->prepare( "WHERE $this->primary_key > %d", $payment_id );
$query[] = $this->add_secondary_where_conditions( $args );
$query[] = "ORDER BY $this->primary_key LIMIT 1";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_row( implode( ' ', $query ) );
}
/**
* Get previous payment.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Where conditions.
*
* @return object|null Object from DB values or null.
*/
public function get_prev( $payment_id, $args = [] ) {
global $wpdb;
if ( empty( $payment_id ) ) {
return null;
}
$query[] = "SELECT * FROM $this->table_name";
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query[] = $wpdb->prepare( "WHERE $this->primary_key < %d", $payment_id );
$query[] = $this->add_secondary_where_conditions( $args );
$query[] = "ORDER BY $this->primary_key DESC LIMIT 1";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_row( implode( ' ', $query ) );
}
/**
* Get previous payments count.
*
* @since 1.8.2
*
* @param int $payment_id Payment ID.
* @param array $args Where conditions.
*
* @return int
*/
public function get_prev_count( $payment_id, $args = [] ) {
global $wpdb;
if ( empty( $payment_id ) ) {
return 0;
}
$query[] = "SELECT COUNT( $this->primary_key ) FROM $this->table_name";
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query[] = $wpdb->prepare( "WHERE $this->primary_key < %d", $payment_id );
$query[] = $this->add_secondary_where_conditions( $args );
$query[] = "ORDER BY $this->primary_key ASC";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
return (int) $wpdb->get_var( implode( ' ', $query ) );
}
/**
* Get subscription payment history for the given subscription ID.
* This function returns an array of subscription payment object and renewal payments associated with the subscription.
*
* @global wpdb $wpdb Instantiation of the wpdb class.
*
* @since 1.8.4
*
* @param string $subscription_id Subscription ID.
* @param string $currency Currency that the payment was made in.
*
* @return array Array of payment objects.
*/
public function get_subscription_payment_history( $subscription_id, $currency = '' ) {
$subscription = null;
$renewals = [];
// Bail early if the subscription ID is empty.
if ( empty( $subscription_id ) ) {
return [ $subscription, $renewals ];
}
// Get the currency, if not provided.
if ( empty( $currency ) ) {
$currency = wpforms_get_currency();
}
// Get the database instance.
global $wpdb;
// Get the general where clause.
$where_clause = $this->add_secondary_where_conditions( [ 'currency' => $currency ] );
// Construct the query using a prepared statement.
// Execute the query and fetch the results.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$this->table_name}
WHERE subscription_id = %s AND (type = 'subscription' OR type = 'renewal') {$where_clause}
ORDER BY type ASC, date_created_gmt DESC",
$subscription_id
)
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// Search for the subscription object in the "$results" array.
foreach ( $results as $key => $result ) {
if ( $result->type === 'subscription' ) {
$subscription = $result;
unset( $results[ $key ] );
break; // Exit the loop after finding the subscription object.
}
}
// Assign the remaining results to renewals.
$renewals = $results;
return [ $subscription, $renewals ];
}
/**
* Determine if given subscription has a renewal payment.
*
* @global wpdb $wpdb Instantiation of the wpdb class.
*
* @since 1.8.4
*
* @param string $subscription_id Subscription ID.
*
* @return bool True if the subscription has a renewal payment, false otherwise.
*/
public function if_subscription_has_renewal( $subscription_id ) {
// Bail early if the subscription ID is empty.
if ( empty( $subscription_id ) ) {
return false;
}
// Get the database instance.
global $wpdb;
$query[] = "SELECT 1 FROM {$this->table_name} AS s";
$query[] = 'WHERE s.subscription_id = %s';
$query[] = "AND s.type = 'subscription'";
$query[] = 'AND EXISTS(';
$query[] = "SELECT 1 FROM {$this->table_name} AS r";
$query[] = 'WHERE s.subscription_id = r.subscription_id';
$query[] = "AND r.type = 'renewal'";
$query[] = ')';
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
return (bool) $wpdb->get_var( $wpdb->prepare( implode( ' ', $query ), $subscription_id ) );
}
/**
* Get subscription payment for given subscription ID.
*
* @since 1.8.4
*
* @param string $subscription_id Subscription ID.
*
* @return object|null
*/
public function get_subscription( $subscription_id ) {
global $wpdb;
$query[] = "SELECT * FROM {$this->table_name}";
$query[] = "WHERE subscription_id = %s AND type = 'subscription'";
$query[] = 'ORDER BY id DESC LIMIT 1';
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
return $wpdb->get_row( $wpdb->prepare( implode( ' ', $query ), $subscription_id ) );
}
/**
* Get renewal payment for given invoice ID.
*
* @since 1.8.4
*
* @param string $invoice_id Invoice ID.
*
* @return object|null
*/
public function get_renewal_by_invoice_id( $invoice_id ) {
global $wpdb;
$meta_table_name = wpforms()->get( 'payment_meta' )->table_name;
$query[] = "SELECT p.* FROM {$this->table_name} as p";
$query[] = "INNER JOIN {$meta_table_name} as pm ON p.id = pm.payment_id";
$query[] = "WHERE pm.meta_key = 'invoice_id' AND pm.meta_value = %s";
$query[] = 'ORDER BY p.id DESC LIMIT 1';
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
return $wpdb->get_row( $wpdb->prepare( implode( ' ', $query ), $invoice_id ) );
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace WPForms\Db\Payments;
/**
* Payment values update helpers class.
*
* @since 1.8.4
*/
class UpdateHelpers {
/**
* Refund payment in database.
*
* @since 1.8.4
*
* @param Payment $payment_db Payment DB object.
* @param int $refunded_amount Refunded amount with cent separated.
* @param string $log Log message.
*
* @return bool
*/
public static function refund_payment( $payment_db, $refunded_amount, $log = '' ) {
$status = $refunded_amount < $payment_db->total_amount ? 'partrefund' : 'refunded';
if ( ! wpforms()->get( 'payment' )->update( $payment_db->id, [ 'status' => $status ] ) ) {
return false;
}
if (
! wpforms()->get( 'payment_meta' )->update_or_add(
$payment_db->id,
'refunded_amount',
$refunded_amount
)
) {
return false;
}
if ( $log ) {
wpforms()->get( 'payment_meta' )->add_log( $payment_db->id, $log );
}
return true;
}
/**
* Cancel subscription in database.
*
* @since 1.8.4
*
* @param int $payment_id Payment ID.
* @param string $log Log message.
*
* @return bool
*/
public static function cancel_subscription( $payment_id, $log = '' ) {
if ( ! wpforms()->get( 'payment' )->update( $payment_id, [ 'subscription_status' => 'cancelled' ] ) ) {
return false;
}
if ( $log ) {
wpforms()->get( 'payment_meta' )->add_log( $payment_id, $log );
}
return true;
}
}

View File

@@ -0,0 +1,203 @@
<?php
namespace WPForms\Db\Payments;
/**
* ValueValidator class.
*
* This class is used to validate values for the Payments DB table.
*
* @since 1.8.2
*/
class ValueValidator {
/**
* Check if value is valid for the given column.
*
* @since 1.8.2
*
* @param string $value Value to check if is valid.
* @param string $column Database column name.
*
* @return bool
*/
public static function is_valid( $value, $column ) {
$method = 'get_allowed_' . self::get_plural_column_name( $column );
if ( ! method_exists( __CLASS__, $method ) ) {
return false;
}
return isset( self::$method()[ $value ] );
}
/**
* Get allowed modes.
*
* @since 1.8.2
*
* @return array
*/
private static function get_allowed_modes() {
return [
'live' => esc_html__( 'Live', 'wpforms-lite' ),
'test' => esc_html__( 'Test', 'wpforms-lite' ),
];
}
/**
* Get allowed gateways.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_gateways() {
/**
* Filter allowed gateways.
*
* @since 1.8.2
*
* @param array $gateways Array of allowed gateways.
*/
return (array) apply_filters(
'wpforms_db_payments_value_validator_get_allowed_gateways',
[
'paypal_standard' => esc_html__( 'PayPal Standard', 'wpforms-lite' ),
'paypal_commerce' => esc_html__( 'PayPal Commerce', 'wpforms-lite' ),
'stripe' => esc_html__( 'Stripe', 'wpforms-lite' ),
'square' => esc_html__( 'Square', 'wpforms-lite' ),
'authorize_net' => esc_html__( 'Authorize.net', 'wpforms-lite' ),
]
);
}
/**
* Get allowed statuses.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_statuses() {
return array_merge(
self::get_allowed_one_time_statuses(),
self::get_allowed_subscription_statuses()
);
}
/**
* Get allowed one-time payment statuses.
*
* @since 1.8.4
*
* @return array
*/
public static function get_allowed_one_time_statuses() {
return [
'processed' => __( 'Processed', 'wpforms-lite' ),
'completed' => __( 'Completed', 'wpforms-lite' ),
'pending' => __( 'Pending', 'wpforms-lite' ),
'failed' => __( 'Failed', 'wpforms-lite' ),
'refunded' => __( 'Refunded', 'wpforms-lite' ),
'partrefund' => __( 'Partially Refunded', 'wpforms-lite' ),
];
}
/**
* Get allowed subscription statuses.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_subscription_statuses() {
return [
'active' => __( 'Active', 'wpforms-lite' ),
'cancelled' => __( 'Cancelled', 'wpforms-lite' ),
'not-synced' => __( 'Not Synced', 'wpforms-lite' ),
'failed' => __( 'Failed', 'wpforms-lite' ),
];
}
/**
* Get allowed types.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_types() {
return array_merge(
[
'one-time' => __( 'One-Time', 'wpforms-lite' ),
],
self::get_allowed_subscription_types()
);
}
/**
* Get allowed subscription types.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_subscription_types() {
return [
'subscription' => __( 'Subscription', 'wpforms-lite' ),
'renewal' => __( 'Renewal', 'wpforms-lite' ),
];
}
/**
* Get allowed subscription intervals.
* The measurement of time between billing occurrences for an automated recurring billing subscription.
*
* @since 1.8.2
*
* @return array
*/
public static function get_allowed_subscription_intervals() {
return [
'daily' => esc_html__( 'day', 'wpforms-lite' ),
'weekly' => esc_html__( 'week', 'wpforms-lite' ),
'monthly' => esc_html__( 'month', 'wpforms-lite' ),
'quarterly' => esc_html__( 'quarter', 'wpforms-lite' ),
'semiyearly' => esc_html__( 'semi-year', 'wpforms-lite' ),
'yearly' => esc_html__( 'year', 'wpforms-lite' ),
];
}
/**
* Map singular to plural column names.
*
* @since 1.8.2
*
* @param string $column Column name.
*
* @return string
*/
private static function get_plural_column_name( $column ) {
$map = [
'mode' => 'modes',
'gateway' => 'gateways',
'status' => 'statuses',
'type' => 'types',
'subscription_type' => 'subscription_types',
'subscription_status' => 'subscription_statuses',
];
return isset( $map[ $column ] ) ? $map[ $column ] : $column;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace WPForms\Emails;
use WPForms\Tasks\Task;
/**
* Action Scheduler task to fetch and cache Email Summaries Info Blocks.
*
* @since 1.6.4
*/
class FetchInfoBlocksTask extends Task {
/**
* Action name for this task.
*
* @since 1.6.4
*/
const ACTION = 'wpforms_email_summaries_fetch_info_blocks';
/**
* Option name to store the timestamp of the last run.
*
* @since 1.6.4
*/
const LAST_RUN = 'wpforms_email_summaries_fetch_info_blocks_last_run';
/**
* Class constructor.
*
* @since 1.6.4
*/
public function __construct() {
parent::__construct( self::ACTION );
$this->init();
}
/**
* Initialize the task with all the proper checks.
*
* @since 1.6.4
*/
public function init() {
$this->hooks();
$tasks = wpforms()->get( 'tasks' );
// Add new if none exists.
if ( $tasks->is_scheduled( self::ACTION ) !== false ) {
return;
}
$this->recurring( $this->generate_start_date(), WEEK_IN_SECONDS )->register();
}
/**
* Add hooks.
*
* @since 1.7.3
*/
private function hooks() {
// Register the action handler.
add_action( self::ACTION, [ $this, 'process' ] );
}
/**
* Randomly pick a timestamp which is not more than 1 week in the future
* starting before Email Summaries dispatch happens.
*
* @since 1.6.4
*
* @return int
*/
private function generate_start_date() {
$tracking = [];
$tracking['days'] = wp_rand( 0, 6 ) * DAY_IN_SECONDS;
$tracking['hours'] = wp_rand( 0, 23 ) * HOUR_IN_SECONDS;
$tracking['minutes'] = wp_rand( 0, 59 ) * MINUTE_IN_SECONDS;
$tracking['seconds'] = wp_rand( 0, 59 );
return strtotime( 'previous monday 1pm' ) + array_sum( $tracking );
}
/**
* Process the task.
*
* @since 1.6.4
*/
public function process() {
$last_run = get_option( self::LAST_RUN );
// Make sure we do not run it more than once a day.
if (
$last_run !== false &&
( time() - $last_run ) < DAY_IN_SECONDS
) {
return;
}
( new InfoBlocks() )->cache_all();
// Update the last run option to the current timestamp.
update_option( self::LAST_RUN, time() );
}
}

Some files were not shown because too many files have changed in this diff Show More