: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Also handle the case when an upgrade was made using the free version.
* @author Leo Fajardo (@leorw)
! is_object( $this->_get_license() )
// Only add license activation logic to the premium version, or in case of a serviceware plugin, also in the free version.
// Add license activation link and AJAX request handler.
if ( self::is_plugins_page() ) {
$is_network_admin = fs_is_network_admin();
( $is_network_admin && $this->is_network_active() && ! $this->is_network_delegated_connection() ) ||
( ! $is_network_admin && ( ! $this->is_network_active() || $this->is_delegated_connection() ) )
( $this->has_paid_plan() && ! $this->has_premium_version() )
* @since 1.2.0 Add license action link only on plugins page.
$this->_add_license_action_link();
// Add license activation AJAX callback.
$this->add_ajax_action( 'activate_license', array( &$this, '_activate_license_ajax_action' ) );
// Add resend license AJAX callback.
$this->add_ajax_action( 'resend_license_key', array( &$this, '_resend_license_key_ajax_action' ) );
* Prepares page to include all required UI and logic for the "Change User" dialog.
* @author Leo Fajardo (@leorw)
function _add_user_change_option() {
if ( ! $this->should_handle_user_change() ) {
$installs_ids_with_foreign_licenses = $this->get_installs_ids_with_foreign_licenses();
if ( empty( $installs_ids_with_foreign_licenses ) ) {
// Handle user change only when the parent product or one of its add-ons is activated with a foreign license.
// Add user change AJAX handler.
$this->add_ajax_action( 'change_user', array( &$this, '_user_change_ajax_action' ) );
* @author Leo Fajardo (@leorw)
function should_handle_user_change() {
if ( ! $this->is_user_admin() ) {
// Only admins can change user.
if ( $this->is_addon() ) {
if ( ! $this->is_registered() ) {
$this->is_network_active() &&
( fs_is_network_admin() || ! $this->is_site_delegated_connection() )
// Handle only on site-level "Account" section for now.
* @author Leo Fajardo (@leorw)
function _add_premium_version_upgrade_selection() {
if ( ! $this->is_user_admin() ) {
if ( ! $this->is_premium() || $this->has_any_active_valid_license() ) {
// This is relevant only to the free versions and premium versions without an active license.
if ( self::is_updates_page() || ( $this->is_plugin() && self::is_plugins_page() ) ) {
$this->_add_premium_version_upgrade_selection_action();
* @author Edgar Melkonyan
* @throws Freemius_Exception
function _toggle_whitelabel_mode_ajax_handler() {
$this->_logger->entrance();
$this->check_ajax_referer( 'toggle_whitelabel_mode' );
if ( ! $this->is_user_admin() ) {
self::shoot_ajax_failure();
$license = $this->get_api_user_scope()->call(
"/licenses/{$this->_site->license_id}.json",
array( 'is_whitelabeled' => ! $this->_license->is_whitelabeled )
if ( ! $this->is_api_result_entity( $license ) ) {
self::shoot_ajax_failure(
FS_Api::is_api_error_object( $license ) ?
$license->error->message :
fs_text_inline( "An unknown error has occurred while trying to toggle the license's white-label mode.", 'unknown-error-occurred', $this->get_slug() )
$this->_license->is_whitelabeled = $license->is_whitelabeled;
$this->_store_licenses();
if ( ! $license->is_whitelabeled ) {
$this->_admin_notices->remove_sticky( 'license_whitelabeled' );
$this->_admin_notices->add_sticky(
'Your %s license was flagged as white-labeled to hide sensitive information from the WP Admin (e.g. your email, license key, prices, billing address & invoices). If you ever wish to revert it back, you can easily do it through your %s. If this was a mistake you can also %s.',
"<strong>{$this->get_plugin_title()}</strong>",
sprintf( '<a href="https://users.freemius.com" target="_blank">%s</a>', $this->get_text_inline( 'User Dashboard', 'user-dashboard' ) ),
sprintf( '<a href="#" class="fs-toggle-whitelabel-mode">%s</a>', $this->get_text_inline( 'revert it now', 'revert-it-now' ) )
self::shoot_ajax_response( array( 'success' => true ) );
* @author Leo Fajardo (@leorw)
function _add_beta_mode_update_handler() {
if ( ! $this->is_user_admin() ) {
if ( ! $this->is_premium() ) {
$this->add_ajax_action( 'set_beta_mode', array( &$this, '_set_beta_mode_ajax_handler' ) );
* @author Leo Fajardo (@leorw)
function _set_beta_mode_ajax_handler() {
$this->_logger->entrance();
$this->check_ajax_referer( 'set_beta_mode' );
if ( ! $this->is_user_admin() ) {
self::shoot_ajax_failure();
$is_beta = trim( fs_request_get( 'is_beta', '', 'post' ) );
if ( empty( $is_beta ) || ! in_array( $is_beta, array( 'true', 'false' ) ) ) {
self::shoot_ajax_failure();
$site = $this->api_site_call(
'is_beta' => ( 'true' == $is_beta ),
if ( ! $this->is_api_result_entity( $site ) ) {
self::shoot_ajax_failure(
FS_Api::is_api_error_object( $site ) ?
fs_text_inline( "An unknown error has occurred while trying to set the user's beta mode.", 'unknown-error-occurred', $this->get_slug() )
$this->_site->is_beta = $site->is_beta;
self::shoot_ajax_response( array( 'success' => true ) );
* License activation WP AJAX handler.
* @author Leo Fajardo (@leorw)
* @uses Freemius::activate_license()
function _activate_license_ajax_action() {
$this->_logger->entrance();
$this->check_ajax_referer( 'activate_license' );
$license_key = trim( fs_request_get_raw( 'license_key' ) );
if ( empty( $license_key ) ) {
$sites = fs_is_network_admin() ?
fs_request_get( 'sites', array(), 'post' ) :
$result = $this->activate_license(
fs_request_get_bool( 'is_marketing_allowed', null ),
fs_request_get( 'blog_id', null ),
fs_request_get( 'module_id', null, 'post' ),
fs_request_get( 'user_id', null ),
fs_request_get_bool( 'is_extensions_tracking_allowed', null ),
fs_request_get_bool( 'is_diagnostic_tracking_allowed', null )
$this->is_bundle_license_auto_activation_enabled()
$license = new FS_Plugin_License();
$license->secret_key = $license_key;
$this->maybe_activate_bundle_license( $license, $sites );
echo json_encode( $result );
* User change WP AJAX handler.
* @author Leo Fajardo (@leorw)
function _user_change_ajax_action() {
$this->_logger->entrance();
$this->check_ajax_referer( 'change_user' );
$new_email_address = trim( fs_request_get( 'email_address', '' ) );
$new_user_id = fs_request_get( 'user_id' );
if ( empty( $new_email_address ) && ! FS_User::is_valid_id( $new_user_id ) ) {
self::shoot_ajax_failure( fs_text_inline( 'Invalid new user ID or email address.', 'invalid-new-user-id-or-email', $this->get_slug() ) );
if ( ! empty( $new_email_address ) ) {
$params['user_email'] = $new_email_address;
$params['user_id'] = $new_user_id;
$installs_info_by_slug_map = $this->get_parent_and_addons_installs_info();
foreach ( $installs_info_by_slug_map as $slug => $install_info ) {
$install_ids[ $slug ] = $install_info['install']->id;
$params['install_ids'] = implode( ',', array_values( $install_ids ) );
$install = $this->get_api_site_scope()->call( $this->add_show_pending( '/' ), 'put', $params );
if ( FS_Api::is_api_error( $install ) ) {
if ( is_object( $install ) ) {
switch ( $install->error->code ) {
$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' .
$this->get_text_inline( 'Sorry, we could not complete the email update. Another user with the same email is already registered.', 'user-exist-message' ) . ' ' .
sprintf( $this->get_text_inline( 'If you would like to give up the ownership of the %s\'s account to %s click the Change Ownership button.', 'user-exist-message_ownership' ), $this->_module_type, '<b>' . $new_email_address . '</b>' ) .
'<a style="line-height: 40px;" href="%s"><button class="button button-primary">%s ➜</button></a>',
$this->get_account_url( 'change_owner', array(
'candidate_email' => $new_email_address
$this->get_text_inline( 'Change Ownership', 'change-ownership' )
$error = FS_Api::is_api_error_object( $install ) ?
$install->error->message :
var_export( $install->error, true );
self::shoot_ajax_failure( $error );
// If successful ownership change.
$this->get_user()->id != $install->user_id ||
! empty( $new_email_address )
$this->complete_ownership_change_by_license( $install->user_id, $install_ids );
self::shoot_ajax_success();
* @author Leo Fajardo (@leorw)
function starting_migration() {
if ( ! empty( $this->_storage->license_migration ) ) {
// Do not overwrite the data if already set.
$this->_storage->license_migration = array(
'start_timestamp' => time()
* @author Leo Fajardo (@leorw)
function is_migration() {
if ( $this->is_addon() ) {
return $this->get_parent_instance()->is_migration();
if ( empty( $this->_storage->license_migration ) ) {
if ( ! $this->_storage->license_migration['is_migrating'] ) {
// Return `true` if the migration is within 5 minutes from the starting time.
( time() - $this->_storage->license_migration['start_timestamp'] ) <= WP_FS__TIME_5_MIN_IN_SEC
* A helper method to activate migrated licenses. If the product is network activated and integrated, the method will network activate the license.
* @author Vova Feldman (@svovaf)
* @param string $license_key
* @param null|bool $is_marketing_allowed
* @param null|number $plugin_id
* @uses Freemius::activate_license()
function activate_migrated_license(
$is_marketing_allowed = null,
$this->_logger->entrance();
$result = $this->activate_license(
( empty( $sites ) && is_null( $blog_id ) && $this->is_network_active() ) ?
$this->get_sites_for_network_level_optin() :
// No need to show the sticky after license activation notice after migrating a license.
$this->_admin_notices->remove_sticky( 'plan_upgraded' );
* @author Leo Fajardo (@leorw)
function get_pricing_js_path() {
if ( ! isset( $this->_pricing_js_path ) ) {
$pricing_js_path = $this->apply_filters( 'freemius_pricing_js_path', '' );
if ( empty( $pricing_js_path ) ) {
global $fs_active_plugins;
foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) {
if ( $data->plugin_path == $this->get_plugin_basename() ) {
$plugin_or_theme_root_dir = ( $this->is_plugin() ? WP_PLUGIN_DIR : get_theme_root( get_stylesheet() ) );
$pricing_js_path = $plugin_or_theme_root_dir
// The basename will be `plugins`, `themes`, or the basename of a custom plugins or themes directory.
. str_replace( '../' . basename( $plugin_or_theme_root_dir ) . '/', '', $sdk_path )
. '/includes/freemius-pricing/freemius-pricing.js';
$this->_pricing_js_path = $pricing_js_path;
return $this->_pricing_js_path;
* @author Leo Fajardo (@leorw)
function should_use_external_pricing() {
if ( is_null( $this->_use_external_pricing ) ) {
$pricing_js_path = $this->get_pricing_js_path();
$this->_use_external_pricing = ( empty( $pricing_js_path ) || ! file_exists( $pricing_js_path ) );
return $this->_use_external_pricing;
* The implementation of this method was previously in `_activate_license_ajax_action()`.
* @author Vova Feldman (@svovaf)
* @since 2.0.0 When a super-admin that hasn't connected before is network activating a license and excluding some of the sites for the license activation, go over the unselected sites in the network and if a site is not connected, skipped, nor delegated, if it's a freemium product then just skip the connection for the site, if it's a premium only product, delegate the connection and license activation to the site admin (Vova Feldman @svovaf).
* @param string $license_key
* @param null|bool $is_marketing_allowed
* @param null|int $blog_id
* @param null|number $plugin_id
* @param null|number $license_owner_id
* @param bool|null $is_extensions_tracking_allowed
* @param bool|null $is_diagnostic_tracking_allowed Since 2.5.0.2 to allow license activation with minimal data footprint.