: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( isset( $install->plan ) && is_object( $install->plan ) ) {
if ( isset( $install->plan->id ) && ! empty( $install->plan->id ) ) {
$install->plan_id = self::_decrypt( $install->plan->id );
$installs[ $module_slug ] = clone $install;
self::set_account_option_by_module(
* @author Vova Feldman (@svovaf)
* @param string $plugin_prev_version
* @param string $plugin_version
function _after_version_update( $plugin_prev_version, $plugin_version ) {
if ( $this->is_theme() ) {
// Expire the cache of the previous tabs since the theme may
$this->_cache->expire( 'tabs' );
$this->_cache->expire( 'tabs_stylesheets' );
* A special migration logic for the $_accounts, executed for all the plugins in the system:
* - Moves some data to the network level storage.
* - If the plugin's connection was skipped for all sites, set the plugin as if it was network skipped.
* - If the plugin's connection was ignored for all sites, don't do anything in terms of the network connection.
* - If the plugin was connected to all sites by the same super-admin, set the plugin as if was network opted-in for all sites.
* - If there's at least one site that was connected by a super-admin, find the "main super-admin" (the one that installed the majority of the plugin installs) and set the plugin as if was network activated with the main super-admin, set all the sites that were skipped or opted-in with a different user to delegated mode. Then, prompt the currently logged super-admin to choose what to do with the ignored sites.
* - If there are any sites in the network which the connection decision was not yet taken for, set this plugin into network activation mode so a super-admin can choose what to do with the rest of the sites.
* @author Vova Feldman (@svovaf)
private static function migrate_accounts_to_network() {
$sites = self::get_sites();
$sites_count = count( $sites );
$connection_status = array();
foreach ( $sites as $site ) {
$blog_id = self::get_site_blog_id( $site );
self::$_accounts->migrate_to_network( $blog_id );
* Build a list of all Freemius powered plugins slugs.
$id_slug_type_path_map = self::$_accounts->get_option( 'id_slug_type_path_map', array(), $blog_id );
foreach ( $id_slug_type_path_map as $module_id => $data ) {
if ( WP_FS__MODULE_TYPE_PLUGIN === $data['type'] ) {
$plugin_slugs[ $data['slug'] ] = true;
$installs = self::get_account_option( 'sites', WP_FS__MODULE_TYPE_PLUGIN, $blog_id );
if ( is_array( $installs ) ) {
foreach ( $installs as $slug => $install ) {
if ( ! isset( $connection_status[ $slug ] ) ) {
$connection_status[ $slug ] = array();
if ( is_object( $install ) &&
FS_Site::is_valid_id( $install->id ) &&
FS_User::is_valid_id( $install->user_id )
$connection_status[ $slug ][ $blog_id ] = $install->user_id;
foreach ( $plugin_slugs as $slug => $true ) {
if ( ! isset( $connection_status[ $slug ] ) ) {
$connection_status[ $slug ] = array();
foreach ( $sites as $site ) {
$blog_id = self::get_site_blog_id( $site );
if ( isset( $connection_status[ $slug ][ $blog_id ] ) ) {
$storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug );
$is_anonymous = $storage->get( 'is_anonymous', null, $blog_id );
if ( ! is_null( $is_anonymous ) ) {
// Since 1.1.3 is_anonymous is an array.
if ( is_array( $is_anonymous ) && isset( $is_anonymous['is'] ) ) {
$is_anonymous = $is_anonymous['is'];
if ( is_bool( $is_anonymous ) && true === $is_anonymous ) {
$connection_status[ $slug ][ $blog_id ] = 'skipped';
if ( ! isset( $connection_status[ $slug ][ $blog_id ] ) ) {
$connection_status[ $slug ][ $blog_id ] = 'ignored';
foreach ( $connection_status as $slug => $blogs_status ) {
$opted_in_users = array();
$opted_in_super_admins = array();
$storage = FS_Storage::instance( WP_FS__MODULE_TYPE_PLUGIN, $slug );
foreach ( $blogs_status as $blog_id => $status_or_user_id ) {
if ( 'skipped' === $status_or_user_id ) {
} else if ( 'ignored' === $status_or_user_id ) {
} else if ( FS_User::is_valid_id( $status_or_user_id ) ) {
if ( ! isset( $opted_in_users[ $status_or_user_id ] ) ) {
$opted_in_users[ $status_or_user_id ] = array();
$opted_in_users[ $status_or_user_id ][] = $blog_id;
if ( isset( $super_admins[ $status_or_user_id ] ) ||
self::is_super_admin( $status_or_user_id )
// Cache super-admin data.
$super_admins[ $status_or_user_id ] = true;
// Remember opted-in super-admins for the plugin.
$opted_in_super_admins[ $status_or_user_id ] = true;
$main_super_admin_user_id = null;
if ( $sites_count == $skips ) {
// All sites were skipped -> network skip by copying the anonymous mode from any of the sites.
$storage->is_anonymous_ms = $storage->is_anonymous;
} else if ( $sites_count == $ignores ) {
// Don't do anything, still in activation mode.
} else if ( 0 < count( $opted_in_super_admins ) ) {
// Find the super-admin with the majority of installs.
$max_installs_by_super_admin = 0;
foreach ( $opted_in_super_admins as $user_id => $true ) {
$installs_count = count( $opted_in_users[ $user_id ] );
if ( $installs_count > $max_installs_by_super_admin ) {
$max_installs_by_super_admin = $installs_count;
$main_super_admin_user_id = $user_id;
if ( $sites_count == $connections && 1 == count( $opted_in_super_admins ) ) {
// Super-admin opted-in for all sites in the network.
$storage->is_network_connected = true;
$storage->network_user_id = $main_super_admin_user_id;
$storage->network_install_blog_id = ( $sites_count == $connections ) ?
// Since all sites are opted-in, associating with the main site.
// Associating with the 1st found opted-in site.
$opted_in_users[ $main_super_admin_user_id ][0];
* Make sure we migrate the plan ID of the network install, otherwise, if after the migration
* the 1st page that will be loaded is the network level WP Admin and $storage->network_install_blog_id
* is different than the main site of the network, the $this->_site will not be set since the plan_id
$storage->migrate_to_network();
self::migrate_install_plan_to_plan_id( $storage, $storage->network_install_blog_id );
// At least one opt-in. All the opt-in were created by a non-super-admin.
// All sites were opted-in or skipped, all by non-super-admin. So delegate all.
$storage->store( 'is_delegated_connection', true, true );
* Delegate all sites that were:
* 1) Opted-in by a user that is NOT the main-super-admin.
* 2) Skipped and non of the sites was opted-in by a super-admin. If any site was opted-in by a super-admin, there will be a main-super-admin, and we consider the skip as if it was done by that user.
foreach ( $blogs_status as $blog_id => $status_or_user_id ) {
if ( $status_or_user_id == $main_super_admin_user_id ) {
if ( FS_User::is_valid_id( $status_or_user_id ) ||
( 'skipped' === $status_or_user_id && is_null( $main_super_admin_user_id ) )
$storage->store( 'is_delegated_connection', true, $blog_id );
if ( ( $connections + $skips > 0 ) ) {
* If admin already opted-in or skipped in any of the network sites, and also
* have sites which the connection decision was not yet taken, set this plugin
* into network activation mode so the super-admin can choose what to do with
self::set_network_upgrade_mode( $storage );
* Set a module into network upgrade mode.
* @author Vova Feldman (@svovaf)
* @param \FS_Storage $storage
private static function set_network_upgrade_mode( FS_Storage $storage ) {
return $storage->is_network_activation = true;
* Will return true after upgrading to the SDK with the network level integration,
* when the super-admin involvement is required regarding the rest of the sites.
* @author Vova Feldman (@svovaf)
function is_network_upgrade_mode() {
return $this->_storage->get( 'is_network_activation' );
* Clear flag after the upgrade mode completion.
* @author Vova Feldman (@svovaf)
* @return bool True if network activation was on and now completed.
private function network_upgrade_mode_completed() {
if ( fs_is_network_admin() && $this->is_network_upgrade_mode() ) {
$this->_storage->remove( 'is_network_activation' );
* This action is connected to the 'plugins_loaded' hook and helps to determine
* if this is a new plugin installation or a plugin update.
* There are 3 different use-cases:
* 1) New plugin installation right with Freemius:
* 1.1 _activate_plugin_event_hook() will be executed first
* 1.2 Since $this->_storage->is_plugin_new_install is not set,
* and $this->_storage->plugin_last_version is not set,
* $this->_storage->is_plugin_new_install will be set to TRUE.
* 1.3 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
* be already set to TRUE.
* 2) Plugin update, didn't have Freemius before, and now have the SDK:
* 2.1 _activate_plugin_event_hook() will not be executed, because
* the activation hook do NOT fires on updates since WP 3.1.
* 2.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install will
* be empty, therefore, it will be set to FALSE.
* 3) Plugin update, had Freemius in prev version as well:
* 3.1 _version_updates_handler() will be executed 1st, since FS was installed
* before, $this->_storage->plugin_last_version will NOT be empty,
* therefore, $this->_storage->is_plugin_new_install will be set to FALSE.
* 3.2 When _plugins_loaded() will be executed, $this->_storage->is_plugin_new_install is
* already set, therefore, it will not be modified.
* Use-case #3 is backward compatible, #3.1 will be executed since 1.0.9.
* The only fallback of this mechanism is if an admin updates a plugin based on use-case #2,
* and then, the next immediate PageView is the plugin's main settings page, it will not
* show the opt-in right away. The reason it will happen is because Freemius execution
* will be turned off till the plugin is fully loaded at least once
* (till $this->_storage->was_plugin_loaded is TRUE).
* @author Vova Feldman (@svovaf)
function _plugins_loaded() {
// Update flag that plugin was loaded with Freemius at least once.
$this->_storage->was_plugin_loaded = true;
* Bug fix - only set to false when it's a plugin, due to the
* execution sequence of the theme hooks and our methods, if
* this will be set for themes, Freemius will always assume
* @author Vova Feldman (@svovaf)
if ( $this->is_plugin() &&
! isset( $this->_storage->is_plugin_new_install )
$this->_storage->is_plugin_new_install = (
! is_plugin_active( $this->_plugin_basename ) &&
empty( $this->_storage->plugin_last_version )
function _run_garbage_collector() {
if ( true !== fs_get_optional_constant( 'WP_FS__ENABLE_GARBAGE_COLLECTOR', true ) ) {
if ( ! $this->is_user_in_admin() ) {
require_once WP_FS__DIR_INCLUDES . '/class-fs-lock.php';
$lock = new FS_Lock( 'garbage_collection' );
if ( $lock->is_locked() ) {
$lock->lock( WP_FS__TIME_24_HOURS_IN_SEC );
FS_Garbage_Collector::instance()->clean();
* Opens the support forum subemenu item in a new browser page.
* @author Vova Feldman (@svovaf)
static function _open_support_forum_in_new_page() {
<script type="text/javascript">
$('.fs-submenu-item.wp-support-forum').parent().attr( { target: '_blank', rel: 'noopener noreferrer' } );
* @author Vova Feldman (@svovaf)
private function register_constructor_hooks() {
$this->_logger->entrance();
add_action( 'admin_init', array( &$this, '_hook_action_links_and_register_account_hooks' ) );
if ( $this->is_plugin() ) {
if ( self::is_plugin_install_page() && true !== fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) {
* Unless the `fs_allow_updater_and_dialog` URL param exists and its value is `true`, make
* Freemius-related updates unavailable on the "Add Plugins" admin page (/plugin-install.php)
* so that they won't interfere with the .org plugins' functionalities on that page (e.g.
* updating of a .org plugin).
add_filter( 'site_transient_update_plugins', array( 'Freemius', '_remove_fs_updates_from_plugin_install_page' ), 10, 2 );
} else if ( self::is_plugins_page() || self::is_updates_page() ) {
* On the "Plugins" and "Updates" admin pages, if there are premium or non–org-compliant plugins, modify their details dialog URLs (add a Freemius-specific param) so that the SDK can determine if the plugin information dialog should show information from Freemius.
* @author Leo Fajardo (@leorw)
add_action( 'admin_footer', array( 'Freemius', '_prepend_fs_allow_updater_and_dialog_flag_url_param' ) );
$plugin_dir = dirname( $this->_plugin_dir_path ) . '/';
* Hook to both free and premium version activations to support
* auto deactivation on the other version activation.
register_activation_hook(
$plugin_dir . $this->_free_plugin_basename,
array( &$this, '_activate_plugin_event_hook' )
register_activation_hook(
$plugin_dir . $this->premium_plugin_basename(),
array( &$this, '_activate_plugin_event_hook' )
add_action( 'after_switch_theme', array( &$this, '_activate_theme_event_hook' ), 10, 2 );
add_action( 'admin_footer', array( &$this, '_style_premium_theme' ) );
* Part of the mechanism to identify new plugin install vs. plugin update.
* @author Vova Feldman (@svovaf)
if ( empty( $this->_storage->was_plugin_loaded ) ) {
* During the plugin activation (not theme), 'plugins_loaded' will be already executed
* when the logic gets here since the activation logic first add the activate plugins,
* then triggers 'plugins_loaded', and only then include the code of the plugin that
* is activated. Which means that _plugins_loaded() will NOT be executed during the
* plugin activation, and that IS intentional.
* @author Vova Feldman (@svovaf)
if ( $this->is_plugin() &&
$this->is_activation_mode( false ) &&
0 == did_action( 'plugins_loaded' )
add_action( 'plugins_loaded', array( &$this, '_plugins_loaded' ) );
// If was activated before, then it was already loaded before.
$this->_plugins_loaded();
add_action( 'plugins_loaded', array( &$this, '_run_garbage_collector' ) );
if ( ! self::is_ajax() ) {
if ( ! $this->is_addon() ) {
add_action( 'init', array( &$this, '_add_default_submenu_items' ), WP_FS__LOWEST_PRIORITY );
if ( $this->_storage->handle_gdpr_admin_notice ) {
add_action( 'init', array( &$this, '_maybe_show_gdpr_admin_notice' ) );
add_action( 'init', array( &$this, '_maybe_add_gdpr_optin_ajax_handler') );
add_action( 'init', array( &$this, '_maybe_add_pricing_ajax_handler' ) );
if ( $this->is_plugin() ) {
if ( version_compare( $GLOBALS['wp_version'], '5.1', '<' ) ) {
add_action( 'wpmu_new_blog', array( $this, '_after_new_blog_callback' ), 10, 6 );
add_action( 'wp_initialize_site', array( $this, '_after_wp_initialize_site_callback' ), 11, 2 );
register_deactivation_hook( $this->_plugin_main_file_path, array( &$this, '_deactivate_plugin_hook' ) );