: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param number|bool $module_id
* @param FS_Plugin_License[] $licenses
private function _store_licenses( $store = true, $module_id = false, $licenses = array() ) {
$this->_logger->entrance();
$all_licenses = self::get_all_licenses();
if ( ! FS_Plugin::is_valid_id( $module_id ) ) {
$module_id = $this->_module_id;
$user_licenses = is_array( $this->_licenses ) ?
if ( empty( $user_licenses ) ) {
// If the context user doesn't have any license, don't update the licenses collection.
$new_user_licenses_map = array();
foreach ( $user_licenses as $user_license ) {
$new_user_licenses_map[ $user_license->id ] = $user_license;
self::store_user_id_license_ids_map( array_keys( $new_user_licenses_map ), $this->_module_id, $this->_user->id );
$licenses_to_update_count = count( $new_user_licenses_map );
foreach ( $all_licenses[ $module_id ] as $key => $license ) {
if ( 0 === $licenses_to_update_count ) {
if ( isset( $new_user_licenses_map[ $license->id ] ) ) {
$all_licenses[ $module_id ][ $key ] = $new_user_licenses_map[ $license->id ];
unset( $new_user_licenses_map[ $license->id ] );
$licenses_to_update_count --;
if ( ! empty( $new_user_licenses_map ) ) {
$all_licenses[ $module_id ] = array_merge( array_values( $new_user_licenses_map ), $all_licenses[ $module_id ] );
$licenses = $all_licenses[ $module_id ];
if ( ! isset( $all_licenses[ $module_id ] ) ) {
$all_licenses[ $module_id ] = array();
$all_licenses[ $module_id ] = $licenses;
self::$_accounts->set_option( 'all_licenses', $all_licenses, $store );
* Update user information.
* @author Vova Feldman (@svovaf)
* @param bool $store Flush to Database if true.
private function _store_user( $store = true ) {
$this->_logger->entrance();
if ( empty( $this->_user->id ) ) {
$this->_logger->error( "Empty user ID, can't store user." );
$users = self::get_all_users();
$users[ $this->_user->id ] = $this->_user;
self::$_accounts->set_option( 'users', $users, $store );
* Update new updates information.
* @author Vova Feldman (@svovaf)
* @param FS_Plugin_Tag|null $update
* @param bool $store Flush to Database if true.
* @param bool|number $plugin_id
private function _store_update( $update, $store = true, $plugin_id = false ) {
$this->_logger->entrance();
if ( $update instanceof FS_Plugin_Tag ) {
$update->updated = time();
if ( ! is_numeric( $plugin_id ) ) {
$plugin_id = $this->_plugin->id;
$updates = self::get_all_updates();
$updates[ $plugin_id ] = $update;
self::$_accounts->set_option( 'updates', $updates, $store );
* Update new updates information.
* @author Vova Feldman (@svovaf)
* @param FS_Plugin[] $plugin_addons
* @param bool $store Flush to Database if true.
private function _store_addons( $plugin_addons, $store = true ) {
$this->_logger->entrance();
$addons = self::get_all_addons();
$addons[ $this->_plugin->id ] = $plugin_addons;
self::$_accounts->set_option( 'addons', $addons, $store );
* Delete plugin's associated add-ons.
* @author Vova Feldman (@svovaf)
private function _delete_account_addons( $store = true ) {
$all_addons = self::get_all_account_addons();
if ( ! isset( $all_addons[ $this->_plugin->id ] ) ) {
unset( $all_addons[ $this->_plugin->id ] );
self::$_accounts->set_option( 'account_addons', $all_addons, $store );
* Update account add-ons list.
* @author Vova Feldman (@svovaf)
* @param FS_Plugin[] $addons
* @param bool $store Flush to Database if true.
private function _store_account_addons( $addons, $store = true ) {
$this->_logger->entrance();
$all_addons = self::get_all_account_addons();
$all_addons[ $this->_plugin->id ] = $addons;
self::$_accounts->set_option( 'account_addons', $all_addons, $store );
* Purges the cache for the valid user licenses API call so that when the `Account` or `Add-Ons` page is loaded,
* the valid user licenses will be fetched again and the account add-ons may be updated.
* @author Leo Fajardo (@leorw)
private function purge_valid_user_licenses_cache() {
if ( ! $this->is_registered() ) {
$this->get_api_user_scope()->purge_cache( $this->get_valid_user_licenses_endpoint() );
* @author Leo Fajardo (@leorw)
* @param array $all_licenses
* @param number|null $site_license_id
* @param bool $include_parent_licenses
private function get_foreign_licenses_info( $all_licenses, $site_license_id = null, $include_parent_licenses = false ) {
$foreign_licenses = array(
'license_keys' => array()
$parent_license_ids_map = array();
foreach ( $all_licenses as $license ) {
if ( $license->user_id == $this->_user->id || $license->id == $site_license_id ) {
$foreign_licenses['ids'][] = $license->id;
$foreign_licenses['license_keys'][] = $license->secret_key;
$include_parent_licenses &&
is_object( $this->_license ) &&
FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) &&
! isset( $parent_license_ids_map[ $this->_license->parent_license_id ] )
* Include the parent license's info only if it has not been included before since child licenses
* can have the same parent license.
$foreign_licenses['ids'][] = $this->_license->parent_license_id;
$foreign_licenses['license_keys'][] = $license->secret_key;
$parent_license_ids_map[ $this->_license->parent_license_id ] = true;
if ( empty( $foreign_licenses['ids'] ) ) {
$foreign_licenses = array();
return $foreign_licenses;
* @author Leo Fajardo (@leorw)
private function get_valid_user_licenses_endpoint() {
$user_licenses_endpoint = '/licenses.json?type=active' .
( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? '&is_enriched=true' : '' );
$foreign_licenses = $this->get_foreign_licenses_info( self::get_all_licenses( $this->_module_id ), null, true );
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'] ) )
$user_licenses_endpoint = add_query_arg( $foreign_licenses, $user_licenses_endpoint );
return $user_licenses_endpoint;
* Fetches active licenses that are enriched with product type if there's a context `bundle_id` and bundle
* licenses enriched with product IDs if there are any. From the licenses, the `get_updated_account_addons`
* method filters out non–add-on product IDs and stores the add-on IDs.
* @author Leo Fajardo (@leorw)
* @return stdClass[] array
private function fetch_valid_user_licenses() {
$this->_logger->entrance();
$result = $this->get_api_user_scope()->get( $this->get_valid_user_licenses_endpoint() );
if ( ! $this->is_api_result_object( $result, 'licenses' ) ||
! is_array( $result->licenses )
return $result->licenses;
* @author Leo Fajardo (@leorw)
* @return number[] Account add-on IDs.
function get_updated_account_addons() {
$addons = $this->get_addons();
if ( empty( $addons ) ) {
$account_addons = $this->get_account_addons();
if ( ! is_array( $account_addons ) ) {
$account_addons = array();
$user_licenses = $this->is_registered() ?
$this->fetch_valid_user_licenses() :
if ( empty( $user_licenses ) ) {
foreach ( $addons as $addon ) {
$addon_ids[] = $addon->id;
$license_product_ids = array();
foreach ( $user_licenses as $license ) {
if ( isset( $license->plugin_type ) && 'bundle' === $license->plugin_type ) {
$license_product_ids = array_merge( $license_product_ids, $license->products );
$license_product_ids[] = $license->plugin_id;
// Filter out non–add-on IDs.
$new_account_addons = array_intersect( $addon_ids, $license_product_ids );
if ( count( $new_account_addons ) !== count( $account_addons ) ) {
$this->_store_account_addons( array_unique( $new_account_addons ) );
return $new_account_addons;
* Store account params in the Database.
* @author Vova Feldman (@svovaf)
* @param null|int $blog_id Since 2.0.0
private function _store_account( $blog_id = null ) {
$this->_logger->entrance();
$this->_store_site( false, $blog_id );
$this->_store_user( false );
$this->_store_plans( false );
$this->_store_licenses( false );
self::$_accounts->store( $blog_id );
* Sync user's information.
* @author Vova Feldman (@svovaf)
private function _handle_account_user_sync() {
$this->_logger->entrance();
$api = $this->get_api_user_scope();
// Get user's information.
$user = $api->get( '/', true );
if ( isset( $user->id ) ) {
$this->_user->first = $user->first;
$this->_user->last = $user->last;
$this->_user->email = $user->email;
$is_menu_item_account_visible = $this->is_submenu_item_visible( 'account' );
if ( $user->is_verified &&
( ! isset( $this->_user->is_verified ) || false === $this->_user->is_verified )
$this->_user->is_verified = true;
$this->do_action( 'account_email_verified', $user->email );
$this->_admin_notices->add(
$this->get_text_inline( 'Your email has been successfully verified - you are AWESOME!', 'email-verified-message' ),
$this->get_text_x_inline( 'Right on', 'a positive response', 'right-on' ) . '!',
// Make admin sticky if account menu item is invisible,
// since the page will be auto redirected to the plugin's
// main settings page, and the non-sticky message
! $is_menu_item_account_visible,
// Flush user details to DB.
$this->do_action( 'after_account_user_sync', $user );
* If account menu item is hidden, redirect to plugin's main settings page.
* @author Vova Feldman (@svovaf)
* @link https://github.com/Freemius/wordpress-sdk/issues/6
if ( ! $is_menu_item_account_visible ) {
fs_redirect( $this->_get_admin_page_url() );
* @author Vova Feldman (@svovaf)
* @param number|bool $license_id
* @return FS_Subscription|object|bool
private function _fetch_site_license_subscription( $license_id = false ) {
$this->_logger->entrance();
$api = $this->get_api_site_scope();
if ( ! is_numeric( $license_id ) ) {
$license_id = FS_Plugin_License::is_valid_id( $this->_license->parent_license_id ) ?
$this->_license->parent_license_id :
$result = $api->get( "/licenses/{$license_id}/subscriptions.json", true );
return ! isset( $result->error ) ?
( ( is_array( $result->subscriptions ) && 0 < count( $result->subscriptions ) ) ?
new FS_Subscription( $result->subscriptions[0] ) :
* @author Vova Feldman (@svovaf)
* @param number|bool $plan_id
* @return FS_Plugin_Plan|object
private function _fetch_site_plan( $plan_id = false ) {
$this->_logger->entrance();
$api = $this->get_api_site_scope();
if ( ! is_numeric( $plan_id ) ) {
$plan_id = $this->_site->plan_id;
$plan = $api->get( "/plans/{$plan_id}.json", true );
return ! isset( $plan->error ) ? new FS_Plugin_Plan( $plan ) : $plan;
* @author Vova Feldman (@svovaf)
* @return FS_Plugin_Plan[]|object
private function _fetch_plugin_plans() {
$this->_logger->entrance();
$api = $this->get_current_or_network_user_api_scope();
* @since 1.2.3 When running in DEV mode, retrieve pending plans as well.
$result = $api->get( $this->add_show_pending( "/plugins/{$this->_module_id}/plans.json" ), true );
if ( $this->is_api_result_object( $result, 'plans' ) && is_array( $result->plans ) ) {
for ( $i = 0, $len = count( $result->plans ); $i < $len; $i ++ ) {
$result->plans[ $i ] = new FS_Plugin_Plan( $result->plans[ $i ] );
$result = $result->plans;
* @author Vova Feldman (@svovaf)
* @return \FS_Plugin_Plan|object
private function fetch_plan_by_id( $plan_id ) {
$this->_logger->entrance();
$api = $this->get_current_or_network_user_api_scope();
$result = $api->get( "/plugins/{$this->_module_id}/plans/{$plan_id}.json", true );