: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( is_object( $install ) ) {
$blog_2_install_map[ $blog_id ] = $install;
if ( ( count( $blog_2_install_map ) + count( $site_ids ) ) > $license->left() ) {
$user = $this->get_current_or_network_user();
if ( ! empty( $blog_2_install_map ) ) {
$result = $this->activate_license_on_many_installs( $user, $license->secret_key, $blog_2_install_map );
if ( true !== $result ) {
if ( ! empty( $site_ids ) ) {
$this->activate_license_on_many_sites( $user, $license->secret_key, $site_ids );
* Tries to activate a bundle license for all supported products if the current product is activated with a bundle license. This is called after activating an available license (not via the license activation dialog but by clicking on a license activation button) for a product via its "Account" page.
* @author Leo Fajardo (@leorw)
* @param FS_Plugin_License $license
private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) {
if ( ! is_object( $license ) && $this->has_active_valid_license() ) {
$license = $this->_license;
if ( ! is_object( $license ) ) {
$parent_license = ( ! empty( $license->products ) ) ?
$this->get_active_parent_license( $license->secret_key );
if ( is_object( $parent_license ) ) {
$this->activate_bundle_license( $parent_license, $sites, $blog_id );
* Try to activate a bundle license for all the bundle products installed on the site.
* (1) If a child product install already has a license, the bundle license won't be activated.
* (2) On multi-site networks, if the attempt to activate the bundle license is triggered from the network admin, the bundle license activation will only work for non-delegated sites and only if none of them is associated with a license. Even if one of the sites has the product installed with a license key, skip the bundle license activation for the product.
* (3) On multi-site networks, if the attempt to activate the bundle license is triggered from a site-level admin, only activate the license if the product is site-level activated or delegated, and the product installation is not yet associated with a license.
* @author Leo Fajardo (@leorw)
* @param FS_Plugin_License $license
* @param int $current_blog_id
private function activate_bundle_license( $license, $sites = array(), $current_blog_id = 0 ) {
$is_network_admin = fs_is_network_admin();
$installs_by_blog_map = array();
$site_info_by_blog_map = array();
* Try to activate the license for all supported products.
foreach ( $license->products as $product_id ) {
$fs = self::get_instance_by_id( $product_id );
if ( ! is_object( $fs ) ) {
if ( ! $fs->has_paid_plan() ) {
! FS_Plan_Manager::instance()->has_paid_plan( $fs->_plans )
* The parent product can be free-only but can have its `has_paid_plan` flag set to `true` when
* there is a context bundle.
if ( $current_blog_id > 0 ) {
$fs->switch_to_blog( $current_blog_id );
if ( $fs->has_active_valid_license() ) {
if ( ! $is_network_admin || $current_blog_id > 0 ) {
if ( $fs->is_network_active() && ! $fs->is_delegated_connection( $current_blog_id ) ) {
// Do not try to activate the license in the site level if the product is network active and the connection was not delegated.
if ( ! $fs->is_network_active() ) {
// Do not try to activate the license in the network level if the product is not network active.
if ( $fs->is_network_delegated_connection() ) {
// Do not try to activate the license in the network level if the activation has been delegated to site admins.
$has_install_with_license = false;
// Collection of sites that have an install entity that is not activated with a license or non-delegated sites that have no install entity, or both types of site.
$filtered_sites = array();
$all_sites = self::get_sites();
foreach ( $all_sites as $site ) {
$sites[] = array( 'blog_id' => self::get_site_blog_id( $site ) );
// Populate the map here to avoid calling `$fs->get_site_info( $site );` in the other `for` loop below.
foreach ( $sites as $site ) {
if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) {
$site_info_by_blog_map[ $site['blog_id'] ] = $site;
foreach ( $sites as $site ) {
if ( ! isset( $site['blog_id'] ) || ! is_numeric( $site['blog_id'] ) ) {
$blog_id = $site['blog_id'];
if ( ! isset( $installs_by_blog_map[ $blog_id ] ) ) {
$installs_by_blog_map[ $blog_id ] = self::get_all_sites( $fs->get_module_type(), $blog_id );
$installs = $installs_by_blog_map[ $blog_id ];
if ( isset( $installs[ $fs->get_slug() ] ) ) {
$install = $installs[ $fs->get_slug() ];
! FS_Site::is_valid_id( $install->id ) ||
! FS_User::is_valid_id( $install->user_id ) ||
! FS_Plugin_Plan::is_valid_id( $install->plan_id )
FS_Plugin_License::is_valid_id( $install->license_id )
$has_install_with_license = true;
if ( $fs->is_site_delegated_connection( $blog_id ) ) {
// Site activation delegated, don't activate bundle license on the site in the network admin.
if ( ! isset( $site_info_by_blog_map[ $blog_id ] ) ) {
$site_info_by_blog_map[ $blog_id ] = $fs->get_site_info( $site );
$filtered_sites[] = $site_info_by_blog_map[ $blog_id ];
if ( $has_install_with_license || empty( $filtered_sites ) ) {
// Do not try to activate the license at the network level if there's any install with a license or there's no site to activate the license on.
$sites = $filtered_sites;
$fs->activate_migrated_license(
( $current_blog_id > 0 ? $current_blog_id : null )
* Returns a parent license that can be activated for the context product.
* @author Leo Fajardo (@leorw)
* @param string|null $license_key
* @return FS_Plugin_License
function get_active_parent_license( $license_key = null, $flush = true ) {
$parent_licenses_endpoint = "/plugins/{$this->get_id()}/parent_licenses.json?filter=activatable";
if ( $this->is_addon() ) {
$parent_instance = $this->get_parent_instance();
if ( is_object( $parent_instance ) && $parent_instance->is_registered() ) {
$foreign_licenses = $fs->get_foreign_licenses_info(
self::get_all_licenses( $this->get_parent_id() )
if ( ! empty ( $foreign_licenses ) ) {
$foreign_licenses = array(
// Prefix with `+` to tell the server to include foreign licenses in the licenses collection.
'ids' => ( urlencode( '+' ) . implode( ',', $foreign_licenses['ids'] ) ),
'license_keys' => implode( ',', array_map( 'urlencode', $foreign_licenses['license_keys'] ) )
$parent_licenses_endpoint = add_query_arg( $foreign_licenses, $parent_licenses_endpoint );
$result = $fs->get_current_or_network_user_api_scope()->get( $parent_licenses_endpoint, $flush );
! $this->is_api_result_object( $result, 'licenses' ) ||
! is_array( $result->licenses ) ||
empty( $result->licenses )
if ( empty( $license_key ) ) {
$parent_license = $result->licenses[0];
foreach ( $result->licenses as $license ) {
if ( $license_key === $license->secret_key ) {
$parent_license = $license;
if ( ! is_null( $parent_license ) ) {
$parent_license = new FS_Plugin_License( $parent_license );
* @author Leo Fajardo (@leorw)
function get_sites_for_network_level_optin() {
$all_sites = self::get_sites();
foreach ( $all_sites as $site ) {
$blog_id = self::get_site_blog_id( $site );
if ( ! $this->is_site_delegated_connection( $blog_id ) &&
! $this->is_installed_on_site( $blog_id )
$sites[] = $this->get_site_info( $site );
* @author Vova Feldman (@svovaf)
* @param bool $check_user Enforce checking if user have plugins activation privileges.
function delete_account_event( $check_user = true ) {
$this->_logger->entrance( 'slug = ' . $this->_slug );
if ( $check_user && ! $this->is_user_admin() ) {
$this->do_action( 'before_account_delete' );
// Clear all admin notices.
$this->_admin_notices->clear_all_sticky( false );
$this->_delete_site( false );
$delete_network_common_data = true;
if ( $this->_is_network_active ) {
$installs = $this->get_blog_install_map();
// Don't delete common network data unless no other installs left.
$delete_network_common_data = empty( $installs );
if ( $delete_network_common_data ) {
$this->_delete_plans( false );
$this->_delete_licenses( false );
// Delete add-ons related to plugin's account.
$this->_delete_account_addons( false );
// @todo Delete plans and licenses of add-ons.
self::$_accounts->store();
* Clear crons must be executed before clearing all storage.
* Otherwise, the cron will not be cleared.
if ( $delete_network_common_data ) {
$this->clear_sync_cron();
$this->clear_install_sync_cron();
// Clear all storage data.
$this->_storage->clear_all( true, array(
'is_delegated_connection',
$this->get_api_site_scope()->call( '/', 'delete' );
$this->do_action( 'after_account_delete' );
* Delete network level account.
* @author Vova Feldman (@svovaf)
* @param bool $check_user Enforce checking if user have plugins activation privileges.
function delete_network_account_event( $check_user = true ) {
$this->_logger->entrance( 'slug = ' . $this->_slug );
if ( $check_user && ! $this->is_user_admin() ) {
$this->do_action( 'before_network_account_delete' );
// Clear all admin notices.
$this->_admin_notices->clear_all_sticky();
$this->_delete_plans( false, false );
$this->_delete_licenses( false );
// Delete add-ons related to plugin's account.
$this->_delete_account_addons( false );
// @todo Delete plans and licenses of add-ons.
self::$_accounts->store( true );
* Clear crons must be executed before clearing all storage.
* Otherwise, the cron will not be cleared.
$this->clear_sync_cron( true );
$this->clear_install_sync_cron( true );
$sites = self::get_sites();
foreach ( $sites as $site ) {
$blog_id = self::get_site_blog_id( $site );
if ( $this->is_site_delegated_connection( $blog_id ) ) {
$install_id = $this->_delete_site( true, $blog_id );
// Clear all storage data.
$this->_storage->clear_all( true, array( 'connectivity_test' ), $blog_id );
if ( FS_Site::is_valid_id( $install_id ) ) {
$install_ids[] = $install_id;
switch_to_blog( $blog_id );
$this->do_action( 'after_account_delete' );
$this->_storage->clear_all( true, array(
if ( ! empty( $install_ids ) ) {
$result = $this->get_current_or_network_user_api_scope()->call( "/plugins/{$this->_module_id}/installs.json?ids=" . implode( ',', $install_ids ), 'delete' );
$this->do_action( 'after_network_account_delete' );
* Plugin deactivation hook.
* @author Vova Feldman (@svovaf)
function _deactivate_plugin_hook() {
$this->_logger->entrance( 'slug = ' . $this->_slug );
if ( ! $this->is_user_admin() ) {
$is_network_deactivation = fs_is_network_admin();
$storage_keys_for_removal = array();
$this->_admin_notices->clear_all_sticky();
$storage_keys_for_removal[] = 'sticky_optin_added';
if ( isset( $this->_storage->sticky_optin_added ) ) {
unset( $this->_storage->sticky_optin_added );
if ( ! isset( $this->_storage->is_plugin_new_install ) ) {
// Remember that plugin was already installed.
$this->_storage->is_plugin_new_install = false;
// Hook to plugin uninstall.
register_uninstall_hook( $this->_plugin_main_file_path, array( 'Freemius', '_uninstall_plugin_hook' ) );
$this->clear_module_main_file_cache();
$this->clear_sync_cron( $this->_is_network_active );
$this->clear_install_sync_cron();
if ( $this->is_registered() ) {
if ( $this->is_premium() && ! $this->has_active_valid_license() ) {
FS_Plugin_Updater::instance( $this )->delete_update_data();
if ( $is_network_deactivation ) {
// Send deactivation event.
$this->sync_installs( array(
// Send deactivation event.
$this->sync_install( array(