diff --git a/classes/controllers/FrmHooksController.php b/classes/controllers/FrmHooksController.php index 3dd23afb22..b8c647f844 100644 --- a/classes/controllers/FrmHooksController.php +++ b/classes/controllers/FrmHooksController.php @@ -199,6 +199,9 @@ public static function load_admin_hooks() { // Cronjob. add_action( 'admin_init', 'FrmCronController::schedule_events' ); + // Cross sell. + add_action( 'admin_menu', 'FrmSalesApi::menu', 1000 ); + // Deactivation feedback. add_action( 'admin_enqueue_scripts', 'FrmDeactivationFeedbackController::enqueue_assets' ); add_action( 'admin_footer', 'FrmDeactivationFeedbackController::footer_html' ); diff --git a/classes/models/FrmSalesApi.php b/classes/models/FrmSalesApi.php index 686d94924e..2b5d539c5d 100644 --- a/classes/models/FrmSalesApi.php +++ b/classes/models/FrmSalesApi.php @@ -29,6 +29,20 @@ class FrmSalesApi extends FrmFormApi { */ private static $best_sale; + /** + * @since x.x + * + * @var string|null + */ + private static $cross_sell_text; + + /** + * @since x.x + * + * @var string|null + */ + private static $cross_sell_link; + public function __construct() { $this->set_cache_key(); @@ -70,7 +84,94 @@ private function set_sales() { foreach ( $api as $sale ) { $this->add_sale( $sale ); + + if ( is_array( $sale ) && isset( $sale['cross_sell_text'] ) ) { + $this->set_cross_sell( $sale ); + } + } + } + + /** + * Check for a special array in the sales API array. + * Normally cross_sell_text and cross_sell_link are not set. + * But one array, that isn't actually a sale, contains cross sell data. + * This should be near the end of the array. + * + * @since x.x + * + * @param array $data + * @return void + */ + private function set_cross_sell( $data ) { + if ( ! self::cross_sell_is_valid( $data ) ) { + return; + } + + $cross_sell_text = $data['cross_sell_text']; + $cross_sell_links = $data['cross_sell_link']; + $index = self::determine_cross_sell_index( $cross_sell_text ); + + self::$cross_sell_text = sanitize_text_field( $cross_sell_text[ $index ] ); + self::$cross_sell_link = esc_url_raw( $cross_sell_links[ $index ] ); + } + + /** + * Check that both cross_sell_text and cross_sell_link are set and are arrays of the same size. + * + * @since x.x + * + * @param array $data + * @return bool + */ + private function cross_sell_is_valid( $data ) { + if ( empty( $data['cross_sell_text'] ) || empty( $data['cross_sell_link'] ) ) { + return false; + } + + if ( ! is_array( $data['cross_sell_text'] ) || ! is_array( $data['cross_sell_link'] ) ) { + return false; } + + return count( $data['cross_sell_link'] ) === count( $data['cross_sell_text'] ); + } + + /** + * Determine which cross sell text to use. + * These are shown in order for 30 days before moving on to the next one. + * + * @since x.x + * + * @param array $cross_sell_text + * @return int + */ + private static function determine_cross_sell_index( $cross_sell_text ) { + $option_name = 'frm_cross_sell_settings'; + $cross_sell_settings = get_option( $option_name ); + + if ( ! is_array( $cross_sell_settings ) ) { + $cross_sell_settings = array( + reset( $cross_sell_text ) => time(), + ); + update_option( $option_name, $cross_sell_settings ); + return 0; + } + + foreach ( $cross_sell_text as $index => $current_text ) { + if ( ! isset( $cross_sell_settings[ $current_text ] ) ) { + $cross_sell_settings[ $current_text ] = time(); + update_option( $option_name, $cross_sell_settings ); + return $index; + } + + $time_elapsed = time() - $cross_sell_settings[ $current_text ]; + if ( $time_elapsed < DAY_IN_SECONDS * 30 ) { + return $index; + } + } + + // If all options are expired, reset the option. + delete_option( $option_name ); + return 0; } /** @@ -383,4 +484,39 @@ private static function is_banner_dismissed( $key ) { $dismissed_sales = get_user_option( 'frm_dismissed_sales', get_current_user_id() ); return is_array( $dismissed_sales ) && in_array( $key, $dismissed_sales, true ); } + + public static function menu() { + if ( false === self::$sales ) { + new self(); + } + + if ( ! self::$cross_sell_text || ! self::$cross_sell_link ) { + return; + } + + add_submenu_page( + 'formidable', + esc_html( self::$cross_sell_text ) . ' | Formidable', + esc_html( self::$cross_sell_text ), + 'activate_plugins', + 'frm-sales-api-cross-sell', + function () { + // There is no page. The redirect logic is handled below, before this callback is triggered. + } + ); + + add_action( + 'admin_init', + function () { + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + if ( 'frm-sales-api-cross-sell' === FrmAppHelper::simple_get( 'page' ) && ! empty( self::$cross_sell_link ) ) { + wp_redirect( self::$cross_sell_link ); + exit; + } + } + ); + } }