: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$cron_blog_id = $this->get_cron_blog_id( $name );
$this->clear_cron_data( $name );
if ( 0 < $cron_blog_id ) {
switch_to_blog( $cron_blog_id );
if ( empty( $action_tag ) ) {
wp_clear_scheduled_hook( $this->get_action_tag( $action_tag ) );
if ( 0 < $cron_blog_id ) {
* Unix timestamp for next cron execution or false if not scheduled.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param string $action_tag Callback action tag.
private function get_next_scheduled_cron( $name, $action_tag = '' ) {
$this->_logger->entrance( $name );
if ( ! $this->is_cron_on( $name ) ) {
$cron_blog_id = $this->get_cron_blog_id( $name );
if ( 0 < $cron_blog_id ) {
switch_to_blog( $cron_blog_id );
if ( empty( $action_tag ) ) {
$next_scheduled = wp_next_scheduled( $this->get_action_tag( $action_tag ) );
if ( 0 < $cron_blog_id ) {
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param string $action_tag Callback action tag.
* @param string $recurrence 'single' or 'daily'.
* @param int $start_at Defaults to now.
* @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away.
* @param int $except_blog_id Target any except the excluded blog ID.
private function schedule_cron(
$start_at = WP_FS__SCRIPT_START_TIME,
$this->_logger->entrance( $name );
$this->clear_cron( $name, $action_tag, true );
$cron_blog_id = $this->get_cron_target_blog_id( $except_blog_id );
if ( is_multisite() && 0 == $cron_blog_id ) {
// Don't schedule cron since couldn't find a target blog.
if ( 0 < $cron_blog_id ) {
switch_to_blog( $cron_blog_id );
if ( 'daily' === $recurrence ) {
if ( $randomize_start ) {
// Schedule first sync with a random 12 hour time range from now.
$start_at += rand( 0, ( WP_FS__TIME_24_HOURS_IN_SEC / 2 ) );
// Schedule daily WP cron.
$this->get_action_tag( $action_tag )
} else if ( 'single' === $recurrence ) {
wp_schedule_single_event(
$this->get_action_tag( $action_tag )
$this->set_cron_data( $name, $cron_blog_id );
if ( 0 < $cron_blog_id ) {
* Consolidated cron execution for performance optimization. The max number of API requests is based on the number of unique opted-in users.
* that doesn't halt page loading.
* @author Vova Feldman (@svovaf)
* @param string $name Cron name.
* @param callable $callable The function that should be executed.
private function execute_cron( $name, $callable ) {
$this->_logger->entrance( $name );
// Store the last time data sync was executed.
$this->set_cron_execution_timestamp( $name );
// Check if API is temporary down.
if ( FS_Api::is_temporary_down() ) {
// @todo Add logic that identifies API latency, and reschedule the next background sync randomly between 8-16 hours.
$users_2_blog_ids = array();
if ( ! is_multisite() ) {
$users_2_blog_ids[0] = array( 0 );
$installs = $this->get_blog_install_map();
foreach ( $installs as $blog_id => $install ) {
if ( $this->is_tracking_allowed( $blog_id, $install ) ) {
if ( ! isset( $users_2_blog_ids[ $install->user_id ] ) ) {
$users_2_blog_ids[ $install->user_id ] = array();
$users_2_blog_ids[ $install->user_id ][] = $blog_id;
$current_blog_id = get_current_blog_id();
foreach ( $users_2_blog_ids as $user_id => $blog_ids ) {
if ( 0 < $blog_ids[0] ) {
$this->switch_to_blog( $blog_ids[0] );
call_user_func_array( $callable, array( $blog_ids, ( is_multisite() ? $current_blog_id : null ) ) );
foreach ( $blog_ids as $blog_id ) {
$this->do_action( "after_{$name}_cron", $blog_id );
$this->switch_to_blog( $current_blog_id, fs_is_network_admin() ? $this->get_network_install() : null );
$this->do_action( "after_{$name}_cron_multisite" );
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
* @author Vova Feldman (@svovaf)
private function is_sync_cron_scheduled() {
return $this->is_cron_on( 'sync' );
* Get the sync cron's executing blog ID.
* @author Vova Feldman (@svovaf)
private function get_sync_cron_blog_id() {
return $this->get_cron_blog_id( 'sync' );
* @author Vova Feldman (@svovaf)
private function run_manual_sync() {
if ( ! $this->is_user_admin() ) {
// Reschedule next cron to run 24 hours from now (performance optimization).
$this->schedule_sync_cron( time() + WP_FS__TIME_24_HOURS_IN_SEC, false );
* Data sync cron job. Replaces the background sync non blocking HTTP request
* that doesn't halt page loading.
* @author Vova Feldman (@svovaf)
* @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users.
$this->_logger->entrance();
$this->execute_cron( 'sync', array( &$this, '_sync_cron_method' ) );
* The actual data sync cron logic.
* @author Vova Feldman (@svovaf)
* @param int|null $current_blog_id @since 2.2.3. This is passed from the `execute_cron` method and used by the
* `_sync_plugin_license` method in order to switch to the previous blog when sending
* updates for a single site in case `execute_cron` has switched to a different blog.
function _sync_cron_method( array $blog_ids, $current_blog_id = null ) {
if ( $this->is_registered() ) {
if ( $this->has_paid_plan() ) {
// Initiate background plan sync.
$this->_sync_license( true, false, $current_blog_id );
if ( $this->is_paying() ) {
// Check for premium plugin updates.
$this->check_updates( true );
// Sync install(s) (only if something changed locally).
if ( 1 < count( $blog_ids ) ) {
$this->maybe_sync_install_user();
* Check if sync was executed in the last $period of seconds.
* @author Vova Feldman (@svovaf)
* @param int $period In seconds
private function is_sync_executed( $period = WP_FS__TIME_24_HOURS_IN_SEC ) {
return $this->is_cron_executed( 'sync', $period );
* @author Vova Feldman (@svovaf)
private function is_sync_cron_on() {
return $this->is_cron_on( 'sync' );
* @author Leo Fajardo (@leorw)
private function maybe_schedule_sync_cron() {
$next_schedule = $this->next_sync_cron();
// The event is properly scheduled, so no need to reschedule it.
is_numeric( $next_schedule ) &&
$this->schedule_sync_cron();
* @author Vova Feldman (@svovaf)
* @param int $start_at Defaults to now.
* @param bool $randomize_start If true, schedule first job randomly during the next 12 hours. Otherwise, schedule job to start right away.
* @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor.
private function schedule_sync_cron(
$start_at = WP_FS__SCRIPT_START_TIME,
* Add the actual sync function to the cron job hook.
* @author Vova Feldman (@svovaf)
private function hook_callback_to_sync_cron() {
$this->add_action( 'data_sync', array( &$this, '_sync_cron' ) );
* @author Vova Feldman (@svovaf)
* @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected.
private function clear_sync_cron( $is_network_clear = false ) {
$this->_logger->entrance();
$this->clear_cron( 'sync', 'data_sync', $is_network_clear );
* Unix timestamp for next sync cron execution or false if not scheduled.
* @author Vova Feldman (@svovaf)
function next_sync_cron() {
return $this->get_next_scheduled_cron( 'sync', 'data_sync' );
* Unix timestamp for previous sync cron execution or false if never executed.
* @author Vova Feldman (@svovaf)
function last_sync_cron() {
return $this->cron_last_execution( 'sync' );
#endregion Daily Sync Cron ------------------------------------------------------------------
#----------------------------------------------------------------------------------
#region Async Install Sync
#----------------------------------------------------------------------------------
* @author Vova Feldman (@svovaf)
private function is_install_sync_scheduled() {
return $this->is_cron_on( 'install_sync' );
* Get the sync cron's executing blog ID.
* @author Vova Feldman (@svovaf)
private function get_install_sync_cron_blog_id() {
return $this->get_cron_blog_id( 'install_sync' );
* Instead of running blocking install sync event, execute non blocking scheduled wp-cron.
* @author Vova Feldman (@svovaf)
* @param int $except_blog_id Since 2.0.0 when running in a multisite network environment, the cron execution is consolidated. This param allows excluding excluded specified blog ID from being the cron executor.
private function schedule_install_sync( $except_blog_id = 0 ) {
if ( $this->is_clone() ) {
$this->schedule_cron( 'install_sync', 'install_sync', 'single', WP_FS__SCRIPT_START_TIME, false, $except_blog_id );
* Unix timestamp for previous install sync cron execution or false if never executed.
* @todo There's some very strange bug that $this->_storage->install_sync_timestamp value is not being updated. But for sure the sync event is working.
* @author Vova Feldman (@svovaf)
function last_install_sync() {
return $this->cron_last_execution( 'install_sync' );
* Unix timestamp for next install sync cron execution or false if not scheduled.
* @author Vova Feldman (@svovaf)
function next_install_sync() {
return $this->get_next_scheduled_cron( 'install_sync', 'install_sync' );
* Add the actual install sync function to the cron job hook.
* @author Vova Feldman (@svovaf)
private function hook_callback_to_install_sync() {
$this->add_action( 'install_sync', array( &$this, '_run_sync_install' ) );
* @author Vova Feldman (@svovaf)
* @param bool $is_network_clear Since 2.0.0 If set to TRUE, clear sync cron even if there are installs that are still connected.
private function clear_install_sync_cron( $is_network_clear = false ) {
$this->_logger->entrance();
$this->clear_cron( 'install_sync', 'install_sync', $is_network_clear );
* @author Vova Feldman (@svovaf)
* @since 2.0.0 Consolidate all the data sync into the same cron for performance optimization. The max number of API requests is based on the number of unique opted-in users.
public function _run_sync_install() {
$this->_logger->entrance();
$this->execute_cron( 'sync', array( &$this, '_sync_install_cron_method' ) );
* The actual install(s) sync cron logic.
* @author Vova Feldman (@svovaf)
* @param int|null $current_blog_id
function _sync_install_cron_method( array $blog_ids, $current_blog_id = null ) {
if ( $this->is_registered() ) {
if ( 1 < count( $blog_ids ) ) {
$this->sync_installs( array(), true );