: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$current_section = $default_sections[ $section_id ];
// Replace the current section's title if a custom section title exists.
if ( isset( $custom_section['title'] ) ) {
$current_section['title'] = $custom_section['title'];
// Insert new rows under the current section or replace the default rows.
if ( isset( $custom_section['rows'] ) && is_array( $custom_section['rows'] ) && ! empty( $custom_section['rows'] ) ) {
foreach ( $custom_section['rows'] as $row_id => $row ) {
$current_section['rows'][ $row_id ] = $row;
$default_sections[ $section_id ] = $current_section;
$vars = array( 'sections' => $default_sections );
$message = fs_get_template( 'email.php', $vars );
// Set the type of email to HTML.
$headers[] = 'Content-type: text/html; charset=UTF-8';
$header_string = implode( "\r\n", $headers );
* Generates the data for the sections of the email content.
* @author Leo Fajardo (@leorw)
private function get_email_sections() {
// Retrieve the current user's information so that we can get the user's email, first name, and last name below.
$current_user = self::_get_current_wp_user();
// Retrieve the cURL version information so that we can get the version number below.
$curl_version_information = curl_version();
$active_plugin = self::get_active_plugins();
// Generate the list of active plugins separated by new line.
$active_plugin_string = '';
foreach ( $active_plugin as $plugin ) {
$active_plugin_string .= sprintf(
'<a href="%s">%s</a> [v%s]<br>',
$server_ip = WP_FS__REMOTE_ADDR;
// Add PHP info for deeper investigation.
$php_info = ob_get_clean();
$api_domain = substr( FS_API__ADDRESS, strpos( FS_API__ADDRESS, ':' ) + 3 );
// Generate the default email sections.
'fs_version' => array( 'FS Version', $this->version ),
'curl_version' => array( 'cURL Version', $curl_version_information['version'] )
'title' => ucfirst( $this->get_module_type() ),
'name' => array( 'Name', $this->get_plugin_name() ),
'version' => array( 'Version', $this->get_plugin_version() )
'title' => 'API Subdomain',
function_exists( 'dns_get_record' ) ?
var_export( dns_get_record( $api_domain, DNS_CNAME ), true ) :
'dns_get_record() disabled/blocked'
function_exists( 'gethostbyname' ) ?
gethostbyname( $api_domain ) :
'gethostbyname() disabled/blocked'
'unique_id' => array( 'Unique ID', $this->get_anonymous_id() ),
'address' => array( 'Address', site_url() ),
( ! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' )
'Hosting Company' => fs_request_has( 'hosting_company' ) ?
fs_request_get( 'hosting_company' ) :
'<a href="http://www.projecthoneypot.org/ip_' . $server_ip . '">' . $server_ip . '</a>'
'email' => array( 'Email', $current_user->user_email ),
'first' => array( 'First', $current_user->user_firstname ),
'last' => array( 'Last', $current_user->user_lastname )
'active_plugins' => array( 'Active Plugins', $active_plugin_string )
'info' => array( $php_info )
// Allow the sections to be modified by other code.
$sections = $this->apply_filters( 'email_template_sections', $sections );
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
* Init plugin's Freemius instance.
* @author Vova Feldman (@svovaf)
* @param string $public_key
* @param bool $is_premium
function init( $id, $public_key, $is_live = true, $is_premium = true ) {
$this->_logger->entrance();
$this->dynamic_init( array(
'public_key' => $public_key,
'is_premium' => $is_premium,
* Dynamic initiator, originally created to support initiation
* with parent_id for add-ons.
* @author Vova Feldman (@svovaf)
* @param array $plugin_info
* @throws Freemius_Exception
function dynamic_init( array $plugin_info ) {
$this->_logger->entrance();
$this->parse_settings( $plugin_info );
$this->register_after_settings_parse_hooks();
* If anonymous but there's already a user entity and the user's site is associated with a valid license or trial period, update the anonymous mode accordingly.
* @todo Remove this entire `if` block after several releases as starting from this version, the anonymous mode will already be updated accordingly after a purchase.
if ( $this->is_anonymous() ) {
$is_network_level = ( $this->_is_network_active && fs_is_network_admin() );
FS_Site::is_valid_id( $this->_storage->network_install_blog_id )
if ( $this->is_paying_or_trial() ) {
$this->reset_anonymous_mode( $is_network_level );
$network = get_network();
if ( is_object( $network ) ) {
$main_blog_id = $network->site_id;
$first_install = $this->get_install_by_blog_id( $main_blog_id );
if ( is_object( $first_install ) ) {
$this->_storage->network_install_blog_id = $main_blog_id;
$this->_storage->network_user_id = $first_install->user_id;
if ( $this->should_stop_execution() ) {
if ( ! $this->is_registered() ) {
if ( $this->is_anonymous() ) {
// If user skipped, no need to test connectivity.
$this->_has_api_connection = true;
if ( false === $this->has_api_connectivity() ) {
if ( $this->_anonymous_mode ) {
// Simulate anonymous mode.
$this->_is_anonymous = true;
* This should be executed even if Freemius is off for the core module,
* otherwise, the add-ons dialog box won't work properly. This is especially
* relevant when the developer decided to turn FS off for existing users.
* @author Vova Feldman (@svovaf)
if ( $this->is_user_in_admin() &&
'plugin-information' === fs_request_get( 'tab', false ) &&
$this->should_use_freemius_updater_and_dialog() &&
( $this->is_addon() && $this->get_slug() == fs_request_get( 'plugin', false ) ) ||
( $this->has_addons() && $this->get_id() == fs_request_get( 'parent_plugin_id', false ) )
require_once WP_FS__DIR_INCLUDES . '/fs-plugin-info-dialog.php';
new FS_Plugin_Info_Dialog( $this->is_addon() ? $this->get_parent_instance() : $this );
// Check if Freemius is on for the current plugin.
// This MUST be executed after all the plugin variables has been loaded.
if ( ! $this->is_registered() && ! $this->is_on() ) {
if ( $this->has_api_connectivity() ) {
$this->hook_callback_to_sync_cron();
} else if ( $this->is_user_in_admin() ) {
* Schedule daily data sync cron if:
* 1. User opted-in (for tracking).
* 2. If skipped, but later upgraded (opted-in via upgrade).
* @author Vova Feldman (@svovaf)
if ( $this->is_registered() && $this->is_tracking_allowed() ) {
$this->maybe_schedule_sync_cron();
* Check if requested for manual blocking background sync.
if ( fs_request_has( 'background_sync' ) ) {
self::require_pluggable_essentials();
self::wp_cookie_constants();
$this->run_manual_sync();
if ( $this->is_registered() ) {
FS_Clone_Manager::instance()->maybe_resolve_new_subsite_install_automatically( $this );
$this->hook_callback_to_install_sync();
if ( $this->is_addon() ) {
if ( $this->is_parent_plugin_installed() ) {
$this->_parent = self::get_instance_by_id( $this->_plugin->parent_plugin_id );
// Get parent plugin reference.
$this->_parent_plugin = $this->_parent->get_plugin();
if ( $this->is_user_in_admin() ) {
if ( $this->is_registered() && fs_request_has( 'purchase_completed' ) ) {
$this->_admin_notices->add_sticky(
/* translators: %s: License type (e.g. you have a professional license) */
$this->get_text_inline( 'You have purchased a %s license.', 'you-have-x-license' ),
fs_request_get( 'purchased_plan' )
$this->get_text_inline(" The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box.", 'post-purchase-email-sent-message' ),
$this->get_module_label( true ),
( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? "products' " : '' ),
( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? 's' : '' ),
fs_request_get( 'purchase_email' )
$this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
if ( $this->is_addon() ) {
if ( ! $this->is_parent_plugin_installed() ) {
$parent_name = $this->get_option( $plugin_info, 'parent_name', null );
if ( isset( $plugin_info['parent'] ) ) {
$parent_name = $this->get_option( $plugin_info['parent'], 'name', null );
$this->_admin_notices->add(
( ! empty( $parent_name ) ?
sprintf( $this->get_text_x_inline( '%s cannot run without %s.', 'addonX cannot run without pluginY', 'addon-x-cannot-run-without-y' ), $this->get_plugin_name(), $parent_name ) :
sprintf( $this->get_text_x_inline( '%s cannot run without the plugin.', 'addonX cannot run...', 'addon-x-cannot-run-without-parent' ), $this->get_plugin_name() )
$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
$is_network_admin = fs_is_network_admin();
if ( ! $this->_parent->is_registered() && $this->is_registered() ) {
// If add-on activated and parent not, automatically install parent for the user.
$this->activate_parent_account( $this->_parent );
$this->_parent->is_registered() &&
! $this->is_registered() &&
* If not registered for add-on and the following conditions for the add-on are met, activate add-on account.
* * Network active and in network admin - network activate add-on account.
* * Network active and not in network admin - activate add-on account for the current blog.
* * Not network active and not in network admin - activate add-on account for the current blog.
* If not registered for add-on, not network active, and in network admin, do not handle the add-on activation.
* @author Leo Fajardo (@leorw)
( $this->is_network_active() || ! $is_network_admin )
! $this->has_free_plan() &&
$this->is_bundle_license_auto_activation_enabled() &&
$this->_parent->is_activated_with_bundle_license()
* If the add-on has no free plan, try to activate the account only when there's a bundle license.
* @author Leo Fajardo (@leorw)
$bundle_license = $this->get_active_parent_license( $this->_parent->_get_license()->secret_key, false );
is_object( $bundle_license ) &&
! empty( $bundle_license->products ) &&
in_array( $this->get_id(), $bundle_license->products )
$premium_license = $bundle_license;
if ( $this->has_free_plan() || is_object( $premium_license) ) {
// If parent plugin activated, automatically install add-on for the user.
$this->_activate_addon_account(
( $this->is_network_active() && $is_network_admin ) ?
// @todo This should be only executed on activation. It should be migrated to register_activation_hook() together with other activation related logic.
if ( $this->is_premium() ) {
// Remove add-on download admin-notice.
$this->_parent->_admin_notices->remove_sticky( array(
'addon_plan_upgraded_' . $this->_slug,
'no_addon_license_' . $this->_slug,
// $this->deactivate_premium_only_addon_without_license();
add_action( 'admin_init', array( &$this, '_admin_init_action' ) );
// if ( $this->is_registered() ||
// $this->is_anonymous() ||
// $this->is_pending_activation()
* Should be called outside `$this->is_user_in_admin()` scope
* because the updater has some logic that needs to be executed
* Currently, we need to hook to the `http_request_host_is_external` filter.
* In the future, there might be additional logic added.
$this->should_use_freemius_updater_and_dialog() &&
* If not premium but the premium version is installed, also instantiate the updater so that the
* plugin information dialog of the premium version will have the information from the server.
* @author Leo Fajardo (@leorw)
( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $this->premium_plugin_basename() ) ) )
$this->has_release_on_freemius() &&
( ! $this->is_unresolved_clone( true ) )
FS_Plugin_Updater::instance( $this );
$this->do_action( 'initiated' );
if ( $this->_storage->prev_is_premium !== $this->_plugin->is_premium ) {
if ( isset( $this->_storage->prev_is_premium ) ) {
'after_code_type_change',
$this->_plugin->is_premium
// Set for code type for the first time.
$this->_storage->prev_is_premium = $this->_plugin->is_premium;
if ( ! $this->is_addon() ) {
if ( $this->is_registered() ) {
// Fix for upgrade from versions < 1.0.9.
if ( ! isset( $this->_storage->activation_timestamp ) ) {
$this->_storage->activation_timestamp = WP_FS__SCRIPT_START_TIME;
$this->do_action( 'after_init_plugin_registered' );
} else if ( $this->is_anonymous() ) {
$this->do_action( 'after_init_plugin_anonymous' );
} else if ( $this->is_pending_activation() ) {