diff --git a/.eslintrc.json b/.eslintrc.json index 3584b5c464..391958cbbc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -36,6 +36,11 @@ }, "plugins": ["react", "jsx-a11y", "sonarjs", "cypress", "no-jquery", "compat"], "settings": { + "import/resolver": { + "webpack": { + "config": "webpack.config.js" + } + }, "react": { "pragma": "wp" } diff --git a/classes/controllers/FrmAddonsController.php b/classes/controllers/FrmAddonsController.php index 5eea518a43..dc56eb8bea 100644 --- a/classes/controllers/FrmAddonsController.php +++ b/classes/controllers/FrmAddonsController.php @@ -5,11 +5,88 @@ class FrmAddonsController { + /** + * @var string + */ + const SCRIPT_HANDLE = 'frm-addons-page'; + + /** + * @var array + */ + private static $categories = array(); + + /** + * @var string + */ + private static $request_addon_url; + /** * @var string */ protected static $plugin; + /** + * @since x.x + */ + public static function load_admin_hooks() { + add_action( 'admin_menu', __CLASS__ . '::menu', 100 ); + add_filter( 'pre_set_site_transient_update_plugins', __CLASS__ . '::check_update' ); + + if ( FrmAppHelper::is_admin_page( 'formidable-addons' ) ) { + self::$request_addon_url = 'https://connect.formidableforms.com/add-on-request/'; + + add_action( 'admin_enqueue_scripts', __CLASS__ . '::enqueue_assets', 15 ); + add_filter( 'frm_show_footer_links', '__return_false' ); + } + } + + /** + * Enqueues the Add-Ons page scripts and styles. + * + * @since x.x + * + * @return void + */ + public static function enqueue_assets() { + $plugin_url = FrmAppHelper::plugin_url(); + $version = FrmAppHelper::plugin_version(); + $js_dependencies = array( + 'wp-i18n', + // This prevents a console error "wp.hooks is undefined" in WP versions older than 5.7. + 'wp-hooks', + 'formidable_dom', + ); + + // Enqueue styles that needed. + wp_enqueue_style( 'formidable-admin' ); + wp_enqueue_style( 'formidable-grids' ); + + // Register and enqueue Add-Ons page style. + wp_register_style( self::SCRIPT_HANDLE, $plugin_url . '/css/admin/addons-page.css', array(), $version ); + wp_enqueue_style( self::SCRIPT_HANDLE ); + + // Register and enqueue Add-Ons page script. + wp_register_script( self::SCRIPT_HANDLE, $plugin_url . '/js/addons-page.js', $js_dependencies, $version, true ); + wp_localize_script( self::SCRIPT_HANDLE, 'frmAddonsVars', self::get_js_variables() ); + wp_enqueue_script( self::SCRIPT_HANDLE ); + + FrmAppHelper::dequeue_extra_global_scripts(); + } + + /** + * Get the Add-Ons page JS variables as an array. + * + * @since x.x + * + * @return array + */ + private static function get_js_variables() { + return array( + 'proIsIncluded' => FrmAppHelper::pro_is_included(), + 'addonRequestURL' => self::$request_addon_url, + ); + } + /** * @return void */ @@ -19,7 +96,7 @@ public static function menu() { } $label = __( 'Add-Ons', 'formidable' ); - $label = '' . $label . ''; + $label = '' . $label . ''; add_submenu_page( 'formidable', 'Formidable | ' . __( 'Add-Ons', 'formidable' ), $label, 'frm_view_forms', 'formidable-addons', 'FrmAddonsController::list_addons' ); @@ -46,11 +123,13 @@ public static function menu() { */ public static function list_addons() { FrmAppHelper::include_svg(); - $installed_addons = apply_filters( 'frm_installed_addons', array() ); - $license_type = ''; - $addons = self::get_api_addons(); - $errors = array(); + $view_path = FrmAppHelper::plugin_path() . '/classes/views/addons/'; + $installed_addons = apply_filters( 'frm_installed_addons', array() ); + $addons = self::get_api_addons(); + $errors = array(); + $license_type = ''; + $request_addon_url = self::$request_addon_url; if ( isset( $addons['error'] ) ) { $api = new FrmFormApi(); @@ -61,11 +140,12 @@ public static function list_addons() { $pro = array( 'pro' => array( - 'title' => 'Formidable Forms Pro', - 'slug' => 'formidable-pro', - 'released' => '2011-02-05', - 'docs' => 'knowledgebase/', - 'excerpt' => 'Create calculators, surveys, smart forms, and data-driven applications. Build directories, real estate listings, job boards, and much more.', + 'title' => 'Formidable Forms Pro', + 'slug' => 'formidable-pro', + 'released' => '2011-02-05', + 'docs' => 'knowledgebase/', + 'categories' => array( 'basic', 'plus', 'business', 'elite' ), + 'excerpt' => 'Create calculators, surveys, smart forms, and data-driven applications. Build directories, real estate listings, job boards, and much more.', ), ); $addons = $pro + $addons; @@ -73,7 +153,98 @@ public static function list_addons() { $pricing = FrmAppHelper::admin_upgrade_link( 'addons' ); - include FrmAppHelper::plugin_path() . '/classes/views/addons/list.php'; + self::organize_and_get_categories(); + $categories = self::$categories; + + include $view_path . 'index.php'; + } + + /** + * Organize and set categories. + * + * @since x.x + * + * @return void + */ + protected static function organize_and_get_categories() { + unset( self::$categories['strategy11'] ); + ksort( self::$categories ); + + $bottom_categories = array(); + $plans = FrmFormsHelper::get_license_types( + array( + 'include_all' => false, + 'case_lower' => true, + ) + ); + + // Extract the elements to move + foreach ( $plans as $plan ) { + if ( isset( self::$categories[ $plan ] ) ) { + $bottom_categories[ $plan ] = self::$categories[ $plan ]; + unset( self::$categories[ $plan ] ); + } + } + + $special_categories = array(); + if ( 'elite' !== self::license_type() ) { + $special_categories['available-addons'] = array( + 'name' => __( 'Available', 'formidable' ), + // To be assigned via JavaScript. + 'count' => 0, + ); + } + + $special_categories['active-addons'] = array( + 'name' => __( 'Active', 'formidable' ), + // To be assigned via JavaScript. + 'count' => 0, + ); + + $special_categories['all-items'] = array( + 'name' => __( 'All Add-Ons', 'formidable' ), + // To be assigned via JavaScript. + 'count' => 0, + ); + + self::$categories = array_merge( + $special_categories, + self::$categories, + $bottom_categories + ); + } + + /** + * Organize and set categories. + * + * @since x.x + * + * @param array $addon The addon array that will be modified by reference. + * @return void + */ + protected static function set_categories( &$addon ) { + if ( ! isset( $addon['categories'] ) ) { + return; + } + + $addon['category-slugs'] = array(); + + foreach ( $addon['categories'] as $category ) { + $category = FrmFormsHelper::convert_legacy_package_names( $category ); + $category_slug = sanitize_title( $category ); + + // Add the slug to the new array. + $addon['category-slugs'][] = $category_slug; + + if ( ! isset( self::$categories[ $category_slug ] ) ) { + self::$categories[ $category_slug ] = array( + 'name' => $category, + 'count' => 0, + ); + } + + ++self::$categories[ $category_slug ]['count']; + } } /** @@ -618,9 +789,11 @@ protected static function prepare_addons( &$addons ) { if ( ! isset( $addon['link'] ) ) { $addon['link'] = 'downloads/' . $slug . '/'; } - self::prepare_addon_link( $addon['link'] ); + self::prepare_addon_link( $addon['link'] ); self::set_addon_status( $addon ); + self::set_categories( $addon ); + $addons[ $id ] = $addon; }//end foreach } @@ -1441,7 +1614,7 @@ public static function show_conditional_action_button( $atts ) { * * @return void */ - protected static function addon_upgrade_link( $addon, $upgrade_link ) { + public static function addon_upgrade_link( $addon, $upgrade_link ) { $atts = is_array( $upgrade_link ) ? $upgrade_link : array(); $upgrade_link = is_array( $upgrade_link ) ? $upgrade_link['link'] : $upgrade_link; diff --git a/classes/controllers/FrmFormTemplatesController.php b/classes/controllers/FrmFormTemplatesController.php index be10f72be4..5540ea0392 100644 --- a/classes/controllers/FrmFormTemplatesController.php +++ b/classes/controllers/FrmFormTemplatesController.php @@ -494,7 +494,7 @@ private static function organize_and_set_categories() { // Filter out certain and redundant categories. // 'PayPal', 'Stripe', and 'Twilio' are included elsewhere and should be ignored in this context. - $redundant_cats = array_merge( array( 'PayPal', 'Stripe', 'Twilio' ), FrmFormsHelper::ignore_template_categories() ); + $redundant_cats = array_merge( array( 'PayPal', 'Stripe', 'Twilio' ), FrmFormsHelper::get_license_types() ); foreach ( $redundant_cats as $redundant_cat ) { $category_slug = sanitize_title( $redundant_cat ); unset( self::$categories[ $category_slug ] ); @@ -522,7 +522,7 @@ private static function organize_and_set_categories() { 'count' => 0, ); } - $special_categories['all-templates'] = array( + $special_categories['all-items'] = array( 'name' => __( 'All Templates', 'formidable' ), 'count' => self::get_template_count(), ); diff --git a/classes/controllers/FrmHooksController.php b/classes/controllers/FrmHooksController.php index 0a358b31a2..f64bc4d2a3 100644 --- a/classes/controllers/FrmHooksController.php +++ b/classes/controllers/FrmHooksController.php @@ -128,10 +128,6 @@ public static function load_admin_hooks() { add_action( 'wp_ajax_frm_dismiss_review', 'FrmAppController::dismiss_review' ); add_action( 'current_screen', 'FrmAppController::filter_admin_notices' ); - // Addons Controller. - add_action( 'admin_menu', 'FrmAddonsController::menu', 100 ); - add_filter( 'pre_set_site_transient_update_plugins', 'FrmAddonsController::check_update' ); - // Entries Controller. add_action( 'admin_menu', 'FrmEntriesController::menu', 12 ); add_filter( 'set-screen-option', 'FrmEntriesController::save_per_page', 10, 3 ); @@ -204,6 +200,7 @@ public static function load_admin_hooks() { FrmStrpLiteHooksController::load_admin_hooks(); FrmSMTPController::load_hooks(); FrmOnboardingWizardController::load_admin_hooks(); + FrmAddonsController::load_admin_hooks(); new FrmPluginSearch(); } diff --git a/classes/helpers/FrmAddonsHelper.php b/classes/helpers/FrmAddonsHelper.php new file mode 100644 index 0000000000..79285fb252 --- /dev/null +++ b/classes/helpers/FrmAddonsHelper.php @@ -0,0 +1,263 @@ + esc_html__( 'Unlock Add-on library', 'formidable' ), + 'description' => esc_html__( 'Renew your subscription today and access our library of add-ons to supercharge your forms.', 'formidable' ), + 'link_text' => esc_html__( 'Renew Now', 'formidable' ), + 'link_url' => FrmAddonsController::is_license_expired(), + 'id' => 'frm-renew-subscription-banner', + ) + ); + } + + /** + * Show 'Upgrade to Pro' banner for users not connected to Pro. + * + * @since x.x + * @return void + */ + private static function show_lite_cta() { + FrmTipsHelper::show_admin_cta( + array( + 'title' => esc_html__( 'Unlock Add-on library', 'formidable' ), + 'description' => esc_html__( 'Upgrade to Pro and access our library of add-ons to supercharge your forms.', 'formidable' ), + 'link_text' => esc_html__( 'Upgrade to PRO', 'formidable' ), + 'link_url' => FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'addons', + 'content' => 'upgrade-cta', + ) + ), + 'id' => 'frm-upgrade-banner', + ) + ); + } + + /** + * Show 'Upgrade' banner for non-elite users. + * + * @since x.x + * @return void + */ + private static function show_elite_cta() { + FrmTipsHelper::show_admin_cta( + array( + 'title' => esc_html__( 'Unlock Even More Add-ons', 'formidable' ), + 'description' => sprintf( + /* translators: %1$s: Open span tag, %2$s: Close span tag */ + esc_html__( 'Your plan includes %1$s%2$s add-ons. Upgrade to take your forms even farther', 'formidable' ), + '', + '' + ), + 'link_text' => esc_html__( 'Upgrade to PRO', 'formidable' ), + 'link_url' => FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'addons', + 'content' => 'upgrade-cta', + ) + ), + 'id' => 'frm-upgrade-banner', + ) + ); + } + + /** + * Displays a reconnect link for checking add-ons status. + * + * @since x.x + * @return void + */ + public static function get_reconnect_link() { + if ( ! FrmAppHelper::pro_is_connected() ) { + return; + } + ?> +
+ + + + +
+ 'acfforms', + 'activecampaign-wordpress-plugin' => 'activecampaign', + 'ai' => 'ai-form', + 'authorize-net-aim' => 'authorize', + 'aweber' => 'aweber', + 'bootstrap' => 'bootstrap', + 'bootstrap-modal' => 'bootstrap', + 'campaign-monitor' => 'campaignmonitor', + 'constant-contact' => 'constant_contact', + 'getresponse-wordpress-plugin' => 'getresponse', + 'google-sheets' => 'googlesheets', + 'highrise' => 'highrise', + 'hubspot-wordpress' => 'hubspot', + 'mailchimp' => 'mailchimp', + 'mailpoet-newsletters' => 'mailpoet', + 'paypal-standard' => 'paypal', + 'polylang' => 'polylang', + 'salesforce' => 'salesforcealt', + 'stripe' => 'stripealt', + 'twilio' => 'twilio', + 'woocommerce' => 'woocommerce', + 'zapier' => 'zapier', + ); + + $icon = array_key_exists( $slug, $icons_map ) ? 'frm_' . $icons_map[ $slug ] . '_icon' : 'frm_logo_icon'; + if ( 'ai' === $slug ) { + $icon = str_replace( '_', '-', $icon ); + } + + FrmAppHelper::icon_by_class( 'frmfont ' . $icon ); + } + + /** + * Echo attributes for a given addon. + * + * @since x.x + * + * @param array $addon + * @return void + */ + public static function add_addon_attributes( $addon ) { + self::set_plan_required( $addon ); + + $attributes = array( + 'tabindex' => '0', + 'frm-search-text' => strtolower( $addon['title'] . ' ' . esc_html( $addon['excerpt'] ) ), + ); + + // Set 'data-slug' attribute. + if ( ! empty( $addon['slug'] ) ) { + $attributes['data-slug'] = $addon['slug']; + } + + // Set 'data-categories' attribute. + if ( ! empty( $addon['category-slugs'] ) ) { + $attributes['data-categories'] = implode( ',', $addon['category-slugs'] ); + } + + $attributes['class'] = self::prepare_single_addon_classes( $addon ); + + FrmAppHelper::array_to_html_params( $attributes, true ); + } + + /** + * Add classes for a given addon. + * + * @since x.x + * + * @param array $addon + * @return string + */ + private static function prepare_single_addon_classes( $addon ) { + $class_names = array( 'frm-card-item frm-flex-col' ); + $class_names[] = 'plugin-card-' . $addon['slug']; + $class_names[] = 'frm-addon-' . $addon['status']['type']; + + if ( self::is_locked() ) { + $class_names[] = 'frm-locked-item'; + } + + return implode( ' ', $class_names ); + } + + /** + * Check if a given addon is locked. + * + * @return bool + */ + public static function is_locked() { + return self::$plan_required || ! FrmAppHelper::pro_is_installed(); + } + + /** + * Set the required plan for the given addon. + * + * @note + * Because the `FrmFormsHelper::get_plan_required` changes $addon by reference, + * we save the result inside a static field called `$plan_required`. + * + * @since x.x + * + * @param array $addon The addon array that will be modified by reference. + * @return void + */ + private static function set_plan_required( $addon ) { + self::$plan_required = FrmFormsHelper::get_plan_required( $addon ); + } + + /** + * Get the required plan. + * + * @since x.x + * + * @return false|string + */ + public static function get_plan() { + return self::$plan_required; + } +} diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 0b884b5fee..0f3acb062d 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -1357,7 +1357,7 @@ public static function template_icon( $categories, $atts = array() ) { $atts = array_merge( $defaults, $atts ); // Filter out ignored categories. - $ignore = self::ignore_template_categories(); + $ignore = self::get_license_types(); $categories = array_diff( $categories, $ignore ); // Define icons mapping. @@ -1416,17 +1416,6 @@ public static function template_icon( $categories, $atts = array() ) { echo ''; } - /** - * Retrieves the list of template categories to ignore. - * - * @since 4.03.01 - * - * @return string[] Array of categories to ignore. - */ - public static function ignore_template_categories() { - return array( 'Business', 'Elite', 'Personal', 'Creator', 'Basic', 'free' ); - } - /** * Get template install link. * @@ -1511,7 +1500,7 @@ public static function show_plan_required( $requires, $link ) { ?>- + @@ -1530,16 +1519,13 @@ public static function get_plan_required( &$item ) { return false; } - $plans = array( 'free', 'Basic', 'Personal', 'Plus', 'Creator', 'Business', 'Elite' ); + $plans = self::get_license_types(); foreach ( $item['categories'] as $k => $category ) { if ( in_array( $category, $plans, true ) ) { unset( $item['categories'][ $k ] ); - if ( in_array( $category, array( 'Creator', 'Personal' ), true ) ) { - // Show the current package name. - $category = 'Plus'; - } + $category = self::convert_legacy_package_names( $category ); return $category; } @@ -1548,6 +1534,49 @@ public static function get_plan_required( &$item ) { return false; } + /** + * Converts legacy package names to the current standard package name. + * + * @since x.x + * @param string $package_name + * @return string The updated package name. + */ + public static function convert_legacy_package_names( $package_name ) { + if ( in_array( $package_name, array( 'Creator', 'Personal' ), true ) ) { + $package_name = 'Plus'; + } + + return $package_name; + } + + /** + * Get the license types. + * + * @since x.x + * + * @param array $args + * @return array + */ + public static function get_license_types( $args = array() ) { + $defaults = array( + 'include_all' => true, + 'case_lower' => false, + ); + $args = wp_parse_args( $args, $defaults ); + + $license_types = array( 'Basic', 'Plus', 'Business', 'Elite' ); + + if ( $args['include_all'] ) { + $license_types = array_merge( array( 'free', 'Personal', 'Creator' ), $license_types ); + } + + if ( $args['case_lower'] ) { + $license_types = array_map( 'strtolower', $license_types ); + } + + return $license_types; + } + /** * Checks for warnings to be displayed after form settings are saved. * @@ -1812,4 +1841,18 @@ public static function actions_dropdown( $atts ) { $links = self::get_action_links( $form_id, $status ); include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/actions-dropdown.php'; } + + /** + * Retrieves the list of template categories to ignore. + * + * @since 4.03.01 + * @deprecated x.x + * + * @return string[] Array of categories to ignore. + */ + public static function ignore_template_categories() { + _deprecated_function( __METHOD__, 'x.x' ); + + return self::get_license_types(); + } } diff --git a/classes/helpers/FrmTipsHelper.php b/classes/helpers/FrmTipsHelper.php index dc9e365360..6f2ed7f28a 100644 --- a/classes/helpers/FrmTipsHelper.php +++ b/classes/helpers/FrmTipsHelper.php @@ -439,6 +439,7 @@ public static function show_admin_cta( $args ) { 'link_url' => '#', 'class' => '', 'id' => '', + 'target' => '_blank', ); $args = wp_parse_args( $args, $defaults ); diff --git a/classes/models/FrmApplicationTemplate.php b/classes/models/FrmApplicationTemplate.php index e9ba8c16d4..50941b9a8e 100644 --- a/classes/models/FrmApplicationTemplate.php +++ b/classes/models/FrmApplicationTemplate.php @@ -99,7 +99,7 @@ private static function category_matches_a_license_type( $category ) { if ( false !== strpos( $category, '+Views' ) ) { return true; } - return in_array( $category, FrmFormsHelper::ignore_template_categories(), true ); + return in_array( $category, FrmFormsHelper::get_license_types(), true ); } /** diff --git a/classes/views/addons/addon.php b/classes/views/addons/addon.php new file mode 100644 index 0000000000..30a26fe973 --- /dev/null +++ b/classes/views/addons/addon.php @@ -0,0 +1,88 @@ + +
+ +
+ + + +- - - - -
- $addon ) { + require $view_path . 'addon.php'; } ?> - - -
-
-
-
-
-
-
-