diff --git a/classes/controllers/FrmAddonsController.php b/classes/controllers/FrmAddonsController.php index b0d90d1b37..4ac16dd37d 100644 --- a/classes/controllers/FrmAddonsController.php +++ b/classes/controllers/FrmAddonsController.php @@ -97,7 +97,7 @@ public static function menu() { } $label = __( 'Add-Ons', 'formidable' ); - $label = '' . $label . ''; + $label = '' . esc_html( $label ) . ''; add_submenu_page( 'formidable', 'Formidable | ' . __( 'Add-Ons', 'formidable' ), $label, 'frm_view_forms', 'formidable-addons', 'FrmAddonsController::list_addons' ); @@ -105,10 +105,15 @@ public static function menu() { remove_submenu_page( 'formidable', 'formidable' ); if ( ! FrmAppHelper::pro_is_installed() ) { + $cta_text = FrmSalesApi::get_best_sale_value( 'menu_cta_text' ); + if ( ! $cta_text ) { + $cta_text = __( 'Upgrade', 'formidable' ); + } + add_submenu_page( 'formidable', 'Formidable | ' . __( 'Upgrade', 'formidable' ), - '' . __( 'Upgrade', 'formidable' ) . '', + '' . esc_html( $cta_text ) . '', 'frm_view_forms', 'formidable-pro-upgrade', function () { @@ -119,7 +124,7 @@ function () { } elseif ( 'formidable-pro-upgrade' === FrmAppHelper::get_param( 'page' ) ) { wp_safe_redirect( admin_url( 'admin.php?page=formidable' ) ); exit; - } + }//end if } /** diff --git a/classes/controllers/FrmAppController.php b/classes/controllers/FrmAppController.php index 977dc81ad3..b00550a184 100644 --- a/classes/controllers/FrmAppController.php +++ b/classes/controllers/FrmAppController.php @@ -337,16 +337,31 @@ private static function get_form_nav_items( $form ) { return (array) apply_filters( 'frm_form_nav_list', $nav_items, $nav_args ); } - // Adds a settings link to the plugins page /** + * Adds a settings link to the plugins page + * + * @param array $links * @return array */ public static function settings_link( $links ) { $settings = array(); if ( ! FrmAppHelper::pro_is_installed() ) { - $label = FrmAddonsController::is_license_expired() ? __( 'Renew', 'formidable' ) : __( 'Upgrade to Pro', 'formidable' ); - $settings[] = '' . esc_html( $label ) . ''; + if ( FrmAddonsController::is_license_expired() ) { + $label = __( 'Renew', 'formidable' ); + } else { + $label = FrmSalesApi::get_best_sale_value( 'plugin_page_cta_text' ); + if ( ! $label ) { + $label = __( 'Upgrade to Pro', 'formidable' ); + } + } + + $upgrade_link = FrmSalesApi::get_best_sale_value( 'plugin_page_cta_link' ); + if ( ! $upgrade_link ) { + $upgrade_link = FrmAppHelper::admin_upgrade_link( 'plugin-row' ); + } + + $settings[] = '' . esc_html( $label ) . ''; } $settings[] = '' . __( 'Build a Form', 'formidable' ) . ''; @@ -557,14 +572,17 @@ public static function admin_init() { } if ( 'formidable-pro-upgrade' === FrmAppHelper::get_param( 'page' ) && ! FrmAppHelper::pro_is_installed() && current_user_can( 'frm_view_forms' ) ) { - wp_redirect( - FrmAppHelper::admin_upgrade_link( + $redirect = FrmSalesApi::get_best_sale_value( 'menu_cta_link' ); + if ( ! $redirect ) { + $redirct = FrmAppHelper::admin_upgrade_link( array( 'medium' => 'upgrade', 'content' => 'submenu-upgrade', ) - ) - ); + ); + } + + wp_redirect( $redirect ); die(); } diff --git a/classes/helpers/FrmApiHelper.php b/classes/helpers/FrmApiHelper.php new file mode 100644 index 0000000000..f6bec2d51f --- /dev/null +++ b/classes/helpers/FrmApiHelper.php @@ -0,0 +1,119 @@ + $cutoff; + } + + /** + * @since x.x + * + * @return bool + */ + private static function is_free_not_first_30() { + return self::is_free() && ! self::is_first_30(); + } + + /** + * Check if the Pro plugin is active. If not, consider the user to be on the free version. + * + * @since x.x + * + * @return bool + */ + private static function is_free() { + return ! FrmAppHelper::pro_is_included(); + } +} diff --git a/classes/helpers/FrmAppHelper.php b/classes/helpers/FrmAppHelper.php index c33f35e351..23e53bf35a 100644 --- a/classes/helpers/FrmAppHelper.php +++ b/classes/helpers/FrmAppHelper.php @@ -1370,16 +1370,26 @@ public static function print_admin_banner( $should_show_lite_upgrade ) { ?>
'settings-license', - 'content' => 'lite-banner', - ) - ); + $cta_text = FrmSalesApi::get_best_sale_value( 'lite_banner_cta_text' ); + if ( ! $cta_text ) { + $cta_text = __( 'upgrading to PRO', 'formidable' ); + } + + $upgrade_link = FrmSalesApi::get_best_sale_value( 'lite_banner_cta_link' ); + if ( ! $upgrade_link ) { + $upgrade_link = self::admin_upgrade_link( + array( + 'medium' => 'settings-license', + 'content' => 'lite-banner', + ) + ); + } + printf( - /* translators: %1$s: Start link HTML, %2$s: End link HTML */ - esc_html__( 'You\'re using Formidable Forms Lite. To unlock more features consider %1$supgrading to PRO%2$s.', 'formidable' ), + /* translators: %1$s: Start link HTML, %2$s: CTA text ("upgrading to PRO" by default), %3$s: End link HTML */ + esc_html__( 'You\'re using Formidable Forms Lite. To unlock more features consider %1$s%2$s%3$s.', 'formidable' ), '', + esc_html( $cta_text ), '' ); ?> diff --git a/classes/helpers/FrmDashboardHelper.php b/classes/helpers/FrmDashboardHelper.php index 2cbd0116e6..f9dcd75209 100644 --- a/classes/helpers/FrmDashboardHelper.php +++ b/classes/helpers/FrmDashboardHelper.php @@ -196,12 +196,20 @@ class="" * @return array */ public static function get_license_buttons() { - $upgrade_link = FrmAppHelper::admin_upgrade_link( - array( - 'medium' => 'settings-license', - 'content' => 'dashboard-license-box', - ) - ); + $cta_text = FrmSalesApi::get_best_sale_value( 'dashboard_license_cta_text' ); + if ( ! $cta_text ) { + $cta_text = __( 'Get Formidable PRO', 'formidable' ); + } + + $upgrade_link = FrmSalesApi::get_best_sale_value( 'dashboard_license_cta_link' ); + if ( ! $upgrade_link ) { + $upgrade_link = FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'settings-license', + 'content' => 'dashboard-license-box', + ) + ); + } return array( array( @@ -210,7 +218,7 @@ public static function get_license_buttons() { 'classes' => 'frm-button-primary frm-show-unauthorized', ), array( - 'label' => __( 'Get Formidable PRO', 'formidable' ), + 'label' => $cta_text, 'link' => $upgrade_link, 'classes' => 'frm-button-secondary frm-show-unauthorized', ), diff --git a/classes/models/FrmAddon.php b/classes/models/FrmAddon.php index 3da3453484..0f52e0d891 100644 --- a/classes/models/FrmAddon.php +++ b/classes/models/FrmAddon.php @@ -330,6 +330,9 @@ protected function delete_cache() { $api = new FrmApplicationApi( $this->license ); $api->reset_cached(); + + $api = new FrmSalesApi(); + $api->reset_cached(); } /** diff --git a/classes/models/FrmInbox.php b/classes/models/FrmInbox.php index 6aae0ea77e..43cc4840dd 100644 --- a/classes/models/FrmInbox.php +++ b/classes/models/FrmInbox.php @@ -19,7 +19,7 @@ class FrmInbox extends FrmFormApi { */ private static $banner_messages; - public function __construct( $for_parent = null ) { + public function __construct() { $this->set_cache_key(); if ( false === self::$messages ) { @@ -205,22 +205,12 @@ private function is_expired( $message ) { * @return bool */ private function is_for_user( $message ) { - if ( ! isset( $message['who'] ) || $message['who'] === 'all' ) { + if ( FrmApiHelper::is_for_user( $message ) ) { return true; } + $who = (array) $message['who']; - if ( $this->is_for_everyone( $who ) ) { - return true; - } - if ( $this->is_user_type( $who ) ) { - return true; - } - if ( in_array( 'free_first_30', $who, true ) && $this->is_free_first_30() ) { - return true; - } - if ( in_array( 'free_not_first_30', $who, true ) && $this->is_free_not_first_30() ) { - return true; - } + /** * Allow for other special inbox cases in other add-ons. * @@ -233,34 +223,6 @@ private function is_for_user( $message ) { return (bool) apply_filters( 'frm_inbox_message_is_for_user', false, $who, $message ); } - /** - * @since 6.16.3 - * - * @param array $who - * @return bool - */ - private function is_for_everyone( $who ) { - return in_array( 'all', $who, true ) || in_array( 'everyone', $who, true ); - } - - /** - * @since 6.16.3 - * - * @param array $who - * @return bool - */ - private function is_user_type( $who ) { - return in_array( $this->get_user_type(), $who, true ); - } - - private function get_user_type() { - if ( ! FrmAppHelper::pro_is_installed() ) { - return 'free'; - } - - return FrmAddonsController::license_type(); - } - /** * @param string $key * @@ -392,54 +354,6 @@ private function update_list() { update_option( $this->option, self::$messages, 'no' ); } - /** - * Check if user is still using the Lite version only, and within - * the first 30 days of activation. - * - * @since 6.16 - * - * @return bool - */ - private function is_free_first_30() { - return $this->is_free() && $this->is_first_30(); - } - - /** - * @since 6.16.3 - * - * @return bool - */ - private function is_first_30() { - $activation_timestamp = get_option( 'frm_first_activation' ); - if ( false === $activation_timestamp ) { - // If the option does not exist, assume that it is - // because the user was active before this option was introduced. - return false; - } - $cutoff = strtotime( '-30 days' ); - return $activation_timestamp > $cutoff; - } - - /** - * @since 6.16.3 - * - * @return bool - */ - private function is_free_not_first_30() { - return $this->is_free() && ! $this->is_first_30(); - } - - /** - * Check if the Pro plugin is active. If not, consider the user to be on the free version. - * - * @since 6.16.3 - * - * @return bool - */ - private function is_free() { - return ! FrmAppHelper::pro_is_included(); - } - /** * Show a banner message if one is available. * diff --git a/classes/models/FrmSalesAPI.php b/classes/models/FrmSalesAPI.php new file mode 100644 index 0000000000..01f87ccc9e --- /dev/null +++ b/classes/models/FrmSalesAPI.php @@ -0,0 +1,229 @@ +set_cache_key(); + + if ( false === self::$sales ) { + $this->set_sales(); + } + } + + /** + * @since x.x + * + * @return void + */ + protected function set_cache_key() { + $this->cache_key = 'frm_sales_cache'; + } + + /** + * @since x.x + * + * @return string + */ + protected function api_url() { + return 'https://plapi.formidableforms.com/sales/'; + } + + /** + * @since x.x + * + * @return void + */ + private function set_sales() { + self::$sales = array(); + + $api = $this->get_api_info(); + if ( empty( $api ) ) { + return; + } + + foreach ( $api as $sale ) { + $this->add_sale( $sale ); + } + } + + /** + * @param array|string $sale + * + * @return void + */ + private function add_sale( $sale ) { + if ( ! is_array( self::$sales ) ) { + // This gets set in the constructor. + // This check is just here for Psalm analysis. + return; + } + + if ( ! is_array( $sale ) || ! isset( $sale['key'] ) ) { + // if the API response is invalid, $sale may not be an array. + // if there are no sales from the API, it is returning a "No Entries Found" item with no key, so check for a key as well. + return; + } + + if ( ! $this->sale_is_active( $sale ) ) { + return; + } + + self::$sales[ $sale['key'] ] = $this->fill_sale( $sale ); + } + + /** + * @param array $sale + * @return array + */ + private function fill_sale( $sale ) { + $defaults = array( + 'key' => '', + 'starts' => '', + 'expires' => '', + // Use 'free', 'personal', 'business', 'elite', 'grandfathered'. + 'who' => 'all', + 'discount_percent' => 0, + 'test_group' => '', + 'lite_banner_cta_link' => '', + 'lite_banner_cta_text' => '', + 'menu_cta_link' => '', + 'menu_cta_text' => '', + 'dashboard_license_cta_link' => '', + 'dashboard_license_cta_text' => '', + 'global_settings_license_cta_link' => '', + 'global_settings_license_cta_text' => '', + 'global_settings_unlock_more_cta_link' => '', + 'global_settings_unlock_more_cta_text' => '', + 'global_settings_upgrade_cta_link' => '', + 'builder_sidebar_cta_link' => '', + 'builder_sidebar_cta_text' => '', + ); + + return array_merge( $defaults, $sale ); + } + + /** + * Check if a sale is within the active period. + * + * @since x.x + * + * @param array $sale + * @return bool + */ + private function sale_is_active( $sale ) { + $starts = $sale['starts']; + $expires = $sale['expires'] + DAY_IN_SECONDS; + $date = new DateTime( 'now', new DateTimeZone( 'America/New_York' ) ); + $today = $date->getTimestamp(); + return $today >= $starts && $today <= $expires; + } + + /** + * @since x.x + * + * @return array|false + */ + public function get_best_sale() { + if ( ! self::$sales ) { + return false; + } + + if ( isset( self::$best_sale ) ) { + return self::$best_sale; + } + + $best_sale = false; + foreach ( self::$sales as $sale ) { + if ( ! FrmApiHelper::is_for_user( $sale ) ) { + continue; + } + + if ( ! $this->matches_ab_group( $sale ) ) { + continue; + } + + if ( ! $best_sale || $sale['discount_percent'] > $best_sale['discount_percent'] ) { + $best_sale = $sale; + } + } + + self::$best_sale = $best_sale; + return self::$best_sale; + } + + /** + * Get text for best sale if applicable. + * + * @since x.x + * + * @param string $key + * @return false|string False if no sale is active. + */ + public static function get_best_sale_value( $key ) { + if ( ! isset( self::$instance ) ) { + self::$instance = new FrmSalesApi(); + } + + $sale = self::$instance->get_best_sale(); + + return is_array( $sale ) && ! empty( $sale[ $key ] ) ? $sale[ $key ] : false; + } + + /** + * @since x.x + * + * @param array $sale + * @return bool True if the sale is a match for the applicable group (if one is defined). + */ + private function matches_ab_group( $sale ) { + if ( ! is_numeric( $sale['test_group'] ) ) { + // No test group, so return true. + return true; + } + + $ab_group = $this->get_ab_group_for_current_site(); + return $ab_group === $sale['test_group']; + } + + /** + * @since x.x + * + * @return int 1 or 0. + */ + private function get_ab_group_for_current_site() { + $option = get_option( 'frm_sale_ab_group' ); + if ( ! is_numeric( $option ) ) { + // Generate either 0 or 1. + $option = mt_rand( 0, 1 ); + update_option( 'frm_sale_ab_group', $option, false ); + } + return (int) $option; + } +} diff --git a/classes/views/dashboard/templates/pro-features-list.php b/classes/views/dashboard/templates/pro-features-list.php index d1e5dc70c4..1db176a37d 100644 --- a/classes/views/dashboard/templates/pro-features-list.php +++ b/classes/views/dashboard/templates/pro-features-list.php @@ -10,12 +10,23 @@ die( 'You are not allowed to call this page directly.' ); } -$discount_link = FrmAppHelper::admin_upgrade_link( - array( - 'medium' => 'dashboard-discount', - 'content' => 'dashboard-defy-limits-cta', - ) +$best_discount = FrmSalesApi::get_best_sale_value( 'discount_percent' ); +$use_discount = $best_discount > 50 ? $best_discount : 50; +$discount_text = sprintf( + // translators: %s is the discount percentage (ie 50%). + __( 'Upgrade to Pro & Get %1$s Off', 'formidable' ), + $use_discount . '%' ); + +$discount_link = FrmSalesApi::get_best_sale_value( 'global_settings_upgrade_cta_link' ); +if ( ! $discount_link ) { + $discount_link = FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'dashboard-discount', + 'content' => 'dashboard-defy-limits-cta', + ) + ); +} ?>
@@ -30,7 +41,7 @@ - +
diff --git a/classes/views/frm-settings/license_box.php b/classes/views/frm-settings/license_box.php index 1a3a516cad..6cf27f4186 100644 --- a/classes/views/frm-settings/license_box.php +++ b/classes/views/frm-settings/license_box.php @@ -3,18 +3,35 @@ die( 'You are not allowed to call this page directly.' ); } -$button_upgrade_link = FrmAppHelper::admin_upgrade_link( - array( - 'medium' => 'settings-license', - 'content' => 'global-settings-license-box-get-formidable-button', - ) -); -$unlock_more_upgrade_link = FrmAppHelper::admin_upgrade_link( - array( - 'medium' => 'settings-license', - 'content' => 'global-settings-license-box-unlock-more', - ) -); +$button_upgrade_text = FrmSalesApi::get_best_sale_value( 'global_settings_license_cta_text' ); +if ( ! $button_upgrade_text ) { + $button_upgrade_text = __( 'Get Formidable Now', 'formidable' ); +} + +$button_upgrade_link = FrmSalesApi::get_best_sale_value( 'global_settings_license_cta_link' ); +if ( ! $button_upgrade_link ) { + $button_upgrade_link = FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'settings-license', + 'content' => 'global-settings-license-box-get-formidable-button', + ) + ); +} + +$unlock_more_upgrade_text = FrmSalesApi::get_best_sale_value( 'global_settings_unlock_more_cta_text' ); +if ( ! $unlock_more_upgrade_text ) { + $unlock_more_upgrade_text = __( 'upgrading to PRO', 'formidable' ); +} + +$unlock_more_upgrade_link = FrmSalesApi::get_best_sale_value( 'global_settings_unlock_more_cta_link' ); +if ( ! $unlock_more_upgrade_link ) { + $unlock_more_upgrade_link = FrmAppHelper::admin_upgrade_link( + array( + 'medium' => 'settings-license', + 'content' => 'global-settings-license-box-unlock-more', + ) + ); +} ?>

@@ -23,7 +40,7 @@ - +

@@ -34,9 +51,10 @@

', + esc_html( $unlock_more_upgrade_text ), '' ); ?> diff --git a/classes/views/shared/admin-footer-links.php b/classes/views/shared/admin-footer-links.php index 81023ee028..6b2b0dc519 100644 --- a/classes/views/shared/admin-footer-links.php +++ b/classes/views/shared/admin-footer-links.php @@ -6,8 +6,11 @@ // Determine the support link based on lite vs pro. $support_link = ! FrmAppHelper::pro_is_installed() ? 'https://wordpress.org/support/plugin/formidable/' : 'https://formidableforms.com/new-topic/'; -// Determine the upgrade link based on lite vs pro. -$upgrade_link = ! FrmAppHelper::pro_is_installed() ? 'https://formidableforms.com/lite-upgrade/' : 'https://formidableforms.com/account/downloads/'; +$upgrade_link = FrmSalesApi::get_best_sale_value( 'footer_cta_link' ); +if ( ! $upgrade_link ) { + // Determine the upgrade link based on lite vs pro. + $upgrade_link = ! FrmAppHelper::pro_is_installed() ? 'https://formidableforms.com/lite-upgrade/' : 'https://formidableforms.com/account/downloads/'; +} ?>

diff --git a/phpstan.neon b/phpstan.neon index 553a54fea6..d426a09b91 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -52,7 +52,6 @@ parameters: message: '#has an unused parameter#' paths: - classes/models/FrmFieldOption.php - - classes/models/FrmInbox.php - classes/models/FrmSolution.php - stubs.php -