: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$this->_admin_notices->remove_sticky( $ids, $network_level_or_blog_id );
#--------------------------------------------------------------------------------
#region Actions / Hooks / Filters
#--------------------------------------------------------------------------------
* @author Vova Feldman (@svovaf)
public function get_action_tag( $tag ) {
return self::get_action_tag_static( $tag, $this->_slug, $this->is_plugin() );
* @author Vova Feldman (@svovaf)
static function get_action_tag_static( $tag, $slug = '', $is_plugin = true ) {
if ( ! empty( $slug ) ) {
$action .= '_' . self::get_module_unique_affix( $slug, $is_plugin );
* Returns a string that can be used to generate a unique action name,
* option name, HTML element ID, or HTML element class.
* @author Leo Fajardo (@leorw)
public function get_unique_affix() {
return self::get_module_unique_affix( $this->_slug, $this->is_plugin() );
* Returns a string that can be used to generate a unique action name,
* option name, HTML element ID, or HTML element class.
* @author Vova Feldman (@svovaf)
static function get_module_unique_affix( $slug, $is_plugin = true ) {
$affix .= '-' . WP_FS__MODULE_TYPE_THEME;
* @author Vova Feldman (@svovaf)
* @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
* based on the slug for backward compatibility.
function get_ajax_action( $tag ) {
return self::get_ajax_action_static( $tag, $this->_module_id );
* @author Vova Feldman (@svovaf)
function get_ajax_security( $tag ) {
return wp_create_nonce( $this->get_ajax_action( $tag ) );
* @author Vova Feldman (@svovaf)
function check_ajax_referer( $tag ) {
check_ajax_referer( $this->get_ajax_action( $tag ), 'security' );
* @author Vova Feldman (@svovaf)
* @since 1.2.2.5 The AJAX action names are based on the module ID, not like the non-AJAX actions that are
* based on the slug for backward compatibility.
* @param number|null $module_id
static function get_ajax_action_static( $tag, $module_id = null ) {
if ( ! empty( $module_id ) ) {
$action .= "_{$module_id}";
* Do action, specific for the current context plugin.
* @author Vova Feldman (@svovaf)
* @param string $tag The name of the action to be executed.
* @param mixed $arg,... Optional. Additional arguments which are passed on to the
* functions hooked to the action. Default empty.
function do_action( $tag, $arg = '' ) {
$this->_logger->entrance( $tag );
call_user_func_array( 'do_action', array_merge(
array( $this->get_action_tag( $tag ) ),
array_slice( $args, 1 ) )
* Add action, specific for the current context plugin.
* @author Vova Feldman (@svovaf)
* @param callable $function_to_add
* @param int $accepted_args
$priority = WP_FS__DEFAULT_PRIORITY,
$this->_logger->entrance( $tag );
add_action( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
* Add AJAX action, specific for the current context plugin.
* @author Vova Feldman (@svovaf)
* @param callable $function_to_add
* @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
function add_ajax_action(
$priority = WP_FS__DEFAULT_PRIORITY
$this->_logger->entrance( $tag );
return self::add_ajax_action_static(
* @author Vova Feldman (@svovaf)
* @param callable $function_to_add
* @param number|null $module_id
* @return bool True if action added, false if no need to add the action since the AJAX call isn't matching.
static function add_ajax_action_static(
$priority = WP_FS__DEFAULT_PRIORITY,
self::$_static_logger->entrance( $tag );
if ( ! self::is_ajax_action_static( $tag, $module_id ) ) {
'wp_ajax_' . self::get_ajax_action_static( $tag, $module_id ),
self::$_static_logger->info( "$tag AJAX callback action added." );
* Send a JSON response back to an Ajax request.
* @author Vova Feldman (@svovaf)
static function shoot_ajax_response( $response ) {
wp_send_json( $response );
* Send a JSON response back to an Ajax request, indicating success.
* @author Vova Feldman (@svovaf)
* @param mixed $data Data to encode as JSON, then print and exit.
static function shoot_ajax_success( $data = null ) {
wp_send_json_success( $data );
* Send a JSON response back to an Ajax request, indicating failure.
* @author Vova Feldman (@svovaf)
* @param mixed $error Optional error message.
static function shoot_ajax_failure( $error = '' ) {
$result = array( 'success' => false );
if ( ! empty( $error ) ) {
$result['error'] = $error;
* Returns an AJAX URL with a special extra param to indicate whether the request was triggered from the network admin or blog admin.
* @author Vova Feldman (@svovaf)
* @param string $wrap_with By default, returns the AJAX URL wrapped with single quotes.
static function ajax_url( $wrap_with = "'") {
if ( fs_is_network_admin() ) {
$param_name = '_fs_network_admin';
$param_name = '_fs_blog_admin';
$url = admin_url( 'admin-ajax.php', 'relative' );
$url .= ( false === strpos( $url, '?' ) ) ? '?' : '&';
$url .= "{$param_name}=true";
return "{$wrap_with}{$url}{$wrap_with}";
* Apply filter, specific for the current context plugin.
* @author Vova Feldman (@svovaf)
* @param string $tag The name of the filter hook.
* @param mixed $value The value on which the filters hooked to `$tag` are applied on.
* @return mixed The filtered value after all hooked functions are applied to it.
function apply_filters( $tag, $value ) {
$this->_logger->entrance( $tag );
array_unshift( $args, $this->get_unique_affix() );
return call_user_func_array( 'fs_apply_filter', $args );
* Add filter, specific for the current context plugin.
* @author Vova Feldman (@svovaf)
* @param callable $function_to_add
* @param int $accepted_args
function add_filter( $tag, $function_to_add, $priority = WP_FS__DEFAULT_PRIORITY, $accepted_args = 1 ) {
$this->_logger->entrance( $tag );
add_filter( $this->get_action_tag( $tag ), $function_to_add, $priority, $accepted_args );
* @author Vova Feldman (@svovaf)
* @param callable|bool $function_to_check Optional. The callback to check for. Default false.
function has_filter( $tag, $function_to_check = false ) {
$this->_logger->entrance( $tag );
return has_filter( $this->get_action_tag( $tag ), $function_to_check );
* Override default i18n text phrases.
* @author Vova Feldman (@svovaf)
* @param string[] string $key_value
* @uses fs_override_i18n()
function override_i18n( $key_value ) {
fs_override_i18n( $key_value, $this->_slug );
------------------------------------------------------------------------------------------------------------------*/
* Update site information.
* @author Vova Feldman (@svovaf)
* @param bool $store Flush to Database if true.
* @param null|int $network_level_or_blog_id Since 2.0.0
* @param \FS_Site $site Since 2.0.0
private function _store_site( $store = true, $network_level_or_blog_id = null, FS_Site $site = null, $is_backup = false ) {
$this->_logger->entrance();
if ( is_null( $site ) ) {
if ( !isset( $site ) || !is_object($site) || empty( $site->id ) ) {
$this->_logger->error( "Empty install ID, can't store site." );
$site_clone = clone $site;
$sites = self::get_all_sites( $this->_module_type, $network_level_or_blog_id, $is_backup );
is_object( $this->_user ) && $this->_user->id != $site->user_id
$this->sync_user_by_current_install( $site->user_id );
$prev_stored_user_id = $this->_storage->get( 'prev_user_id', false, $network_level_or_blog_id );
if ( empty( $prev_stored_user_id ) &&
is_object($this->_user) && $this->_user->id != $site->user_id
* Store the current user ID as the previous user ID so that the previous user can be used
* as the install's owner while the new owner's details are not yet available.
* This will be executed only in the `replica` site. For example, there are 2 sites, namely `original`
* and `replica`, then an ownership change was initiated and completed in the `original`, the `replica`
* will be using the previous user until it is updated again (e.g.: until the next clone of `original`
* @author Leo Fajardo (@leorw)
$this->_storage->store( 'prev_user_id', $sites[ $this->_slug ]->user_id, $network_level_or_blog_id );
$sites[ $this->_slug ] = $site_clone;
$this->set_account_option(
( $is_backup ? 'prev_' : '' ) . 'sites',
$network_level_or_blog_id
* Stores the context site in the sites backup storage. This logic is used before deleting the site info so that it can be restored later on if necessary (e.g., if the automatic clone resolution attempt fails).
* @author Leo Fajardo (@leorw)
private function back_up_site() {
$this->_logger->entrance();
$site_clone = clone $this->_site;
$this->_store_site( true, null, $site_clone, true );
* Update plugin's plans information.
* @author Vova Feldman (@svovaf)
* @param bool $store Flush to Database if true.
private function _store_plans( $store = true ) {
$this->_logger->entrance();
$plans = self::get_all_plans( $this->_module_type );
$encrypted_plans = array();
for ( $i = 0, $len = count( $this->_plans ); $i < $len; $i ++ ) {
$encrypted_plans[] = self::_encrypt_entity( $this->_plans[ $i ] );
$plans[ $this->_slug ] = $encrypted_plans;
$this->set_account_option( 'plans', $plans, $store );
* Update user's plugin licenses.
* @author Vova Feldman (@svovaf)