: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( false !== $plan_name ) {
$plan = $this->get_plan_by_name( $plan_name );
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'Plan %s do not exist, therefore, can\'t start a trial.', 'trial-plan-x-not-exist' ), $plan_name ),
if ( ! $plan->has_trial() ) {
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'Plan %s does not support a trial period.', 'plan-x-no-trial' ), $plan_name ),
if ( ! $this->has_trial_plan() ) {
// None of the plans have a trial.
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'None of the %s\'s plans supports a trial period.', 'no-trials' ), $this->_module_type ),
$plans_with_trial = FS_Plan_Manager::instance()->get_trial_plans( $this->_plans );
$plan = $plans_with_trial[0];
$api = $this->get_api_site_scope();
$plan = $api->call( "plans/{$plan->id}/trials.json", 'post' );
if ( ! $this->is_api_result_entity( $plan ) ) {
// Some API error while trying to start the trial.
$this->_admin_notices->add(
$this->get_api_error_message( $plan ),
return $this->is_trial();
* @author Vova Feldman (@svovaf)
private function _cancel_trial() {
$this->_logger->entrance();
if ( ! $this->is_trial() ) {
'error' => (object) array(
'message' => $this->get_text_inline( 'It looks like you are not in trial mode anymore so there\'s nothing to cancel :)', 'trial-cancel-no-trial-message' )
$trial_plan = $this->get_trial_plan();
$api = $this->get_api_site_scope();
$site = $api->call( 'trials.json', 'delete' );
$trial_cancelled = false;
if ( $this->is_api_result_entity( $site ) ) {
$prev_trial_ends = $this->_site->trial_ends;
if ( $this->is_paid_trial() ) {
$this->_license->expiration = $site->trial_ends;
$this->_license->is_cancelled = true;
$this->_update_site_license( $this->_license );
$this->_store_licenses();
// Clear subscription reference.
$this->_sync_site_subscription( null );
$this->_site = new FS_Site( $site );
$trial_cancelled = ( $prev_trial_ends != $site->trial_ends );
// @todo handle different error cases.
if ( ! $trial_cancelled ) {
'error' => (object) array(
'message' => $this->get_text_inline( 'Seems like we are having some temporary issue with your trial cancellation. Please try again in few minutes.', 'trial-cancel-failure-message' )
// Remove previous sticky messages about upgrade or trial (if exist).
$this->_admin_notices->remove_sticky( array(
if ( ! $this->is_addon() ||
! $this->deactivate_premium_only_addon_without_license( true )
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'Your %s free trial was successfully cancelled.', 'trial-cancel-message' ), $trial_plan->title )
* @author Vova Feldman (@svovaf)
* @param bool|number $plugin_id
private function _is_addon_id( $plugin_id ) {
return is_numeric( $plugin_id ) && ( $this->get_id() != $plugin_id );
* Check if user eligible to download premium version updates.
* @author Vova Feldman (@svovaf)
private function _can_download_premium() {
return $this->has_any_active_valid_license() ||
( $this->is_trial() && ! $this->get_trial_plan()->is_free() );
* @author Vova Feldman (@svovaf)
* @param bool|number $addon_id
* @param string $type "json" or "zip"
private function _get_latest_version_endpoint( $addon_id = false, $type = 'json' ) {
$is_addon = $this->_is_addon_id( $addon_id );
$is_premium = ( $this->is_premium() || $this->_can_download_premium() );
} else if ( $this->is_addon_activated( $addon_id ) ) {
$fs_addon = self::get_instance_by_id( $addon_id );
$is_premium = ( $fs_addon->is_premium() || $fs_addon->_can_download_premium() );
// If add-on, then append add-on ID.
$endpoint = ( $is_addon ? "/addons/$addon_id" : '' ) .
'/updates/latest.' . $type;
// If add-on and not yet activated, try to fetch based on server licensing.
if ( is_bool( $is_premium ) ) {
$endpoint = add_query_arg( 'is_premium', json_encode( $is_premium ), $endpoint );
if ( $this->has_secret_key() ) {
$endpoint = add_query_arg( 'type', 'all', $endpoint );
} else if ( is_object( $this->_site ) && $this->_site->is_beta() ) {
$endpoint = add_query_arg( 'type', 'beta', $endpoint );
* @author Vova Feldman (@svovaf)
* @param bool|number $addon_id
* @param bool $flush Since 1.1.7.3
* @param int $expiration Since 1.2.2.7
* @param bool|string $newer_than Since 2.2.1
* @param bool|string $fetch_readme Since 2.2.1
* @return object|false Plugin latest tag info.
function _fetch_latest_version(
$expiration = WP_FS__TIME_24_HOURS_IN_SEC,
$this->_logger->entrance();
if ( $this->is_unresolved_clone( true ) ) {
$switch_to_blog_id = null;
* @since 1.1.7.3 Check for plugin updates from Freemius only if opted-in.
* @since 1.1.7.4 Also check updates for add-ons.
( ! $this->is_registered() || ! FS_Permission_Manager::instance( $this )->is_essentials_tracking_allowed() ) &&
! $this->_is_addon_id( $addon_id )
if ( ! is_multisite() ) {
$installs_map = $this->get_blog_install_map();
foreach ( $installs_map as $blog_id => $install ) {
if ( ! FS_Permission_Manager::instance( $this )->is_essentials_tracking_allowed( $blog_id ) ) {
if ( $install->is_trial() ) {
$switch_to_blog_id = $blog_id;
if ( FS_Plugin_License::is_valid_id( $install->license_id ) ) {
$license = $this->get_license_by_id( $install->license_id );
if ( is_object( $license ) && $license->is_features_enabled() ) {
$switch_to_blog_id = $blog_id;
if ( is_null( $switch_to_blog_id ) ) {
$current_blog_id = is_numeric( $switch_to_blog_id ) ?
if ( is_numeric( $switch_to_blog_id ) ) {
$this->switch_to_blog( $switch_to_blog_id );
$latest_version_endpoint = $this->_get_latest_version_endpoint( $addon_id, 'json' );
if ( ! empty( $newer_than ) ) {
$latest_version_endpoint = add_query_arg( 'newer_than', $newer_than, $latest_version_endpoint );
if ( true === $fetch_readme ) {
$latest_version_endpoint = add_query_arg( 'readme', 'true', $latest_version_endpoint );
$tag = $this->get_api_site_or_plugin_scope()->get(
$latest_version_endpoint,
if ( is_numeric( $switch_to_blog_id ) ) {
$this->switch_to_blog( $current_blog_id );
$latest_version = ( is_object( $tag ) && isset( $tag->version ) ) ? $tag->version : 'couldn\'t get';
$this->_logger->departure( 'Latest version ' . $latest_version );
return ( is_object( $tag ) && isset( $tag->version ) ) ? $tag : false;
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
* Download latest plugin version, based on plan.
* Not like _download_latest(), this will redirect the page
* to secure download url to prevent dual download (from FS to WP server,
* and then from WP server to the client / browser).
* @author Vova Feldman (@svovaf)
* @param bool|number $plugin_id
private function download_latest_directly( $plugin_id = false ) {
$this->_logger->entrance();
wp_redirect( $this->get_latest_download_api_url( $plugin_id ) );
* Get latest plugin FS API download URL.
* @author Vova Feldman (@svovaf)
* @param bool|number $plugin_id
private function get_latest_download_api_url( $plugin_id = false ) {
$this->_logger->entrance();
$download_api_url = $this->get_api_site_scope()->get_signed_url(
$this->_get_latest_version_endpoint( $plugin_id, 'zip' )
return str_replace( 'http:', 'https:', $download_api_url );
* Get payment invoice URL.
* @author Vova Feldman (@svovaf)
* @param bool|number $payment_id
function _get_invoice_api_url( $payment_id = false ) {
$this->_logger->entrance();
$url = $this->get_api_user_scope()->get_signed_url(
"/payments/{$payment_id}/invoice.pdf"
if ( ! fs_starts_with( $url, 'https://' ) ) {
// Always use HTTPS for invoices.
$url = 'https' . substr( $url, 4 );
* Get latest plugin download link.
* @author Vova Feldman (@svovaf)
* @param bool|number $plugin_id
private function get_latest_download_link( $label, $plugin_id = false ) {
'<a target="_blank" rel="noopener" href="%s">%s</a>',
$this->_get_latest_download_local_url( $plugin_id ),
* Get latest plugin download local URL.
* @author Vova Feldman (@svovaf)
* @param bool|number $plugin_id
function _get_latest_download_local_url( $plugin_id = false ) {
// Add timestamp to protect from caching.
$params = array( 'ts' => WP_FS__SCRIPT_START_TIME );
if ( ! empty( $plugin_id ) ) {
$params['plugin_id'] = $plugin_id;
} else if ( $this->is_addon() ) {
$params['plugin_id'] = $this->get_id();
$fs = $this->is_addon() ?
$this->get_parent_instance() :
return $this->apply_filters( 'download_latest_url', $fs->get_account_url( 'download_latest', $params ) );
#endregion Download Plugin ------------------------------------------------------------------
* @author Vova Feldman (@svovaf)
* @param bool $background Hints the method if it's a background updates check. If false, it means that
* was initiated by the admin.
* @param bool|number $plugin_id
* @param bool $flush Since 1.1.7.3
* @param int $expiration Since 1.2.2.7
* @param bool|string $newer_than Since 2.2.1
private function check_updates(
$expiration = WP_FS__TIME_24_HOURS_IN_SEC,
$this->_logger->entrance();
// Check if there's a newer version for download.
$new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than );
if ( is_object( $new_version ) ) {
$update = new FS_Plugin_Tag( $new_version );
$this->_admin_notices->add(
/* translators: %s: Numeric version number (e.g. '2.1.9' */
$this->get_text_inline( 'Version %s was released.', 'version-x-released' ) . ' ' . $this->get_text_inline( 'Please download %s.', 'please-download-x' ),
'<a href="%s" target="_blank" rel="noopener">%s</a>',
$this->get_account_url( 'download_latest' ),
/* translators: %s: plan name (e.g. latest "Professional" version) */
$this->get_text_inline( 'the latest %s version here', 'latest-x-version' ),
$this->get_text_inline( 'New', 'new' ) . '!'
} else if ( false === $new_version && ! $background ) {
$this->_admin_notices->add(
$this->get_text_inline( 'Seems like you got the latest release.', 'you-have-latest' ),
$this->get_text_inline( 'You are all good!', 'you-are-good' )
$this->_store_update( $update, true, $plugin_id );
* @author Vova Feldman (@svovaf)
* @param bool $flush Since 1.1.7.3 add 24 hour cache by default.