Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms;
|
||||
|
||||
use WPForms\Admin\Tools\Views\Import;
|
||||
|
||||
/**
|
||||
* Class API.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class API {
|
||||
|
||||
/**
|
||||
* Registry.
|
||||
* Contains name of the class and method to be called.
|
||||
* For non-static methods, should contain the id to operate via wpforms->get( 'class' ).
|
||||
*
|
||||
* @todo Add non-static methods processing.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
private $registry = [
|
||||
'import_forms' => [
|
||||
'class' => Import::class,
|
||||
'method' => 'import_forms',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Magic method to call a method from registry.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $name Method name.
|
||||
* @param array $args Arguments.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function __call( string $name, array $args ) {
|
||||
|
||||
$callback = $this->registry[ $name ] ?? null;
|
||||
|
||||
if ( $callback === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func( [ $callback['class'], $callback['method'] ], ...$args );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* WPForms addons text domains.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $addons_text_domains = [];
|
||||
|
||||
/**
|
||||
* WPForms addons titles.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $addons_titles = [];
|
||||
|
||||
/**
|
||||
* Determine if the class is allowed to load.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_load() {
|
||||
|
||||
global $pagenow;
|
||||
|
||||
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
|
||||
$allowed_pages = in_array( $pagenow ?? '', [ 'plugins.php', 'update-core.php', 'plugin-install.php' ], true );
|
||||
$allowed_ajax = $pagenow === 'admin-ajax.php' && isset( $_POST['action'] ) && $_POST['action'] === 'update-plugin'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
$allowed_requests = $allowed_pages || $allowed_ajax || 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()->obj( 'addons_cache' );
|
||||
|
||||
global $pagenow;
|
||||
|
||||
// Force update addons cache if we are on the update-core.php page.
|
||||
// This is necessary to update addons data while checking for all available updates.
|
||||
if ( $pagenow === 'update-core.php' ) {
|
||||
$this->cache->update( true );
|
||||
}
|
||||
|
||||
$this->addons = $this->cache->get();
|
||||
|
||||
$this->populate_addons_data();
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*/
|
||||
protected function hooks() {
|
||||
|
||||
global $pagenow;
|
||||
|
||||
/**
|
||||
* Fire before admin addons init.
|
||||
*
|
||||
* @since 1.6.7
|
||||
*/
|
||||
do_action( 'wpforms_admin_addons_init' );
|
||||
|
||||
// Filter Gettext only on Plugin list and Updates pages.
|
||||
if ( $pagenow === 'update-core.php' || $pagenow === 'plugins.php' ) {
|
||||
add_action( 'gettext', [ $this, 'filter_gettext' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( bool $force_cache_update = false ) {
|
||||
|
||||
if ( ! $this->allow_load() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( $force_cache_update ) {
|
||||
$this->cache->update( true );
|
||||
|
||||
$this->addons = $this->cache->get();
|
||||
}
|
||||
|
||||
// WPForms 1.8.7 core includes Custom Captcha.
|
||||
// The Custom Captcha addon will only work on WPForms 1.8.6 and earlier versions.
|
||||
unset( $this->addons['wpforms-captcha'] );
|
||||
|
||||
return $this->get_sorted_addons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sorted addons data.
|
||||
* Recommended addons will be displayed first,
|
||||
* then new addons, then featured addons,
|
||||
* and then all other addons.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_sorted_addons(): array {
|
||||
|
||||
if ( empty( $this->addons ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$recommended = array_filter(
|
||||
$this->addons,
|
||||
static function ( $addon ) {
|
||||
|
||||
return ! empty( $addon['recommended'] );
|
||||
}
|
||||
);
|
||||
|
||||
$new = array_filter(
|
||||
$this->addons,
|
||||
static function ( $addon ) {
|
||||
|
||||
return ! empty( $addon['new'] );
|
||||
}
|
||||
);
|
||||
|
||||
$featured = array_filter(
|
||||
$this->addons,
|
||||
static function ( $addon ) {
|
||||
|
||||
return ! empty( $addon['featured'] );
|
||||
}
|
||||
);
|
||||
|
||||
return array_merge( $recommended, $new, $featured, $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( array $addons, array $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( string $category ) {
|
||||
|
||||
return $this->get_filtered( $this->get_available(), [ '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( string $license ) {
|
||||
|
||||
return $this->get_filtered( $this->get_available(), [ 'license' => $license ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available addons data by slugs.
|
||||
*
|
||||
* @since 1.6.8
|
||||
*
|
||||
* @param array|mixed $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|bool $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 = (string) $slug;
|
||||
$slug = 'wpforms-' . str_replace( 'wpforms-', '', sanitize_key( $slug ) );
|
||||
|
||||
$addon = $this->get_available()[ $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if addon is active.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @param string $slug Addon slug.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active( string $slug ): bool {
|
||||
|
||||
$addon = $this->get_addon( $slug );
|
||||
|
||||
return isset( $addon['status'] ) && $addon['status'] === 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 a 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 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of addons available to display. All data is prepared and normalized.
|
||||
* "Available to display" means that addon needs to be displayed as an education item (addon is not installed or not activated).
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_available() {
|
||||
|
||||
static $available_addons = [];
|
||||
|
||||
if ( $available_addons ) {
|
||||
return $available_addons;
|
||||
}
|
||||
|
||||
if ( empty( $this->addons ) || ! is_array( $this->addons ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$available_addons = array_map( [ $this, 'prepare_addon_data' ], $this->addons );
|
||||
$available_addons = array_filter(
|
||||
$available_addons,
|
||||
static function ( $addon ) {
|
||||
|
||||
return isset( $addon['status'], $addon['plugin_allow'] ) && ( $addon['status'] !== 'active' || ! $addon['plugin_allow'] );
|
||||
}
|
||||
);
|
||||
|
||||
return $available_addons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare addon data.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @param array|mixed $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|mixed $addon Addon data.
|
||||
* @param string $key Key.
|
||||
* @param mixed $default_data Default data.
|
||||
*
|
||||
* @return array|string|mixed
|
||||
*/
|
||||
private function default_data( $addon, string $key, $default_data ) {
|
||||
|
||||
if ( is_string( $default_data ) ) {
|
||||
return ! empty( $addon[ $key ] ) ? $addon[ $key ] : $default_data;
|
||||
}
|
||||
|
||||
if ( is_array( $default_data ) ) {
|
||||
return ! empty( $addon[ $key ] ) ? (array) $addon[ $key ] : $default_data;
|
||||
}
|
||||
|
||||
return $addon[ $key ] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate addons data.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function populate_addons_data() {
|
||||
|
||||
foreach ( $this->addons as $addon ) {
|
||||
$this->addons_text_domains[] = $addon['slug'];
|
||||
$this->addons_titles[] = 'WPForms ' . str_replace( ' Addon', '', $addon['title'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Gettext.
|
||||
*
|
||||
* This filter allows us to prevent empty translations from being returned
|
||||
* on the `plugins` page for addon name and description.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param string|mixed $translation Translated text.
|
||||
* @param string|mixed $text Text to translate.
|
||||
* @param string|mixed $domain Text domain.
|
||||
*
|
||||
* @return string Translated text.
|
||||
*/
|
||||
public function filter_gettext( $translation, $text, $domain ): string {
|
||||
|
||||
$translation = (string) $translation;
|
||||
$text = (string) $text;
|
||||
$domain = (string) $domain;
|
||||
|
||||
if ( ! in_array( $domain, $this->addons_text_domains, true ) ) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
// Prevent empty translations from being returned and don't translate addon names.
|
||||
if ( ! trim( $translation ) || in_array( $text, $this->addons_titles, true ) ) {
|
||||
$translation = $text;
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Addons;
|
||||
|
||||
use WPForms\Helpers\CacheBase;
|
||||
|
||||
/**
|
||||
* Addons cache handler.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*/
|
||||
class AddonsCache extends CacheBase {
|
||||
|
||||
/**
|
||||
* Remote source URL.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/addons/';
|
||||
|
||||
/**
|
||||
* 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' => $this->get_remote_source(),
|
||||
|
||||
// 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',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote source URL.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_remote_source(): string {
|
||||
|
||||
return defined( 'WPFORMS_ADDONS_REMOTE_SOURCE' ) ? WPFORMS_ADDONS_REMOTE_SOURCE : self::REMOTE_SOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ): array {
|
||||
|
||||
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 when plugin addon renamed, for instance:
|
||||
// Sendinblue to Brevo, or ConvertKit to Kit,
|
||||
// but we keep the old slug for compatibility.
|
||||
foreach (
|
||||
[
|
||||
'wpforms-sendinblue' => [
|
||||
'old' => 'sendinblue',
|
||||
'new' => 'brevo',
|
||||
],
|
||||
'wpforms-convertkit' => [
|
||||
'old' => 'convertkit',
|
||||
'new' => 'kit',
|
||||
],
|
||||
] as $slug => $renamed
|
||||
) {
|
||||
if ( $addon['slug'] === $slug ) {
|
||||
$addon['icon'] = str_replace( $renamed['old'], $renamed['new'], $addon['icon'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Use slug as a key for further usage.
|
||||
$addons_cache[ $addon['slug'] ] = $addon;
|
||||
}
|
||||
|
||||
return $addons_cache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
<?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 the admin bar menu.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_access(): bool {
|
||||
|
||||
$access = false;
|
||||
|
||||
if (
|
||||
is_admin_bar_showing() &&
|
||||
wpforms_current_user_can() &&
|
||||
! wpforms_setting( 'hide-admin-bar', false )
|
||||
) {
|
||||
$access = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether the current user has access to see the admin bar menu.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param bool $access Whether the current user has access to see the admin bar menu.
|
||||
*/
|
||||
return (bool) apply_filters( 'wpforms_admin_adminbarmenu_has_access', $access ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether new notifications are available.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_notifications() {
|
||||
|
||||
return wpforms()->obj( '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() {
|
||||
|
||||
wp_add_inline_script(
|
||||
'admin-bar',
|
||||
"( 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 );
|
||||
}
|
||||
};
|
||||
document.addEventListener( 'DOMContentLoaded', wpforms_admin_bar_menu_init );
|
||||
}() );",
|
||||
'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',
|
||||
'settings_menu',
|
||||
'tools_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 );
|
||||
}
|
||||
|
||||
$this->register_settings_submenu( $wp_admin_bar );
|
||||
$this->register_tools_submenu( $wp_admin_bar );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Settings submenu.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
private function register_settings_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
/**
|
||||
* Filters the Settings submenu items.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param array $items Array of submenu items.
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
$items = (array) apply_filters(
|
||||
'wpforms_admin_bar_menu_register_settings_submenu',
|
||||
[
|
||||
'wpforms-general-settings' => [
|
||||
'title' => __( 'General', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=general',
|
||||
],
|
||||
'wpforms-email-settings' => [
|
||||
'title' => __( 'Email', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=email',
|
||||
],
|
||||
'wpforms-captcha-settings' => [
|
||||
'title' => __( 'CAPTCHA', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=captcha',
|
||||
],
|
||||
'wpforms-validation-settings' => [
|
||||
'title' => __( 'Validation', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=validation',
|
||||
],
|
||||
'wpforms-payments-settings' => [
|
||||
'title' => __( 'Payments', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=payments',
|
||||
],
|
||||
'wpforms-integrations-settings' => [
|
||||
'title' => __( 'Integrations', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=integrations',
|
||||
],
|
||||
'wpforms-geolocation-settings' => [
|
||||
'title' => __( 'Geolocation', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=geolocation',
|
||||
],
|
||||
'wpforms-access-settings' => [
|
||||
'title' => __( 'Access Control', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=access',
|
||||
],
|
||||
'wpforms-misc-settings' => [
|
||||
'title' => __( 'Misc', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-settings&view=misc',
|
||||
],
|
||||
],
|
||||
$wp_admin_bar
|
||||
);
|
||||
|
||||
foreach ( $items as $item_id => $args ) {
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wpforms-settings',
|
||||
'id' => sanitize_key( $item_id ),
|
||||
'title' => esc_html( $args['title'] ),
|
||||
'href' => admin_url( $args['path'] ),
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires after the Settings submenu item is registered.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
do_action( "wpforms_admin_bar_menu_register_settings_submenu_{$item_id}_after", $wp_admin_bar );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Tools submenu.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
private function register_tools_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
/**
|
||||
* Filters the Tools submenu items.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param array $items Array of submenu items.
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
$items = (array) apply_filters(
|
||||
'wpforms_admin_bar_menu_register_tools_submenu',
|
||||
[
|
||||
'wpforms-tools-import' => [
|
||||
'title' => esc_html__( 'Import', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=import',
|
||||
],
|
||||
'wpforms-tools-export' => [
|
||||
'title' => esc_html__( 'Export', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=export',
|
||||
],
|
||||
'wpforms-tools-system' => [
|
||||
'title' => esc_html__( 'System Info', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=system',
|
||||
],
|
||||
'wpforms-tools-action-scheduler' => [
|
||||
'title' => esc_html__( 'Scheduled Actions', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms',
|
||||
],
|
||||
'wpforms-tools-logs' => [
|
||||
'title' => esc_html__( 'Logs', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=logs',
|
||||
],
|
||||
'wpforms-tools-wpcode' => [
|
||||
'title' => esc_html__( 'Code Snippets', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=wpcode',
|
||||
],
|
||||
],
|
||||
$wp_admin_bar
|
||||
);
|
||||
|
||||
foreach ( $items as $item_id => $args ) {
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wpforms-tools',
|
||||
'id' => sanitize_key( $item_id ),
|
||||
'title' => esc_html( $args['title'] ),
|
||||
'href' => admin_url( $args['path'] ),
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires after the Tools submenu item is registered.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
do_action( "wpforms_admin_bar_menu_register_tools_submenu_{$item_id}_after", $wp_admin_bar );
|
||||
}
|
||||
|
||||
$this->register_action_scheduler_submenu( $wp_admin_bar );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Action Scheduler submenu.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
private function register_action_scheduler_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
/**
|
||||
* Filters the Action Scheduler submenu items.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param array $items Array of submenu items.
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
$items = apply_filters(
|
||||
'wpforms_admin_bar_menu_register_action_scheduler_submenu',
|
||||
[
|
||||
'wpforms-tools-action-scheduler-all' => [
|
||||
'title' => esc_html__( 'View All', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms&orderby=hook&order=desc',
|
||||
],
|
||||
'wpforms-tools-action-scheduler-complete' => [
|
||||
'title' => esc_html__( 'Completed Actions', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms&status=complete&orderby=hook&order=desc',
|
||||
],
|
||||
'wpforms-tools-action-scheduler-failed' => [
|
||||
'title' => esc_html__( 'Failed Actions', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms&status=failed&orderby=hook&order=desc',
|
||||
],
|
||||
'wpforms-tools-action-scheduler-pending' => [
|
||||
'title' => esc_html__( 'Pending Actions', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms&status=pending&orderby=hook&order=desc',
|
||||
],
|
||||
'wpforms-tools-action-scheduler-past-due' => [
|
||||
'title' => esc_html__( 'Past Due Actions', 'wpforms-lite' ),
|
||||
'path' => 'admin.php?page=wpforms-tools&view=action-scheduler&s=wpforms&status=past-due&orderby=hook&order=desc',
|
||||
],
|
||||
],
|
||||
$wp_admin_bar
|
||||
);
|
||||
|
||||
foreach ( $items as $item_id => $args ) {
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wpforms-tools-action-scheduler',
|
||||
'id' => sanitize_key( $item_id ),
|
||||
'title' => esc_html( $args['title'] ),
|
||||
'href' => admin_url( $args['path'] ),
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires after the Action Scheduler submenu item is registered.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
do_action( "wpforms_admin_bar_menu_register_action_scheduler_submenu_{$item_id}_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 Settings admin bar menu item.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WordPress Admin Bar object.
|
||||
*/
|
||||
public function settings_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wpforms-menu',
|
||||
'id' => 'wpforms-settings',
|
||||
'title' => esc_html__( 'Settings', 'wpforms-lite' ),
|
||||
'href' => admin_url( 'admin.php?page=wpforms-settings' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Tools menu to the admin bar.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar The admin bar object.
|
||||
*/
|
||||
public function tools_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wpforms-menu',
|
||||
'id' => 'wpforms-tools',
|
||||
'title' => esc_html__( 'Tools', 'wpforms-lite' ),
|
||||
'href' => admin_url( 'admin.php?page=wpforms-tools' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
'…'
|
||||
);
|
||||
}
|
||||
|
||||
$has_payments = wpforms()->obj( '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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Base\Tables\DataObjects;
|
||||
|
||||
/**
|
||||
* Column data object base class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
abstract class ColumnBase {
|
||||
|
||||
/**
|
||||
* Column ID.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string|int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Column label.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Label HTML markup.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label_html;
|
||||
|
||||
/**
|
||||
* Is column draggable.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_draggable;
|
||||
|
||||
/**
|
||||
* Column type.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Is column readonly.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $readonly;
|
||||
|
||||
/**
|
||||
* Column constructor.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param int|string $id Column ID.
|
||||
* @param array $settings Column settings.
|
||||
*/
|
||||
public function __construct( $id, array $settings ) {
|
||||
|
||||
$this->id = $id;
|
||||
$this->label = $settings['label'] ?? '';
|
||||
$this->label_html = empty( $settings['label_html'] ) ? $this->label : $settings['label_html'];
|
||||
$this->is_draggable = $settings['draggable'] ?? true;
|
||||
$this->type = empty( $settings['type'] ) ? $id : $settings['type'];
|
||||
$this->readonly = $settings['readonly'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column ID.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string|int
|
||||
*/
|
||||
public function get_id() {
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column label.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_label(): string {
|
||||
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column label HTML.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_label_html(): string {
|
||||
|
||||
return $this->label_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column type.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is column draggable.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_draggable(): bool {
|
||||
|
||||
return $this->is_draggable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is column readonly.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_readonly(): bool {
|
||||
|
||||
return $this->readonly;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Base\Tables\Facades;
|
||||
|
||||
/**
|
||||
* Column facade class.
|
||||
*
|
||||
* Hides the complexity of columns' collection behind a simple interface.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
abstract class ColumnsBase {
|
||||
|
||||
/**
|
||||
* Get columns.
|
||||
*
|
||||
* Returns all possible columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array Array of columns as objects.
|
||||
*/
|
||||
protected static function get_all(): array {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns' keys for the columns which user selected to be displayed.
|
||||
*
|
||||
* It returns an array of keys in the order they should be displayed.
|
||||
* It returns draggable and non-draggable columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_selected_columns_keys(): array {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form has selected columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_selected_columns(): bool {
|
||||
|
||||
return ! empty( static::get_selected_columns_keys() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns' keys for the columns which the user has not selected to be displayed.
|
||||
*
|
||||
* It returns draggable and non-draggable columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_not_selected_columns_keys(): array {
|
||||
|
||||
$selected = static::get_selected_columns_keys();
|
||||
$all = array_keys( static::get_all() );
|
||||
|
||||
return array_diff( $all, $selected );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate column key.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string|int $key Column key.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate_column_key( $key ): bool {
|
||||
|
||||
return isset( static::get_all()[ $key ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder;
|
||||
|
||||
use WPForms\Requirements\Requirements;
|
||||
|
||||
/**
|
||||
* Addons class.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
class Addons {
|
||||
|
||||
/**
|
||||
* List of addon options.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
const FIELD_OPTIONS = [
|
||||
'calculations' => [
|
||||
'calculation_code',
|
||||
'calculation_code_js',
|
||||
'calculation_code_php',
|
||||
'calculation_is_enabled',
|
||||
],
|
||||
'form-locker' => [
|
||||
'unique_answer',
|
||||
],
|
||||
'geolocation' => [
|
||||
'display_map',
|
||||
'enable_address_autocomplete',
|
||||
'map_position',
|
||||
],
|
||||
'surveys-polls' => [
|
||||
'survey',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Field options for disabled addons.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $disabled_field_options = [];
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of fields options added by disabled addons.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_disabled_field_options(): array {
|
||||
|
||||
$disabled_field_options = [];
|
||||
|
||||
foreach ( self::FIELD_OPTIONS as $addon_slug => $addon_fields ) {
|
||||
if ( wpforms_is_addon_initialized( $addon_slug ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$disabled_field_options[] = $addon_fields;
|
||||
}
|
||||
|
||||
if ( empty( $disabled_field_options ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_merge( ...$disabled_field_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hooks.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
add_filter( 'wpforms_save_form_args', [ $this, 'save_disabled_addons_options' ], 10, 3 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Field's options added by an addon can be deleted when the addon is deactivated or have incompatible status.
|
||||
* The options are fully controlled by the addon when addon is active and compatible.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param array|mixed $post_data Post data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function save_disabled_addons_options( $post_data ): array {
|
||||
|
||||
$post_data = (array) $post_data;
|
||||
$post_content = json_decode( wp_unslash( $post_data['post_content'] ?? '' ), true );
|
||||
$form_obj = wpforms()->obj( 'form' );
|
||||
|
||||
if ( ! $form_obj || empty( $post_content['id'] ) ) {
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
$previous_form_data = $form_obj->get( $post_content['id'], [ 'content_only' => true ] );
|
||||
|
||||
if ( empty( $previous_form_data ) ) {
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
$post_content = $this->preserve_fields( $post_content, $previous_form_data );
|
||||
$post_content = $this->preserve_providers( $post_content, $previous_form_data );
|
||||
$post_content = $this->preserve_payments( $post_content, $previous_form_data );
|
||||
$post_content = $this->preserve_settings( $post_content, $previous_form_data );
|
||||
|
||||
$post_data['post_content'] = wpforms_encode( $post_content );
|
||||
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve fields data from inactive addons.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
* @param array $previous_form_data Previous form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function preserve_fields( array $form_data, array $previous_form_data ): array {
|
||||
|
||||
if ( empty( $form_data['fields'] ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
$this->disabled_field_options = $this->get_disabled_field_options();
|
||||
$previous_fields = $previous_form_data['fields'] ?? [];
|
||||
|
||||
if ( empty( $this->disabled_field_options ) || empty( $previous_fields ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
foreach ( $form_data['fields'] as $field_id => $new_field ) {
|
||||
if ( empty( $previous_fields[ $field_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$form_data['fields'][ $field_id ] =
|
||||
$this->add_disabled_addons_options_field( (array) $new_field, (array) $previous_fields[ $field_id ] );
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve Providers that are not active.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
* @param array $previous_form_data Previous form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function preserve_providers( array $form_data, array $previous_form_data ): array {
|
||||
|
||||
if ( empty( $previous_form_data['providers'] ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
$active_providers = wpforms_get_providers_available();
|
||||
|
||||
foreach ( $previous_form_data['providers'] as $provider_id => $provider ) {
|
||||
if ( ! empty( $active_providers[ $provider_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$form_data['providers'][ $provider_id ] = $provider;
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve Payments providers that are not active.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
* @param array $previous_form_data Previous form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function preserve_payments( array $form_data, array $previous_form_data ): array {
|
||||
|
||||
if ( empty( $previous_form_data['payments'] ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
foreach ( $previous_form_data['payments'] as $slug => $value ) {
|
||||
if ( ! empty( $form_data['payments'][ $slug ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$form_data['payments'][ $slug ] = $value;
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve addon notifications.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param string $slug Addon slug.
|
||||
* @param array $new_notifications List of form notifications.
|
||||
* @param array $previous_notifications Previously saved list of form notifications.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function preserve_addon_notifications( string $slug, array &$new_notifications, array $previous_notifications ): void {
|
||||
|
||||
$prefix = $this->prepare_prefix( $slug );
|
||||
|
||||
foreach ( $previous_notifications as $notification_id => $notification_settings ) {
|
||||
if ( empty( $new_notifications[ $notification_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed_notifications = array_diff_key(
|
||||
$notification_settings,
|
||||
$new_notifications[ $notification_id ]
|
||||
);
|
||||
|
||||
foreach ( $changed_notifications as $setting_name => $value ) {
|
||||
if ( strpos( $setting_name, $prefix ) === 0 ) {
|
||||
$new_notifications[ $notification_id ][ $setting_name ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve settings of not active addons from the Settings tab.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
* @param array $previous_form_data Previous form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function preserve_settings( array $form_data, array $previous_form_data ): array {
|
||||
|
||||
$requirements = Requirements::get_instance();
|
||||
$not_validated_addons = $requirements->get_not_validated_addons();
|
||||
|
||||
foreach ( $not_validated_addons as $path ) {
|
||||
$slug = str_replace( 'wpforms-', '', basename( $path, '.php' ) );
|
||||
$panel = $this->prepare_prefix( $slug );
|
||||
|
||||
// The addon settings stored its own panel, e.g., $form_data[lead_forms], $form_data[webhooks], etc.
|
||||
if ( ! empty( $previous_form_data[ $panel ] ) ) {
|
||||
$form_data[ $panel ] = $previous_form_data[ $panel ];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $previous_form_data['settings'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->preserve_addon_settings( $panel, $form_data, $previous_form_data );
|
||||
|
||||
if ( ! empty( $form_data['settings']['notifications'] ) && ! empty( $previous_form_data['settings']['notifications'] ) ) {
|
||||
$this->preserve_addon_notifications( $slug, $form_data['settings']['notifications'], $previous_form_data['settings']['notifications'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve addon settings stored inside the settings panel with a specific prefix.
|
||||
* e.g. $form_data[settings][{$prefix}_enabled], $form_data[settings][{$prefix}_email], etc.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param string $prefix Addon option prefix.
|
||||
* @param array $form_data Form data.
|
||||
* @param array $previous_form_data Previous form data.
|
||||
*/
|
||||
private function preserve_addon_settings( string $prefix, array &$form_data, array $previous_form_data ): void {
|
||||
|
||||
static $legacy_options = [
|
||||
'offline_forms' => [ 'offline_form' ],
|
||||
'user_registration' => [ 'user_login_hide', 'user_reset_hide' ],
|
||||
'surveys_polls' => [ 'survey_enable', 'poll_enable' ],
|
||||
];
|
||||
|
||||
// BC: User Registration addon has `registration_` prefix instead of `user_registration`.
|
||||
if ( $prefix === 'user_registration' ) {
|
||||
$prefix = 'registration';
|
||||
}
|
||||
|
||||
foreach ( $previous_form_data['settings'] as $setting_name => $value ) {
|
||||
if ( strpos( $setting_name, $prefix ) === 0 ) {
|
||||
$form_data['settings'][ $setting_name ] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// BC: The options don't have a prefix and hard-coded in the `$legacy_options` variable.
|
||||
if ( isset( $legacy_options[ $prefix ] ) && in_array( $setting_name, $legacy_options[ $prefix ], true ) ) {
|
||||
$form_data['settings'][ $setting_name ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add disabled addons options to the field.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param array $new_field Updated field data.
|
||||
* @param array $old_field Old field data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function add_disabled_addons_options_field( array $new_field, array $old_field ): array {
|
||||
|
||||
foreach ( $this->disabled_field_options as $option ) {
|
||||
if ( isset( $old_field[ $option ] ) ) {
|
||||
$new_field[ $option ] = $old_field[ $option ];
|
||||
}
|
||||
}
|
||||
|
||||
return $new_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert slug to a addon prefix.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param string $slug Addon slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function prepare_prefix( string $slug ): string {
|
||||
|
||||
return str_replace( '-', '_', $slug );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder\Ajax;
|
||||
|
||||
/**
|
||||
* Form Builder Panel Loader AJAX actions.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class PanelLoader {
|
||||
|
||||
/**
|
||||
* Determine if the class is allowed to load.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allow_load(): bool {
|
||||
|
||||
// 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 form the Form Builder.
|
||||
return wpforms_is_admin_ajax() && strpos( $action, 'wpforms_builder_' ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function init(): void {
|
||||
|
||||
if ( ! $this->allow_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function hooks(): void {
|
||||
|
||||
add_action( 'wp_ajax_wpforms_builder_load_panel', [ $this, 'load_panel_content' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save tags.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function load_panel_content(): void {
|
||||
|
||||
check_ajax_referer( 'wpforms-builder', 'nonce' );
|
||||
|
||||
$form_id = absint( filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_NUMBER_INT ) );
|
||||
|
||||
if ( ! wpforms_current_user_can( 'edit_forms', $form_id ) ) {
|
||||
wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
$data = $this->get_prepared_data( 'load_panel' );
|
||||
$panel = $data['panel'] ?? '';
|
||||
$panel_class = '\WPForms_Builder_Panel_' . ucfirst( $panel );
|
||||
$panel_obj = $this->get_panel_obj( $panel_class, $panel );
|
||||
|
||||
ob_start();
|
||||
$panel_obj->panel_output( [], $panel );
|
||||
|
||||
$panel_content = ob_get_clean();
|
||||
|
||||
wp_send_json_success( $panel_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get panel object.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param string $panel_class Panel class name.
|
||||
* @param string $panel Panel name.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
private function get_panel_obj( string $panel_class, string $panel ) {
|
||||
|
||||
if ( ! class_exists( $panel_class ) ) {
|
||||
// Load panel base class.
|
||||
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/panels/class-base.php';
|
||||
|
||||
$file = WPFORMS_PLUGIN_DIR . "includes/admin/builder/panels/class-{$panel}.php";
|
||||
$file_pro = WPFORMS_PLUGIN_DIR . "pro/includes/admin/builder/panels/class-{$panel}.php";
|
||||
|
||||
if ( file_exists( $file_pro ) && wpforms()->is_pro() ) {
|
||||
require_once $file_pro;
|
||||
} elseif ( file_exists( $file ) ) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
|
||||
$panel_obj = $panel_class::instance();
|
||||
|
||||
if ( ! method_exists( $panel_obj, 'panel_content' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Invalid panel.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
return $panel_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared data before perform ajax action.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $action Action: `save` OR `delete`.
|
||||
*
|
||||
* @return array
|
||||
* @noinspection PhpSameParameterValueInspection
|
||||
*/
|
||||
private function get_prepared_data( string $action ): array {
|
||||
|
||||
// Run a security check.
|
||||
if ( ! check_ajax_referer( 'wpforms-builder', '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 = [];
|
||||
|
||||
if ( $action === 'load_panel' ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$data['panel'] = ! empty( $_POST['panel'] ) ? sanitize_key( $_POST['panel'] ) : '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder\Ajax;
|
||||
|
||||
/**
|
||||
* Save the form data.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*/
|
||||
class SaveForm {
|
||||
|
||||
/**
|
||||
* The form fields processing while saving the form.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $fields Form fields data.
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process_fields( array $fields, array $form_data ): array {
|
||||
|
||||
$form_obj = wpforms()->obj( 'form' );
|
||||
|
||||
if ( ! $form_obj || empty( $fields ) || empty( $form_data['id'] ) ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$saved_form_data = $form_obj->get( $form_data['id'], [ 'content_only' => true ] );
|
||||
|
||||
foreach ( $fields as $field_id => $field_data ) {
|
||||
if ( empty( $field_data['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter field settings before saving the form.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $field_data Field data.
|
||||
* @param array $form_data Forms data.
|
||||
* @param array $saved_form_data Saved form data.
|
||||
*/
|
||||
$fields[ $field_id ] = apply_filters( "wpforms_admin_builder_ajax_save_form_field_{$field_data['type']}", $field_data, $form_data, $saved_form_data );
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
<?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 = $this->update_settings_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_v3',
|
||||
$this->form_data,
|
||||
__( 'Enable modern anti-spam protection', 'wpforms-lite' ),
|
||||
[
|
||||
'value' => (int) ! empty( $this->form_data['settings']['antispam_v3'] ),
|
||||
'tooltip' => __( 'Turn on invisible modern 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']['antispam'] ) && empty( $this->form_data['settings']['antispam_v3'] ) ) {
|
||||
wpforms_panel_field(
|
||||
'toggle',
|
||||
'settings',
|
||||
'antispam',
|
||||
$this->form_data,
|
||||
__( 'Enable anti-spam protection', 'wpforms-lite' ),
|
||||
[
|
||||
'tooltip' => __( 'Turn on invisible spam protection.', 'wpforms-lite' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $this->form_data['settings']['honeypot'] ) && empty( $this->form_data['settings']['antispam_v3'] ) ) {
|
||||
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();
|
||||
|
||||
// Hidden setting to store blocked entries by filtering as a spam.
|
||||
// This setting is needed to keep backward compatibility with old forms.
|
||||
wpforms_panel_field(
|
||||
'checkbox',
|
||||
'anti_spam',
|
||||
'filtering_store_spam',
|
||||
$this->form_data,
|
||||
'',
|
||||
[
|
||||
'parent' => 'settings',
|
||||
'class' => 'wpforms-hidden',
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* 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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the form data on the builder settings panel.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function update_settings_form_data( array $form_data ): array {
|
||||
|
||||
if ( ! $form_data ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
// Update `Filtering` store spam entries behaviour.
|
||||
// Enable for new forms and old forms without any `Filtering` setting enabled.
|
||||
if (
|
||||
empty( $form_data['settings']['anti_spam']['filtering_store_spam'] ) &&
|
||||
empty( $form_data['settings']['anti_spam']['country_filter']['enable'] ) &&
|
||||
empty( $form_data['settings']['anti_spam']['keyword_filter']['enable'] )
|
||||
) {
|
||||
$form_data['settings']['anti_spam']['filtering_store_spam'] = true;
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$captcha_settings['provider'] !== 'hcaptcha' && (
|
||||
empty( $captcha_settings['site_key'] ) || empty( $captcha_settings['secret_key'] )
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $captcha_settings['provider'] === 'hcaptcha' && empty( $captcha_settings['site_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() {
|
||||
|
||||
if ( ! wpforms()->is_pro() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$disable_entries = $this->form_data['settings']['disable_entries'] ?? 0;
|
||||
|
||||
wpforms_panel_field(
|
||||
'toggle',
|
||||
'settings',
|
||||
'store_spam_entries',
|
||||
$this->form_data,
|
||||
__( 'Store spam entries in the database', 'wpforms-lite' ),
|
||||
[
|
||||
'value' => $this->form_data['settings']['store_spam_entries'] ?? 0,
|
||||
'class' => $disable_entries ? 'wpforms-hidden' : '',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => 2,
|
||||
'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 →', '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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder;
|
||||
|
||||
/**
|
||||
* Context Menu class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class ContextMenu {
|
||||
|
||||
/**
|
||||
* Init class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
protected function hooks() {
|
||||
|
||||
add_action( 'wpforms_builder_enqueues', [ $this, 'enqueues' ] );
|
||||
add_action( 'wpforms_admin_page', [ $this, 'output' ], 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function enqueues() {
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-builder-context-menu',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/context-menu{$min}.js",
|
||||
[ 'wpforms-builder' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output context menu markup.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function output() {
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wpforms_render( 'builder/field-context-menu' );
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder;
|
||||
|
||||
use WPForms\Helpers\CacheBase;
|
||||
|
||||
/**
|
||||
* Form Builder Help Cache.
|
||||
*
|
||||
* @since 1.8.2
|
||||
*/
|
||||
class HelpCache extends CacheBase {
|
||||
|
||||
/**
|
||||
* Remote source URL.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/docs/';
|
||||
|
||||
/**
|
||||
* 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' => self::REMOTE_SOURCE,
|
||||
'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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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/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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder\Settings;
|
||||
|
||||
/**
|
||||
* Themes panel.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
class Themes {
|
||||
|
||||
/**
|
||||
* Init class.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
protected function hooks() {
|
||||
|
||||
add_action( 'wpforms_form_settings_panel_content', [ $this, 'panel_content' ], 10, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a content for `Themes` panel.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function panel_content() {
|
||||
?>
|
||||
<div class="wpforms-panel-content-section wpforms-panel-content-section-themes">
|
||||
<div class="wpforms-panel-content-section-themes-inner">
|
||||
<div class="wpforms-panel-content-section-themes-top">
|
||||
<div class="wpforms-panel-content-section-title">
|
||||
<?php esc_html_e( 'Form Themes', 'wpforms-lite' ); ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$this->get_top_alert();
|
||||
$this->get_main_content();
|
||||
?>
|
||||
|
||||
</div> <!-- .wpforms-panel-content-section-themes-top -->
|
||||
<div class="wpforms-panel-content-section-themes-bottom">
|
||||
<?php $this->get_bottom_alert(); ?>
|
||||
</div> <!-- .wpforms-panel-content-section-themes-bottom -->
|
||||
</div> <!-- .wpforms-panel-content-section-themes-inner -->
|
||||
</div> <!-- .wpforms-panel-content-section-themes -->
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top alert.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
private function get_top_alert() {
|
||||
|
||||
if ( $this->is_using_modern_markup() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wpforms-alert wpforms-alert-warning">
|
||||
<div class="wpforms-aside-left">
|
||||
<p class="wpforms-alert-heading">
|
||||
<?php esc_html_e( 'Before You Can Use Form Themes', 'wpforms-lite' ); ?>
|
||||
</p>
|
||||
<p class="wpforms-alert-content">
|
||||
<?php esc_html_e( 'Upgrade your forms to use our modern markup and unlock form themes and style controls.', 'wpforms-lite' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wpforms-aside-right">
|
||||
<a class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey"
|
||||
href="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-settings' ) ); ?>">
|
||||
<?php esc_html_e( 'Enable Modern Markup', 'wpforms-lite' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bottom alert.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
private function get_bottom_alert() {
|
||||
|
||||
if ( ! $this->is_using_modern_markup() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = wpforms_utm_link( 'https://wpforms.com/features/suggest/', 'Builder Themes', 'Theme Request Link' );
|
||||
?>
|
||||
<div class="wpforms-alert wpforms-alert-info wpforms-bottom">
|
||||
<div class="wpforms-aside-left">
|
||||
<p class="wpforms-alert-heading">
|
||||
<?php esc_html_e( 'Not Using the Block Editor? Let us know!', 'wpforms-lite' ); ?>
|
||||
</p>
|
||||
<p class="wpforms-alert-content">
|
||||
<?php esc_html_e( 'If we get enough requests for themes in the form builder we may add them.', 'wpforms-lite' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wpforms-aside-right">
|
||||
<a class="wpforms-btn wpforms-btn-md wpforms-btn-light-grey"
|
||||
rel="noopener noreferrer"
|
||||
href="<?php echo esc_url( $url ); ?>"
|
||||
target="_blank">
|
||||
<?php esc_html_e( 'Request Feature', 'wpforms-lite' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main content.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @noinspection HtmlUnknownTarget
|
||||
*/
|
||||
private function get_main_content() {
|
||||
|
||||
$url = wpforms_utm_link( 'https://wpforms.com/docs/styling-your-forms/', 'Builder Themes', 'Description Link' );
|
||||
$video = 'https://www.youtube.com/embed/Km5kV-2SMLg';
|
||||
?>
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
/* translators: %s - URL to the documentation. */
|
||||
__( 'Customize the look and feel of your form with premade themes or simple style settings that allow you to use your own colors to match your brand. Themes and style settings are in the Block Editor, where you can see a realtime preview. <br /><a href="%s" target="_blank">Learn more about styling your forms</a>', 'wpforms-lite' ),
|
||||
esc_url( $url )
|
||||
),
|
||||
[
|
||||
'a' => [
|
||||
'href' => [],
|
||||
'target' => [],
|
||||
],
|
||||
'br' => [],
|
||||
]
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<div class="wpforms-panel-content-section-video">
|
||||
<iframe
|
||||
src="<?php echo esc_url( $video ); ?>"
|
||||
title="<?php esc_attr_e( 'Form Themes', 'wpforms-lite' ); ?>"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
width="640"
|
||||
height="360"
|
||||
loading="lazy"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form is using modern markup.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_using_modern_markup(): bool {
|
||||
// phpcs:ignore WPForms.Formatting.EmptyLineAfterFunctionDeclaration.AddEmptyLineAfterFunctionDeclaration
|
||||
return wpforms_get_render_engine() === 'modern';
|
||||
}
|
||||
}
|
||||
@@ -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' ), // It is 'alt s' on Windows/Linux, dynamically changed in the modal in admin-builder.js openKeyboardShortcutsModal().
|
||||
'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 class="shortcut-key shortcut-key-%2$s-%3$s">
|
||||
<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>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?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 ( ! $this->updated ) {
|
||||
$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()->obj( '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 ): array {
|
||||
|
||||
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 ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
$content = file_get_contents( $filename );
|
||||
|
||||
if ( empty( $content ) || trim( $content ) === '[]' ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
|
||||
unlink( $filename );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Builder;
|
||||
|
||||
use WPForms\Helpers\CacheBase;
|
||||
use WPForms\Helpers\File;
|
||||
|
||||
/**
|
||||
* Form templates cache handler.
|
||||
*
|
||||
* @since 1.6.8
|
||||
*/
|
||||
class TemplatesCache extends CacheBase {
|
||||
|
||||
/**
|
||||
* Templates list content cache files.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const CONTENT_CACHE_FILES = [
|
||||
'admin-page' => 'templates-admin-page.html',
|
||||
'builder' => 'templates-builder.html',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of plugins that can use the templates cache.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const PLUGINS = [
|
||||
'wpforms',
|
||||
'wpforms-lite',
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine if the class is allowed to load.
|
||||
*
|
||||
* @since 1.6.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function allow_load(): bool {
|
||||
|
||||
$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' ) ||
|
||||
wpforms_is_admin_page( 'tools', 'action-scheduler' );
|
||||
$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
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
|
||||
|
||||
parent::init();
|
||||
|
||||
// Upgrade cached templates data after the plugin update.
|
||||
add_action( 'upgrader_process_complete', [ $this, 'upgrade_templates' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade cached templates data after the plugin update.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param object $upgrader WP_Upgrader instance.
|
||||
*/
|
||||
public function upgrade_templates( $upgrader ) {
|
||||
|
||||
if ( $this->allow_update_cache( $upgrader ) ) {
|
||||
$this->update( true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if allowed to update the cache.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param object $upgrader WP_Upgrader instance.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allow_update_cache( $upgrader ): bool {
|
||||
|
||||
$result = $upgrader->result ?? null;
|
||||
|
||||
// Check if plugin was updated.
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if updated plugin is WPForms.
|
||||
if ( ! in_array( $result['destination_name'], self::PLUGINS, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ): array {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cache.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param bool $force Whether to force update the cache.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update( bool $force = false ): bool {
|
||||
|
||||
$result = parent::update( $force );
|
||||
|
||||
if ( ! $result ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->wipe_content_cache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached templates content.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_cache(): string {
|
||||
|
||||
// phpcs:ignore Universal.Operators.DisallowShortTernary.Found
|
||||
return File::get_contents( $this->get_content_cache_file() ) ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save templates content cache.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string|mixed $content Templates content.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save_content_cache( $content ): bool {
|
||||
|
||||
return File::put_contents( $this->get_content_cache_file(), (string) $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe cached templates content.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function wipe_content_cache() {
|
||||
|
||||
$cache_dir = $this->get_cache_dir();
|
||||
|
||||
// Delete the template content cache files. They will be regenerated on the first visit.
|
||||
foreach ( self::CONTENT_CACHE_FILES as $file ) {
|
||||
|
||||
$cache_file = $cache_dir . $file;
|
||||
|
||||
if ( is_file( $cache_file ) && is_readable( $cache_file ) ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
|
||||
unlink( $cache_file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get templates content cache file path.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_content_cache_file(): string {
|
||||
|
||||
$context = wpforms_is_admin_page( 'templates' ) ? 'admin-page' : 'builder';
|
||||
|
||||
return File::get_cache_dir() . self::CONTENT_CACHE_FILES[ $context ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
<?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/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(),
|
||||
'frozen_tooltip' => esc_html__( 'Challenge is frozen.', 'wpforms-lite' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
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/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/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/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() {
|
||||
|
||||
// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
|
||||
return (bool) wpforms()->obj( '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, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 on WPForms admin pages or Builder page.
|
||||
if ( ! wpforms_is_admin_page() && ! wpforms_is_admin_page( 'builder' ) ) {
|
||||
$can_start = false;
|
||||
|
||||
// No need to check something else in this case.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The challenge should not start if this is the Forms' Overview page.
|
||||
if ( wpforms_is_admin_page( 'overview' ) ) {
|
||||
$can_start = false;
|
||||
|
||||
// No need to check something else in this case.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force start the Challenge.
|
||||
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 true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?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_welcome_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;
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( $action === 'get' ) {
|
||||
$meta_value = absint( get_user_meta( $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( $user_id, $meta_key, $value );
|
||||
}
|
||||
|
||||
if ( $action === 'set' && empty( $value ) ) {
|
||||
return delete_user_meta( $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(): array {
|
||||
|
||||
$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 the recommended plugins.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final protected function get_recommended_plugin(): array {
|
||||
|
||||
$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(): bool {
|
||||
|
||||
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(): bool {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return wpforms_is_admin_ajax() && isset( $_REQUEST['action'] ) && strpos( sanitize_key( $_REQUEST['action'] ), 'wpforms_dash_widget' ) !== false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education;
|
||||
|
||||
use WPForms\Admin\Addons\Addons;
|
||||
use WPForms\Requirements\Requirements;
|
||||
|
||||
/**
|
||||
* 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 Core
|
||||
*/
|
||||
protected $education;
|
||||
|
||||
/**
|
||||
* Instance of the Education\Addons class.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @var Addons
|
||||
*/
|
||||
protected $addons;
|
||||
|
||||
/**
|
||||
* Template name for rendering single addon item.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $single_addon_template;
|
||||
|
||||
/**
|
||||
* Indicate if the current Education feature is allowed to load.
|
||||
* Should be called from the child feature class.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
*/
|
||||
abstract public function allow_load();
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
if ( ! $this->allow_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the instance of the Education core class.
|
||||
$this->education = wpforms()->obj( 'education' );
|
||||
|
||||
// Store the instance of the Education\Addons class.
|
||||
$this->addons = wpforms()->obj( '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.
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare field data-attributes for the education actions.
|
||||
* E.g., install, activate, incompatible.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $addon Current addon information.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_field_action_data( array $addon ): array {
|
||||
|
||||
if ( empty( $addon['plugin_allow'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( $addon['action'] === 'install' ) {
|
||||
return [
|
||||
'data' => [
|
||||
'action' => 'install',
|
||||
'name' => $addon['modal_name'],
|
||||
'url' => $addon['url'],
|
||||
'nonce' => wp_create_nonce( 'wpforms-admin' ),
|
||||
'license' => $addon['license_level'],
|
||||
],
|
||||
'class' => 'education-modal',
|
||||
];
|
||||
}
|
||||
|
||||
if ( $addon['action'] === 'activate' ) {
|
||||
return [
|
||||
'data' => [
|
||||
'action' => 'activate',
|
||||
'name' => sprintf( /* translators: %s - addon name. */
|
||||
esc_html__( '%s addon', 'wpforms-lite' ),
|
||||
$addon['name']
|
||||
),
|
||||
'path' => $addon['path'],
|
||||
'nonce' => wp_create_nonce( 'wpforms-admin' ),
|
||||
],
|
||||
'class' => 'education-modal',
|
||||
];
|
||||
}
|
||||
|
||||
if ( $addon['action'] === 'incompatible' ) {
|
||||
return [
|
||||
'data' => [
|
||||
'action' => 'incompatible',
|
||||
'message' => Requirements::get_instance()->get_notice( $addon['path'] ),
|
||||
],
|
||||
'class' => 'education-modal',
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -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 ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education\Admin;
|
||||
|
||||
use WP_Post;
|
||||
use WPForms\Admin\Education\EducationInterface;
|
||||
|
||||
/**
|
||||
* Admin/EditPost Education feature.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class EditPost implements EducationInterface {
|
||||
|
||||
/**
|
||||
* Determine if the website has some forms.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $has_forms;
|
||||
|
||||
/**
|
||||
* Indicate if edit post education is allowed to load.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_load() {
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! wpforms_current_user_can( 'view_forms' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip it if it's the Challenge flow.
|
||||
if ( wpforms()->obj( 'challenge' )->is_form_embed_page() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$form_embed_wizard = wpforms()->obj( 'form_embed_wizard' );
|
||||
|
||||
// Skip it if it's the Form Embed Wizard flow.
|
||||
if ( $form_embed_wizard->is_form_embed_page( 'edit' ) && $form_embed_wizard->get_meta() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$dismissed = get_user_meta( $user_id, 'wpforms_dismissed', true );
|
||||
|
||||
return empty( $dismissed['edu-edit-post-notice'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
if ( ! $this->allow_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
|
||||
$this->has_forms = (bool) wpforms()->obj( '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, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
|
||||
]
|
||||
);
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hooks.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
add_action( 'edit_form_after_title', [ $this, 'classic_editor_notice' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is gutenberg Editor.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_gutenberg_editor() {
|
||||
|
||||
return (bool) get_current_screen()->is_block_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
wp_enqueue_style(
|
||||
'wpforms-edit-post-education',
|
||||
WPFORMS_PLUGIN_URL . "assets/css/admin/edit-post-education{$min}.css",
|
||||
[],
|
||||
WPFORMS_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-edit-post-education',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/education/edit-post.es5{$min}.js",
|
||||
[ 'jquery', 'underscore' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$strings = [
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'education_nonce' => wp_create_nonce( 'wpforms-education' ),
|
||||
];
|
||||
|
||||
if ( $this->is_gutenberg_editor() ) {
|
||||
$strings = array_merge( $strings, $this->get_gutenberg_strings() );
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-edit-post-education',
|
||||
'wpforms_edit_post_education',
|
||||
$strings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gutenberg i18n strings.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_gutenberg_strings() {
|
||||
|
||||
$strings = [
|
||||
'gutenberg_notice' => [
|
||||
'template' => $this->get_gutenberg_notice_template(),
|
||||
'button' => __( 'Get Started', 'wpforms-lite' ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! $this->has_forms ) {
|
||||
$strings['gutenberg_notice']['url'] = add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) );
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
$strings['gutenberg_guide'] = [
|
||||
[
|
||||
'image' => WPFORMS_PLUGIN_URL . '/assets/images/edit-post-education-page-1.png',
|
||||
'title' => __( 'Easily add your contact form', 'wpforms-lite' ),
|
||||
'content' => __( 'Oh hey, it looks like you\'re working on a contact page. Don\'t forget to embed your contact form. Click the plus icon above and search for WPForms.', 'wpforms-lite' ),
|
||||
],
|
||||
[
|
||||
'image' => WPFORMS_PLUGIN_URL . '/assets/images/edit-post-education-page-2.png',
|
||||
'title' => __( 'Embed your form', 'wpforms-lite' ),
|
||||
'content' => __( 'Then click on the WPForms block to embed your desired contact form.', 'wpforms-lite' ),
|
||||
],
|
||||
];
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice to classic editor.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param WP_Post $post Add notice to classic editor.
|
||||
*/
|
||||
public function classic_editor_notice( $post ) {
|
||||
|
||||
$message = $this->has_forms
|
||||
? __( 'Don\'t forget to embed your contact form. Simply click the Add Form button below.', 'wpforms-lite' )
|
||||
: sprintf( /* translators: %1$s - link to create a new form. */
|
||||
__( 'Did you know that with <a href="%1$s" target="_blank" rel="noopener noreferrer">WPForms</a>, you can create an easy-to-use contact form in a matter of minutes?', 'wpforms-lite' ),
|
||||
esc_url( add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) ) )
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wpforms_render(
|
||||
'education/admin/edit-post/classic-notice',
|
||||
[
|
||||
'message' => $message,
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Gutenberg notice template.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_gutenberg_notice_template() {
|
||||
|
||||
$message = $this->has_forms
|
||||
? __( 'You\'ve already created a form, now add it to the page so your customers can get in touch.', 'wpforms-lite' )
|
||||
: sprintf( /* translators: %1$s - link to create a new form. */
|
||||
__( 'Did you know that with <a href="%1$s" target="_blank" rel="noopener noreferrer">WPForms</a>, you can create an easy-to-use contact form in a matter of minutes?', 'wpforms-lite' ),
|
||||
esc_url( add_query_arg( 'page', 'wpforms-overview', admin_url( 'admin.php' ) ) )
|
||||
);
|
||||
|
||||
return wpforms_render(
|
||||
'education/admin/edit-post/notice',
|
||||
[
|
||||
'message' => $message,
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
$settings[ self::SLUG ][ self::SLUG . '-page' ] = [
|
||||
'id' => self::SLUG . '-page',
|
||||
'content' => wpforms_render( 'education/admin/page', $this->template_data(), true ),
|
||||
'type' => 'content',
|
||||
'no_label' => true,
|
||||
'class' => [ 'wpforms-education-container-page' ],
|
||||
];
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template data.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function template_data(): array {
|
||||
|
||||
$addon = $this->addons->get_addon( 'geolocation' );
|
||||
$images_url = WPFORMS_PLUGIN_URL . 'assets/images/geolocation-education/';
|
||||
$params = [
|
||||
'features' => [
|
||||
__( 'City', 'wpforms-lite' ),
|
||||
__( 'Latitude/Longitude', 'wpforms-lite' ),
|
||||
__( 'Google Places API', 'wpforms-lite' ),
|
||||
__( 'Country', 'wpforms-lite' ),
|
||||
__( 'Address Autocomplete', 'wpforms-lite' ),
|
||||
__( 'Mapbox API', 'wpforms-lite' ),
|
||||
__( 'Postal/Zip Code', 'wpforms-lite' ),
|
||||
__( 'Embedded Map in Forms', 'wpforms-lite' ),
|
||||
],
|
||||
'images' => [
|
||||
[
|
||||
'url' => $images_url . 'entry-location.jpg',
|
||||
'url2x' => $images_url . 'entry-location@2x.jpg',
|
||||
'title' => __( 'Location Info in Entries', 'wpforms-lite' ),
|
||||
],
|
||||
[
|
||||
'url' => $images_url . 'address-autocomplete.jpg',
|
||||
'url2x' => $images_url . 'address-autocomplete@2x.jpg',
|
||||
'title' => __( 'Address Autocomplete Field', 'wpforms-lite' ),
|
||||
],
|
||||
[
|
||||
'url' => $images_url . 'smart-address-field.jpg',
|
||||
'url2x' => $images_url . 'smart-address-field@2x.jpg',
|
||||
'title' => __( 'Smart Address Field', 'wpforms-lite' ),
|
||||
],
|
||||
],
|
||||
'utm_medium' => 'Settings - Geolocation',
|
||||
'utm_content' => 'Geolocation Addon',
|
||||
'heading_title' => __( 'Geolocation', 'wpforms-lite' ),
|
||||
'heading_description' => sprintf(
|
||||
'<p>%1$s</p>',
|
||||
__( 'Do you want to learn more about visitors who fill out your online forms? Our geolocation addon allows you to collect and store your website visitors geolocation data along with their form submission. This insight can help you to be better informed and turn more leads into customers. Furthermore, add a smart address field that autocompletes using the Google Places API.', 'wpforms-lite' )
|
||||
),
|
||||
'badge' => __( 'Pro', 'wpforms-lite' ),
|
||||
'features_description' => __( 'Powerful location-based insights and features…', 'wpforms-lite' ),
|
||||
];
|
||||
|
||||
return array_merge( $params, $addon );
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education\Builder;
|
||||
|
||||
use WPForms\Admin\Education\AddonsItemBase;
|
||||
use WPForms\Admin\Education\Helpers;
|
||||
use WPForms\Integrations\AI\Helpers as AIHelpers;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public const BASIC_OPTIONS_NOTICE_FIELD_TYPES = [ 'number', 'payment-single' ];
|
||||
|
||||
/**
|
||||
* Indicate if the current Education feature is allowed to load.
|
||||
*
|
||||
* @since 1.8.4.1
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
*/
|
||||
public function allow_load() {
|
||||
|
||||
return wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.4.1
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
*/
|
||||
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 ReturnTypeCanBeDeclaredInspection
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
* @noinspection HtmlUnknownAnchorTarget
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
if (
|
||||
AIHelpers::is_disabled() ||
|
||||
(
|
||||
wpforms_version_compare(
|
||||
$addon['version'] ?? '1.5.0',
|
||||
'1.5.0',
|
||||
'<='
|
||||
)
|
||||
)
|
||||
) {
|
||||
$this->print_standard_education( $dismiss_section );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$badge = esc_html__( 'NEW FEATURE', 'wpforms-lite' );
|
||||
$notice_header = esc_html__( 'AI Calculations Are Here!', 'wpforms-lite' );
|
||||
|
||||
$notice = sprintf(
|
||||
wp_kses( /* translators: %1$s - link to the WPForms.com doc article. */
|
||||
__( 'Easily create advanced calculations with WPForms AI. 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/generating-calculation-formulas-with-wpforms-ai/', 'Calculations Education', 'Calculations Documentation' ) )
|
||||
);
|
||||
|
||||
printf(
|
||||
'<div class="wpforms-alert-ai wpforms-alert wpforms-educational-alert wpforms-calculations wpforms-dismiss-container">
|
||||
<span class="wpforms-badge wpforms-badge-sm wpforms-badge-block wpforms-badge-purple wpforms-badge-rounded">
|
||||
%5$s
|
||||
</span>
|
||||
<button type="button" class="wpforms-dismiss-button" title="%1$s" data-section="%2$s"></button>
|
||||
<h3>%4$s</h3>
|
||||
<p>%3$s</p>
|
||||
</div>',
|
||||
esc_html__( 'Dismiss this notice.', 'wpforms-lite' ),
|
||||
esc_attr( $dismiss_section ),
|
||||
$notice, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$notice_header, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$badge // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print standard education notice.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param string $dismiss_section Dismiss section.
|
||||
*/
|
||||
private function print_standard_education( $dismiss_section ) {
|
||||
|
||||
$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>
|
||||
<p>%3$s</p>
|
||||
</div>',
|
||||
esc_html__( 'Dismiss this notice.', 'wpforms-lite' ),
|
||||
esc_attr( $dismiss_section ),
|
||||
$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.
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
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( $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( array $addon ): array {
|
||||
|
||||
$data = $this->prepare_field_action_data( $addon );
|
||||
$default = [
|
||||
'slug' => 'calculation_is_enabled',
|
||||
];
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
return wp_parse_args( $data, $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 Enable Calculation field.
|
||||
*
|
||||
* @since 1.8.4.1
|
||||
*
|
||||
* @param array $addon Addon data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_field_attributes( array $addon ): array {
|
||||
|
||||
$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 displaying educational items according to the addon status.
|
||||
*
|
||||
* @since 1.8.4.1
|
||||
*
|
||||
* @param array $addon Addon data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_edu_required_by_status( array $addon ): bool {
|
||||
|
||||
return ! (
|
||||
empty( $addon ) ||
|
||||
empty( $addon['action'] ) ||
|
||||
empty( $addon['status'] ) || (
|
||||
$addon['status'] === 'active' && $addon['action'] !== 'upgrade'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?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()->obj( '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. */
|
||||
__( 'Please complete the setup in your <a href="%1$s" target="_blank">WPForms Settings</a>, and check out <a href="%2$s" target="_blank" rel="noopener noreferrer">our guide</a> to learn about available CAPTCHA solutions.', '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' ) )
|
||||
),
|
||||
],
|
||||
'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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education\Builder;
|
||||
|
||||
use WPForms\Admin\Education\AddonsItemBase;
|
||||
use WPForms\Admin\Education\Fields as EducationFields;
|
||||
|
||||
/**
|
||||
* 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 EducationFields
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* Indicate if current Education feature is allowed to load.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_load(): bool {
|
||||
|
||||
return wp_doing_ajax() || wpforms_is_admin_page( 'builder' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*/
|
||||
public function init(): void {
|
||||
|
||||
parent::init();
|
||||
|
||||
// Store the instance of the Education\Fields class.
|
||||
$this->fields = wpforms()->obj( 'education_fields' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the form preview notice.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $texts Notice texts.
|
||||
*/
|
||||
protected function print_form_preview_notice( $texts ): void {
|
||||
|
||||
printf(
|
||||
'<div class="wpforms-alert %1$s wpforms-alert-dismissible wpforms-pro-fields-notice wpforms-dismiss-container">
|
||||
<div class="wpforms-alert-message">
|
||||
<h3>%2$s</h3>
|
||||
<p>%3$s</p>
|
||||
</div>
|
||||
<div class="wpforms-alert-buttons">
|
||||
<button type="button" class="wpforms-dismiss-button" data-section="%4$s" title="%5$s" />
|
||||
</div>
|
||||
</div>',
|
||||
esc_attr( $texts['class'] ),
|
||||
esc_html( $texts['title'] ),
|
||||
$texts['content'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
esc_html( $texts['dismiss_section'] ),
|
||||
esc_attr__( 'Dismiss this notice', 'wpforms-lite' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?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 the current Education feature is allowed to load.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
*/
|
||||
public function allow_load() {
|
||||
|
||||
return wpforms_is_admin_page( 'builder' ) || wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @noinspection ReturnTypeCanBeDeclaredInspection
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
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( $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( array $addon ): array {
|
||||
|
||||
$data = $this->prepare_field_action_data( $addon );
|
||||
$default = [
|
||||
'slug' => 'enable_address_autocomplete',
|
||||
];
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
return wp_parse_args( $data, $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 $addon Current addon information.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_address_autocomplete_field_attributes( array $addon ): array {
|
||||
|
||||
$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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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() {
|
||||
|
||||
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/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' );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education;
|
||||
|
||||
/**
|
||||
* Education core.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*/
|
||||
class Core {
|
||||
|
||||
use StringsTrait;
|
||||
|
||||
/**
|
||||
* Indicate if Education core is allowed to load.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_load(): bool {
|
||||
|
||||
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/admin/education/core{$min}.js",
|
||||
[ 'jquery', 'jquery-confirm' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-admin-education-core',
|
||||
'wpforms_education',
|
||||
$this->get_js_strings()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax handler for the education dismisses 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(): bool {
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?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-columns',
|
||||
'name' => esc_html__( 'Layout', 'wpforms-lite' ),
|
||||
'name_en' => 'Layout',
|
||||
'type' => 'layout',
|
||||
'group' => 'fancy',
|
||||
'order' => '140',
|
||||
],
|
||||
[
|
||||
'icon' => 'fa-list',
|
||||
'name' => esc_html__( 'Repeater', 'wpforms-lite' ),
|
||||
'name_en' => 'Repeater',
|
||||
'type' => 'repeater',
|
||||
'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-pencil-square-o',
|
||||
'name' => esc_html__( 'Rich Text', 'wpforms-lite' ),
|
||||
'name_en' => 'Rich Text',
|
||||
'type' => 'richtext',
|
||||
'group' => 'fancy',
|
||||
'order' => '170',
|
||||
],
|
||||
[
|
||||
'icon' => 'fa-file-image-o',
|
||||
'name' => esc_html__( 'Content', 'wpforms-lite' ),
|
||||
'name_en' => 'Content',
|
||||
'type' => 'content',
|
||||
'group' => 'fancy',
|
||||
'order' => '180',
|
||||
],
|
||||
[
|
||||
'icon' => 'fa-code',
|
||||
'name' => esc_html__( 'HTML', 'wpforms-lite' ),
|
||||
'name_en' => 'HTML',
|
||||
'type' => 'html',
|
||||
'group' => 'fancy',
|
||||
'order' => '185',
|
||||
],
|
||||
[
|
||||
'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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education;
|
||||
|
||||
/**
|
||||
* Helpers class.
|
||||
*
|
||||
* @since 1.8.5
|
||||
*/
|
||||
class Helpers {
|
||||
|
||||
/**
|
||||
* Get badge HTML.
|
||||
*
|
||||
* @since 1.8.5
|
||||
* @since 1.8.6 Added `$icon` parameter.
|
||||
*
|
||||
* @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.
|
||||
* @param string $icon Badge icon name in Font Awesome "format", e.g. `fa-check`, defaults to empty string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_badge(
|
||||
string $text,
|
||||
string $size = 'sm',
|
||||
string $position = 'inline',
|
||||
string $color = 'titanium',
|
||||
string $shape = 'rounded',
|
||||
string $icon = ''
|
||||
): string {
|
||||
|
||||
if ( ! empty( $icon ) ) {
|
||||
$icon = self::get_inline_icon( $icon );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<span class="wpforms-badge wpforms-badge-%1$s wpforms-badge-%2$s wpforms-badge-%3$s wpforms-badge-%4$s">%5$s%6$s</span>',
|
||||
esc_attr( $size ),
|
||||
esc_attr( $position ),
|
||||
esc_attr( $color ),
|
||||
esc_attr( $shape ),
|
||||
wp_kses(
|
||||
$icon,
|
||||
[
|
||||
'i' => [
|
||||
'class' => [],
|
||||
'aria-hidden' => [],
|
||||
],
|
||||
]
|
||||
),
|
||||
esc_html( $text )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print badge HTML.
|
||||
*
|
||||
* @since 1.8.5
|
||||
* @since 1.8.6 Added `$icon` parameter.
|
||||
*
|
||||
* @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.
|
||||
* @param string $icon Badge icon name in Font Awesome "format", e.g. `fa-check`, defaults to empty string.
|
||||
*/
|
||||
public static function print_badge(
|
||||
string $text,
|
||||
string $size = 'sm',
|
||||
string $position = 'inline',
|
||||
string $color = 'titanium',
|
||||
string $shape = 'rounded',
|
||||
string $icon = ''
|
||||
) {
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo self::get_badge( $text, $size, $position, $color, $shape, $icon );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get addon badge HTML.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @param array $addon Addon data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_addon_badge( array $addon ): string {
|
||||
|
||||
// List of possible badges.
|
||||
$badges = [
|
||||
'recommended' => [
|
||||
'text' => esc_html__( 'Recommended', 'wpforms-lite' ),
|
||||
'color' => 'green',
|
||||
'icon' => 'fa-star',
|
||||
],
|
||||
'new' => [
|
||||
'text' => esc_html__( 'New', 'wpforms-lite' ),
|
||||
'color' => 'blue',
|
||||
],
|
||||
'featured' => [
|
||||
'text' => esc_html__( 'Featured', 'wpforms-lite' ),
|
||||
'color' => 'orange',
|
||||
],
|
||||
];
|
||||
|
||||
$badge = [];
|
||||
|
||||
// Get first badge that exists.
|
||||
foreach ( $badges as $key => $value ) {
|
||||
if ( ! empty( $addon[ $key ] ) ) {
|
||||
$badge = $value;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $badge ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::get_badge( $badge['text'], 'sm', 'inline', $badge['color'], 'rounded', $badge['icon'] ?? '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inline icon HTML.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $name Font Awesome icon name, e.g. `fa-check`.
|
||||
*
|
||||
* @return string HTML markup for the icon element.
|
||||
*/
|
||||
public static function get_inline_icon( string $name ): string {
|
||||
|
||||
return sprintf( '<i class="fa %1$s" aria-hidden="true"></i>', esc_attr( $name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available education addons.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_edu_addons(): array {
|
||||
|
||||
static $addons = null;
|
||||
|
||||
if ( $addons !== null ) {
|
||||
return $addons;
|
||||
}
|
||||
|
||||
$addons_obj = wpforms()->obj( 'addons' );
|
||||
|
||||
if ( ! $addons_obj ) {
|
||||
$addons = [];
|
||||
|
||||
return $addons;
|
||||
}
|
||||
|
||||
$addons = $addons_obj->get_available();
|
||||
|
||||
return $addons;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education\Pointers;
|
||||
|
||||
use WPForms\Integrations\Stripe;
|
||||
use WPForms\Admin\Payments\Views\Overview\Page as PaymentsPage;
|
||||
|
||||
/**
|
||||
* Education class for handling Payments education pointer functionality.
|
||||
*
|
||||
* This class extends the abstract Pointers class and provides functionality
|
||||
* specific to the Payments feature in WPForms.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
class Payment extends Pointer {
|
||||
|
||||
/**
|
||||
* Unique ID for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pointer_id = 'admin_menu_payments';
|
||||
|
||||
/**
|
||||
* Selector for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector = '[href$="-payments"]';
|
||||
|
||||
/**
|
||||
* Make sure that pointer is visible across other dashboard pages.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $top_level_visible = true;
|
||||
|
||||
/**
|
||||
* Determine if the Payments feature pointer is allowed to load.
|
||||
*
|
||||
* Checks various conditions to determine if the Payments feature pointer
|
||||
* should be allowed to load for the current user.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function allow_load(): bool {
|
||||
|
||||
// Bail early if the user doesn't have a Lite, Basic, or Plus license.
|
||||
if ( ! in_array( $this->get_license_type(), [ 'lite', 'basic', 'plus' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bail early if it has been less than 90 days since activation or the installation wasn't upgraded.
|
||||
if ( ! get_option( 'wpforms_version_upgraded_from' ) || wpforms_get_activated_timestamp() > ( time() - 90 * DAY_IN_SECONDS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bail early if Stripe account is connected.
|
||||
if ( Stripe\Helpers::has_stripe_keys() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bail early if the user doesn't have the capability to manage options.
|
||||
if ( ! wpforms_current_user_can() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bail early if there are no published forms.
|
||||
if ( ! wpforms()->obj( 'form' )->forms_exist() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All conditions passed, allow loading the Payments feature pointer.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
|
||||
// Enqueue the pointer static assets.
|
||||
parent::enqueue_assets();
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-education-pointers-payment',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/education/pointers/payment{$min}.js",
|
||||
[ 'wp-pointer' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$admin_l10n = [
|
||||
'pointer' => sanitize_key( $this->pointer_id ),
|
||||
'nonce' => sanitize_text_field( $this->get_nonce_token() ),
|
||||
];
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-education-pointers-payment',
|
||||
'wpforms_education_pointers_payment',
|
||||
$admin_l10n
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set arguments for the Payments feature pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
protected function set_args() {
|
||||
|
||||
$this->args['title'] = __( 'Payment and Donation Forms are here!', 'wpforms-lite' );
|
||||
$this->args['message'] = sprintf( /* translators: %1$s - Payments page URL. */
|
||||
__(
|
||||
'Now available for you: create forms that accept credit cards, Apple Pay, and Google Pay payments. Visit our new <a href="%1$s" id="wpforms-education-pointers-payments">Payments area</a> to get started.',
|
||||
'wpforms-lite'
|
||||
),
|
||||
esc_url( PaymentsPage::get_url() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current installation license type in lowercase.
|
||||
* If no license type is found, defaults to 'lite'.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_license_type(): string {
|
||||
|
||||
$type = wpforms_get_license_type();
|
||||
|
||||
// Set default to 'lite' if no license type is detected.
|
||||
if ( empty( $type ) ) {
|
||||
$type = 'lite';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education\Pointers;
|
||||
|
||||
/**
|
||||
* Abstract class representing Pointers functionality.
|
||||
*
|
||||
* This abstract class provides a foundation for implementing pointers in WPForms.
|
||||
* Child classes should extend this class and implement the necessary methods to set properties and allow loading.
|
||||
*
|
||||
* The class separates concerns by implementing methods for different functionalities such as initializing pointers,
|
||||
* handling interactions, printing scripts, etc., which enhances code maintainability and security.
|
||||
* Additionally, the class is designed to be abstract, allowing for customization and extension while enforcing certain security measures in child classes.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
abstract class Pointer {
|
||||
|
||||
/**
|
||||
* Unique ID for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pointer_id;
|
||||
|
||||
/**
|
||||
* Selector for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Arguments for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $args;
|
||||
|
||||
/**
|
||||
* Top-level menu selector.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $top_level_menu = '#toplevel_page_wpforms-overview';
|
||||
|
||||
/**
|
||||
* Determines whether the pointer should be visible outside the "WPForms" primary menu.
|
||||
* Note that setting this property to true will display the pointer on other dashboard pages as well.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $top_level_visible = false;
|
||||
|
||||
/**
|
||||
* Option name for storing interactions with pointers.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
private const OPTION_NAME = 'wpforms_pointers';
|
||||
|
||||
/**
|
||||
* Initialize the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function init(): void {
|
||||
|
||||
// If loading is not allowed, or if the pointer is already dismissed, return.
|
||||
if ( ! $this->allow_display() || ! $this->allow_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial arguments.
|
||||
$this->set_initial_args();
|
||||
|
||||
// Register hooks.
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the pointer is already dismissed or interacted with.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allow_display(): bool {
|
||||
|
||||
// If the pointer ID is empty, return.
|
||||
// Check if announcements are allowed to be displayed.
|
||||
if ( empty( $this->pointer_id ) || wpforms_setting( 'hide-announcements' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get pointers.
|
||||
$pointers = (array) get_option( self::OPTION_NAME, [] );
|
||||
|
||||
// Check if the pointer ID exists in the engagement list.
|
||||
if ( isset( $pointers['engagement'] ) && in_array( $this->pointer_id, (array) $pointers['engagement'], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the pointer ID exists in the dismissed list.
|
||||
if ( isset( $pointers['dismiss'] ) && in_array( $this->pointer_id, (array) $pointers['dismiss'], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
private function hooks(): void {
|
||||
|
||||
// Enqueue assets.
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
|
||||
// Print the pointer script.
|
||||
add_action( 'admin_print_footer_scripts', [ $this, 'print_script' ] );
|
||||
|
||||
// Add Ajax callback for the engagement.
|
||||
add_action( 'wp_ajax_wpforms_education_pointers_engagement', [ $this, 'engagement_callback' ] );
|
||||
|
||||
// Add Ajax callback for dismissing the pointer.
|
||||
add_action( 'wp_ajax_wpforms_education_pointers_dismiss', [ $this, 'dismiss_callback' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
|
||||
// Enqueue the pointer CSS.
|
||||
wp_enqueue_style( 'wp-pointer' );
|
||||
|
||||
// Enqueue the pointer script.
|
||||
wp_enqueue_script( 'wp-pointer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the pointer script.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function print_script(): void {
|
||||
|
||||
// Encode the $args array into JSON format.
|
||||
$encoded_args = $this->get_prepared_args();
|
||||
|
||||
if ( empty( $encoded_args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize pointer ID and selector.
|
||||
$pointer_id = sanitize_text_field( $this->pointer_id );
|
||||
$selector = sanitize_text_field( $this->get_selector() );
|
||||
|
||||
// Get the admin-ajax URL.
|
||||
$ajaxurl = esc_url_raw( admin_url( 'admin-ajax.php' ) );
|
||||
|
||||
// Create nonce for the pointer.
|
||||
$nonce = sanitize_text_field( $this->get_nonce_token() );
|
||||
|
||||
// Menu flyout selector.
|
||||
$menu_flyout = "{$this->top_level_menu}:not(.wp-menu-open)";
|
||||
|
||||
// Inline CSS style id.
|
||||
$inline_css_id = "wpforms-{$pointer_id}-inline-css";
|
||||
|
||||
// The type of echo being used in this PHP code is a HEREDOC syntax.
|
||||
// HEREDOC allows you to create strings that span multiple lines without
|
||||
// needing to concatenate them with dots (.) as you would with double quotes.
|
||||
|
||||
// phpcs:disable
|
||||
echo <<<HTML
|
||||
<script type="text/javascript">
|
||||
( function( $ ) {
|
||||
let options = $encoded_args, setup;
|
||||
|
||||
if ( ! options ) {
|
||||
return;
|
||||
}
|
||||
|
||||
options = $.extend( options, {
|
||||
show: function() {
|
||||
if ( ! $( '#$inline_css_id' ).length && $( '$menu_flyout' ).length ) {
|
||||
$( '<style id="$inline_css_id">' ).text( '$menu_flyout:after, $menu_flyout .wp-submenu-wrap{ display: none }' ).appendTo( 'head' );
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
$( '#$inline_css_id' ).remove();
|
||||
$.post(
|
||||
'$ajaxurl',
|
||||
{
|
||||
pointer_id: '$pointer_id',
|
||||
_ajax_nonce: '$nonce',
|
||||
action: 'wpforms_education_pointers_dismiss',
|
||||
}
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
setup = function() {
|
||||
$( '$selector' ).first().pointer( options ).pointer( 'open' );
|
||||
};
|
||||
|
||||
if ( options.position && options.position.defer_loading ) {
|
||||
$( window ).on( 'load.wp-pointers', setup );
|
||||
} else {
|
||||
$( function() {
|
||||
setup();
|
||||
} );
|
||||
}
|
||||
} )( jQuery );
|
||||
</script>
|
||||
HTML;
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for engaging with a pointer.
|
||||
*
|
||||
* This function is triggered via AJAX when a user interacts with a pointer, indicating engagement.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function engagement_callback(): void {
|
||||
|
||||
check_ajax_referer( $this->pointer_id, '_ajax_nonce' );
|
||||
|
||||
if ( ! wpforms_current_user_can() ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
[ $pointer_id, $pointers ] = $this->handle_pointer_interaction();
|
||||
|
||||
// Add the current pointer to the engagement list.
|
||||
$pointers['engagement'][] = $pointer_id;
|
||||
|
||||
// Update the pointer state.
|
||||
update_option( self::OPTION_NAME, $pointers );
|
||||
|
||||
// Indicate that the pointer was engaged.
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for dismissing the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function dismiss_callback(): void {
|
||||
|
||||
check_ajax_referer( $this->pointer_id, '_ajax_nonce' );
|
||||
|
||||
if ( ! wpforms_current_user_can() ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
[ $pointer_id, $pointers ] = $this->handle_pointer_interaction();
|
||||
|
||||
// Add the current pointer to the dismissed list.
|
||||
$pointers['dismiss'][] = $pointer_id;
|
||||
|
||||
// Update the pointer state.
|
||||
update_option( self::OPTION_NAME, $pointers );
|
||||
|
||||
// Indicate that the pointer was dismissed.
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nonce for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_nonce_token(): string {
|
||||
|
||||
return wp_create_nonce( $this->pointer_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pointer interaction via AJAX.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return array Pointer ID and pointers state.
|
||||
*/
|
||||
private function handle_pointer_interaction(): array {
|
||||
|
||||
// Check if the request is valid.
|
||||
check_ajax_referer( $this->pointer_id );
|
||||
|
||||
// Get the pointer ID from the request.
|
||||
$pointer_id = isset( $_POST['pointer_id'] ) ? sanitize_key( $_POST['pointer_id'] ) : '';
|
||||
|
||||
// If the pointer ID is empty, return an error response.
|
||||
if ( empty( $pointer_id ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
// Get the current pointers state.
|
||||
$pointers = (array) get_option(
|
||||
self::OPTION_NAME,
|
||||
[
|
||||
'engagement' => [],
|
||||
'dismiss' => [],
|
||||
]
|
||||
);
|
||||
|
||||
return [ $pointer_id, $pointers ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set initial arguments to use in a pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
private function set_initial_args(): void {
|
||||
|
||||
// Set default arguments.
|
||||
$this->args = [
|
||||
'content' => '',
|
||||
'pointerWidth' => 395,
|
||||
'position' => [
|
||||
'edge' => 'left',
|
||||
'align' => 'center',
|
||||
],
|
||||
];
|
||||
|
||||
// Set additional arguments for the pointer.
|
||||
$this->set_args();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the selector based on conditions.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_selector(): string {
|
||||
|
||||
// If the sublevel menu is defined, and it's an admin page, return the combined selector.
|
||||
if ( ! empty( $this->selector ) && wpforms_is_admin_page() ) {
|
||||
return "{$this->top_level_menu} {$this->selector}";
|
||||
}
|
||||
|
||||
// Default returns the top-level menu.
|
||||
return $this->top_level_menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and encode args for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_prepared_args(): string {
|
||||
|
||||
// Retrieve title and message from an argument array, fallback to empty strings if not set.
|
||||
$title = $this->args['title'] ?? '';
|
||||
$message = $this->args['message'] ?? '';
|
||||
|
||||
// Return early if both title and message are empty.
|
||||
if ( empty( $message ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Pointer markup uses <h3> tag for the title and <p> tag for the message.
|
||||
$content = ! empty( $title ) ? sprintf( '<h3>%s</h3>', esc_html( $title ) ) : '';
|
||||
$content .= sprintf( '<p style="font-size:14px">%s</p>', wp_kses( $message, $this->get_allowed_html() ) );
|
||||
|
||||
$this->args['content'] = $content;
|
||||
|
||||
// Unset title and message to clean up an argument array.
|
||||
unset( $this->args['title'], $this->args['message'] );
|
||||
|
||||
// If RTL and position edge are 'left', switch it to 'right'.
|
||||
if ( ! empty( $this->args['position']['edge'] ) && $this->args['position']['edge'] === 'left' && is_rtl() ) {
|
||||
$this->args['position']['edge'] = 'right';
|
||||
}
|
||||
|
||||
// Encode arguments array to JSON.
|
||||
return wp_json_encode( $this->args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed HTML tags for wp_kses.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_allowed_html(): array {
|
||||
|
||||
return [
|
||||
'a' => [
|
||||
'id' => [],
|
||||
'class' => [],
|
||||
'href' => [],
|
||||
'target' => [],
|
||||
'rel' => [],
|
||||
],
|
||||
'strong' => [],
|
||||
'em' => [],
|
||||
'br' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if loading of the pointer is allowed.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function allow_load(): bool;
|
||||
|
||||
/**
|
||||
* Set arguments for the pointer.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
abstract protected function set_args();
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Education;
|
||||
|
||||
/**
|
||||
* Strings trait.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
trait StringsTrait {
|
||||
|
||||
/**
|
||||
* Localize common strings.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_js_strings(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
$strings = [];
|
||||
$name = '%name%';
|
||||
|
||||
$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';
|
||||
|
||||
if ( wpforms_is_block_editor() ) {
|
||||
$upgrade_utm_medium = 'gutenberg';
|
||||
}
|
||||
|
||||
$strings['upgrade'] = [
|
||||
'pro' => $this->get_upgrade_strings( 'Pro', $name, $upgrade_utm_medium ),
|
||||
'elite' => $this->get_upgrade_strings( 'Elite', $name, $upgrade_utm_medium ),
|
||||
];
|
||||
|
||||
$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' );
|
||||
|
||||
/**
|
||||
* Filters the education strings.
|
||||
*
|
||||
* @since 1.6.6
|
||||
*
|
||||
* @param array $strings Education strings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
return (array) apply_filters( 'wpforms_admin_education_strings', $strings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upgrade strings.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string $level Upgrade level.
|
||||
* @param string $name Addon name.
|
||||
* @param string $upgrade_utm_medium UTM medium for the upgrade link.
|
||||
*
|
||||
* @return array
|
||||
* @noinspection HtmlUnknownTarget
|
||||
*/
|
||||
private function get_upgrade_strings( string $level, string $name, string $upgrade_utm_medium ): array {
|
||||
// phpcs:ignore WPForms.Formatting.EmptyLineAfterFunctionDeclaration.AddEmptyLineAfterFunctionDeclaration
|
||||
|
||||
return [
|
||||
'title' => esc_html(
|
||||
sprintf( /* translators: %s - level name, either Pro or Elite. */
|
||||
__( 'is a %s Feature', 'wpforms-lite' ),
|
||||
$level
|
||||
)
|
||||
),
|
||||
'title_plural' => esc_html(
|
||||
sprintf( /* translators: %s - level name, either Pro or Elite. */
|
||||
__( 'are a %s Feature', 'wpforms-lite' ),
|
||||
$level
|
||||
)
|
||||
),
|
||||
'message' => '<p>' . esc_html(
|
||||
sprintf( /* translators: %1$s - addon name, %2$s - level name, either Pro or Elite. */
|
||||
__( 'We\'re sorry, the %1$s is not available on your plan. Please upgrade to the %2$s plan to unlock all these awesome features.', 'wpforms-lite' ),
|
||||
$name,
|
||||
$level
|
||||
)
|
||||
) . '</p>',
|
||||
'message_plural' => '<p>' . esc_html(
|
||||
sprintf( /* translators: %1$s - addon name, %2$s - level name, either Pro or Elite. */
|
||||
__( 'We\'re sorry, %1$s are not available on your plan. Please upgrade to the %2$s plan to unlock all these awesome features.', 'wpforms-lite' ),
|
||||
$name,
|
||||
$level
|
||||
)
|
||||
) . '</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, '%name%' ) ),
|
||||
esc_html__( 'Already purchased?', 'wpforms-lite' )
|
||||
),
|
||||
'button' => esc_html(
|
||||
sprintf( /* translators: %s - level name, either Pro or Elite. */
|
||||
__( 'Upgrade to %s', 'wpforms-lite' ),
|
||||
$level
|
||||
)
|
||||
),
|
||||
'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( strtolower( $level ) ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( '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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
<?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/admin/form-embed-wizard{$min}.js",
|
||||
[ 'jquery', 'underscore' ],
|
||||
WPFORMS_VERSION,
|
||||
false
|
||||
);
|
||||
|
||||
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()->obj( '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() {
|
||||
|
||||
// Run a security check.
|
||||
check_admin_referer( 'wpforms_admin_form_embed_wizard_nonce' );
|
||||
|
||||
// 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' ) );
|
||||
}
|
||||
|
||||
$page_id = ! empty( $_POST['pageId'] ) ? absint( $_POST['pageId'] ) : 0;
|
||||
$meta = $this->prepare_meta_data( $page_id );
|
||||
|
||||
$this->set_meta( $meta );
|
||||
|
||||
// Update challenge option to properly continue challenge on the embed page.
|
||||
$this->update_challenge_option( $meta );
|
||||
|
||||
wp_send_json_success( $meta['url'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare meta data for the embed page.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param int $page_id Page ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepare_meta_data( int $page_id ): array {
|
||||
|
||||
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'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
];
|
||||
}
|
||||
|
||||
$meta['form_id'] = ! empty( $_POST['formId'] ) ? absint( $_POST['formId'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$meta['url'] = $url;
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update challenge option to properly continue challenge on the embed page.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param array $meta Meta data.
|
||||
*/
|
||||
private function update_challenge_option( array $meta ): void {
|
||||
|
||||
if ( $this->is_challenge_active() ) {
|
||||
$challenge = wpforms()->obj( 'challenge' );
|
||||
|
||||
if ( $challenge && method_exists( $challenge, 'set_challenge_option' ) ) {
|
||||
$challenge->set_challenge_option( [ 'embed_page' => $meta['embed_page'] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 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' ) );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms\Ajax;
|
||||
|
||||
use WPForms\Admin\Forms\Table\Facades;
|
||||
|
||||
/**
|
||||
* Columns AJAX actions on Forms Overview list page.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class Columns {
|
||||
|
||||
/**
|
||||
* Determine if the class is allowed to load.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allow_load(): bool {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
|
||||
|
||||
// Load only in the case of AJAX calls on Forms Overview page.
|
||||
return wpforms_is_admin_ajax() && strpos( $action, 'wpforms_admin_forms_overview_' ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function init(): void {
|
||||
|
||||
if ( ! $this->allow_load() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function hooks(): void {
|
||||
|
||||
add_action( 'wp_ajax_wpforms_admin_forms_overview_save_columns_order', [ $this, 'save_order' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save columns' order.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function save_order(): void {
|
||||
|
||||
check_ajax_referer( 'wpforms-admin', 'nonce' );
|
||||
|
||||
if ( ! wpforms_current_user_can( 'view_forms' ) ) {
|
||||
wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
$data = $this->get_prepared_data();
|
||||
|
||||
// Prepare the new columns' order.
|
||||
$columns = [];
|
||||
|
||||
foreach ( $data['columns'] as $column ) {
|
||||
$columns[] = str_replace( '-foot', '', $column );
|
||||
}
|
||||
|
||||
$result = Facades\Columns::sanitize_and_save_columns( $columns );
|
||||
|
||||
if ( $result === false ) {
|
||||
wp_send_json_error( esc_html__( 'Cannot save columns order.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared data before perform ajax action.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_prepared_data(): array {
|
||||
|
||||
// Run a security check.
|
||||
if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ) {
|
||||
wp_send_json_error( esc_html__( 'Most likely, your session expired. Please reload the page.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
return [
|
||||
'columns' => ! empty( $_POST['columns'] ) ? map_deep( (array) wp_unslash( $_POST['columns'] ), 'sanitize_key' ) : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<?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()->obj( '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()->obj( '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;
|
||||
}
|
||||
}
|
||||
|
||||
// The tag was not found among the forms, no need to continue.
|
||||
if ( empty( $forms ) ) {
|
||||
wp_send_json_success( [ 'deleted' => $deleted ] );
|
||||
}
|
||||
|
||||
// Remove tags from the settings of the forms.
|
||||
foreach ( (array) $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()->obj( '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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
<?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()->obj( '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() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$query_args = [];
|
||||
|
||||
if ( count( $this->ids ) === 1 ) {
|
||||
$query_args['type'] = wpforms_is_form_template( $this->ids[0] ) ? 'template' : 'form';
|
||||
}
|
||||
|
||||
$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 = $result[1] ?? 0;
|
||||
}
|
||||
|
||||
$query_args[ rtrim( $this->action, 'e' ) . 'ed' ] = $count_result;
|
||||
|
||||
// Unset get vars and perform redirect to avoid action reuse.
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
$query_args,
|
||||
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()->obj( '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()->obj( '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()->obj( '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()->obj( '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()->obj( '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 [
|
||||
'' => '—',
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin notices.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*/
|
||||
public function notices() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
// 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,
|
||||
'type' => ! empty( $_REQUEST['type'] ) ? sanitize_key( $_REQUEST['type'] ) : 'form',
|
||||
];
|
||||
// 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( array $results ) {
|
||||
|
||||
$type = $results['type'] ?? '';
|
||||
|
||||
if ( ! in_array( $type, [ 'form', 'template' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$method = "get_notice_success_for_{$type}";
|
||||
$actions = [ 'trashed', 'restored', 'deleted', 'duplicated' ];
|
||||
|
||||
foreach ( $actions as $action ) {
|
||||
$count = (int) $results[ $action ];
|
||||
|
||||
if ( ! $count ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notice = $this->$method( $action, $count );
|
||||
|
||||
if ( ! $notice ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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';
|
||||
$removable_query_args[] = 'type';
|
||||
|
||||
return $removable_query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notice success message for form.
|
||||
*
|
||||
* @since 1.9.2.3
|
||||
*
|
||||
* @param string $action Action type.
|
||||
* @param int $count Count of forms.
|
||||
*
|
||||
* @return string
|
||||
* @noinspection PhpUnusedPrivateMethodInspection
|
||||
*/
|
||||
private function get_notice_success_for_form( string $action, int $count ): string {
|
||||
|
||||
switch ( $action ) {
|
||||
case 'restored':
|
||||
/* translators: %1$d - restored forms count. */
|
||||
$notice = _n( '%1$d form was successfully restored.', '%1$d forms were successfully restored.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'deleted':
|
||||
/* translators: %1$d - deleted forms count. */
|
||||
$notice = _n( '%1$d form was successfully permanently deleted.', '%1$d forms were successfully permanently deleted.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'duplicated':
|
||||
/* translators: %1$d - duplicated forms count. */
|
||||
$notice = _n( '%1$d form was successfully duplicated.', '%1$d forms were successfully duplicated.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'trashed':
|
||||
/* translators: %1$d - trashed forms count. */
|
||||
$notice = _n( '%1$d form was successfully moved to Trash.', '%1$d forms were successfully moved to Trash.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
default:
|
||||
// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf( $notice, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notice success message for template.
|
||||
*
|
||||
* @since 1.9.2.3
|
||||
*
|
||||
* @param string $action Action type.
|
||||
* @param int $count Count of forms.
|
||||
*
|
||||
* @return string
|
||||
* @noinspection PhpUnusedPrivateMethodInspection
|
||||
*/
|
||||
private function get_notice_success_for_template( string $action, int $count ): string {
|
||||
|
||||
switch ( $action ) {
|
||||
case 'restored':
|
||||
/* translators: %1$d - restored templates count. */
|
||||
$notice = _n( '%1$d template was successfully restored.', '%1$d templates were successfully restored.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'deleted':
|
||||
/* translators: %1$d - deleted templates count. */
|
||||
$notice = _n( '%1$d template was successfully permanently deleted.', '%1$d templates were successfully permanently deleted.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'duplicated':
|
||||
/* translators: %1$d - duplicated templates count. */
|
||||
$notice = _n( '%1$d template was successfully duplicated.', '%1$d templates were successfully duplicated.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
case 'trashed':
|
||||
/* translators: %1$d - trashed templates count. */
|
||||
$notice = _n( '%1$d template was successfully moved to Trash.', '%1$d templates were successfully moved to Trash.', $count, 'wpforms-lite' );
|
||||
break;
|
||||
|
||||
default:
|
||||
// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf( $notice, $count );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms;
|
||||
|
||||
use WP_List_Table;
|
||||
use WP_Post;
|
||||
use WP_Screen;
|
||||
use WPForms\Admin\Forms\Table\Facades\Columns;
|
||||
use WPForms\Forms\Locator;
|
||||
use WPForms\Integrations\LiteConnect\LiteConnect;
|
||||
use WPForms\Integrations\LiteConnect\Integration as LiteConnectIntegration;
|
||||
|
||||
// IMPORTANT NOTICE:
|
||||
// This line is needed to prevent fatal errors in the third-party plugins.
|
||||
// We know about Jetpack (probably others also) can load WP classes during cron jobs or something similar.
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
|
||||
/**
|
||||
* Generate the table on the plugin overview page.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class ListTable extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Number of forms to show per page.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $per_page;
|
||||
|
||||
/**
|
||||
* Number of forms in different views.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* Current view.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/**
|
||||
* Primary class constructor.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
// Utilize the parent constructor to build the main class properties.
|
||||
parent::__construct(
|
||||
[
|
||||
'singular' => 'form',
|
||||
'plural' => 'forms',
|
||||
'ajax' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$this->hooks();
|
||||
|
||||
// Determine the current view.
|
||||
$this->view = wpforms()->obj( 'forms_views' )->get_current_view();
|
||||
|
||||
/**
|
||||
* Filters the default number of forms to show per page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int $forms_per_page Number of forms to show per page.
|
||||
*/
|
||||
$this->per_page = (int) apply_filters( 'wpforms_overview_per_page', 20 ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
add_filter( 'default_hidden_columns', [ $this, 'default_hidden_columns' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of a class and store it in itself.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
static $instance;
|
||||
|
||||
if ( ! $instance ) {
|
||||
$instance = new self();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the table columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array $columns Array of all the list table columns.
|
||||
*/
|
||||
public function get_columns() {
|
||||
|
||||
return Columns::get_list_table_columns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the checkbox column.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form Form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $form ) {
|
||||
|
||||
return '<input type="checkbox" name="form_id[]" value="' . absint( $form->ID ) . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form CPT object as a form representation.
|
||||
* @param string $column_name Column Name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_default( $form, $column_name ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity
|
||||
|
||||
switch ( $column_name ) {
|
||||
case 'id':
|
||||
$value = $form->ID;
|
||||
break;
|
||||
|
||||
case 'shortcode':
|
||||
$value = '[wpforms id="' . $form->ID . '"]';
|
||||
|
||||
if ( wpforms_is_form_template( $form->ID ) ) {
|
||||
$value = __( 'N/A', 'wpforms-lite' );
|
||||
}
|
||||
break;
|
||||
|
||||
// This slug is not changed to 'date' for backward compatibility.
|
||||
case 'created':
|
||||
if ( gmdate( 'Ymd', strtotime( $form->post_date ) ) === gmdate( 'Ymd', strtotime( $form->post_modified ) ) ) {
|
||||
$value = wp_kses(
|
||||
sprintf( /* translators: %1$s - Post created date. */
|
||||
__( 'Created<br/>%1$s', 'wpforms-lite' ),
|
||||
esc_html( wpforms_datetime_format( $form->post_date ) )
|
||||
),
|
||||
[ 'br' => [] ]
|
||||
);
|
||||
} else {
|
||||
$value = wp_kses(
|
||||
sprintf( /* translators: %1$s - Post modified date. */
|
||||
__( 'Last Modified<br/>%1$s', 'wpforms-lite' ),
|
||||
esc_html( wpforms_datetime_format( $form->post_modified ) )
|
||||
),
|
||||
[ 'br' => [] ]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'entries':
|
||||
$value = sprintf(
|
||||
'<span class="wpforms-lite-connect-entries-count"><a href="%s" data-title="%s">%s%d</a></span>',
|
||||
esc_url( admin_url( 'admin.php?page=wpforms-entries' ) ),
|
||||
esc_attr__( 'Entries are securely backed up in the cloud. Upgrade to restore.', 'wpforms-lite' ),
|
||||
'<svg viewBox="0 0 16 12"><path d="M10.8 2c1.475 0 2.675 1.175 2.775 2.625C15 5.125 16 6.475 16 8a3.6 3.6 0 0 1-3.6 3.6H4a3.98 3.98 0 0 1-4-4 4.001 4.001 0 0 1 2.475-3.7A4.424 4.424 0 0 1 6.8.4c1.4 0 2.625.675 3.425 1.675C10.4 2.025 10.6 2 10.8 2ZM4 10.4h8.4a2.4 2.4 0 0 0 0-4.8.632.632 0 0 0-.113.013.678.678 0 0 1-.112.012c.125-.25.225-.525.225-.825 0-.875-.725-1.6-1.6-1.6a1.566 1.566 0 0 0-1.05.4 3.192 3.192 0 0 0-2.95-2 3.206 3.206 0 0 0-3.2 3.2v.05A2.757 2.757 0 0 0 1.2 7.6 2.795 2.795 0 0 0 4 10.4Zm6.752-4.624a.64.64 0 1 0-.905-.905L6.857 7.86 5.38 6.352a.64.64 0 1 0-.914.896l1.93 1.97a.64.64 0 0 0 .91.004l3.446-3.446Z"/></svg>',
|
||||
LiteConnectIntegration::get_form_entries_count( $form->ID )
|
||||
);
|
||||
break;
|
||||
|
||||
case 'modified':
|
||||
$value = get_post_modified_time( get_option( 'date_format' ), false, $form );
|
||||
break;
|
||||
|
||||
case 'author':
|
||||
$value = '';
|
||||
$author = get_userdata( $form->post_author );
|
||||
|
||||
if ( ! $author ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$value = $author->display_name;
|
||||
$user_edit_url = get_edit_user_link( $author->ID );
|
||||
|
||||
if ( ! empty( $user_edit_url ) ) {
|
||||
$value = '<a href="' . esc_url( $user_edit_url ) . '">' . esc_html( $value ) . '</a>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'php':
|
||||
$value = '<code style="display:block;font-size:11px;">if( function_exists( \'wpforms_get\' ) ){ wpforms_get( ' . $form->ID . ' ); }</code>';
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the Forms Overview list table culumn value.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $value Column value.
|
||||
* @param WP_Post $form CPT object as a form representation.
|
||||
* @param string $column_name Column Name.
|
||||
*/
|
||||
return apply_filters( 'wpforms_overview_table_column_value', $value, $form, $column_name ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the default list of hidden columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string[] $hidden Array of IDs of columns hidden by default.
|
||||
* @param WP_Screen $screen WP_Screen object of the current screen.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function default_hidden_columns( $hidden, $screen ) {
|
||||
|
||||
if ( $screen->id !== 'toplevel_page_wpforms-overview' ) {
|
||||
return $hidden;
|
||||
}
|
||||
|
||||
if ( Columns::has_selected_columns() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'tags',
|
||||
'author',
|
||||
Locator::COLUMN_NAME,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the form name column with action links.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form Form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_name( $form ) {
|
||||
|
||||
$title = $this->get_column_name_title( $form );
|
||||
|
||||
$states = _post_states( $form, false );
|
||||
|
||||
$actions = $this->get_column_name_row_actions( $form );
|
||||
|
||||
// Build the row action links and return the value.
|
||||
return $title . $states . $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the form tags column.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form Form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_tags( $form ) {
|
||||
|
||||
return wpforms()->obj( 'forms_tags' )->column_tags( $form );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form name HTML for the form name column.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form Form object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_column_name_title( $form ) {
|
||||
|
||||
$title = ! empty( $form->post_title ) ? $form->post_title : $form->post_name;
|
||||
$name = sprintf(
|
||||
'<span><strong>%s</strong></span>',
|
||||
esc_html( $title )
|
||||
);
|
||||
|
||||
if ( $this->view === 'trash' ) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ( wpforms_current_user_can( 'view_form_single', $form->ID ) ) {
|
||||
$name = sprintf(
|
||||
'<a href="%s" title="%s" class="row-title" target="_blank" rel="noopener noreferrer"><strong>%s</strong></a>',
|
||||
esc_url( wpforms_get_form_preview_url( $form->ID ) ),
|
||||
esc_attr__( 'View preview', 'wpforms-lite' ),
|
||||
esc_html( $title )
|
||||
);
|
||||
}
|
||||
|
||||
if ( wpforms_current_user_can( 'view_entries_form_single', $form->ID ) ) {
|
||||
$name = sprintf(
|
||||
'<a href="%s" title="%s"><strong>%s</strong></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( $title )
|
||||
);
|
||||
}
|
||||
|
||||
if ( wpforms_current_user_can( 'edit_form_single', $form->ID ) ) {
|
||||
$name = sprintf(
|
||||
'<a href="%s" title="%s"><strong>%s</strong></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( $title )
|
||||
);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the row actions HTML for the form name column.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param WP_Post $form Form object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_column_name_row_actions( $form ) {
|
||||
|
||||
// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
/**
|
||||
* Filters row action links on the 'All Forms' admin page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $row_actions An array of action links for a given form.
|
||||
* @param WP_Post $form Form object.
|
||||
*/
|
||||
return $this->row_actions( apply_filters( 'wpforms_overview_row_actions', [], $form ) );
|
||||
// phpcs:enable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Define bulk actions available for our table listing.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_bulk_actions() {
|
||||
|
||||
return wpforms()->obj( 'forms_bulk_actions' )->get_dropdown_items();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the table navigation above or below the table.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $which The location of the table navigation: 'top' or 'bottom'.
|
||||
*/
|
||||
protected function display_tablenav( $which ) {
|
||||
|
||||
// If there are some forms just call the parent method.
|
||||
if ( $this->has_items() ) {
|
||||
parent::display_tablenav( $which );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, display bulk actions menu and "0 items" on the right (pagination).
|
||||
?>
|
||||
<div class="tablenav <?php echo esc_attr( $which ); ?>">
|
||||
<div class="alignleft actions bulkactions">
|
||||
<?php $this->bulk_actions( $which ); ?>
|
||||
</div>
|
||||
<?php
|
||||
$this->extra_tablenav( $which );
|
||||
|
||||
if ( $which === 'top' ) {
|
||||
$this->pagination( $which );
|
||||
}
|
||||
?>
|
||||
<br class="clear" />
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra controls to be displayed between bulk actions and pagination.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $which The location of the table navigation: 'top' or 'bottom'.
|
||||
*/
|
||||
protected function extra_tablenav( $which ) {
|
||||
|
||||
wpforms()->obj( 'forms_tags' )->extra_tablenav( $which, $this );
|
||||
wpforms()->obj( 'forms_views' )->extra_tablenav( $which );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to be displayed when there are no forms.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function no_items() {
|
||||
|
||||
wpforms()->obj( 'forms_views' )->get_current_view() === 'templates' ?
|
||||
esc_html_e( 'No form templates found.', 'wpforms-lite' ) :
|
||||
esc_html_e( 'No forms found.', 'wpforms-lite' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and set up the final data for the table.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function prepare_items() {
|
||||
|
||||
// Set up the columns.
|
||||
$columns = $this->get_columns();
|
||||
|
||||
// Hidden columns (none).
|
||||
$hidden = get_hidden_columns( $this->screen );
|
||||
|
||||
// Define which columns can be sorted - form name, author, date.
|
||||
$sortable = [
|
||||
'id' => [ 'ID', false ],
|
||||
'name' => [ 'title', false ],
|
||||
'author' => [ 'author', false ],
|
||||
'created' => [ 'date', false ],
|
||||
];
|
||||
|
||||
// Set column headers.
|
||||
$this->_column_headers = [ $columns, $hidden, $sortable ];
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$page = $this->get_pagenum();
|
||||
$order = isset( $_GET['order'] ) && $_GET['order'] === 'asc' ? 'ASC' : 'DESC';
|
||||
$orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'ID';
|
||||
$per_page = $this->get_items_per_page( 'wpforms_forms_per_page', $this->per_page );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( $orderby === 'date' ) {
|
||||
$orderby = [
|
||||
'modified' => $order,
|
||||
'date' => $order,
|
||||
];
|
||||
}
|
||||
|
||||
$args = [
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
'nopaging' => false,
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $page,
|
||||
'no_found_rows' => false,
|
||||
'post_status' => 'publish',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters the `get_posts()` arguments while preparing items for the forms overview table.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @param array $args Arguments array.
|
||||
*/
|
||||
$args = (array) apply_filters( 'wpforms_overview_table_prepare_items_args', $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
// Giddy up.
|
||||
$this->items = wpforms()->obj( 'form' )->get( '', $args );
|
||||
$per_page = $args['posts_per_page'] ?? $this->get_items_per_page( 'wpforms_forms_per_page', $this->per_page );
|
||||
|
||||
$this->update_count( $args );
|
||||
|
||||
$count_current_view = empty( $this->count[ $this->view ] ) ? 0 : $this->count[ $this->view ];
|
||||
|
||||
// Finalize pagination.
|
||||
$this->set_pagination_args(
|
||||
[
|
||||
'total_items' => $count_current_view,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => (int) ceil( $count_current_view / $per_page ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and update form counts.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $args Get forms arguments.
|
||||
*/
|
||||
private function update_count( $args ) {
|
||||
|
||||
/**
|
||||
* Allow counting forms filtered by a given search criteria.
|
||||
*
|
||||
* If result will not contain `all` key, count All Forms without filtering will be performed.
|
||||
*
|
||||
* @since 1.7.2
|
||||
*
|
||||
* @param array $count Contains counts of forms in different views.
|
||||
* @param array $args Arguments of the `get_posts`.
|
||||
*/
|
||||
$this->count = (array) apply_filters( 'wpforms_overview_table_update_count', [], $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
// We do not need to perform all forms count if we have the result already.
|
||||
if ( isset( $this->count['all'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Count all forms.
|
||||
$this->count['all'] = wpforms_current_user_can( 'wpforms_view_others_forms' )
|
||||
? (int) wp_count_posts( 'wpforms' )->publish
|
||||
: (int) count_user_posts( get_current_user_id(), 'wpforms', true );
|
||||
|
||||
/**
|
||||
* Filters forms count data after counting all forms.
|
||||
*
|
||||
* This filter executes only if the result of `wpforms_overview_table_update_count` filter
|
||||
* doesn't contain `all` key.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @param array $count Contains counts of forms in different views.
|
||||
* @param array $args Arguments of the `get_posts`.
|
||||
*/
|
||||
$this->count = (array) apply_filters( 'wpforms_overview_table_update_count_all', $this->count, $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the pagination.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $which The location of the table pagination: 'top' or 'bottom'.
|
||||
*/
|
||||
protected function pagination( $which ) {
|
||||
|
||||
if ( $this->has_items() ) {
|
||||
parent::pagination( $which );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<div class="tablenav-pages one-page">
|
||||
<span class="displaying-num">%s</span>
|
||||
</div>',
|
||||
esc_html__( '0 items', 'wpforms-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extending the `display_rows()` method in order to add hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function display_rows() {
|
||||
|
||||
/**
|
||||
* Fires before displaying the table rows.
|
||||
*
|
||||
* @since 1.5.6.2
|
||||
*
|
||||
* @param ListTable $list_table_obj ListTable instance.
|
||||
*/
|
||||
do_action( 'wpforms_admin_overview_before_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
parent::display_rows();
|
||||
|
||||
/**
|
||||
* Fires after displaying the table rows.
|
||||
*
|
||||
* @since 1.5.6.2
|
||||
*
|
||||
* @param ListTable $list_table_obj ListTable instance.
|
||||
*/
|
||||
do_action( 'wpforms_admin_overview_after_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms search markup.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @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 ) {
|
||||
|
||||
wpforms()->obj( 'forms_search' )->search_box( $text, $input_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of views available on forms overview table.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
protected function get_views() {
|
||||
|
||||
return wpforms()->obj( 'forms_views' )->get_views();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms;
|
||||
|
||||
use WPForms\Admin\Forms\Table\Facades\Columns;
|
||||
use WPForms\Admin\Traits\HasScreenOptions;
|
||||
|
||||
/**
|
||||
* Primary overview page inside the admin which lists all forms.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class Page {
|
||||
|
||||
use HasScreenOptions;
|
||||
|
||||
/**
|
||||
* Overview Table instance.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var ListTable
|
||||
*/
|
||||
private $overview_table;
|
||||
|
||||
/**
|
||||
* Primary class constructor.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
$this->screen_options_id = 'wpforms_forms_overview_screen_options';
|
||||
|
||||
$this->screen_options = [
|
||||
'pagination' => [
|
||||
'heading' => esc_html__( 'Pagination', 'wpforms-lite' ),
|
||||
'options' => [
|
||||
[
|
||||
'label' => esc_html__( 'Number of forms per page:', 'wpforms-lite' ),
|
||||
'option' => 'per_page',
|
||||
'default' => wpforms()->obj( 'form' )->get_count_per_page(),
|
||||
'type' => 'number',
|
||||
'args' => [
|
||||
'min' => 1,
|
||||
'max' => 999,
|
||||
'step' => 1,
|
||||
'maxlength' => 3,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'view' => [
|
||||
'heading' => esc_html__( 'View', 'wpforms-lite' ),
|
||||
'options' => [
|
||||
[
|
||||
'label' => esc_html__( 'Show form templates', 'wpforms-lite' ),
|
||||
'option' => 'show_form_templates',
|
||||
'default' => true,
|
||||
'type' => 'checkbox',
|
||||
'checked' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->init_screen_options( wpforms_is_admin_page( 'overview' ) );
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
// Reset columns settings.
|
||||
add_filter( 'manage_toplevel_page_wpforms-overview_columns', [ $this, 'screen_settings_columns' ] );
|
||||
|
||||
// Rewrite forms per page value from Form Overview page screen options.
|
||||
add_filter( 'wpforms_forms_per_page', [ $this, 'get_wpforms_forms_per_page' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the template visibility option is enabled.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function overview_show_form_templates() {
|
||||
|
||||
return get_user_option( $this->screen_options_id . '_view_show_form_templates' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forms per page value from Form Overview page screen options.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_wpforms_forms_per_page() {
|
||||
|
||||
return get_user_option( $this->screen_options_id . '_pagination_per_page' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is viewing the overview page, if so, party on.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
|
||||
|
||||
// Only load if we are actually on the overview page.
|
||||
if ( ! wpforms_is_admin_page( 'overview' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid recursively include _wp_http_referer in the REQUEST_URI.
|
||||
$this->remove_referer();
|
||||
|
||||
add_action( 'current_screen', [ $this, 'init_overview_table' ], 5 );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
|
||||
add_action( 'wpforms_admin_page', [ $this, 'output' ] );
|
||||
add_action( 'wpforms_admin_page', [ $this, 'field_column_setting' ] );
|
||||
|
||||
/**
|
||||
* Fires after the form overview page initialization.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'wpforms_overview_init' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Init overview table class.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function init_overview_table() {
|
||||
|
||||
$this->overview_table = ListTable::get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove previous `_wp_http_referer` variable from the REQUEST_URI.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function remove_referer() {
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$_SERVER['REQUEST_URI'] = remove_query_arg( '_wp_http_referer', wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add per-page screen option to the Forms table.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @depecated 1.8.8 Use HasScreenOptions trait instead.
|
||||
*/
|
||||
public function screen_options() {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.8.8 of the WPForms plugin' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter screen settings columns data.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $columns Columns.
|
||||
*
|
||||
* @return array
|
||||
* @noinspection PhpMissingParamTypeInspection
|
||||
*/
|
||||
public function screen_settings_columns( $columns ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets for the overview page.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function enqueues() {
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-htmx',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/htmx.min.js',
|
||||
[],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-admin-forms-overview',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/forms/overview{$min}.js",
|
||||
[ 'jquery', 'underscore', 'wpforms-htmx' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'wpforms-admin-list-table-ext',
|
||||
WPFORMS_PLUGIN_URL . "assets/css/admin-list-table-ext{$min}.css",
|
||||
[],
|
||||
WPFORMS_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-admin-list-table-ext',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/share/list-table-ext{$min}.js",
|
||||
[ 'jquery', 'jquery-ui-sortable', 'underscore', 'wpforms-admin', 'wpforms-multiselect-checkboxes' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires after enqueue the forms overview page assets.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
do_action( 'wpforms_overview_enqueue' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if it is an empty state.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
private function is_empty_state() {
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
return empty( $this->overview_table->items ) &&
|
||||
! isset( $_GET['search']['term'] ) &&
|
||||
! isset( $_GET['status'] ) &&
|
||||
! isset( $_GET['tags'] ) &&
|
||||
array_sum( wpforms()->obj( 'forms_views' )->get_count() ) === 0;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the output for the overview page.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function output() {
|
||||
|
||||
?>
|
||||
<div id="wpforms-overview" class="wrap wpforms-admin-wrap">
|
||||
|
||||
<h1 class="page-title">
|
||||
<?php esc_html_e( 'Forms Overview', 'wpforms-lite' ); ?>
|
||||
<?php if ( wpforms_current_user_can( 'create_forms' ) ) : ?>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-builder&view=setup' ) ); ?>" class="page-title-action wpforms-btn add-new-h2 wpforms-btn-orange" data-action="add">
|
||||
<svg viewBox="0 0 14 14" class="page-title-action-icon">
|
||||
<path d="M14 5.385v3.23H8.615V14h-3.23V8.615H0v-3.23h5.385V0h3.23v5.385H14Z"/>
|
||||
</svg>
|
||||
<span class="page-title-action-text"><?php esc_html_e( 'Add New', 'wpforms-lite' ); ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</h1>
|
||||
|
||||
<div class="wpforms-admin-content">
|
||||
|
||||
<?php
|
||||
$this->overview_table->prepare_items();
|
||||
|
||||
/**
|
||||
* Fires before forms overview list table output.
|
||||
*
|
||||
* @since 1.6.0.1
|
||||
*/
|
||||
do_action( 'wpforms_admin_overview_before_table' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
if ( $this->is_empty_state() ) {
|
||||
|
||||
// Output no forms screen.
|
||||
echo wpforms_render( 'admin/empty-states/no-forms' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
} else {
|
||||
?>
|
||||
<form id="wpforms-overview-table" method="get" action="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-overview' ) ); ?>">
|
||||
|
||||
<input type="hidden" name="post_type" value="wpforms" />
|
||||
<input type="hidden" name="page" value="wpforms-overview" />
|
||||
|
||||
<?php
|
||||
$this->overview_table->search_box( esc_html__( 'Search Forms', 'wpforms-lite' ), 'wpforms-overview-search' );
|
||||
$this->overview_table->views();
|
||||
$this->overview_table->display();
|
||||
?>
|
||||
|
||||
</form>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin notices.
|
||||
*
|
||||
* @since 1.5.7
|
||||
* @deprecated 1.7.3
|
||||
*/
|
||||
public function notices() {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.7.3 of the WPForms', "wpforms()->obj( 'forms_bulk_actions' )->notices()" );
|
||||
|
||||
wpforms()->obj( 'forms_bulk_actions' )->notices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the bulk table actions.
|
||||
*
|
||||
* @since 1.5.7
|
||||
* @deprecated 1.7.3
|
||||
*/
|
||||
public function process_bulk_actions() {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.7.3 of the WPForms', "wpforms()->obj( 'forms_bulk_actions' )->process()" );
|
||||
|
||||
wpforms()->obj( 'forms_bulk_actions' )->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove certain arguments from a query string that WordPress should always hide for users.
|
||||
*
|
||||
* @since 1.5.7
|
||||
* @deprecated 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 ) {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.7.3 of the WPForms', "wpforms()->obj( 'forms_bulk_actions' )->removable_query_args()" );
|
||||
|
||||
return wpforms()->obj( 'forms_bulk_actions' )->removable_query_args( $removable_query_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for field column personalization.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
public function field_column_setting() {
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->get_columns_multiselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns multiselect menu.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string HTML menu markup.
|
||||
*/
|
||||
private function get_columns_multiselect(): string {
|
||||
|
||||
$columns = Columns::get_columns();
|
||||
$selected_keys = Columns::get_selected_columns_keys();
|
||||
$options = '';
|
||||
|
||||
$html = '
|
||||
<div id="wpforms-list-table-ext-edit-columns-select-container" class="wpforms-hidden wpforms-forms-overview-page">
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="action" value="wpforms_admin_forms_overview_save_columns_order"/>
|
||||
<select name="fields[]"
|
||||
id="wpforms-forms-table-edit-columns-select"
|
||||
class="wpforms-forms-table-edit-columns-select wpforms-list-table-ext-edit-columns-select"
|
||||
multiple="multiple">
|
||||
<optgroup label="' . esc_html__( 'Columns', 'wpforms-lite' ) . '">
|
||||
%s
|
||||
</optgroup>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
|
||||
foreach ( $columns as $column ) {
|
||||
$selected = in_array( $column->get_id(), $selected_keys, true ) ? 'selected' : '';
|
||||
$disabled = $column->is_readonly() ? 'disabled="true"' : '';
|
||||
$options .= sprintf( '<option value="%s" %s %s>%s</option>', esc_attr( $column->get_id() ), $selected, $disabled, esc_html( $column->get_label() ) );
|
||||
}
|
||||
|
||||
return sprintf( $html, $options );
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( 'forms_views' )->update_count()" );
|
||||
|
||||
return wpforms()->obj( '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()->obj( '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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms\Table\DataObjects;
|
||||
|
||||
use WPForms\Admin\Base\Tables\DataObjects\ColumnBase;
|
||||
|
||||
/**
|
||||
* Column data object.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class Column extends ColumnBase {}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms\Table\Facades;
|
||||
|
||||
use WPForms\Admin\Base\Tables\Facades\ColumnsBase;
|
||||
use WPForms\Admin\Forms\Table\DataObjects\Column;
|
||||
use WPForms\Integrations\LiteConnect\LiteConnect;
|
||||
|
||||
/**
|
||||
* Column facade class.
|
||||
*
|
||||
* Hides the complexity of columns' collection behind a simple interface.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
class Columns extends ColumnsBase {
|
||||
|
||||
/**
|
||||
* Saved columns order user meta name.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
const COLUMNS_USER_META_NAME = 'wpforms_overview_table_columns';
|
||||
|
||||
/**
|
||||
* Legacy saved columns order user meta name.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*/
|
||||
const LEGACY_COLUMNS_USER_META_NAME = 'managetoplevel_page_wpforms-overviewcolumnshidden';
|
||||
|
||||
/**
|
||||
* Get columns.
|
||||
*
|
||||
* Returns all possible columns for the Forms table.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return Column[] Array of columns as objects.
|
||||
*/
|
||||
protected static function get_all(): array {
|
||||
|
||||
static $columns = null;
|
||||
|
||||
if ( ! $columns ) {
|
||||
$columns = self::get_columns();
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forms' list table columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return Column[] Array of columns as objects.
|
||||
*/
|
||||
public static function get_columns(): array {
|
||||
|
||||
$columns_data = [
|
||||
'id' => [
|
||||
'label' => esc_html__( 'ID', 'wpforms-lite' ),
|
||||
],
|
||||
'name' => [
|
||||
'label' => esc_html__( 'Name', 'wpforms-lite' ),
|
||||
'readonly' => true,
|
||||
],
|
||||
'tags' => [
|
||||
'label' => esc_html__( 'Tags', 'wpforms-lite' ),
|
||||
],
|
||||
'author' => [
|
||||
'label' => esc_html__( 'Author', 'wpforms-lite' ),
|
||||
],
|
||||
'shortcode' => [
|
||||
'label' => esc_html__( 'Shortcode', 'wpforms-lite' ),
|
||||
],
|
||||
'created' => [
|
||||
'label' => esc_html__( 'Date', 'wpforms-lite' ),
|
||||
],
|
||||
'entries' => [
|
||||
'label' => esc_html__( 'Entries', 'wpforms-lite' ),
|
||||
],
|
||||
];
|
||||
|
||||
// In Lite, we should not show Entries column if Lite Connect is not enabled.
|
||||
if ( ! wpforms()->is_pro() && ! ( LiteConnect::is_allowed() && LiteConnect::is_enabled() ) ) {
|
||||
unset( $columns_data['entries'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the forms overview table columns data.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array[] $columns Columns data.
|
||||
*/
|
||||
$columns_data = apply_filters( 'wpforms_admin_forms_table_facades_columns_data', $columns_data );
|
||||
|
||||
$columns_data = self::set_columns_data_defaults( $columns_data );
|
||||
|
||||
$columns = [];
|
||||
|
||||
foreach ( $columns_data as $id => $column ) {
|
||||
$columns[ $id ] = new Column( $id, $column );
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns' keys for the columns which user selected to be displayed.
|
||||
*
|
||||
* It returns an array of keys in the order they should be displayed.
|
||||
* It returns draggable and non-draggable columns.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_selected_columns_keys(): array {
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$user_meta_columns = get_user_meta( $user_id, self::COLUMNS_USER_META_NAME, true );
|
||||
$user_meta_legacy_columns_hidden = get_user_meta( $user_id, self::LEGACY_COLUMNS_USER_META_NAME, true );
|
||||
|
||||
$user_meta_columns = $user_meta_columns ? $user_meta_columns : [];
|
||||
$user_meta_legacy_columns_hidden = $user_meta_legacy_columns_hidden ? $user_meta_legacy_columns_hidden : [];
|
||||
|
||||
// Make form id column hidden by default.
|
||||
if ( empty( $user_meta_columns ) ) {
|
||||
$user_meta_legacy_columns_hidden[] = 'id';
|
||||
}
|
||||
|
||||
// Always include readonly columns.
|
||||
$user_meta_columns = array_unique( array_merge( $user_meta_columns, self::get_readonly_columns_keys() ) );
|
||||
|
||||
if ( ! empty( $user_meta_columns ) && empty( $user_meta_legacy_columns_hidden ) ) {
|
||||
return $user_meta_columns;
|
||||
}
|
||||
|
||||
// If custom order is not saved, let's check if there is a legacy user meta-option.
|
||||
// It is a kind of migration from legacy user meta-option to the new one.
|
||||
$user_meta_columns = array_diff( array_keys( self::get_all() ), $user_meta_legacy_columns_hidden );
|
||||
|
||||
// Update user meta option.
|
||||
if ( update_user_meta( $user_id, self::COLUMNS_USER_META_NAME, $user_meta_columns ) ) {
|
||||
// Remove legacy user meta-option.
|
||||
delete_user_meta( $user_id, self::LEGACY_COLUMNS_USER_META_NAME );
|
||||
}
|
||||
|
||||
return $user_meta_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get draggable columns ordered keys.
|
||||
*
|
||||
* It will return custom order if user has already saved it, otherwise it will return default order.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_draggable_ordered_keys(): array {
|
||||
|
||||
// First, let's check if user has already saved custom order.
|
||||
$custom_order = self::get_selected_columns_keys();
|
||||
$all_columns = self::get_all();
|
||||
|
||||
if ( $custom_order ) {
|
||||
// If a user has saved custom order, let's filter out columns which are not draggable.
|
||||
return array_filter(
|
||||
$custom_order,
|
||||
static function ( $column ) use ( $all_columns ) {
|
||||
|
||||
return isset( $all_columns[ $column ] ) && $all_columns[ $column ]->is_draggable();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// If a user has not saved custom order, let's use the default order.
|
||||
$draggable = array_filter(
|
||||
$all_columns,
|
||||
static function ( $column ) {
|
||||
|
||||
return $column->is_draggable();
|
||||
}
|
||||
);
|
||||
|
||||
return array_keys( $draggable );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save columns keys array into user meta.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $columns_keys Array of columns keys in desired display order.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sanitize_and_save_columns( array $columns_keys ): bool {
|
||||
|
||||
$columns_keys = array_map( [ __CLASS__, 'sanitize_column_key' ], $columns_keys );
|
||||
$columns_keys = array_filter( $columns_keys, [ __CLASS__, 'validate_column_key' ] );
|
||||
|
||||
// Add readonly columns.
|
||||
$columns_keys = array_unique( array_merge( $columns_keys, self::get_readonly_columns_keys() ) );
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$user_meta_columns = get_user_meta( $user_id, self::COLUMNS_USER_META_NAME, true );
|
||||
|
||||
// If user has already saved custom order, let's check if it has been changed.
|
||||
if ( $user_meta_columns === $columns_keys ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update user meta option.
|
||||
return update_user_meta( $user_id, self::COLUMNS_USER_META_NAME, $columns_keys );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize column key.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param string $key Column key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitize_column_key( string $key ): string {
|
||||
|
||||
return sanitize_key( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns' data ready to use in the list table object.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_list_table_columns(): array {
|
||||
|
||||
$columns = [
|
||||
'cb' => '<input type="checkbox" />',
|
||||
];
|
||||
|
||||
$order = self::get_draggable_ordered_keys();
|
||||
$all_columns = self::get_all();
|
||||
|
||||
foreach ( $order as $column_id ) {
|
||||
$columns[ $column_id ] = $all_columns[ $column_id ]->get_label_html();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the forms overview table columns.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $columns Columns data.
|
||||
*/
|
||||
$columns = apply_filters( 'wpforms_overview_table_columns', $columns ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
// Add empty column for the cog icon.
|
||||
$columns['cog'] = '';
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get readonly columns keys.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_readonly_columns_keys(): array {
|
||||
|
||||
$readonly = array_filter(
|
||||
self::get_all(),
|
||||
static function ( $column ) {
|
||||
|
||||
return $column->is_readonly();
|
||||
}
|
||||
);
|
||||
|
||||
return array_keys( $readonly );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set columns data defaults.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $columns_data Columns data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function set_columns_data_defaults( array $columns_data ): array {
|
||||
|
||||
return array_map(
|
||||
static function ( $column ) {
|
||||
$column['type'] = $column['type'] ?? '';
|
||||
$column['draggable'] = $column['draggable'] ?? true;
|
||||
$column['label_html'] = $column['label_html'] ?? '';
|
||||
$column['readonly'] = $column['readonly'] ?? false;
|
||||
|
||||
return $column;
|
||||
},
|
||||
$columns_data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms;
|
||||
|
||||
use WP_Post;
|
||||
use WPForms_Form_Handler;
|
||||
|
||||
/**
|
||||
* 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()->obj( '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',
|
||||
[],
|
||||
'10.2.0',
|
||||
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' ),
|
||||
'searchEnabled' => true,
|
||||
'searchChoices' => true,
|
||||
'searchFloor' => 1,
|
||||
'searchResultLimit' => 100,
|
||||
'searchFields' => [ 'label' ],
|
||||
'allowHTML' => true,
|
||||
// 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 = ListTable::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">—</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 ListTable $overview_table Instance of the ListTable 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 ListTable $list_table Overview list 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Forms;
|
||||
|
||||
use WP_Post;
|
||||
use WPForms\Admin\Notice;
|
||||
use WPForms\Pro\Tasks\Actions\PurgeTemplateEntryTask;
|
||||
|
||||
/**
|
||||
* User Templates class.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
class UserTemplates {
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function hooks() {
|
||||
|
||||
add_action( 'init', [ $this, 'register_post_type' ] );
|
||||
|
||||
// Add template states.
|
||||
add_filter( 'display_post_states', [ $this, 'add_template_states' ], 10, 2 );
|
||||
|
||||
// Modify get form args on the overview page.
|
||||
add_filter( 'wpforms_get_form_args', [ $this, 'add_template_post_type' ] );
|
||||
|
||||
// Modify Show Templates user option.
|
||||
add_filter( 'get_user_option_wpforms_forms_overview_show_form_templates', [ $this, 'get_forms_overview_show_form_templates_option' ] );
|
||||
|
||||
// Disable payment processing for user templates.
|
||||
add_filter( 'wpforms_process_before_form_data', [ $this, 'process_before_form_data' ] );
|
||||
|
||||
// Add user templates to the form templates list.
|
||||
add_filter( 'wpforms_form_templates', [ $this, 'add_form_templates' ] );
|
||||
|
||||
// AJAX handler for deleting user templates.
|
||||
add_action( 'wp_ajax_wpforms_user_template_remove', [ $this, 'ajax_remove_user_template' ] );
|
||||
|
||||
// Disable Lite Connect integration for user templates on form submission.
|
||||
add_action( 'wpforms_process', [ $this, 'process_entry' ], 10, 3 );
|
||||
|
||||
if ( wpforms()->is_pro() ) {
|
||||
// Add notices about entry(ies) being purged.
|
||||
add_action( 'admin_notices', [ $this, 'get_template_entries_notice' ] );
|
||||
add_action( 'admin_notices', [ $this, 'get_template_entry_notice' ] );
|
||||
|
||||
// Add purge entry task.
|
||||
add_filter( 'wpforms_tasks_get_tasks', [ $this, 'add_purge_entry_task' ] );
|
||||
|
||||
// Disable edit entry for templates.
|
||||
add_filter( 'wpforms_current_user_can', [ $this, 'disable_edit_entry' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the `wpforms-template` post type.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function register_post_type() {
|
||||
|
||||
/**
|
||||
* Filter the arguments for the `wpforms-template` post type.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Post type arguments.
|
||||
*/
|
||||
$args = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
'wpforms_template_post_type_args',
|
||||
[
|
||||
'label' => 'WPForms Template',
|
||||
'public' => false,
|
||||
'exclude_from_search' => true,
|
||||
'show_ui' => false,
|
||||
'show_in_admin_bar' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'can_export' => false,
|
||||
'supports' => [ 'title', 'author', 'revisions' ],
|
||||
'capability_type' => 'wpforms_form', // Not using 'capability_type' anywhere. It just has to be custom for security reasons.
|
||||
'map_meta_cap' => false, // Don't let WP to map meta caps to have a granular control over this process via 'map_meta_cap' filter.
|
||||
]
|
||||
);
|
||||
|
||||
register_post_type( 'wpforms-template', $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template states.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $post_states Array of post states.
|
||||
* @param WP_Post $post Post object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_template_states( $post_states, $post ) {
|
||||
|
||||
if ( ! ( wpforms_is_admin_page( 'overview' ) || wpforms_is_admin_page( 'entries' ) ) ) {
|
||||
return $post_states;
|
||||
}
|
||||
|
||||
// No need to show template states on the templates page.
|
||||
if ( wpforms_is_admin_page( 'overview' ) && wpforms()->obj( 'forms_views' )->get_current_view() === 'templates' ) {
|
||||
return $post_states;
|
||||
}
|
||||
|
||||
if ( $post->post_type === 'wpforms-template' ) {
|
||||
$post_states['wpforms_template'] = __( 'Template', 'wpforms-lite' );
|
||||
}
|
||||
|
||||
return $post_states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable edit entry for templates.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param bool $user_can Whether the current user can perform the given capability.
|
||||
* @param string $caps Capability name.
|
||||
* @param int $id The ID of the object to check against.
|
||||
*
|
||||
* @return bool Whether the current user can perform the given capability.
|
||||
*/
|
||||
public function disable_edit_entry( bool $user_can, $caps, $id ): bool {
|
||||
|
||||
if ( $caps === 'edit_entries_form_single' && wpforms_is_form_template( $id ) ) {
|
||||
$user_can = false;
|
||||
}
|
||||
|
||||
return $user_can;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display admin notice for the entries page.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function get_template_entries_notice() {
|
||||
|
||||
if ( ! wpforms_is_admin_page( 'entries', 'list' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$form_id = ! empty( $_REQUEST['form_id'] ) ? absint( $_REQUEST['form_id'] ) : 0;
|
||||
|
||||
// The notice should be displayed only for form templates.
|
||||
if ( ! wpforms_is_form_template( $form_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no entries, we don't need to display the notice on the empty state page.
|
||||
$entries = wpforms()->obj( 'entry' )->get_entries(
|
||||
[
|
||||
'form_id' => $form_id,
|
||||
'limit' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
if ( empty( $entries ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** This filter is documented in wpforms/src/Pro/Tasks/Actions/PurgeTemplateEntryTask.php */
|
||||
$delay = (int) apply_filters( 'wpforms_pro_tasks_actions_purge_template_entry_task_delay', DAY_IN_SECONDS ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
|
||||
|
||||
Notice::warning(
|
||||
sprintf(
|
||||
/* translators: %s - delay in formatted time. */
|
||||
esc_html__( 'Form template entries are for testing purposes and will be automatically deleted after %s.', 'wpforms-lite' ),
|
||||
// The `- 1` hack is to avoid the "1 day" message in favor of "24 hours".
|
||||
human_time_diff( time(), time() + $delay - 1 )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display admin notice for the entry page.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function get_template_entry_notice() {
|
||||
|
||||
if ( ! wpforms_is_admin_page( 'entries', 'details' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$entry_id = ! empty( $_REQUEST['entry_id'] ) ? absint( $_REQUEST['entry_id'] ) : 0;
|
||||
|
||||
$entry = wpforms()->obj( 'entry' )->get( $entry_id );
|
||||
|
||||
// If entry does not exist, we don't need to display the notice on the empty state page.
|
||||
if ( empty( $entry ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The notice should be displayed only for form template entry.
|
||||
if ( ! wpforms_is_form_template( $entry->form_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meta = wpforms()->obj( 'entry_meta' )->get_meta(
|
||||
[
|
||||
'entry_id' => absint( $entry_id ),
|
||||
'type' => 'purge_template_entry_task',
|
||||
'number' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
if ( empty( $meta ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$task = wpforms_json_decode( $meta[0]->data,true );
|
||||
|
||||
if ( empty( $task['timestamp'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notice::warning(
|
||||
sprintf(
|
||||
/* translators: %s - delay in formatted time. */
|
||||
esc_html__( 'Form template entries are for testing purposes. This entry will be automatically deleted in %s.', 'wpforms-lite' ),
|
||||
human_time_diff( time(), $task['timestamp'] )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Show Templates user option.
|
||||
*
|
||||
* If the user has not set the Show Templates screen option, it will default to showing templates.
|
||||
* In this case, we want to show templates by default.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool Whether to show templates by default.
|
||||
*/
|
||||
public function get_forms_overview_show_form_templates_option(): bool {
|
||||
|
||||
$screen_options = get_user_option( 'wpforms_forms_overview_options' );
|
||||
|
||||
$result = $screen_options['wpforms_forms_overview_show_form_templates'] ?? true;
|
||||
|
||||
return $result !== '0'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
|
||||
}
|
||||
|
||||
/**
|
||||
* Add `wpforms-template` post type to the form args.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Form arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_template_post_type( array $args ): array {
|
||||
|
||||
// Only add the post type to the form args on the overview page.
|
||||
if ( ! wpforms_is_admin_page( 'overview' ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
// Only add the template post type if the Show Templates screen option is enabled
|
||||
// and `post_type` is not already set.
|
||||
if ( ! isset( $args['post_type'] ) && wpforms()->obj( 'forms_overview' )->overview_show_form_templates() ) {
|
||||
$args['post_type'] = wpforms()->obj( 'form' )::POST_TYPES;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user templates to the form templates list.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $templates Form templates.
|
||||
*
|
||||
* @return array Form templates.
|
||||
*/
|
||||
public function add_form_templates( array $templates ): array {
|
||||
|
||||
$user_templates = wpforms()->obj( 'form' )->get( '', [ 'post_type' => 'wpforms-template' ] );
|
||||
|
||||
if ( empty( $user_templates ) ) {
|
||||
return $templates;
|
||||
}
|
||||
|
||||
foreach ( $user_templates as $template ) {
|
||||
$template_data = wpforms_decode( $template->post_content );
|
||||
|
||||
$edit_url = add_query_arg(
|
||||
[
|
||||
'page' => 'wpforms-builder',
|
||||
'form_id' => $template->ID,
|
||||
],
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
||||
$create_url = add_query_arg(
|
||||
[
|
||||
'page' => 'wpforms-builder',
|
||||
'form_id' => $template->ID,
|
||||
'action' => 'template_to_form',
|
||||
'_wpnonce' => wp_create_nonce( 'wpforms_template_to_form_form_nonce' ),
|
||||
],
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
||||
$templates[] = [
|
||||
'name' => $template->post_title,
|
||||
'slug' => 'wpforms-user-template-' . $template->ID,
|
||||
'action_text' => wpforms_is_admin_page( 'builder' ) || wp_doing_ajax() ? esc_html__( 'Use Template', 'wpforms-lite' ) : esc_html__( 'Create Form', 'wpforms-lite' ),
|
||||
'edit_action_text' => esc_html__( 'Edit Template', 'wpforms-lite' ),
|
||||
'description' => ! empty( $template_data['settings']['template_description'] ) ? $template_data['settings']['template_description'] : '',
|
||||
'source' => 'wpforms-user-template',
|
||||
'create_url' => $create_url,
|
||||
'edit_url' => $edit_url,
|
||||
'categories' => [ 'user' ],
|
||||
'has_access' => true,
|
||||
'data' => $template_data,
|
||||
'post_id' => $template->ID,
|
||||
];
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for removing user templates.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
public function ajax_remove_user_template() {
|
||||
|
||||
// Run a security check.
|
||||
check_ajax_referer( 'wpforms-form-templates', 'nonce' );
|
||||
|
||||
// Check for permissions.
|
||||
if ( ! wpforms_current_user_can( 'delete_forms' ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['template'] ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
wp_delete_post( absint( $_POST['template'] ), true );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add purge entry task.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $tasks Task class list.
|
||||
*/
|
||||
public function add_purge_entry_task( $tasks ) {
|
||||
|
||||
$tasks[] = PurgeTemplateEntryTask::class;
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the form data before it is processed to disable payment processing.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $form_data Form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process_before_form_data( $form_data ) {
|
||||
|
||||
if ( ! isset( $form_data['id'] ) ) {
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
if ( wpforms_is_form_template( $form_data['id'] ) ) {
|
||||
$form_data['payments'] = [];
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Lite Connect integration for user templates while processing submission.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $fields Form fields.
|
||||
* @param array $entry Form entry.
|
||||
* @param array $form_data Form data.
|
||||
*/
|
||||
public function process_entry( array $fields, array $entry, array $form_data ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
|
||||
|
||||
if ( ! wpforms_is_form_template( $form_data['id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'wpforms_integrations_lite_connect_is_allowed', '__return_false' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,798 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Show form templates.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $show_form_templates;
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'args' => [
|
||||
'post_status' => 'trash',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->show_form_templates = wpforms()->obj( 'forms_overview' )->overview_show_form_templates();
|
||||
|
||||
// Add Forms and Templates views if Show Templates setting is enabled.
|
||||
if ( $this->show_form_templates ) {
|
||||
$views = wpforms_array_insert(
|
||||
$views,
|
||||
[
|
||||
'forms' => [
|
||||
'title' => __( 'Forms', 'wpforms-lite' ),
|
||||
'get_var' => 'type',
|
||||
'get_var_value' => 'wpforms',
|
||||
'args' => [
|
||||
'post_type' => 'wpforms',
|
||||
],
|
||||
],
|
||||
'templates' => [
|
||||
'title' => __( 'Templates', 'wpforms-lite' ),
|
||||
'get_var' => 'type',
|
||||
'get_var_value' => 'wpforms-template',
|
||||
'args' => [
|
||||
'post_type' => 'wpforms-template',
|
||||
],
|
||||
],
|
||||
],
|
||||
'all'
|
||||
);
|
||||
}
|
||||
|
||||
// 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.
|
||||
* @param array $args Additional arguments to be passed to `wpforms()->obj( 'form' )->get()` method.
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
$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(): bool {
|
||||
|
||||
// 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_args' ], 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',
|
||||
'type',
|
||||
]
|
||||
);
|
||||
|
||||
$this->base_url = remove_query_arg( $get_vars );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current view slug.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_current_view(): string {
|
||||
|
||||
return $this->current_view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base URL.
|
||||
*
|
||||
* @since 1.7.5
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_base_url(): string {
|
||||
|
||||
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( string $slug ): array {
|
||||
|
||||
return $this->views[ $slug ] ?? []; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
|
||||
$defaults = [
|
||||
'nopaging' => true,
|
||||
'no_found_rows' => true,
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
'fields' => 'ids',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => wpforms()->obj( 'form' )::POST_TYPES,
|
||||
];
|
||||
|
||||
$args = array_merge( $args, $defaults );
|
||||
|
||||
$count['all'] = $this->get_all_items_count( $args );
|
||||
$count['trash'] = $this->get_trashed_forms_count( $args );
|
||||
|
||||
// Count forms and templates separately only if Show Templates screen setting is enabled.
|
||||
if ( $this->show_form_templates ) {
|
||||
$count['forms'] = $this->get_forms_count( $args );
|
||||
$count['templates'] = $this->get_form_templates_count( $args );
|
||||
}
|
||||
|
||||
// Store in class property for further use.
|
||||
$this->count = $count;
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of all items.
|
||||
*
|
||||
* May include only forms or both forms and form templates, depending on the
|
||||
* Screen Options settings whether to show form templates or not.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Get forms arguments.
|
||||
*
|
||||
* @return int Number of forms and templates.
|
||||
*/
|
||||
private function get_all_items_count( array $args ): int {
|
||||
|
||||
if ( ! $this->show_form_templates ) {
|
||||
$args['post_type'] = 'wpforms';
|
||||
}
|
||||
|
||||
$all_items = wpforms()->obj( 'form' )->get( '', $args );
|
||||
|
||||
return is_array( $all_items ) ? count( $all_items ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of forms.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Get forms arguments.
|
||||
*
|
||||
* @return int Number of published forms.
|
||||
*/
|
||||
private function get_forms_count( array $args ): int {
|
||||
|
||||
$args['post_type'] = 'wpforms';
|
||||
|
||||
$forms = wpforms()->obj( 'form' )->get( '', $args );
|
||||
|
||||
return is_array( $forms ) ? count( $forms ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of form templates.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Get forms arguments.
|
||||
*
|
||||
* @return int Number of published templates.
|
||||
*/
|
||||
private function get_form_templates_count( array $args ): int {
|
||||
|
||||
$args['post_type'] = 'wpforms-template';
|
||||
|
||||
$templates = wpforms()->obj( 'form' )->get( '', $args );
|
||||
|
||||
return is_array( $templates ) ? count( $templates ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of trashed items.
|
||||
*
|
||||
* May include only forms or both forms and form templates, depending on the
|
||||
* Screen Options settings whether to show form templates or not.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Get forms arguments.
|
||||
*
|
||||
* @return int Number of trashed forms.
|
||||
*/
|
||||
private function get_trashed_forms_count( array $args ): int {
|
||||
|
||||
if ( ! $this->show_form_templates ) {
|
||||
$args['post_type'] = 'wpforms';
|
||||
}
|
||||
|
||||
$args['post_status'] = 'trash';
|
||||
|
||||
$forms = wpforms()->obj( 'form' )->get( '', $args );
|
||||
|
||||
return is_array( $forms ) ? count( $forms ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get counts of forms in different views.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_count(): array {
|
||||
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare items arguments for list table.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param array $args Get multiple forms arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prepare_items_args( $args ): array {
|
||||
|
||||
$view_args = $this->views[ $this->current_view ]['args'] ?? [];
|
||||
|
||||
if ( ! empty( $view_args ) ) {
|
||||
$args = array_merge( $args, $view_args );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forms from Trash when preparing items for list table.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @depecated 1.8.8 The `prepare_items_args()` now handles all cases, uses `$this->views`.
|
||||
*
|
||||
* @param array $args Get multiple forms arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function prepare_items_trash( $args ) {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.8.8 of the WPForms plugin' );
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate views items.
|
||||
*
|
||||
* @since 1.7.3
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_views(): array {
|
||||
|
||||
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( string $slug ): string {
|
||||
|
||||
if ( empty( $this->views[ $slug ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$view = $this->views[ $slug ];
|
||||
|
||||
return sprintf(
|
||||
'<a href="%1$s"%2$s>%3$s <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 views "All", "Forms", "Templates".
|
||||
*
|
||||
* @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
|
||||
|
||||
// Modify row actions only for these views.
|
||||
$allowed_views = [ 'all', 'forms', 'templates' ];
|
||||
|
||||
if ( ! in_array( $this->current_view, $allowed_views, true ) ) {
|
||||
return $row_actions;
|
||||
}
|
||||
|
||||
$is_form_template = wpforms_is_form_template( $form );
|
||||
$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' )
|
||||
)
|
||||
),
|
||||
$is_form_template ? esc_attr__( 'Edit this template', 'wpforms-lite' ) : 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()->obj( '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="%1$s" title="%2$s" data-type="%3$s">%4$s</a>',
|
||||
esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'action' => 'duplicate',
|
||||
'form_id' => $form->ID,
|
||||
],
|
||||
$this->base_url
|
||||
),
|
||||
'wpforms_duplicate_form_nonce'
|
||||
)
|
||||
),
|
||||
$is_form_template ? esc_attr__( 'Duplicate this template', 'wpforms-lite' ) : esc_attr__( 'Duplicate this form', 'wpforms-lite' ),
|
||||
$is_form_template ? 'template' : 'form',
|
||||
esc_html__( 'Duplicate', 'wpforms-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
// Trash.
|
||||
if ( wpforms_current_user_can( 'delete_form_single', $form->ID ) ) {
|
||||
$query_arg = [
|
||||
'action' => 'trash',
|
||||
'form_id' => $form->ID,
|
||||
];
|
||||
|
||||
if ( $this->current_view !== 'all' ) {
|
||||
$query_arg['type'] = $this->current_view === 'templates' ? 'wpforms-template' : 'wpforms';
|
||||
}
|
||||
|
||||
$row_actions['trash'] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg( $query_arg, $this->base_url ),
|
||||
'wpforms_trash_form_nonce'
|
||||
)
|
||||
),
|
||||
$is_form_template ? esc_attr__( 'Move this form template to trash', 'wpforms-lite' ) : 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;
|
||||
}
|
||||
|
||||
$is_form_template = wpforms_is_form_template( $form );
|
||||
$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'
|
||||
)
|
||||
),
|
||||
$is_form_template ? esc_attr__( 'Restore this template', 'wpforms-lite' ) : esc_attr__( 'Restore this form', 'wpforms-lite' ),
|
||||
esc_html__( 'Restore', 'wpforms-lite' )
|
||||
);
|
||||
|
||||
// Delete permanently.
|
||||
$row_actions['delete'] = sprintf(
|
||||
'<a href="%1$s" title="%2$s" data-type="%3$s">%4$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'
|
||||
)
|
||||
),
|
||||
$is_form_template ? esc_attr__( 'Delete this template permanently', 'wpforms-lite' ) : esc_attr__( 'Delete this form permanently', 'wpforms-lite' ),
|
||||
$is_form_template ? 'template' : 'form',
|
||||
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' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 = wp_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 won’t 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.
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?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 = wp_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 = wp_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
|
||||
* @since 1.8.8 Added $days_diff optional parameter.
|
||||
*
|
||||
* @param DateTimeImmutable $start_date Start date for the timespan.
|
||||
* @param DateTimeImmutable $end_date End date for the timespan.
|
||||
* @param null|int $days_diff Optional. Number of days in the timespan. If provided, it won't be calculated.
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function get_prev_timespan_dates( $start_date, $end_date, $days_diff = null ) {
|
||||
|
||||
if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate $days_diff if not provided.
|
||||
if ( ! is_numeric( $days_diff ) ) {
|
||||
$days_diff = $end_date->diff( $start_date )->format( '%a' );
|
||||
}
|
||||
|
||||
// If $days_diff is non-positive, set $days_modifier to 1; otherwise, use $days_diff.
|
||||
$days_modifier = max( (int) $days_diff, 1 );
|
||||
|
||||
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', wp_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 ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Order of notices by type.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
const ORDER = [
|
||||
'error',
|
||||
'warning',
|
||||
'info',
|
||||
'success',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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/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', [] ) );
|
||||
$dismissed_notices = array_filter( wp_list_pluck( $dismissed_notices, 'dismissed' ) );
|
||||
$this->notices = array_diff_key( $this->notices, $dismissed_notices );
|
||||
|
||||
$output = implode( '', array_column( $this->sort_notices(), 'data' ) );
|
||||
|
||||
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
// Enqueue script only when it's needed.
|
||||
if ( strpos( $output, 'is-dismissible' ) !== false ) {
|
||||
$this->enqueues();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort notices by type.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @return array Notices.
|
||||
*/
|
||||
private function sort_notices(): array {
|
||||
|
||||
$ordered_notices = [];
|
||||
|
||||
foreach ( self::ORDER as $order ) {
|
||||
foreach ( $this->notices as $key => $notice ) {
|
||||
if ( $notice['type'] === $order ) {
|
||||
$ordered_notices[ $key ] = $notice;
|
||||
|
||||
unset( $this->notices[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notices that are not in the self::ORDER array merged to the end of array.
|
||||
return array_merge( $ordered_notices, $this->notices );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
$notice = [
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
$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['data'] = 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()->obj( 'notice' )->notices[] = $notice;
|
||||
} else {
|
||||
wpforms()->obj( '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;
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( '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()->obj( '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()->obj( 'entry' );
|
||||
$entry_meta_handler = wpforms()->obj( '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.DirectQuery, 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()->obj( '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__( 'We’re grateful that you chose WPForms for your website! Now that you’ve installed the plugin, you’re less than 5 minutes away from publishing your first form. To make it easy, we’ve got %s form templates to get you started!', 'wpforms-lite' ),
|
||||
'2000+'
|
||||
),
|
||||
'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__( 'Don’t 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 recipient’s 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, you’ll 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. That’s why many small businesses love our awesome Surveys and Polls addon. Instantly unlock full survey reporting right in your WordPress dashboard. And don’t 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 didn’t hit Submit! From there, it’s 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__( 'What’s 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. We’d 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… and this is just the beginning! Now it’s 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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,793 @@
|
||||
<?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://wpformsapi.com/feeds/v1/notifications';
|
||||
|
||||
/**
|
||||
* 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', false );
|
||||
|
||||
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()->obj( '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();
|
||||
|
||||
update_option( 'wpforms_notifications', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/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() {
|
||||
|
||||
// Leave early if there are no forms.
|
||||
if ( ! wpforms()->obj( 'form' )->forms_exist() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifications = $this->get();
|
||||
|
||||
if ( empty( $notifications ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notifications_html = '';
|
||||
$current_class = ' current';
|
||||
$content_allowed_tags = $this->get_allowed_tags();
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allowed HTML tags and their attributes.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_tags(): array {
|
||||
|
||||
return [
|
||||
'br' => [],
|
||||
'em' => [],
|
||||
'strong' => [],
|
||||
'span' => [
|
||||
'style' => [],
|
||||
],
|
||||
'p' => [
|
||||
'id' => [],
|
||||
'class' => [],
|
||||
],
|
||||
'a' => [
|
||||
'href' => [],
|
||||
'target' => [],
|
||||
'rel' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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/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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?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.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
|
||||
|
||||
// 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['announcements'] = [
|
||||
'title' => esc_html__( 'WPForms Announcements', 'wpforms-lite' ),
|
||||
'description' => esc_html__( 'Check out the latest releases from WPForms. Our team is always innovating to bring you powerful features and functionality that are simple to use. Every release is designed with you in mind!', 'wpforms-lite' ),
|
||||
'button_text' => esc_html__( 'View WPForms Announcements', 'wpforms-lite' ),
|
||||
'button_link' => 'https://wpforms.com/blog/?utm_source=WordPress&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&utm_content=Announcements',
|
||||
'cover_bg_color' => '#EFF8E9',
|
||||
'cover_img' => 'announcements.png',
|
||||
'cover_img2x' => 'announcements@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&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&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' => '#FCEDE4',
|
||||
'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&utm_medium=Community&utm_campaign=' . esc_attr( $type ) . '&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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
<?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' ] );
|
||||
add_action( 'wpforms_plugin_activated', [ $this, 'smtp_activated' ] );
|
||||
}
|
||||
|
||||
// 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 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/admin/pages/smtp{$min}.js",
|
||||
[ 'jquery' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-admin-page-smtp',
|
||||
'wpforms_pluginlanding',
|
||||
$this->get_js_strings()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set wp_mail_smtp_source option to 'wpforms' on WP Mail SMTP plugin activation.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param string $plugin_basename Plugin basename.
|
||||
*/
|
||||
public function smtp_activated( $plugin_basename ) {
|
||||
|
||||
if ( $plugin_basename !== $this->config['lite_plugin'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If user came from some certain page to install WP Mail SMTP, we can get the source and write it instead of default one.
|
||||
$source = isset( $_POST['source'] ) ? sanitize_text_field( wp_unslash( $_POST['source'] ) ) : 'wpforms'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
update_option( 'wp_mail_smtp_source', $source );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" data-source="%5$s">%2$s</button>';
|
||||
$button_allowed_html = [
|
||||
'button' => [
|
||||
'class' => true,
|
||||
'data-plugin' => true,
|
||||
'data-action' => true,
|
||||
'data-source' => 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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$source = isset( $_GET['source'] ) && $_GET['source'] === 'woocommerce' ? 'wpforms-woocommerce' : 'wpforms';
|
||||
$button = sprintf( $button_format, esc_attr( $step['plugin'] ), esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ), esc_attr( $source ) );
|
||||
|
||||
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.
|
||||
* @since 1.8.7 Use always $phpmailer v6.
|
||||
*
|
||||
* @return \PHPMailer|\PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer.
|
||||
*/
|
||||
protected function get_phpmailer() {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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' ) &&
|
||||
! wpforms_is_admin_ajax()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addons_obj = wpforms()->obj( '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/admin/pages/form-templates{$min}.js",
|
||||
[ 'underscore', 'wp-util' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'tooltipster',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.css',
|
||||
[],
|
||||
'4.2.6'
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'tooltipster',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.tooltipster/jquery.tooltipster.min.js',
|
||||
[ 'jquery', 'wpforms-admin-form-templates' ],
|
||||
'4.2.6',
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-admin-form-templates',
|
||||
'wpforms_admin_form_templates',
|
||||
[
|
||||
'nonce' => wp_create_nonce( 'wpforms-builder' ),
|
||||
'openAIFormUrl' => admin_url( 'admin.php?page=wpforms-builder&view=setup&ai-form' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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">We’d 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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 wpforms-admin-settings">
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?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()->obj( '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/page', $this->template_data(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template data.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function template_data(): array {
|
||||
|
||||
$images_url = WPFORMS_PLUGIN_URL . 'assets/images/coupons-education/';
|
||||
$utm_medium = 'Payments - Coupons';
|
||||
$utm_content = 'Coupons Addon';
|
||||
$upgrade_link = $this->addon['action'] === 'upgrade'
|
||||
? sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */
|
||||
' <strong><a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a></strong>',
|
||||
esc_url( wpforms_admin_upgrade_link( $utm_medium, $utm_content ) ),
|
||||
esc_html__( 'Upgrade to WPForms Pro', 'wpforms-lite' )
|
||||
)
|
||||
: '';
|
||||
$params = [
|
||||
'features' => [
|
||||
__( 'Custom Coupon Codes', 'wpforms-lite' ),
|
||||
__( 'Percentage or Fixed Discounts', 'wpforms-lite' ),
|
||||
__( 'Start and End Dates', 'wpforms-lite' ),
|
||||
__( 'Maximum Usage Limit', 'wpforms-lite' ),
|
||||
__( 'Once Per Email Address Limit', 'wpforms-lite' ),
|
||||
__( 'Usage Statistics', 'wpforms-lite' ),
|
||||
],
|
||||
'images' => [
|
||||
[
|
||||
'url' => $images_url . 'coupons-addon-thumbnail-01.png',
|
||||
'url2x' => $images_url . 'coupons-addon-screenshot-01.png',
|
||||
'title' => __( 'Coupons Overview', 'wpforms-lite' ),
|
||||
],
|
||||
[
|
||||
'url' => $images_url . 'coupons-addon-thumbnail-02.png',
|
||||
'url2x' => $images_url . 'coupons-addon-screenshot-02.png',
|
||||
'title' => __( 'Coupon Settings', 'wpforms-lite' ),
|
||||
],
|
||||
],
|
||||
'utm_medium' => $utm_medium,
|
||||
'utm_content' => $utm_content,
|
||||
'upgrade_link' => $upgrade_link,
|
||||
'heading_description' => '<p>' . sprintf( /* translators: %1$s - WPForms.com Upgrade page URL. */
|
||||
esc_html__( 'With the Coupons addon, you can offer customers discounts using custom coupon codes. Create your own percentage or fixed rate discount, then add the Coupon field to any payment form. When a customer enters your unique code, they’ll receive the specified discount. You can also add limits to restrict when coupons are available and how often they can be used. The Coupons addon requires a license level of Pro or higher.%s', 'wpforms-lite' ),
|
||||
wp_kses(
|
||||
$upgrade_link,
|
||||
[
|
||||
'a' => [
|
||||
'href' => [],
|
||||
'rel' => [],
|
||||
'target' => [],
|
||||
],
|
||||
'strong' => [],
|
||||
]
|
||||
)
|
||||
) . '</p>',
|
||||
'features_description' => __( 'Easy to Use, Yet Powerful', 'wpforms-lite' ),
|
||||
];
|
||||
|
||||
return array_merge( $params, $this->addon );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
<?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() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
// Run a security check.
|
||||
check_ajax_referer( 'wpforms_payments_overview_nonce' );
|
||||
|
||||
// Check for permissions.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
$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()->obj( '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 ( $report === Chart::ACTIVE_REPORT && 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() {
|
||||
|
||||
// Run a security check.
|
||||
check_ajax_referer( 'wpforms_payments_overview_nonce' );
|
||||
|
||||
// Check for permissions.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( esc_html__( 'You are not allowed to perform this action.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
$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.DirectQuery, 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.DirectQuery, 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 );
|
||||
|
||||
$group_by = Chart::ACTIVE_REPORT;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$results = $wpdb->get_row(
|
||||
"SELECT $clause FROM (SELECT $query) AS results GROUP BY $group_by",
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, 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()->obj( '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()->obj( '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;
|
||||
}
|
||||
|
||||
// Split the input by space to look for the count.
|
||||
$input_arr = (array) explode( ' ', $value );
|
||||
|
||||
// If the given stat card doesn't have a count, leave early.
|
||||
if ( empty( $this->stat_cards[ $key ]['has_count'] ) || ! isset( $input_arr[1] ) ) {
|
||||
// Format the given amount and split the input by space.
|
||||
$results[ $key ] = wpforms_format_amount( $value, true );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// The fields are stored as a `decimal` in the DB, and appears here as the string.
|
||||
// But all strings values, passed to wpforms_format_amount() are sanitized.
|
||||
// There is no need to sanitize it, as it is already a regular numeric string.
|
||||
$amount = wpforms_format_amount( (float) ( $input_arr[0] ?? $value ), true );
|
||||
|
||||
// Format the amount with the concatenation of count in parentheses.
|
||||
// Example: 2185.52000000 (79).
|
||||
$results[ $key ] = sprintf(
|
||||
'%s <span>%s</span>',
|
||||
esc_html( $amount ),
|
||||
esc_html( $input_arr[1] ) // 1: Would be count of the records.
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'bulk-wpforms_page_wpforms-payments' ) ) {
|
||||
wp_die( esc_html__( 'Your session expired. Please reload the page.', 'wpforms-lite' ) );
|
||||
}
|
||||
|
||||
$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 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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()->obj( '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()->obj( '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()->obj( '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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
<?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,
|
||||
'hidden_fields' => [ 'statcard' ],
|
||||
],
|
||||
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',
|
||||
$this->get_reports_template_args(),
|
||||
true
|
||||
);
|
||||
|
||||
echo '</div>';
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user’s 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()->obj( '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()->obj( '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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the arguments for the reports template.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_reports_template_args(): array {
|
||||
|
||||
// Retrieve the stat cards.
|
||||
$stat_cards = self::stat_cards();
|
||||
|
||||
// Set default arguments.
|
||||
$args = [
|
||||
'current' => self::ACTIVE_REPORT,
|
||||
'statcards' => $stat_cards,
|
||||
];
|
||||
|
||||
// Check if the statcard is set in the URL.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_GET['statcard'] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
// Sanitize and retrieve the tab value from the URL.
|
||||
$active_report = sanitize_text_field( wp_unslash( $_GET['statcard'] ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
// If the statcard is not valid, return default arguments.
|
||||
if ( ! isset( $stat_cards[ $active_report ] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
// If the statcard is not going to be displayed, return default arguments.
|
||||
if ( isset( $stat_cards[ $active_report ]['condition'] ) && ! $stat_cards[ $active_report ]['condition'] ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
// Set the current statcard.
|
||||
$args['current'] = $active_report;
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( '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()->obj( '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' );
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( '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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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()->obj( '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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for at least one payment in test mode.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_test_payment_exists(): bool {
|
||||
|
||||
$published = wpforms()->obj( 'payment' )->get_payments(
|
||||
[
|
||||
'mode' => 'test',
|
||||
'number' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
if ( $published ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for trashed payments.
|
||||
return ! empty(
|
||||
wpforms()->obj( 'payment' )->get_payments(
|
||||
[
|
||||
'mode' => 'test',
|
||||
'number' => 1,
|
||||
'is_published' => 0,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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 ( ! Helpers::is_test_payment_exists() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
<?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' ],
|
||||
'4.4.4',
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-chart-adapter-moment',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/chartjs-adapter-moment.min.js',
|
||||
[ 'moment', 'wpforms-chart' ],
|
||||
'1.0.1',
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-admin-payments-overview',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/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;
|
||||
|
||||
if ( ! wpforms_is_admin_ajax() && ! wpforms_is_admin_page( 'payments' ) && ! wpforms_is_admin_page( 'entries' ) ) {
|
||||
return 'live';
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
if ( empty( $mode ) || ! Helpers::is_test_payment_exists() ) {
|
||||
$mode = 'live';
|
||||
}
|
||||
|
||||
return $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. ", '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()->obj( 'payment' )->get_payments(
|
||||
[
|
||||
'mode' => 'any',
|
||||
'number' => 1,
|
||||
]
|
||||
)
|
||||
) > 0;
|
||||
|
||||
// Check on trashed payments.
|
||||
if ( ! $has_any_mode_payment ) {
|
||||
$has_any_mode_payment = count(
|
||||
wpforms()->obj( '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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()->obj( '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()->obj( 'payment' )->table_name;
|
||||
$meta_table = wpforms()->obj( '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()->obj( 'payment' )->table_name;
|
||||
$meta_table = wpforms()->obj( '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
@@ -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()->obj( '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()->obj( '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 ) ) )
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()->obj( '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()->obj( '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__( 'You’re about to save a form revision. Continuing will make this the current version.', 'wpforms-lite' );
|
||||
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?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}";
|
||||
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -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' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 is 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' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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' ),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Temporary storage for the style overrides preferences.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $style_overrides;
|
||||
|
||||
/**
|
||||
* Determines if the user has the education modal.
|
||||
* If true, the value will be used to add the Education modal class to the setting controls.
|
||||
* This is only available in the free version.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $has_education;
|
||||
|
||||
/**
|
||||
* Determines if the user has the legacy template.
|
||||
* If true, the value will be used to add the Legacy template class to the setting controls.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $has_legacy_template;
|
||||
|
||||
/**
|
||||
* 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-contrast-checker',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/share/contrast-checker{$min}.js",
|
||||
[],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-xor',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/share/xor{$min}.js",
|
||||
[],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpforms-admin-email-settings',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/email/settings{$min}.js",
|
||||
[ 'jquery', 'wpforms-admin', 'wp-escape-html', 'wp-url', 'choicesjs', 'wpforms-contrast-checker', 'wpforms-xor' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-admin-email-settings',
|
||||
'wpforms_admin_email_settings',
|
||||
[
|
||||
'contrast_fail' => esc_html__( 'This color combination may be hard to read. Try increasing the contrast between the body and text colors.', 'wpforms-lite' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Remove the appearance mode switcher from the settings array.
|
||||
unset( $settings['email-appearance'] );
|
||||
|
||||
// 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'] );
|
||||
$settings['email-background-color-dark'] = sanitize_hex_color( $settings['email-color-scheme-dark']['email_background_color_dark'] );
|
||||
|
||||
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'] );
|
||||
$settings['email-color-scheme-dark']['email_background_color_dark'] = sanitize_hex_color( $settings['email-background-color-dark'] );
|
||||
|
||||
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 ) {
|
||||
|
||||
$this->plain_text = Helpers::is_plain_text_template();
|
||||
$this->has_education = ! wpforms()->is_pro() ? 'education-modal' : '';
|
||||
$this->style_overrides = Helpers::get_current_template_style_overrides();
|
||||
$this->has_legacy_template = Helpers::is_legacy_html_template() ? 'legacy-template' : '';
|
||||
$preview_link = $this->get_current_template_preview_link();
|
||||
|
||||
// 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(),
|
||||
],
|
||||
// The reason that we're using the 'content' type is to avoid saving the value in the option storage.
|
||||
// The value is used only for the appearance mode switcher, and merely acts as a switch between dark and light mode controls.
|
||||
'email-appearance' => [
|
||||
'id' => 'email-appearance',
|
||||
'name' => esc_html__( 'Appearance', 'wpforms-lite' ),
|
||||
'desc' => esc_html__( 'Modern email clients support viewing emails in light and dark modes. You can upload a header image and customize the style for each appearance mode independently to ensure an optimal reading experience.', 'wpforms-lite' ),
|
||||
'type' => 'radio',
|
||||
'class' => [ 'wpforms-setting-row-radio', 'hide-for-template-none', 'email-appearance-mode-toggle', $this->has_legacy_template ],
|
||||
'default' => 'light',
|
||||
'is_hidden' => $this->plain_text,
|
||||
'options' => [
|
||||
'light' => esc_html__( 'Light', 'wpforms-lite' ),
|
||||
'dark' => esc_html__( 'Dark', '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 the style controls.
|
||||
$settings['email'] = $this->add_appearance_controls( $settings['email'] );
|
||||
|
||||
// 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'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appearance controls.
|
||||
* This will include controls for both Light and Dark modes.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $settings Email settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function add_appearance_controls( $settings ) {
|
||||
|
||||
// Education modal arguments.
|
||||
$education_args = [ 'action' => 'upgrade' ];
|
||||
|
||||
// New settings for the Light mode.
|
||||
$new_setting = [
|
||||
'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', 'has-preview-changes', 'email-light-mode', $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', 'has-preview-changes', 'email-light-mode' ],
|
||||
'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' => [ 'email-color-scheme', 'hide-for-template-none', 'has-preview-changes', 'email-light-mode', $this->has_education, $this->has_legacy_template ],
|
||||
'type' => 'color_scheme',
|
||||
'is_hidden' => $this->plain_text,
|
||||
'education_badge' => $this->get_pro_education_badge(),
|
||||
'data_attributes' => $this->has_education ? array_merge( [ 'name' => esc_html__( 'Color Scheme', 'wpforms-lite' ) ], $education_args ) : [],
|
||||
'colors' => $this->get_color_scheme_controls(),
|
||||
],
|
||||
'email-typography' => [
|
||||
'id' => 'email-typography',
|
||||
'name' => esc_html__( 'Typography', 'wpforms-lite' ),
|
||||
'desc' => esc_html__( 'Choose the style that’s applied to all text in email notifications.', 'wpforms-lite' ),
|
||||
'class' => [ 'hide-for-template-none', 'has-preview-changes', 'email-typography', 'email-light-mode', $this->has_education, $this->has_legacy_template ],
|
||||
'education_badge' => $this->get_pro_education_badge(),
|
||||
'data_attributes' => $this->has_education ? 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' ),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Add background color control if the Pro version is not active or Legacy template is selected.
|
||||
$new_setting = $this->maybe_add_background_color_control( $new_setting );
|
||||
|
||||
return wpforms_array_insert(
|
||||
$settings,
|
||||
$this->add_appearance_dark_mode_controls( $new_setting ),
|
||||
'email-appearance'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appearance dark mode controls.
|
||||
*
|
||||
* This function will duplicate the default "Light" color
|
||||
* controls to create corresponding controls for dark mode.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param array $settings Email settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function add_appearance_dark_mode_controls( $settings ) {
|
||||
|
||||
// Duplicate and modify each item for dark mode.
|
||||
foreach ( $settings as $key => $item ) {
|
||||
// Duplicate the item with '-dark' added to the key.
|
||||
$dark_key = "{$key}-dark";
|
||||
$settings[ $dark_key ] = $item;
|
||||
|
||||
// Modify the 'name' within the duplicated item.
|
||||
if ( isset( $settings[ $dark_key ]['id'] ) ) {
|
||||
$settings[ $dark_key ]['id'] .= '-dark';
|
||||
$classes = &$settings[ $dark_key ]['class'];
|
||||
$classes[] = 'email-dark-mode';
|
||||
$classes[] = 'wpforms-hide';
|
||||
|
||||
// Remove classes related to light mode.
|
||||
$classes = array_filter(
|
||||
$classes,
|
||||
static function ( $class_name ) {
|
||||
|
||||
return $class_name !== 'email-light-mode' && $class_name !== 'has-external-image-url';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Override the description for the header image control.
|
||||
if ( $key === 'email-header-image' ) {
|
||||
$settings[ $dark_key ]['desc'] = esc_html__( 'Upload or choose a logo to be displayed at the top of email notifications. Light mode image will be used if not set.', 'wpforms-lite' );
|
||||
}
|
||||
|
||||
// Override the background color control attributes.
|
||||
if ( $key === 'email-background-color' ) {
|
||||
$settings[ $dark_key ]['default'] = sanitize_hex_color( $this->style_overrides['email_background_color_dark'] );
|
||||
$settings[ $dark_key ]['data']['fallback-color'] = sanitize_hex_color( $this->style_overrides['email_background_color_dark'] );
|
||||
}
|
||||
|
||||
// Override the color scheme control attributes.
|
||||
if ( $key === 'email-color-scheme' ) {
|
||||
$settings[ $dark_key ]['colors'] = $this->get_color_scheme_controls( true );
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Email settings heading content.
|
||||
*
|
||||
* @since 1.8.5
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_heading_content() {
|
||||
|
||||
return wpforms_render( 'admin/settings/email-heading' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Email settings education badge.
|
||||
* This is only available in the free version.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_pro_education_badge() {
|
||||
|
||||
// Leave early if the user has the Lite version.
|
||||
if ( empty( $this->has_education ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Output the education badge.
|
||||
return EducationHelpers::get_badge( 'Pro' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate color scheme controls for the color picker.
|
||||
*
|
||||
* @since 1.8.6
|
||||
*
|
||||
* @param bool $is_dark_mode Whether the color scheme is for dark mode.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_color_scheme_controls( $is_dark_mode = false ) {
|
||||
|
||||
// Append '_dark' to keys if it's for dark mode.
|
||||
$is_dark_mode_suffix = $is_dark_mode ? '_dark' : '';
|
||||
|
||||
// Data attributes to disable extensions from appearing in the input field.
|
||||
$color_scheme_data = [
|
||||
'1p-ignore' => 'true', // 1Password ignore.
|
||||
'lp-ignore' => 'true', // LastPass ignore.
|
||||
];
|
||||
|
||||
$colors = [];
|
||||
$controls = [
|
||||
"email_background_color{$is_dark_mode_suffix}" => esc_html__( 'Background', 'wpforms-lite' ),
|
||||
"email_body_color{$is_dark_mode_suffix}" => esc_html__( 'Body', 'wpforms-lite' ),
|
||||
"email_text_color{$is_dark_mode_suffix}" => esc_html__( 'Text', 'wpforms-lite' ),
|
||||
"email_links_color{$is_dark_mode_suffix}" => esc_html__( 'Links', 'wpforms-lite' ),
|
||||
];
|
||||
|
||||
foreach ( $controls as $key => $label ) {
|
||||
// Construct the color controls array.
|
||||
$colors[ $key ] = [
|
||||
'name' => $label,
|
||||
'data' => array_merge(
|
||||
[
|
||||
'fallback-color' => $this->style_overrides[ $key ],
|
||||
],
|
||||
$color_scheme_data
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" class="wpforms-btn-preview" target="_blank" rel="noopener">Preview Email Template</a>', 'wpforms-lite' ),
|
||||
[
|
||||
'a' => [
|
||||
'class' => true,
|
||||
'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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function maybe_add_background_color_control( $settings ) {
|
||||
|
||||
// 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', 'has-preview-changes', 'email-light-mode' ],
|
||||
'type' => 'color',
|
||||
'is_hidden' => $this->plain_text,
|
||||
'default' => '#e9eaec',
|
||||
'data' => [
|
||||
'fallback-color' => $this->style_overrides['email_background_color'],
|
||||
'1p-ignore' => 'true', // 1Password ignore.
|
||||
'lp-ignore' => 'true', // LastPass ignore.
|
||||
],
|
||||
],
|
||||
],
|
||||
'email-color-scheme',
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Note that this evaluation is only available for the "Light" mode,
|
||||
* as the "Dark" mode is a new feature and doesn't have the legacy header image control.
|
||||
*
|
||||
* @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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Settings;
|
||||
|
||||
use WPForms\Helpers\Transient;
|
||||
|
||||
/**
|
||||
* Modern Markup setting element.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
class ModernMarkup {
|
||||
|
||||
/**
|
||||
* 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|mixed $settings Settings data.
|
||||
*
|
||||
* @return array
|
||||
* @noinspection HtmlUnknownTarget
|
||||
* @noinspection NullPointerExceptionInspection
|
||||
*/
|
||||
public function register_field( $settings ): array {
|
||||
|
||||
/**
|
||||
* Allows to show/hide the Modern Markup setting field on the Settings page.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param mixed $is_hidden 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;
|
||||
}
|
||||
|
||||
$settings = (array) $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. */
|
||||
__( 'Check this option to 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">Read our form markup documentation</a> to learn more.', '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()->obj( 'form' )->get( '', [ 'post_status' => 'publish' ] );
|
||||
$is_disabled_transient = ( ! empty( $forms ) && 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 enabling/disabling 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, when it is disabled because of the legacy CC field, add the 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 you’re using the deprecated Credit Card field.</strong> If you’d 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 opens 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' );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin;
|
||||
|
||||
/**
|
||||
* Site Health WPForms Info.
|
||||
*
|
||||
* @since 1.5.5
|
||||
*/
|
||||
class SiteHealth {
|
||||
|
||||
/**
|
||||
* Init Site Health.
|
||||
*
|
||||
* @since 1.5.5
|
||||
*/
|
||||
final public function init() {
|
||||
|
||||
$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()->obj( '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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Splash;
|
||||
|
||||
use WPForms\Helpers\CacheBase;
|
||||
|
||||
/**
|
||||
* Splash cache handler.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
class SplashCache extends CacheBase {
|
||||
|
||||
use SplashTrait;
|
||||
|
||||
/**
|
||||
* Remote source URL.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const REMOTE_SOURCE = 'https://wpformsapi.com/feeds/v1/splash/';
|
||||
|
||||
/**
|
||||
* Determine if the class is allowed to load.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function allow_load(): bool {
|
||||
|
||||
return is_admin() || wp_doing_cron() || wpforms_doing_wp_cli();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide settings.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return array Settings array.
|
||||
*/
|
||||
protected function setup(): array {
|
||||
|
||||
return [
|
||||
|
||||
// Remote source URL.
|
||||
'remote_source' => $this->get_remote_source(),
|
||||
|
||||
// Splash cache file name.
|
||||
'cache_file' => 'splash.json',
|
||||
|
||||
/**
|
||||
* Time-to-live of the splash cache file in seconds.
|
||||
*
|
||||
* This applies to `uploads/wpforms/cache/splash.json` file.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param integer $cache_ttl Cache time-to-live, in seconds.
|
||||
* Default value: WEEK_IN_SECONDS.
|
||||
*/
|
||||
'cache_ttl' => (int) apply_filters( 'wpforms_admin_splash_cache_ttl', WEEK_IN_SECONDS ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote source URL.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_remote_source(): string {
|
||||
|
||||
return defined( 'WPFORMS_SPLASH_REMOTE_SOURCE' ) ? WPFORMS_SPLASH_REMOTE_SOURCE : self::REMOTE_SOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare splash modal data.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $data Splash modal data.
|
||||
*/
|
||||
protected function prepare_cache_data( $data ): array {
|
||||
|
||||
if ( empty( $data ) || ! is_array( $data ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$blocks = $this->prepare_blocks( $data );
|
||||
|
||||
if ( empty( $blocks ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prepared_data['blocks'] = $blocks;
|
||||
|
||||
return $prepared_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare blocks.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $data Splash modal data.
|
||||
*
|
||||
* @return array Prepared blocks.
|
||||
*/
|
||||
private function prepare_blocks( array $data ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
$version = $this->get_splash_data_version();
|
||||
$version = $this->get_major_version( $version );
|
||||
|
||||
$latest_version = $this->get_latest_splash_version();
|
||||
|
||||
// If the latest version is empty - set the latest version to the previous WPForms version.
|
||||
// This is needed for the first update from the version without the "What's New?" feature.
|
||||
$latest_version = empty( $latest_version ) ? $this->get_previous_plugin_version() : $latest_version;
|
||||
|
||||
// If the latest version is bigger than the current - set latest version to current.
|
||||
$latest_version = version_compare( $latest_version, $version, '>' ) ? $version : $latest_version;
|
||||
$latest_version = $this->get_major_version( $latest_version );
|
||||
|
||||
// Filter data by plugin version.
|
||||
$blocks = array_filter(
|
||||
$data,
|
||||
static function ( $block ) use ( $version, $latest_version ) {
|
||||
|
||||
$block_version = $block['version'] ?? '';
|
||||
|
||||
// If the version is latest - return only blocks with the current version.
|
||||
if ( $version === $latest_version ) {
|
||||
return version_compare( $block_version, $version, '=' );
|
||||
}
|
||||
|
||||
// If the version is not latest - return only blocks between latest and current versions.
|
||||
return version_compare( $block_version, $latest_version, '>' ) && version_compare( $block_version, $version, '<=' );
|
||||
}
|
||||
);
|
||||
|
||||
// Reset indexes.
|
||||
$blocks = array_values( $blocks );
|
||||
|
||||
return array_map(
|
||||
function ( $block ) {
|
||||
|
||||
// Prepare buttons URLs.
|
||||
$block['buttons'] = $this->prepare_buttons( $block['btns'] ?? [] );
|
||||
|
||||
// Set layout based on an image type.
|
||||
$block['layout'] = $this->get_block_layout( $block['img'] );
|
||||
|
||||
unset( $block['btns'] );
|
||||
|
||||
return $block;
|
||||
},
|
||||
$blocks,
|
||||
array_keys( $blocks )
|
||||
) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Splash;
|
||||
|
||||
/**
|
||||
* What's New class.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
class SplashScreen {
|
||||
|
||||
use SplashTrait;
|
||||
|
||||
/**
|
||||
* Splash data.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $splash_data = [];
|
||||
|
||||
/**
|
||||
* Whether it is a new WPForms installation.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_new_install;
|
||||
|
||||
/**
|
||||
* Whether the splash link is added.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $splash_link_added = false;
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
add_action( 'admin_init', [ $this, 'initialize_splash_data' ], 15 );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
|
||||
add_action( 'admin_footer', [ $this, 'admin_footer' ] );
|
||||
add_filter( 'wpforms_pro_admin_dashboard_widget_welcome_block_html_message', [ $this, 'add_splash_link' ] );
|
||||
add_filter( 'wpforms_lite_admin_dashboard_widget_welcome_block_html_message', [ $this, 'add_splash_link' ] );
|
||||
add_filter( 'update_footer', [ $this, 'add_splash_link' ], PHP_INT_MAX );
|
||||
add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize splash data.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function initialize_splash_data() {
|
||||
|
||||
if ( ! $this->is_allow_splash() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $this->splash_data ) ) {
|
||||
$cached_data_obj = wpforms()->obj( 'splash_cache' );
|
||||
$cached_data = $cached_data_obj ? $cached_data_obj->get() : null;
|
||||
|
||||
if ( empty( $cached_data ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$default_data = $this->get_default_data();
|
||||
|
||||
$this->splash_data = wp_parse_args( $cached_data, $default_data );
|
||||
|
||||
$version = $this->get_major_version( WPFORMS_VERSION );
|
||||
|
||||
$this->update_splash_data_version( $version );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function admin_enqueue_scripts() {
|
||||
|
||||
$min = wpforms_get_min_suffix();
|
||||
|
||||
// jQuery confirm.
|
||||
wp_register_script(
|
||||
'jquery-confirm',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.js',
|
||||
[ 'jquery' ],
|
||||
'1.0.0',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'jquery-confirm',
|
||||
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.confirm/jquery-confirm.min.css',
|
||||
[],
|
||||
'1.0.0'
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'wpforms-splash-modal',
|
||||
WPFORMS_PLUGIN_URL . "assets/js/admin/splash/modal{$min}.js",
|
||||
[ 'jquery', 'wp-util' ],
|
||||
WPFORMS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'wpforms-splash-modal',
|
||||
WPFORMS_PLUGIN_URL . "assets/css/admin/admin-splash-modal{$min}.css",
|
||||
[],
|
||||
WPFORMS_VERSION
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpforms-splash-modal',
|
||||
'wpforms_splash_data',
|
||||
[
|
||||
'nonce' => wp_create_nonce( 'wpforms_dash_widget_nonce' ),
|
||||
'triggerForceOpen' => $this->should_open_splash(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output splash modal.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function admin_footer() {
|
||||
|
||||
if ( $this->is_splash_empty() || ! $this->is_allow_splash() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not add splash modal HTML, JS, and CSS if the link is not added.
|
||||
// This happens only on the Dashboard when user hid welcome block.
|
||||
if ( ! $this->splash_link_added && $this->is_dashboard() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->render_modal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash data is empty.
|
||||
*
|
||||
* @since 1.8.7
|
||||
* @since 1.8.8 Changed method visibility from private to public.
|
||||
*
|
||||
* @return bool True if empty, false otherwise.
|
||||
*/
|
||||
public function is_splash_empty(): bool {
|
||||
|
||||
if ( empty( $this->splash_data ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return empty( $this->retrieve_blocks_for_user( $this->splash_data['blocks'] ?? [] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve blocks for user.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $blocks Splash modal blocks.
|
||||
*/
|
||||
private function retrieve_blocks_for_user( array $blocks ): array {
|
||||
|
||||
$user_license = $this->get_user_license();
|
||||
|
||||
if ( ! $user_license ) {
|
||||
$user_license = 'lite';
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$blocks,
|
||||
static function ( $block ) use ( $user_license ) { //phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement
|
||||
|
||||
return in_array( $user_license, $block['type'] ?? [], true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render splash modal.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $data Splash modal data.
|
||||
*/
|
||||
public function render_modal( array $data = [] ) {
|
||||
|
||||
wp_enqueue_script( 'jquery-confirm' );
|
||||
wp_enqueue_style( 'jquery-confirm' );
|
||||
|
||||
wp_enqueue_script( 'wpforms-splash-modal' );
|
||||
wp_enqueue_style( 'wpforms-splash-modal' );
|
||||
|
||||
if ( $this->should_open_splash() ) {
|
||||
$this->update_splash_version();
|
||||
}
|
||||
|
||||
if ( empty( $data ) ) {
|
||||
$data = $this->splash_data ?? [];
|
||||
|
||||
$data['blocks'] = $this->retrieve_blocks_for_user( $data['blocks'] ?? [] );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wpforms_render( 'admin/splash/modal', $data, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a splash link to footer.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param string|mixed $content Footer content.
|
||||
*
|
||||
* @return string Footer content.
|
||||
*/
|
||||
public function add_splash_link( $content ): string {
|
||||
|
||||
$content = (string) $content;
|
||||
|
||||
if ( ! $this->is_available_for_display() ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Allow only on WPForms pages and the Dashboard.
|
||||
if ( ! $this->is_allow_splash() || $this->is_new_install() ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Do not output the link in the footer on the dashboard.
|
||||
if ( $this->is_dashboard() && current_filter() === 'update_footer' ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$this->splash_link_added = true;
|
||||
|
||||
$content .= sprintf(
|
||||
' <span>-</span> <a href="#" class="wpforms-splash-modal-open">%s</a>',
|
||||
__( 'See the new features!', 'wpforms-lite' )
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal can be displayed manually, via a link.
|
||||
* Used in footer and in form builder context menu.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available_for_display(): bool {
|
||||
|
||||
// Return if splash data is empty.
|
||||
if ( $this->is_splash_empty() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return if a splash data version is different from the current plugin major version.
|
||||
if ( $this->get_splash_data_version() !== $this->get_major_version( WPFORMS_VERSION ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal is allowed.
|
||||
* Only allow in Form Builder, WPForms pages, and the Dashboard.
|
||||
* And only if it's not a new installation.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return bool True if allowed, false otherwise.
|
||||
*/
|
||||
public function is_allow_splash(): bool {
|
||||
|
||||
if ( ! $this->is_force_open() && ( $this->is_new_install() || $this->is_minor_update() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only show on WPForms pages OR dashboard.
|
||||
return wpforms_is_admin_page( 'builder' ) || wpforms_is_admin_page() || $this->is_dashboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is the dashboard.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool True if it is the dashboard, false otherwise.
|
||||
*/
|
||||
private function is_dashboard(): bool {
|
||||
|
||||
global $pagenow;
|
||||
|
||||
return $pagenow === 'index.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal should be forced open.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool True if it should be forced open, false otherwise.
|
||||
*/
|
||||
private function is_force_open(): bool {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return sanitize_key( $_GET['wpforms_action'] ?? '' ) === 'preview-splash-screen';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal should be opened.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return bool True if splash should open, false otherwise.
|
||||
*/
|
||||
private function should_open_splash(): bool {
|
||||
|
||||
// Skip if announcements are hidden, or it is the dashboard page.
|
||||
if ( $this->is_dashboard() || $this->hide_splash_modal() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow if a splash version different from the current plugin major version, and it's not a new installation.
|
||||
$should_open_splash = $this->get_latest_splash_version() !== $this->get_major_version( WPFORMS_VERSION ) &&
|
||||
( ! $this->is_new_install() || $this->is_force_open() );
|
||||
|
||||
if ( ! $should_open_splash ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if user on the builder page and the Challenge can be started.
|
||||
if ( wpforms_is_admin_page( 'builder' ) ) {
|
||||
return $this->is_allow_builder_splash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal should be allowed on the builder page.
|
||||
* If the Challenge can be started, the splash modal should not be displayed.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool True if allowed, false otherwise.
|
||||
*/
|
||||
private function is_allow_builder_splash(): bool {
|
||||
|
||||
$challenge = wpforms()->obj( 'challenge' );
|
||||
|
||||
return ! ( $challenge->challenge_force_start() || $challenge->challenge_can_start() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the plugin is newly installed.
|
||||
*
|
||||
* Get all migrations that have run.
|
||||
* If the only migration with a timestamp is the current version, it's a new installation.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool True if new install, false otherwise.
|
||||
*/
|
||||
private function is_new_install(): bool {
|
||||
|
||||
if ( isset( $this->is_new_install ) ) {
|
||||
return $this->is_new_install;
|
||||
}
|
||||
|
||||
$option_name = wpforms()->is_pro() ? 'wpforms_versions' : 'wpforms_versions_lite';
|
||||
|
||||
$migrations_run = get_option( $option_name, [] );
|
||||
|
||||
if ( empty( $migrations_run ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
unset( $migrations_run[ WPFORMS_VERSION ] );
|
||||
|
||||
$this->is_new_install = empty( end( $migrations_run ) );
|
||||
|
||||
return $this->is_new_install;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current update is a minor update.
|
||||
*
|
||||
* This method checks the version history of migrations run and compares
|
||||
* the last recorded version with the current version to determine if
|
||||
* the update is minor or major.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool True if it's a minor update, false otherwise.
|
||||
*/
|
||||
private function is_minor_update(): bool {
|
||||
|
||||
return $this->get_major_version( $this->get_previous_plugin_version() ) === $this->get_major_version( WPFORMS_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if splash modal should be hidden.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return bool True if hidden, false otherwise.
|
||||
*/
|
||||
private function hide_splash_modal(): bool {
|
||||
|
||||
/**
|
||||
* Force to hide splash modal.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param bool $hide_splash_modal True to hide, false otherwise.
|
||||
*/
|
||||
return (bool) apply_filters( 'wpforms_admin_splash_screen_hide_splash_modal', wpforms_setting( 'hide-announcements' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove certain arguments from a query string that WordPress should always hide for users.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @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[] = 'wpforms_action';
|
||||
|
||||
return $removable_query_args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Splash;
|
||||
|
||||
use WPForms\Migrations\Base as MigrationsBase;
|
||||
|
||||
trait SplashTrait {
|
||||
|
||||
/**
|
||||
* Default plugin version.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $default_plugin_version = '1.8.6'; // The last version before the "What's New?" feature.
|
||||
|
||||
/**
|
||||
* Previous plugin version.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $previous_plugin_version;
|
||||
|
||||
/**
|
||||
* Latest splash version.
|
||||
*
|
||||
* @since 1.9.3
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $latest_splash_version;
|
||||
|
||||
/**
|
||||
* Get splash data version.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return string Splash data version.
|
||||
*/
|
||||
private function get_splash_data_version(): string {
|
||||
|
||||
return get_option( 'wpforms_splash_data_version', WPFORMS_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update splash data version.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param string $version Splash data version.
|
||||
*/
|
||||
private function update_splash_data_version( string $version ) {
|
||||
|
||||
update_option( 'wpforms_splash_data_version', $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest splash version.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return string Splash version.
|
||||
*/
|
||||
private function get_latest_splash_version(): string {
|
||||
|
||||
if ( $this->latest_splash_version ) {
|
||||
return $this->latest_splash_version;
|
||||
}
|
||||
|
||||
$this->latest_splash_version = get_option( 'wpforms_splash_version', '' );
|
||||
|
||||
// Create option if it doesn't exist.
|
||||
if ( empty( $this->latest_splash_version ) ) {
|
||||
$this->latest_splash_version = $this->default_plugin_version;
|
||||
|
||||
update_option( 'wpforms_splash_version', $this->latest_splash_version );
|
||||
}
|
||||
|
||||
return $this->latest_splash_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update option with the latest splash version.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
private function update_splash_version() {
|
||||
|
||||
update_option( 'wpforms_splash_version', $this->get_major_version( WPFORMS_VERSION ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove hide_welcome_block widget meta key for all users.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
private function remove_hide_welcome_block_widget_meta() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->delete(
|
||||
$wpdb->usermeta,
|
||||
[
|
||||
'meta_key' => 'wpforms_dash_widget_hide_welcome_block', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user license type.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_user_license(): string {
|
||||
|
||||
/**
|
||||
* License type used for splash screen.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string $license License type.
|
||||
*/
|
||||
return (string) apply_filters( 'wpforms_admin_splash_splashtrait_get_user_license', wpforms_get_license_type() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default splash modal data.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @return array Splash modal data.
|
||||
*/
|
||||
private function get_default_data(): array {
|
||||
|
||||
return [
|
||||
'license' => $this->get_user_license(),
|
||||
'buttons' => [
|
||||
'get_started' => __( 'Get Started', 'wpforms-lite' ),
|
||||
'learn_more' => __( 'Learn More', 'wpforms-lite' ),
|
||||
],
|
||||
'header' => [
|
||||
'image' => WPFORMS_PLUGIN_URL . 'assets/images/splash/sullie.svg',
|
||||
'title' => __( 'What’s New in WPForms', 'wpforms-lite' ),
|
||||
'description' => __( 'Since you’ve been gone, we’ve added some great new features to help grow your business and generate more leads. Here are some highlights...', 'wpforms-lite' ),
|
||||
],
|
||||
'footer' => [
|
||||
'title' => __( 'Start Building Smarter WordPress Forms', 'wpforms-lite' ),
|
||||
'description' => __( 'Add advanced form fields and conditional logic, plus offer more payment options, manage entries, and connect to your favorite marketing tools – all when you purchase a premium plan.', 'wpforms-lite' ),
|
||||
'upgrade' => [
|
||||
'text' => __( 'Upgrade to Pro Today', 'wpforms-lite' ),
|
||||
'url' => wpforms_admin_upgrade_link( 'splash-modal', 'Upgrade to Pro Today' ),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare buttons.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $buttons Buttons.
|
||||
*
|
||||
* @return array Prepared buttons.
|
||||
*/
|
||||
private function prepare_buttons( array $buttons ): array {
|
||||
|
||||
return array_map(
|
||||
function ( $button ) {
|
||||
return [
|
||||
'url' => $this->prepare_url( $button['url'] ),
|
||||
'text' => $button['text'],
|
||||
];
|
||||
},
|
||||
$buttons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare URL.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param string $url URL.
|
||||
*
|
||||
* @return string Prepared URL.
|
||||
*/
|
||||
private function prepare_url( string $url ): string {
|
||||
|
||||
$replace_tags = [
|
||||
'{admin_url}' => admin_url(),
|
||||
'{license_key}' => wpforms_get_license_key(),
|
||||
];
|
||||
|
||||
return str_replace( array_keys( $replace_tags ), array_values( $replace_tags ), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block layout.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param array $image Image data.
|
||||
*
|
||||
* @return string Block layout.
|
||||
*/
|
||||
private function get_block_layout( array $image ): string {
|
||||
|
||||
$image_type = $image['type'] ?? 'icon';
|
||||
|
||||
switch ( $image_type ) {
|
||||
case 'icon':
|
||||
$layout = 'one-third-two-thirds';
|
||||
break;
|
||||
|
||||
case 'illustration':
|
||||
$layout = 'fifty-fifty';
|
||||
break;
|
||||
|
||||
default:
|
||||
$layout = 'full-width';
|
||||
break;
|
||||
}
|
||||
|
||||
return $layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a major version.
|
||||
*
|
||||
* @since 1.8.7.2
|
||||
*
|
||||
* @param string $version Version.
|
||||
*
|
||||
* @return string Major version.
|
||||
*/
|
||||
private function get_major_version( $version ): string {
|
||||
|
||||
// Allow only digits and dots.
|
||||
$clean_version = preg_replace( '/[^0-9.]/', '.', $version );
|
||||
|
||||
// Get version parts.
|
||||
$version_parts = explode( '.', $clean_version );
|
||||
|
||||
// If a version has more than 3 parts - use only first 3. Get block data only for major versions.
|
||||
if ( count( $version_parts ) > 3 ) {
|
||||
$version = implode( '.', array_slice( $version_parts, 0, 3 ) );
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPForms plugin previous version.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @return string Previous WPForms version.
|
||||
*/
|
||||
private function get_previous_plugin_version(): string {
|
||||
|
||||
if ( $this->previous_plugin_version ) {
|
||||
return $this->previous_plugin_version;
|
||||
}
|
||||
|
||||
$this->previous_plugin_version = get_option( MigrationsBase::PREVIOUS_CORE_VERSION_OPTION_NAME, '' );
|
||||
|
||||
if ( empty( $this->previous_plugin_version ) ) {
|
||||
$this->previous_plugin_version = $this->default_plugin_version; // The last version before the "What's New?" feature.
|
||||
}
|
||||
|
||||
return $this->previous_plugin_version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace WPForms\Admin\Splash;
|
||||
|
||||
use WPForms\Migrations\Base as MigrationsBase;
|
||||
|
||||
/**
|
||||
* Splash upgrader.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
class SplashUpgrader {
|
||||
|
||||
use SplashTrait;
|
||||
|
||||
/**
|
||||
* Available plugins.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const AVAILABLE_PLUGINS = [
|
||||
'wpforms-lite',
|
||||
'wpforms',
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*/
|
||||
private function hooks() {
|
||||
|
||||
// Update splash data after plugin update.
|
||||
add_action( 'wpforms_migrations_base_core_upgraded', [ $this, 'update_splash_data_on_migration' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update splash modal data.
|
||||
*
|
||||
* @since 1.8.7
|
||||
* @deprecated 1.8.8
|
||||
*
|
||||
* @param object $upgrader Upgrader object.
|
||||
*/
|
||||
public function update_splash_data( $upgrader ) {
|
||||
|
||||
_deprecated_function( __METHOD__, '1.8.8 of the WPForms plugin', '\WPForms\Admin\Splash\SplashUpgrader::update_splash_data_on_migration()' );
|
||||
|
||||
$result = $upgrader->result ?? null;
|
||||
|
||||
// Check if plugin was updated successfully.
|
||||
if ( ! $result ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if WPForms plugin was updated.
|
||||
$wpforms_updated = $this->is_wpforms_updated( $upgrader );
|
||||
|
||||
if ( ! $wpforms_updated ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve plugin version after update.
|
||||
$version = $this->get_plugin_updated_version( $upgrader );
|
||||
|
||||
if ( empty( $version ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if plugin wasn't updated.
|
||||
// Continue if plugin was upgraded to the PRO version.
|
||||
if ( version_compare( $version, WPFORMS_VERSION, '<' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$version = $this->get_major_version( $version );
|
||||
|
||||
// Store updated plugin major version.
|
||||
$this->update_splash_data_version( $version );
|
||||
|
||||
// Force update splash data cache.
|
||||
wpforms()->obj( 'splash_cache' )->update( true );
|
||||
|
||||
// Reset hide_welcome_block widget meta for all users.
|
||||
$this->remove_hide_welcome_block_widget_meta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update splash modal data on migration.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param string|mixed $previous_version Previous plugin version.
|
||||
* @param MigrationsBase $migrations_obj Migrations object.
|
||||
*/
|
||||
public function update_splash_data_on_migration( $previous_version, MigrationsBase $migrations_obj ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
|
||||
$plugin_version = $this->get_major_version( WPFORMS_VERSION );
|
||||
$data_version = $this->get_major_version( $this->get_splash_data_version() );
|
||||
$previous_version = $this->get_major_version( $previous_version );
|
||||
|
||||
// Skip if when the splash data is already updated.
|
||||
// It is possible when the plugin was downgraded.
|
||||
if (
|
||||
version_compare( $previous_version, '1.8.7', '>' ) &&
|
||||
version_compare( $plugin_version, $data_version, '<' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force update splash data cache.
|
||||
wpforms()->obj( 'splash_cache' )->update( true );
|
||||
|
||||
// Reset hide_welcome_block widget meta for all users.
|
||||
$this->remove_hide_welcome_block_widget_meta();
|
||||
|
||||
// Store updated plugin major version.
|
||||
$this->update_splash_data_version( $plugin_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WPForms plugin was updated.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param object $upgrader Upgrader object.
|
||||
*
|
||||
* @return bool True if WPForms plugin was updated, false otherwise.
|
||||
*/
|
||||
private function is_wpforms_updated( $upgrader ): bool {
|
||||
|
||||
// Check if updated plugin is WPForms.
|
||||
if ( ! in_array( $upgrader->result['destination_name'] ?? '', self::AVAILABLE_PLUGINS, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin updated version.
|
||||
*
|
||||
* @since 1.8.7
|
||||
*
|
||||
* @param object $upgrader Upgrader object.
|
||||
*
|
||||
* @return string Plugin updated version.
|
||||
*/
|
||||
private function get_plugin_updated_version( $upgrader ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
// Get plugin data after update.
|
||||
$new_plugin_data = $upgrader->new_plugin_data ?? null;
|
||||
|
||||
if ( ! $new_plugin_data ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $new_plugin_data['Version'] ?? '';
|
||||
}
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()->obj( '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 );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user