: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( ! $updated_subscription ) {
$subscriptions[] = $subscription;
$this->_storage->subscriptions = $subscriptions;
* @author Leo Fajardo (@leorw)
function delete_unused_subscriptions() {
if ( ! isset( $this->_storage->subscriptions ) ||
empty( $this->_storage->subscriptions ) ||
// Clean up only if there are already at least 3 subscriptions.
( count( $this->_storage->subscriptions ) < 3 )
if ( ! is_multisite() ) {
// If not multisite, there should only be 1 subscription, so just clear the array.
$this->_storage->subscriptions = array();
$subscriptions_to_keep_by_license_id_map = array();
$sites = self::get_sites();
foreach ( $sites as $site ) {
$blog_id = self::get_site_blog_id( $site );
$install = $this->get_install_by_blog_id( $blog_id );
if ( ! is_object( $install ) ||
! FS_Plugin_License::is_valid_id( $install->license_id )
$subscriptions_to_keep_by_license_id_map[ $install->license_id ] = true;
if ( empty( $subscriptions_to_keep_by_license_id_map ) ) {
$this->_storage->subscriptions = array();
foreach ( $this->_storage->subscriptions as $key => $subscription ) {
if ( ! isset( $subscriptions_to_keep_by_license_id_map[ $subscription->license_id ] ) ) {
unset( $this->_storage->subscriptions[ $key ] );
* @author Vova Feldman (@svovaf)
* @param string $plan Plan name
* @param bool $exact If true, looks for exact plan. If false, also check "higher" plans.
function is_plan( $plan, $exact = false ) {
$this->_logger->entrance();
if ( ! $this->is_registered() ) {
$plan = strtolower( $plan );
$current_plan_name = $this->get_plan_name();
if ( $current_plan_name === $plan ) {
// Required exact, but plans are different.
$current_plan_order = - 1;
$required_plan_order = PHP_INT_MAX;
for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
if ( $plan === $this->_plans[ $i ]->name ) {
$required_plan_order = $i;
} else if ( $current_plan_name === $this->_plans[ $i ]->name ) {
$current_plan_order = $i;
return ( $current_plan_order > $required_plan_order );
* Check if module has only one plan.
* @author Vova Feldman (@svovaf)
* @param bool $double_check In some cases developers prefer to release their paid offering as premium-only, even though there is a free version. For those cases, looking at the 'is_premium_only' value isn't enough because the result will return false even when the product has only signle paid plan.
function is_single_plan( $double_check = false ) {
$this->_logger->entrance();
if ( ! $this->is_registered() ||
! is_array( $this->_plans ) ||
0 === count( $this->_plans )
$has_free_plan = $this->has_free_plan();
if ( ! $has_free_plan && $double_check ) {
foreach ( $this->_plans as $plan ) {
if ( $plan->is_free() ) {
return ( 1 === ( count( $this->_plans ) - ( $has_free_plan ? 1 : 0 ) ) );
* Check if plan based on trial. If not in trial mode, should return false.
* @param string $plan Plan name
* @param bool $exact If true, looks for exact plan. If false, also check "higher" plans.
function is_trial_plan( $plan, $exact = false ) {
$this->_logger->entrance();
if ( ! $this->is_registered() ) {
if ( ! $this->is_trial() ) {
$trial_plan = $this->get_trial_plan();
if ( $trial_plan->name === $plan ) {
// Required exact, but plans are different.
$current_plan_order = - 1;
$required_plan_order = - 1;
for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
if ( $plan === $this->_plans[ $i ]->name ) {
$required_plan_order = $i;
} else if ( $trial_plan->name === $this->_plans[ $i ]->name ) {
$current_plan_order = $i;
return ( $current_plan_order > $required_plan_order );
* Check if plugin has any paid plans.
* @author Vova Feldman (@svovaf)
function has_paid_plan() {
return $this->_has_paid_plans ||
FS_Plan_Manager::instance()->has_paid_plan( $this->_plans );
* Check if plugin has any plan with a trail.
* @author Vova Feldman (@svovaf)
function has_trial_plan() {
* @author Vova Feldman(@svovaf)
* Allow setting a trial from the SDK without calling the API.
* But, if the user did opt-in, continue using the real data from the API.
if ( $this->_trial_days >= 0 ) {
return $this->_storage->get( 'has_trial_plan', false );
* Check if plugin has any free plan, or is it premium only.
* Note: If no plans configured, assume plugin is free.
* @author Vova Feldman (@svovaf)
function has_free_plan() {
return ! $this->is_only_premium();
* Displays a license activation dialog box when the user clicks on the "Activate License"
* or "Change License" link on the plugins
* @author Leo Fajardo (@leorw)
function _add_license_activation_dialog_box() {
'id' => $this->_module_id,
fs_require_template( 'forms/license-activation.php', $vars );
fs_require_template( 'forms/resend-key.php', $vars );
* Displays an email address update dialog box when the user clicks on the email address "Edit" button on the "Account" page.
* @author Leo Fajardo (@leorw)
function _add_email_address_update_dialog_box() {
$vars = array( 'id' => $this->_module_id );
fs_require_template( 'forms/email-address-update.php', $vars );
* @author Leo Fajardo (@leorw)
function _add_email_address_update_option() {
if ( ! $this->should_handle_user_change() ) {
// Add email address update AJAX handler.
$this->add_ajax_action( 'update_email_address', array( &$this, '_email_address_update_ajax_handler' ) );
* @author Leo Fajardo (@leorw)
function _email_address_update_ajax_handler() {
$this->check_ajax_referer( 'update_email_address' );
$new_email_address = fs_request_get( 'email_address' );
$transfer_type = fs_request_get( 'transfer_type' );
$result = $this->update_email( $new_email_address );
if ( ! FS_Api::is_api_error( $result ) ) {
self::shoot_ajax_success();
if ( FS_Api::is_api_error_object( $result ) ) {
switch ( $result->error->code ) {
case 'account_verification_required':
'code' => 'change_ownership',
'url' => $this->get_account_url( 'change_owner', array(
'candidate_email' => $new_email_address,
'transfer_type' => $transfer_type,
$error = is_object( $result ) ?
var_export( $result->error, true ) :
self::shoot_ajax_failure( $error );
* Returns a collection of IDs of installs that are associated with the context product and its add-ons, and activated with foreign licenses.
* @author Leo Fajardo (@leorw)
function get_installs_ids_with_foreign_licenses() {
is_object( $this->_license ) &&
$this->_site->user_id != $this->_license->user_id
$installs[] = $this->_site->id;
* Also try to get foreign licenses for the context product's add-ons.
$installs_by_slug_map = $this->get_parent_and_addons_installs_info();
foreach ( $installs_by_slug_map as $slug => $install_info ) {
if ( $slug == $this->get_slug() ) {
$install = $install_info['install'];
$license = $install_info['license'];
$install->user_id != $license->user_id
$installs[] = $install->id;
* Displays the "Change User" dialog box when the user clicks on the "Change User" button on the "Account" page.
* @author Leo Fajardo (@leorw)
* @param number[] $install_ids
function _add_user_change_dialog_box( $install_ids ) {
'id' => $this->_module_id,
'license_owners' => $this->fetch_installs_licenses_owners_data( $install_ids )
fs_require_template( 'forms/user-change.php', $vars );
* @author Leo Fajardo (@leorw)
function _add_data_debug_mode_dialog_box() {
'id' => $this->_module_id,
fs_require_template( 'forms/data-debug-mode.php', $vars );
* Displays a subscription cancellation dialog box when the user clicks on the "Deactivate License"
* link on the "Account" page or deactivates a plugin and there's an active subscription that is
* either associated with a non-lifetime single-site license or non-lifetime multisite license that
* is only activated on a single production site.
* @author Leo Fajardo (@leorw)
* @param bool $is_license_deactivation
function _get_subscription_cancellation_dialog_box_template_params( $is_license_deactivation = false ) {
if ( fs_is_network_admin() ) {
// Subscription cancellation dialog box is currently not supported for multisite networks.
if ( $this->is_whitelabeled() ) {
$license = $this->_get_license();
* If the installation is associated with a non-lifetime license, which is either a single-site or only activated on a single production site (or zero), and connected to an active subscription, suggest the customer to cancel the subscription upon deactivation.
* @author Leo Fajardo (@leorw) (Comment added by Vova Feldman @svovaf)
if ( ! is_object( $license ) ||
$license->is_lifetime() ||
( ! $license->is_single_site() && $license->activated > 1 )
* @var FS_Subscription $subscription
$subscription = $this->_get_subscription( $license->id );
if ( ! is_object( $subscription ) || ! $subscription->is_active() ) {
'id' => $this->_module_id,
'has_trial' => $this->is_paid_trial(),
'is_license_deactivation' => $is_license_deactivation,
* @author Leo Fajardo (@leorw)
function _add_premium_version_upgrade_selection_dialog_box() {
$modules_update = get_site_transient( $this->is_theme() ? 'update_themes' : 'update_plugins' );
if ( ! isset( $modules_update->response[ $this->_plugin_basename ] ) ) {
'id' => $this->_module_id,
'new_version' => is_object( $modules_update->response[ $this->_plugin_basename ] ) ?
$modules_update->response[ $this->_plugin_basename ]->new_version :
$modules_update->response[ $this->_plugin_basename ]['new_version']
fs_require_template( 'forms/premium-versions-upgrade-metadata.php', $vars );
fs_require_once_template( 'forms/premium-versions-upgrade-handler.php', $vars );
* Displays the opt-out dialog box when the user clicks on the "Opt Out" link on the "Plugins"
* @author Leo Fajardo (@leorw)
function _add_optout_dialog() {
if ( $this->is_theme() ) {
fs_require_once_template( '/js/jquery.content-change.php', $vars );
$vars = array( 'id' => $this->_module_id );
fs_require_template( 'forms/optout.php', $vars );
* Prepare page to include all required UI and logic for the license activation dialog.
* @author Vova Feldman (@svovaf)
function _add_license_activation() {
if ( $this->is_migration() ) {
if ( ! $this->is_user_admin() ) {
// Only admins can activate a license.
if ( ! $this->has_paid_plan() ) {
// Module doesn't have any paid plans.
$this->has_premium_version() &&