: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
private function deactivate_premium_only_addon_without_license( $is_after_trial_cancel = false ) {
if ( ! $this->has_free_plan() &&
! $this->has_features_enabled_license() &&
! $this->_has_premium_license()
if ( $this->is_registered() ) {
// IF wrapper is turned off because activation_timestamp is currently only stored for plugins (not addons).
// if (empty($this->_storage->activation_timestamp) ||
// (WP_FS__SCRIPT_START_TIME - $this->_storage->activation_timestamp) > 30
* @todo When it's first fail, there's no reason to try and re-sync because the licenses were just synced after initial activation.
* Retry syncing the user add-on licenses.
// Try to activate premium license.
$this->_activate_license( true );
if ( ! $this->has_free_plan() &&
! $this->has_features_enabled_license() &&
! $this->_has_premium_license()
// @todo Check if deactivate plugins also call the deactivation hook.
$this->_parent->_admin_notices->add_sticky(
( $is_after_trial_cancel ?
$this->_parent->get_text_inline(
'%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.',
'addon-trial-cancelled-message'
$this->_parent->get_text_inline(
'%s is a premium only add-on. You have to purchase a license first before activating the plugin.',
'addon-no-license-message'
'<b>' . $this->_plugin->title . '</b>'
'<a href="%s" aria-label="%s" class="button button-primary" style="margin-left: 10px; vertical-align: middle;">%s ➜</a>',
$this->_parent->addon_url( $this->_slug ),
esc_attr( sprintf( $this->_parent->get_text_inline( 'More information about %s', 'more-information-about-x' ), $this->_plugin->title ) ),
$this->_parent->get_text_inline( 'Purchase License', 'purchase-license' )
'no_addon_license_' . $this->_slug,
( $is_after_trial_cancel ? '' : $this->_parent->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...' ),
( $is_after_trial_cancel ? 'success' : 'error' )
deactivate_plugins( array( $this->_plugin_basename ), true );
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
* Set Freemius into sandbox mode for debugging.
* @author Vova Feldman (@svovaf)
* @param string $secret_key
function init_sandbox( $secret_key ) {
$this->_plugin->secret_key = $secret_key;
// Update plugin details.
FS_Plugin_Manager::instance( $this->_module_id )->update( $this->_plugin, true );
* Check if running payments in sandbox mode.
* @author Vova Feldman (@svovaf)
function is_payments_sandbox() {
return ( ! $this->is_live() ) || isset( $this->_plugin->secret_key );
* Check if running test vs. live plugin.
* @author Vova Feldman (@svovaf)
return $this->_plugin->is_live;
* Check if super-admin skipped connection for all sites in the network.
* @author Vova Feldman (@svovaf)
function is_network_anonymous() {
if ( ! $this->_is_network_active ) {
$is_anonymous_ms = $this->_storage->get( 'is_anonymous_ms' );
if ( empty( $is_anonymous_ms ) ) {
return $is_anonymous_ms['is'];
* Check if super-admin opted-in for all sites in the network.
* @author Vova Feldman (@svovaf)
function is_network_connected() {
if ( ! $this->_is_network_active ) {
return $this->_storage->get( 'is_network_connected' );
* Check if the user skipped connecting the account with Freemius.
* @author Vova Feldman (@svovaf)
function is_anonymous() {
if ( ! isset( $this->_is_anonymous ) ) {
if ( $this->is_network_anonymous() ) {
$this->_is_anonymous = true;
} else if ( fs_is_network_admin() ) {
* When not-network-anonymous, yet, running in the network admin, consider as anonymous only when ALL non-delegated sites are set to anonymous.
$non_delegated_sites = $this->get_non_delegated_blog_ids();
foreach ( $non_delegated_sites as $blog_id ) {
$is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id );
if ( empty( $is_anonymous ) || false === $is_anonymous[ 'is' ] ) {
$this->_is_anonymous = false;
if ( false !== $this->_is_anonymous ) {
$this->_is_anonymous = true;
if ( ! isset( $this->_storage->is_anonymous ) ) {
$this->_is_anonymous = false;
} else if ( is_bool( $this->_storage->is_anonymous ) ) {
// For back compatibility, since the variable was boolean before.
$this->_is_anonymous = $this->_storage->is_anonymous;
// Upgrade stored data format to 1.1.3 format.
$this->set_anonymous_mode( $this->_storage->is_anonymous );
// Version 1.1.3 and later.
$this->_is_anonymous = $this->_storage->is_anonymous['is'];
return $this->_is_anonymous;
* Check if the user skipped the connection of a specified site.
* @author Vova Feldman (@svovaf)
function is_anonymous_site( $blog_id = 0 ) {
if ( $this->is_network_anonymous() ) {
$is_anonymous = $this->_storage->get( 'is_anonymous', false, $blog_id );
if ( empty( $is_anonymous ) ) {
return $is_anonymous['is'];
* Check if user connected his account and install pending email activation.
* @author Vova Feldman (@svovaf)
function is_pending_activation() {
return $this->_storage->get( 'is_pending_activation', false );
* @author Leo Fajardo (@leorw)
private function clear_pending_activation_mode() {
// Remove the pending activation sticky notice (if it still exists).
$this->_admin_notices->remove_sticky( 'activation_pending' );
// Clear the plugin's pending activation mode.
unset( $this->_storage->is_pending_activation );
* Check if plugin must be WordPress.org compliant.
function is_org_repo_compliant() {
return $this->_is_org_compliant;
#--------------------------------------------------------------------------------
#--------------------------------------------------------------------------------
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function get_cron_data( $name ) {
$this->_logger->entrance( $name );
return $this->_storage->get( "{$name}_cron", null );
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function clear_cron_data( $name ) {
$this->_logger->entrance( $name );
$this->_storage->remove( "{$name}_cron" );
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param int $cron_blog_id The cron executing blog ID.
private function set_cron_data( $name, $cron_blog_id = 0 ) {
$this->_logger->entrance( $name );
$this->_storage->store( "{$name}_cron", (object) array(
'version' => $this->get_plugin_version(),
'blog_id' => $cron_blog_id,
'sdk_version' => $this->version,
'timestamp' => WP_FS__SCRIPT_START_TIME,
* Get the cron's executing blog ID.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function get_cron_blog_id( $name ) {
$this->_logger->entrance( $name );
if ( ! is_multisite() ) {
$cron_data = $this->get_cron_data( $name );
return ( is_object( $cron_data ) && is_numeric( $cron_data->blog_id ) ) ?
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function is_cron_on( $name ) {
$this->_logger->entrance( $name );
$cron_data = $this->get_cron_data( $name );
return ( ! is_null( $cron_data ) && true === $cron_data->on );
* Unix timestamp for previous cron execution or false if never executed.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function cron_last_execution( $name ) {
$this->_logger->entrance( $name );
return $this->_storage->get( "{$name}_timestamp" );
* Set cron execution time to now.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
private function set_cron_execution_timestamp( $name ) {
$this->_logger->entrance( $name );
$this->_storage->store( "{$name}_timestamp", time() );
* Sets the keepalive time to now.
* @author Leo Fajardo (@leorw)
* @param bool|null $use_network_level_storage
private function set_keepalive_timestamp( $use_network_level_storage = null ) {
$this->_logger->entrance();
$this->_storage->store( 'keepalive_timestamp', time(), $use_network_level_storage );
* Check if cron was executed in the last $period of seconds.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param int $period In seconds
private function is_cron_executed( $name, $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
$this->_logger->entrance( $name );
$last_execution = $this->cron_last_execution( $name );
if ( ! is_numeric( $last_execution ) ) {
return ( $last_execution > ( WP_FS__SCRIPT_START_TIME - $period ) );
* WP Cron is executed on a site level. When running in a multisite network environment
* with the network integration activated, for optimization reasons, we are consolidating
* the installs data sync cron to be executed only from a single site.
* @author Vova Feldman (@svovaf)
* @param int $except_blog_id Target any except the excluded blog ID.
private function get_cron_target_blog_id( $except_blog_id = 0 ) {
if ( ! is_multisite() ) {
if ( $this->_is_network_active ) {
$network_install_blog_id = $this->_storage->network_install_blog_id;
is_numeric( $network_install_blog_id ) &&
$except_blog_id != $network_install_blog_id &&
self::is_site_active( $network_install_blog_id )
// Try to run cron from the main network blog.
$install = $this->get_install_by_blog_id( $network_install_blog_id );
$this->is_tracking_allowed( $network_install_blog_id, $install )
return $network_install_blog_id;
// Get first opted-in blog ID with active tracking.
$installs = $this->get_blog_install_map();
foreach ( $installs as $blog_id => $install ) {
if ( $except_blog_id != $blog_id &&
self::is_site_active( $blog_id ) &&
$this->is_tracking_allowed( $blog_id, $install )
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param string $action_tag Callback action tag.
* @param bool $is_network_clear If set to TRUE, clear sync cron even if there are installs that are still connected.
private function clear_cron( $name, $action_tag = '', $is_network_clear = false ) {
$this->_logger->entrance( $name );
if ( ! $this->is_cron_on( $name ) ) {
if ( ! $is_network_clear && $this->_is_network_active ) {
$installs = $this->get_blog_install_map();
foreach ( $installs as $blog_id => $install ) {
if ( $this->is_tracking_allowed( $blog_id, $install ) ) {