: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @copyright Copyright (c) 2015, Freemius, Inc.
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
* @link https://github.com/easydigitaldownloads/EDD-License-handler/blob/master/EDD_SL_Plugin_Updater.php
if ( ! defined( 'ABSPATH' ) ) {
class FS_Plugin_Updater {
private $_update_details;
private $_translation_updates;
private static $_upgrade_basename = null;
#--------------------------------------------------------------------------------
#--------------------------------------------------------------------------------
* @var FS_Plugin_Updater[]
private static $_INSTANCES = array();
* @param Freemius $freemius
* @return FS_Plugin_Updater
static function instance( Freemius $freemius ) {
$key = $freemius->get_id();
if ( ! isset( self::$_INSTANCES[ $key ] ) ) {
self::$_INSTANCES[ $key ] = new self( $freemius );
return self::$_INSTANCES[ $key ];
private function __construct( Freemius $freemius ) {
$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
* Initiate required filters.
* @author Vova Feldman (@svovaf)
private function filters() {
// Override request for plugin information
add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
$this->add_transient_filters();
* If user has the premium plugin's code but do NOT have an active license,
* encourage him to upgrade by showing that there's a new release, but instead
* of showing an update link, show upgrade link to the pricing page.
add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
'catch_plugin_update_row'
add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
'edit_and_echo_plugin_update_row'
if ( ! $this->_fs->has_any_active_valid_license() ) {
add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
if ( ! WP_FS__IS_PRODUCTION_MODE ) {
add_filter( 'http_request_host_is_external', array(
'http_request_host_is_external_filter'
if ( $this->_fs->is_premium() ) {
if ( ! $this->is_correct_folder_name() ) {
add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 );
add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
if ( ! $this->_fs->has_any_active_valid_license() ) {
add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 );
* @author Leo Fajardo (@leorw)
function catch_plugin_information_dialog_contents() {
'plugin-information' !== fs_request_get( 'tab', false ) ||
$this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false )
add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
* @author Leo Fajardo (@leorw)
* @param string $hook_suffix
function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
'plugin-information' !== fs_request_get( 'tab', false ) ||
$this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false )
$license = $this->_fs->_get_license();
$subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ?
$this->_fs->_get_subscription( $license->id ) :
$contents = ob_get_clean();
$install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' );
if ( false === $install_or_update_button_id_attribute_pos ) {
$install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
if ( false !== $install_or_update_button_id_attribute_pos ) {
$install_or_update_button_start_pos = strrpos(
substr( $contents, 0, $install_or_update_button_id_attribute_pos ),
$install_or_update_button_end_pos = ( strpos( $contents, '</a>', $install_or_update_button_id_attribute_pos ) + strlen( '</a>' ) );
* The part of the contents without the update button.
* @author Leo Fajardo (@leorw)
$modified_contents = substr( $contents, 0, $install_or_update_button_start_pos );
$install_or_update_button = substr( $contents, $install_or_update_button_start_pos, ( $install_or_update_button_end_pos - $install_or_update_button_start_pos ) );
* Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
* the text will be "Renew license" and will link to the checkout page with the license's billing cycle
* and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
$install_or_update_button = preg_replace(
'/(\<a.+)(id="plugin_(install|update)_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
$this->_fs->checkout_url(
is_object( $subscription ) ?
( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
array( 'licenses' => $license->quota )
fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
$this->_fs->pricing_url(),
fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
$install_or_update_button
* Append the modified button.
* @author Leo Fajardo (@leorw)
$modified_contents .= $install_or_update_button;
* Append the remaining part of the contents after the update button.
* @author Leo Fajardo (@leorw)
$modified_contents .= substr( $contents, $install_or_update_button_end_pos );
$contents = $modified_contents;
* @author Vova Feldman (@svovaf)
private function add_transient_filters() {
$this->_fs->is_premium() &&
$this->_fs->is_registered() &&
! FS_Permission_Manager::instance( $this->_fs )->is_essentials_tracking_allowed()
$this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
add_filter( 'pre_set_site_transient_update_plugins', array(
'pre_set_site_transient_update_plugins_filter'
add_filter( 'pre_set_site_transient_update_themes', array(
'pre_set_site_transient_update_plugins_filter'
* @author Vova Feldman (@svovaf)
private function remove_transient_filters() {
remove_filter( 'pre_set_site_transient_update_plugins', array(
'pre_set_site_transient_update_plugins_filter'
remove_filter( 'pre_set_site_transient_update_themes', array(
'pre_set_site_transient_update_plugins_filter'
* Capture plugin update row by turning output buffering.
* @author Vova Feldman (@svovaf)
function catch_plugin_update_row() {
* Overrides default update message format with "renew your license" message.
* @author Vova Feldman (@svovaf)
* @param array $plugin_data
function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
$plugin_update_row = ob_get_clean();
$current = get_site_transient( 'update_plugins' );
if ( ! isset( $current->response[ $file ] ) ) {
$r = $current->response[ $file ];
$has_beta_update = $this->_fs->has_beta_update();
if ( $this->_fs->has_any_active_valid_license() ) {
if ( $has_beta_update ) {
* Turn the "new version" text into "new Beta version".
* There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
* There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
* @author Leo Fajardo (@leorw)
$plugin_update_row = preg_replace(
'/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
$this->_fs->get_plugin_title()
* Turn the "new version" text into a link that opens the plugin information dialog when clicked and
* make the "View version x details" text link to the checkout page instead of opening the plugin
* information dialog when clicked.
* There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
* There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
* There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
* @author Leo Fajardo (@leorw)
$plugin_update_row = preg_replace(
'/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
$this->_fs->get_plugin_title()
$this->_fs->version_upgrade_checkout_link( $r->new_version ) .
$this->_fs->is_plugin() &&
isset( $r->upgrade_notice ) &&
strlen( trim( $r->upgrade_notice ) ) > 0
$slug = $this->_fs->get_slug();
$upgrade_notice_html = sprintf(
'<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
$this->_fs->get_module_type(),
fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
esc_html( $r->upgrade_notice )
$plugin_update_row = str_replace( '</div>', '</div>' . $upgrade_notice_html, $plugin_update_row );
* @author Leo Fajardo (@leorw)
* @param array $prepared_themes
function change_theme_update_info_html( $prepared_themes ) {
$theme_basename = $this->_fs->get_plugin_basename();
if ( ! isset( $prepared_themes[ $theme_basename ] ) ) {
$themes_update = get_site_transient( 'update_themes' );
if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
empty( $themes_update->response[ $theme_basename ]['package'] )
$prepared_themes[ $theme_basename ]['update'] = preg_replace(
'/(\<p.+>)(.+)(\<a.+\<a.+)\.(.+\<\/p\>)/is',
'$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) .
$prepared_themes[ $theme_basename ]['update']
// Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page.
$prepared_themes[ $theme_basename ]['hasPackage'] = false;
* Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
* During development mode we want to be able updating plugin versions via our localhost repository. This
* filter white-list all domains including "api.freemius".
* @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
* @author Vova Feldman (@svovaf)
function http_request_host_is_external_filter( $allow, $host, $url ) {
return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
* Check for Updates at the defined API endpoint and modify the update array.
* This function dives into the update api just when WordPress creates its update array,
* then adds a custom API call and injects the custom plugin data retrieved from the API.
* It is reassembled from parts of the native WordPress plugin update code.
* See wp-includes/update.php line 121 for the original wp_update_plugins() function.
* @author Vova Feldman (@svovaf)
* @param object $transient_data Update array build by WordPress.
* @return object Modified update array with custom plugin data.
function pre_set_site_transient_update_plugins_filter( $transient_data ) {
$this->_logger->entrance();
* @author Leo Fajardo (@leorw)
$module_type = $this->_fs->get_module_type() . 's';
* Ensure that we don't mix plugins update info with themes update info.
* @author Leo Fajardo (@leorw)
if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
if ( empty( $transient_data ) ||