: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$this->do_action( 'after_init_plugin_pending_activations' );
if ( $this->is_registered() ) {
$this->do_action( 'after_init_addon_registered' );
} else if ( $this->is_anonymous() ) {
$this->do_action( 'after_init_addon_anonymous' );
} else if ( $this->is_pending_activation() ) {
$this->do_action( 'after_init_addon_pending_activations' );
* @author Leo Fajardo (@leorw)
private function should_use_freemius_updater_and_dialog() {
* Allow updater and dialog when the `fs_allow_updater_and_dialog` URL query param exists and has `true`
* value, or when the current page is not the "Add Plugins" page (/plugin-install.php) and the `action`
* URL query param doesn't exist or its value is not `install-plugin` so that there will be no conflicts
* with the .org plugins' functionalities (e.g. installation from the "Add Plugins" page and viewing
* plugin details from .org).
( true === fs_request_get_bool( 'fs_allow_updater_and_dialog' ) ) ||
! self::is_plugin_install_page() &&
// Disallow updater and dialog when installing a plugin, otherwise .org "add-on" plugins will be affected.
( 'install-plugin' !== fs_request_get( 'action' ) )
* @param string[] $permissions
* @param bool $is_enabled
* @param int|null $blog_id
* @return true|object `true` on success, API error object on failure.
private function update_site_permissions( array $permissions, $is_enabled, $blog_id = null ) {
$this->_logger->entrance();
'permissions' => implode( ',', $permissions ),
'is_enabled' => $is_enabled,
$current_blog_id = get_current_blog_id();
$is_blog_switched = false;
if ( is_numeric( $blog_id ) && $current_blog_id != $blog_id ) {
$is_blog_switched = $this->switch_to_blog( $blog_id );
$result = $this->api_site_call( '/permissions.json', 'put', $params );
if ( $is_blog_switched ) {
$this->switch_to_blog( $current_blog_id );
! $this->is_api_result_object( $result ) ||
! isset( $result->install_id )
$this->_logger->api_error( $result );
* @param string[] $permissions
* @param bool $is_enabled
* @param bool $has_site_delegated_connection
* @return true|object `true` on success, API error object on failure.
private function update_network_permissions(
&$has_site_delegated_connection
$this->_logger->entrance();
$install_id_2_blog_id = array();
$install_by_blog_id = $this->get_blog_install_map();
$has_site_delegated_connection = false;
foreach ( $install_by_blog_id as $blog_id => $install ) {
if ( $this->is_site_delegated_connection( $blog_id ) ) {
// Only update permissions of non-delegated installs.
$has_site_delegated_connection = true;
$install_id_2_blog_id[ $install->id ] = $blog_id;
if ( empty( $install_id_2_blog_id ) ) {
'permissions' => implode( ',', $permissions ),
'is_enabled' => $is_enabled,
'install_ids' => implode( ',', array_keys( $install_id_2_blog_id ) ),
$result = $this->get_current_or_network_user_api_scope()->call(
"/plugins/{$this->_module_id}/installs/permissions.json",
if ( ! $this->is_api_result_object( $result, 'installs_metadata' ) ) {
$this->_logger->api_error( $result );
private function get_api_error_message( $result ) {
$error_message = sprintf( $this->get_text_inline( 'There was an unexpected API error while processing your request. Please try again in a few minutes and if it still doesn\'t work, contact the %s\'s author with the following:',
'unexpected-api-error' ), $this->_module_type ) . ' ';
$this->is_api_error( $result ) &&
$code = empty( $result->error->code ) ? '' : " Code: {$result->error->code}";
$error_message .= "<b>{$result->error->message}{$code}</b>";
$error_message .= var_export( $result, true );
* @author Vova Feldman (@svovaf)
function _toggle_permission_tracking_callback() {
$this->_logger->entrance();
$this->check_ajax_referer( 'toggle_permission_tracking' );
if ( ! $this->is_registered( true ) ) {
self::shoot_ajax_failure( 'User never opted-in.' );
$is_enabled = fs_request_get_bool( 'is_enabled' );
$permissions = fs_request_get( 'permissions' );
if ( ! is_string( $permissions ) ) {
self::shoot_ajax_failure( 'The permissions param must be a string.' );
$permissions = explode( ',', $permissions );
$result = $this->toggle_permission_tracking( $permissions, $is_enabled );
if ( true !== $result ) {
self::shoot_ajax_failure( $this->get_api_error_message( $result ) );
self::shoot_ajax_success();
* @param string[] $permissions
* @param bool $is_enabled
* @param int|null $blog_id
* @return bool|mixed `true` if updated successfully or no update is needed.
private function toggle_permission_tracking( $permissions, $is_enabled, $blog_id = null ) {
if ( ! $this->is_registered( true ) ) {
// Check if permissions are already set as needed.
if ( FS_Permission_Manager::instance( $this )->are_permissions( $permissions, $is_enabled, $blog_id ) ) {
* When running on the network admin, there's no need to iterate through all the installs individually since network opt-in permissions are managed for ALL non-delegated installs through a single option (per permission) on the network-level storage.
$api_managed_permissions = array_intersect(
FS_Permission_Manager::get_api_managed_permission_ids()
in_array( FS_Permission_Manager::PERMISSION_ESSENTIALS, $permissions ) &&
! in_array( FS_Permission_Manager::PERMISSION_SITE, $permissions )
$api_managed_permissions[] = FS_Permission_Manager::PERMISSION_SITE;
if ( ! empty( $api_managed_permissions ) ) {
$has_site_delegated_connection = false;
! in_array( FS_Permission_Manager::PERMISSION_EXTENSIONS, $api_managed_permissions ) &&
false === FS_Permission_Manager::instance( $this )->is_extensions_tracking_allowed( $blog_id )
* If we are turning off a permission and the extensions permission is off too, enrich the permissions update request to also turn off extensions tracking, as currently when opting in with extensions tracking disabled the extensions tracking is off but the API isn't aware of it.
* @todo Remove this entire `if` after implementing granular opt-in that also sends the permissions to the API when opting in.
$api_managed_permissions[] = FS_Permission_Manager::PERMISSION_EXTENSIONS;
if ( is_null( $blog_id ) && fs_is_network_admin() ) {
$result = $this->update_network_permissions(
$api_managed_permissions,
$has_site_delegated_connection
$result = $this->update_site_permissions(
$api_managed_permissions,
if ( true !== $result ) {
if ( in_array( FS_Permission_Manager::PERMISSION_SITE, $api_managed_permissions ) ) {
$this->schedule_sync_cron();
$this->clear_sync_cron( ! $has_site_delegated_connection );
if ( in_array( FS_Permission_Manager::PERMISSION_USER, $api_managed_permissions ) ) {
$this->toggle_user_permission( $is_enabled, $blog_id );
$this->update_tracking_permissions(
* @param bool $is_enabled
* @param int|null $blog_id
private function toggle_user_permission( $is_enabled, $blog_id = null ) {
$network_or_blog_ids = is_numeric( $blog_id ) ?
$this->reset_anonymous_mode( $network_or_blog_ids );
$this->skip_connection( $network_or_blog_ids );
* Opt-in back into usage tracking.
* Note: This will only work if the user opted-in previously.
* 1. FALSE - If the user never opted-in.
* 2. TRUE - If successfully opted-in back to usage tracking.
* 3. object - API result on failure.
* @author Leo Fajardo (@leorw)
private function toggle_site_tracking( $is_enabled, $blog_id = null ) {
$this->_logger->entrance();
return $this->toggle_permission_tracking(
FS_Permission_Manager::instance( $this )->get_site_tracking_permission_names(),
* If user opted-in and later disabled usage-tracking,
* re-allow tracking for licensing and updates.
* @author Leo Fajardo (@leorw)
* @param bool $is_context_single_site
private function reconnect_locally( $is_context_single_site = false ) {
$this->_logger->entrance();
if ( ! $this->is_registered() ) {
if ( ! fs_is_network_admin() || $is_context_single_site ) {
if ( $this->is_tracking_prohibited() ) {
FS_Permission_Manager::instance( $this )->update_site_tracking( true );
$installs_map = $this->get_blog_install_map();
foreach ( $installs_map as $blog_id => $install ) {
if ( ! $this->is_tracking_allowed( $blog_id, $install ) ) {
FS_Permission_Manager::instance( $this )->update_site_tracking( true, $blog_id );
* Update permission tracking flags. When updating in a network context, in addition to updating the network-level flags, also update the permissions on the site-level for all non-delegated sites.
* @param string[] $permissions
* @param bool $is_enabled
* @param int|null $blog_id
private function update_tracking_permissions( $permissions, $is_enabled, $blog_id = null ) {
$permission_manager = FS_Permission_Manager::instance( $this );
$network_or_blog_ids = is_numeric( $blog_id ) ?
if ( true === $network_or_blog_ids ) {
// Update the permission for all non-delegated sub-sites.
$blog_ids = $this->get_non_delegated_blog_ids();
// Add the network-level to the array, to update the permission on the network-level storage.
array_unshift( $blog_ids, null );
if ( false === $network_or_blog_ids ) {
$network_or_blog_ids = null;
$blog_ids = is_array( $network_or_blog_ids ) ?
array( $network_or_blog_ids );
foreach ( $permissions as $permission ) {
$permission = trim( $permission );
$is_permission_supported = true;
foreach ( $blog_ids as $id ) {
$is_permission_supported = $permission_manager->update_permission_tracking_flag(
if ( ! $is_permission_supported ) {
$permission = 'no_match';
$result[ $permission ] = $is_enabled;
* Parse plugin's settings (as defined by the plugin dev).
* @author Vova Feldman (@svovaf)
* @param array $plugin_info
* @throws \Freemius_Exception
private function parse_settings( &$plugin_info ) {
$this->_logger->entrance();
$id = $this->get_numeric_option( $plugin_info, 'id', false );
$public_key = $this->get_option( $plugin_info, 'public_key', false );
$secret_key = $this->get_option( $plugin_info, 'secret_key', null );
$parent_id = $this->get_numeric_option( $plugin_info, 'parent_id', null );
$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
* @author Vova Feldman (@svovaf)
* @since 1.1.9 Try to pull secret key from external config.
if ( is_null( $secret_key ) && defined( "WP_FS__{$this->_slug}_SECRET_KEY" ) ) {
$secret_key = constant( "WP_FS__{$this->_slug}_SECRET_KEY" );
if ( isset( $plugin_info['parent'] ) ) {
$parent_id = $this->get_numeric_option( $plugin_info['parent'], 'id', null );
// $parent_slug = $this->get_option( $plugin_info['parent'], 'slug', null );
// $parent_public_key = $this->get_option( $plugin_info['parent'], 'public_key', null );
// $parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
throw new Freemius_Exception( array(
'type' => 'ParameterNotSet',
'message' => 'Plugin id parameter is not set.',
'code' => 'plugin_id_not_set',
if ( false === $public_key ) {
throw new Freemius_Exception( array(
'type' => 'ParameterNotSet',
'message' => 'Plugin public_key parameter is not set.',
'code' => 'plugin_public_key_not_set',
$plugin = ( $this->_plugin instanceof FS_Plugin ) ?
$premium_suffix = $this->get_option( $plugin_info, 'premium_suffix', '(Premium)' );
'type' => $this->get_option( $plugin_info, 'type', $this->_module_type ),
'public_key' => $public_key,
'premium_slug' => $this->get_option( $plugin_info, 'premium_slug', "{$this->_slug}-premium" ),
'parent_plugin_id' => $parent_id,
'version' => $this->get_plugin_version(),
'title' => $this->get_plugin_name( $premium_suffix ),
'file' => $this->_plugin_basename,
'is_premium' => $this->get_bool_option( $plugin_info, 'is_premium', true ),
'premium_suffix' => $premium_suffix,
'is_live' => $this->get_bool_option( $plugin_info, 'is_live', true ),
'affiliate_moderation' => $this->get_option( $plugin_info, 'has_affiliation' ),
'bundle_id' => $this->get_option( $plugin_info, 'bundle_id', null ),
'bundle_public_key' => $this->get_option( $plugin_info, 'bundle_public_key', null ),
'opt_in_moderation' => $this->get_option( $plugin_info, 'opt_in', null ),
if ( $plugin->is_updated() ) {
// Update plugin details.
$this->_plugin = FS_Plugin_Manager::instance( $this->_module_id )->store( $plugin );
// Set the secret key after storing the plugin, we don't want to store the key in the storage.
$this->_plugin->secret_key = $secret_key;
* If the product is network integrated and activated and the current view is in the network level Admin dashboard, if the product's network-level menu located differently from the sub-site level, then use the network menu details (when set).