: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$is_network_action = $this->is_network_level_action();
$blog_id = $this->is_network_level_site_specific_action();
$is_parent_plugin_action = ( $plugin_id == $this->get_id() );
if ( is_numeric( $blog_id ) ) {
$this->switch_to_blog( $blog_id );
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
if ( $is_parent_plugin_action ) {
if ( $is_network_action && ! empty( $blog_id ) ) {
if ( ! $this->is_registered() ) {
$this->install_with_user(
$this->get_network_user(),
$this->_admin_notices->add(
$this->get_text_inline( 'Site successfully opted in.', 'successful-opt-in' ),
$this->get_text_inline( 'Awesome', 'awesome' )
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
if ( $is_parent_plugin_action ) {
if ( $is_network_action && ! empty( $blog_id ) ) {
if ( $this->is_registered( true ) ) {
if ( $this->is_tracking_prohibited( $blog_id ) ) {
if ( $this->toggle_site_tracking( true, $blog_id ) ) {
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'Sharing diagnostic data with %s helps to provide functionality that\'s more relevant to your website, avoid WordPress or PHP version incompatibilities that can break your website, and recognize which languages & regions the plugin should be translated and tailored to.', 'opt-out-message-appreciation' ), "<b>{$this->get_plugin_title()}</b>" ),
$this->get_text_inline( 'Thank you!', 'thank-you' )
if ( $this->toggle_site_tracking( false, $blog_id ) ) {
$install = $this->get_install_by_blog_id( $blog_id );
$this->_admin_notices->add(
$this->get_text_inline( 'Diagnostic data will no longer be sent from %s to %s.', 'opted-out-successfully' ),
self::get_unfiltered_site_url( $blog_id, true ),
"<b>{$this->get_plugin_title()}</b>"
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
$is_network_deletion = $is_network_action && empty( $blog_id );
if ( $is_parent_plugin_action ) {
// Delete add-on installs if have any.
$installed_addons = $this->get_installed_addons();
foreach ( $installed_addons as $fs_addon ) {
if ( $is_network_deletion ) {
$fs_addon->delete_network_account_event();
$fs_addon->delete_account_event();
if ( $is_network_deletion ) {
$this->delete_network_account_event();
$this->delete_account_event();
$this->maybe_set_slug_and_network_menu_exists_flag();
fs_redirect( $this->get_activation_url() );
if ( $this->is_addon_activated( $plugin_id ) ) {
$fs_addon = self::get_instance_by_id( $plugin_id );
if ( $is_network_deletion ) {
$fs_addon->delete_network_account_event();
$fs_addon->delete_account_event();
fs_redirect( $this->_get_admin_page_url( 'account' ) );
case 'downgrade_account':
if ( is_numeric( $blog_id ) ) {
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
check_admin_referer( $action );
$switch_to_network_install_blog_after_cancellation = (
is_numeric( $blog_id ) &&
$plugin_id == $this->get_id() &&
$result = $this->cancel_subscription_or_trial( $plugin_id );
if ( $this->is_api_error( $result ) ) {
$this->_admin_notices->add(
$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
if ( $switch_to_network_install_blog_after_cancellation ) {
$this->switch_to_blog( $this->_storage->network_install_blog_id );
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
if ( $plugin_id != $this->get_id() ) {
$fs = $this->is_addon_activated( $plugin_id ) ?
self::get_instance_by_id( $plugin_id ) :
if ( is_object( $fs ) ) {
$fs->_activate_license();
* Remove the product ID from `$_REQUEST` so that the syncing of the license for the other products will work properly.
* @author Leo Fajardo (@leorw)
unset( $_REQUEST['plugin_id'] );
if ( $this->is_bundle_license_auto_activation_enabled() ) {
$fs->maybe_activate_bundle_license( null, array(), is_numeric( $blog_id ) ? $blog_id : 0 );
case 'deactivate_license':
check_admin_referer( trim( "{$action}:{$blog_id}:{$install_id}", ':' ) );
if ( $plugin_id == $this->get_id() ) {
$this->_deactivate_license();
if ( $this->is_only_premium() ) {
if ( ! $is_network_action ) {
fs_redirect( $this->get_activation_url() );
} else if ( is_numeric( $blog_id ) ) {
$this->switch_to_blog( $this->_storage->network_install_blog_id );
if ( $this->is_addon_activated( $plugin_id ) ) {
$fs_addon = self::get_instance_by_id( $plugin_id );
$fs_addon->_deactivate_license();
check_admin_referer( $action );
$state = fs_request_get( 'state', 'init' );
// The nonce is injected by the error handler in `_email_address_update_ajax_handler` function.
check_admin_referer( 'change_owner' );
$candidate_email = fs_request_get( 'candidate_email' );
$transfer_type = fs_request_get( 'transfer_type' );
if ( $this->init_change_owner( $candidate_email, $transfer_type ) ) {
if ( 'transfer' === $transfer_type ) {
$this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.', 'change-owner-request-sent-x-transfer' ), '<b>' . $this->_user->email . '</b>' ) );
$this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
// We cannot (or need not to) check the nonce and referer here, because the link comes from the email sent by our API.
$candidate_email = fs_request_get( 'candidate_email', '' );
if ( ! is_email($candidate_email ) ) {
$this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
case 'candidate_confirmed':
// We do not need to validate the authenticity of this request here, because the `complete_change_owner` does that for us through API calls.
if ( $this->complete_change_owner() ) {
$this->_admin_notices->add_sticky(
sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
$this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!'
// @todo Handle failed ownership change message.
check_admin_referer( 'update_user_name' );
$result = $this->update_user_name();
if ( isset( $result->error ) ) {
$this->_admin_notices->add(
$this->get_text_inline( 'Please provide your full name.', 'name-update-failed-message' ),
$this->_admin_notices->add( $this->get_text_inline( 'Your name was successfully updated.', 'name-updated-message' ) );
#region Actions that might be called from external links (e.g. email)
* !!IMPORTANT!!: We cannot check for a valid nonce in this region, because the links could be coming from emails.
$result = $this->cancel_subscription_or_trial( $plugin_id );
if ( $this->is_api_error( $result ) ) {
$this->_admin_notices->add(
$this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
$this->_handle_account_user_sync();
case $this->get_unique_affix() . '_sync_license':
$this->download_latest_directly( $plugin_id );
if ( WP_FS__IS_POST_REQUEST ) {
$properties = array( 'site_secret_key', 'site_id', 'site_public_key' );
foreach ( $properties as $p ) {
if ( 'update_' . $p === $action ) {
check_admin_referer( $action );
$this->_logger->log( $action );
$site_property = substr( $p, strlen( 'site_' ) );
$site_property_value = fs_request_get( 'fs_' . $p . '_' . $this->get_unique_affix(), '' );
$this->get_site()->{$site_property} = $site_property_value;
// Store account after modification.
$this->do_action( 'account_property_edit', 'site', $site_property, $site_property_value );
$this->_admin_notices->add( sprintf(
/* translators: %s: User's account property (e.g. email address, name) */
$this->get_text_inline( 'You have successfully updated your %s.', 'x-updated' ),
'<b>' . str_replace( '_', ' ', $p ) . '</b>'
* Adds CSS classes for the body tag in the admin.
* @param string $classes Space-separated string of class names.
* @return string $classes FS Admin body tag class names.
public function fs_addons_body_class( $classes ) {
$classes .= ' plugins-php';
* Account page resources load.
* @author Vova Feldman (@svovaf)
function _account_page_load() {
$this->_logger->entrance();
$this->_logger->info( var_export( $_REQUEST, true ) );
fs_enqueue_local_style( 'fs_account', '/admin/account.css' );
if ( $this->has_addons() ) {
wp_enqueue_script( 'plugin-install' );
add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) );
if ( $this->has_paid_plan() &&
! $this->has_any_license() &&
! $this->is_sync_executed() &&
$this->is_tracking_allowed()
* If no licenses found and no sync job was executed during the last 24 hours,
* just execute the sync job right away (blocking execution).
$this->run_manual_sync();
$this->_handle_account_edits();
is_object( $this->_license ) &&
$this->_license->user_id == $this->_user->id &&
! $this->is_whitelabeled( true )
$this->_admin_notices->add(
$this->get_text_inline( "Is this your client's site? %s if you wish to hide sensitive info like your email, license key, prices, billing address & invoices from the WP Admin.", 'license_not_whitelabeled' ),
'<a href="#" class="fs-toggle-whitelabel-mode">%s</a>',
$this->get_text_inline( 'Click here', 'click-here' )
'license_not_whitelabeled'
$this->do_action( 'account_page_load_before_departure' );
* Renders the "Affiliation" page.
* @author Leo Fajardo (@leorw)
function _affiliation_page_render() {
$this->_logger->entrance();
$this->fetch_affiliate_and_terms();
fs_enqueue_local_style( 'fs_affiliation', '/admin/affiliation.css' );
$is_bundle_context = $this->has_bundle_context();
$plugin_title = $this->get_plugin_title();
if ( $is_bundle_context ) {
$plugin_title = $this->plugin_affiliate_terms->plugin_title;
// Add the suffix "Bundle" only if the word is not present in the title itself.
if ( false === mb_stripos( $plugin_title, fs_text_inline( 'Bundle', 'bundle' ) ) ) {
$plugin_title = $this->apply_filters(
'formatted_bundle_title',
$plugin_title . ' ' . fs_text_inline( 'Bundle', 'bundle' )
'id' => $this->_module_id,
'plugin_title' => $plugin_title,
echo $this->apply_filters( "/forms/affiliation.php", fs_get_template( '/forms/affiliation.php', $vars ) );
* @author Vova Feldman (@svovaf)
function _account_page_render() {
$this->_logger->entrance();
$template = 'account.php';
$vars = array( 'id' => $this->_module_id );
* Added filter to the template to allow developers wrapping the template
* in custom HTML (e.g. within a wizard/tabs).
* @author Vova Feldman (@svovaf)
echo $this->apply_filters( "templates/{$template}", fs_get_template( $template, $vars ) );
* Render account connect page.
* @author Vova Feldman (@svovaf)
function _connect_page_render() {
$this->_logger->entrance();
$vars = array( 'id' => $this->_module_id );
* Added filter to the template to allow developers wrapping the template
* in custom HTML (e.g. within a wizard/tabs).
* @author Vova Feldman (@svovaf)
echo $this->apply_filters( 'templates/connect.php', fs_get_template( 'connect.php', $vars ) );
* Load required resources before add-ons page render.
* @author Vova Feldman (@svovaf)
function _addons_page_load() {
$this->_logger->entrance();
fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
wp_enqueue_script( 'plugin-install' );
add_filter( 'admin_body_class', array( $this, 'fs_addons_body_class' ) );
if ( ! $this->is_registered() && $this->is_org_repo_compliant() ) {
$this->_admin_notices->add(
sprintf( $this->get_text_inline( 'Just letting you know that the add-ons information of %s is being pulled from an external server.', 'addons-info-external-message' ), '<b>' . $this->get_plugin_name() . '</b>' ),
$this->get_text_x_inline( 'Heads up', 'advance notice of something that will need attention.', 'heads-up' ),