Repository URL to install this package:
<?php
/**
* @package Freemius
* @copyright Copyright (c) 2015, Freemius, Inc.
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
* @author Leo Fajardo (@leorw)
* @since 2.5.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Manages the detection of clones and provides the logged-in WordPress user with options for manually resolving them.
*
* @since 2.5.0
*
* @property int $clone_identification_timestamp
* @property int $temporary_duplicate_mode_selection_timestamp
* @property int $temporary_duplicate_notice_shown_timestamp
* @property string $request_handler_id
* @property int $request_handler_timestamp
* @property int $request_handler_retries_count
* @property bool $hide_manual_resolution
* @property array $new_blog_install_map
*/
class FS_Clone_Manager {
/**
* @var FS_Option_Manager
*/
private $_storage;
/**
* @var FS_Option_Manager
*/
private $_network_storage;
/**
* @var FS_Admin_Notices
*/
private $_notices;
/**
* @var FS_Logger
*/
protected $_logger;
/**
* @var int 3 minutes
*/
const CLONE_RESOLUTION_MAX_EXECUTION_TIME = 180;
/**
* @var int
*/
const CLONE_RESOLUTION_MAX_RETRIES = 3;
/**
* @var int
*/
const TEMPORARY_DUPLICATE_PERIOD = WP_FS__TIME_WEEK_IN_SEC * 2;
/**
* @var string
*/
const OPTION_NAME = 'clone_resolution';
/**
* @var string
*/
const OPTION_MANAGER_NAME = 'clone_management';
/**
* @var string
*/
const OPTION_TEMPORARY_DUPLICATE = 'temporary_duplicate';
/**
* @var string
*/
const OPTION_LONG_TERM_DUPLICATE = 'long_term_duplicate';
/**
* @var string
*/
const OPTION_NEW_HOME = 'new_home';
#--------------------------------------------------------------------------------
#region Singleton
#--------------------------------------------------------------------------------
/**
* @var FS_Clone_Manager
*/
private static $_instance;
/**
* @return FS_Clone_Manager
*/
static function instance() {
if ( ! isset( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
#endregion
private function __construct() {
$this->_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true );
$this->_network_storage = FS_Option_Manager::get_manager( WP_FS___OPTION_PREFIX . self::OPTION_MANAGER_NAME, true, true );
$this->maybe_migrate_options();
$this->_notices = FS_Admin_Notices::instance( 'global_clone_resolution_notices', '', '', true );
$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . '_clone_manager', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
}
/**
* Migrate clone resolution options from 2.5.0 array-based structure, to a new flat structure.
*
* The reason this logic is not in a separate migration script is that we want to be 100% sure data is migrated before any execution of clone logic.
*
* @todo Delete this one in the future.
*/
private function maybe_migrate_options() {
$storages = array(
$this->_storage,
$this->_network_storage
);
foreach ( $storages as $storage ) {
$clone_data = $storage->get_option( self::OPTION_NAME );
if ( is_array( $clone_data ) && ! empty( $clone_data ) ) {
foreach ( $clone_data as $key => $val ) {
if ( ! is_null( $val ) ) {
$storage->set_option( $key, $val );
}
}
$storage->unset_option( self::OPTION_NAME, true );
}
}
}
/**
* @author Leo Fajardo (@leorw)
* @since 2.5.0
*/
function _init() {
if ( is_admin() ) {
if ( Freemius::is_admin_post() ) {
add_action( 'admin_post_fs_clone_resolution', array( $this, '_handle_clone_resolution' ) );
}
if ( Freemius::is_ajax() ) {
Freemius::add_ajax_action_static( 'handle_clone_resolution', array( $this, '_clone_resolution_action_ajax_handler' ) );
} else {
if (
empty( $this->get_clone_identification_timestamp() ) &&
(
! fs_is_network_admin() ||
! ( $this->is_clone_resolution_options_notice_shown() || $this->is_temporary_duplicate_notice_shown() )
)
) {
$this->hide_clone_admin_notices();
} else if ( ! Freemius::is_cron() && ! Freemius::is_admin_post() ) {
$this->try_resolve_clone_automatically();
$this->maybe_show_clone_admin_notice();
add_action( 'admin_footer', array( $this, '_add_clone_resolution_javascript' ) );
}
}
}
}
/**
* Retrieves the timestamp that was stored when a clone was identified.
*
* @return int|null
*/
function get_clone_identification_timestamp() {
return $this->get_option( 'clone_identification_timestamp', true );
}
/**
* @author Leo Fajardo (@leorw)
* @since 2.5.1
*
* @param string $sdk_last_version
*/
function maybe_update_clone_resolution_support_flag( $sdk_last_version ) {
if ( null !== $this->hide_manual_resolution ) {
return;
}
$this->hide_manual_resolution = (
! empty( $sdk_last_version ) &&
version_compare( $sdk_last_version, '2.5.0', '<' )
);
}
/**
* Stores the time when a clone was identified.
*/
function store_clone_identification_timestamp() {
$this->clone_identification_timestamp = time();
}
/**
* Retrieves the timestamp for the temporary duplicate mode's expiration.
*
* @return int
*/
function get_temporary_duplicate_expiration_timestamp() {
$temporary_duplicate_mode_start_timestamp = $this->was_temporary_duplicate_mode_selected() ?
$this->temporary_duplicate_mode_selection_timestamp :
$this->get_clone_identification_timestamp();
return ( $temporary_duplicate_mode_start_timestamp + self::TEMPORARY_DUPLICATE_PERIOD );
}
/**
* Determines if the SDK should handle clones. The SDK handles clones only up to 3 times with 3 min interval.
*
* @return bool
*/
private function should_handle_clones() {
if ( ! isset( $this->request_handler_timestamp ) ) {
return true;
}
if ( $this->request_handler_retries_count >= self::CLONE_RESOLUTION_MAX_RETRIES ) {
return false;
}
// Give the logic that handles clones enough time to finish (it is given 3 minutes for now).
return ( time() > ( $this->request_handler_timestamp + self::CLONE_RESOLUTION_MAX_EXECUTION_TIME ) );
}
/**
* @author Leo Fajardo (@leorw)
* @since 2.5.1
*
* @return bool
*/
function should_hide_manual_resolution() {
return ( true === $this->hide_manual_resolution );
}
/**
* Executes the clones handler logic if it should be executed, i.e., based on the return value of the should_handle_clones() method.
*
* @author Leo Fajardo (@leorw)
* @since 2.5.0
*/
function maybe_run_clone_resolution() {
if ( ! $this->should_handle_clones() ) {
return;
}
$this->request_handler_retries_count = isset( $this->request_handler_retries_count ) ?
( $this->request_handler_retries_count + 1 ) :
1;
$this->request_handler_timestamp = time();
$handler_id = ( rand() . microtime() );
$this->request_handler_id = $handler_id;
// Add cookies to trigger request with the same user access permissions.
$cookies = array();
foreach ( $_COOKIE as $name => $value ) {
$cookies[] = new WP_Http_Cookie( array(
'name' => $name,
'value' => $value,
) );
}
wp_remote_post(
admin_url( 'admin-post.php' ),
array(
'method' => 'POST',
'body' => array(
'action' => 'fs_clone_resolution',
'handler_id' => $handler_id,
),
'timeout' => 0.01,
'blocking' => false,
'sslverify' => false,
'cookies' => $cookies,
)
);
}
/**
* Executes the clones handler logic.
*
* @author Leo Fajardo (@leorw)
* @since 2.5.0
*/
function _handle_clone_resolution() {
$handler_id = fs_request_get( 'handler_id' );
if ( empty( $handler_id ) ) {
return;
}
if (
! isset( $this->request_handler_id ) ||
$this->request_handler_id !== $handler_id
) {
return;
}
if ( ! $this->try_automatic_resolution() ) {
$this->clear_temporary_duplicate_notice_shown_timestamp();
}
}
#--------------------------------------------------------------------------------
#region Automatic Clone Resolution
#--------------------------------------------------------------------------------
/**
* @var array All installs cache.
*/
private $all_installs;
/**
* Checks if a given instance's install is a clone of another subsite in the network.
*
* @author Vova Feldman (@svovaf)
*
* @return FS_Site
*/
private function find_network_subsite_clone_install( Freemius $instance ) {
if ( ! is_multisite() ) {
// Not a multi-site network.
return null;
}
if ( ! isset( $this->all_installs ) ) {
$this->all_installs = Freemius::get_all_modules_sites();
}
// Check if there's another blog that has the same site.
$module_type = $instance->get_module_type();
$sites_by_module_type = ! empty( $this->all_installs[ $module_type ] ) ?
$this->all_installs[ $module_type ] :
array();
$slug = $instance->get_slug();
Loading ...