Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions includes/class-freemius.php
Original file line number Diff line number Diff line change
Expand Up @@ -3692,12 +3692,14 @@ static function _load_textdomain() {

global $fs_active_plugins;

// Works both for plugins and themes.
load_plugin_textdomain(
'freemius',
false,
$fs_active_plugins->newest->sdk_path . '/languages/'
);
// Defensive guard: $fs_active_plugins may not be initialized yet in deferred load scenarios.
if ( ! is_object( $fs_active_plugins ) ||
! isset( $fs_active_plugins->newest ) ||
! is_object( $fs_active_plugins->newest ) ||
empty( $fs_active_plugins->newest->sdk_path ) ) {
return;
}

}

#endregion
Expand Down
47 changes: 38 additions & 9 deletions includes/managers/class-fs-debug-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ class FS_DebugManager {
* @since 1.0.8
*/
static function _add_debug_section() {
if ( ! is_super_admin() ) {
// Add debug page only for super-admins.
// Allow only proper admins: single-site admins or network admins in multisite.
$capability = fs_is_network_admin() ? 'manage_network_options' : 'manage_options';
if ( ! current_user_can( $capability ) ) {
return;
}

Expand All @@ -30,20 +31,25 @@ static function _add_debug_section() {
$hook = FS_Admin_Menu_Manager::add_page(
$title,
$title,
'manage_options',
$capability,
'freemius',
array( self::class, '_debug_page_render' )
);
} else {
// Add hidden debug page.
$hook = FS_Admin_Menu_Manager::add_subpage(
'',
$title,
$title,
'manage_options',
// Register as a top-level page and immediately hide it from the menu.
// This guarantees a valid screen + title while keeping the page undiscoverable via UI.
$hook = FS_Admin_Menu_Manager::add_page(
$title, // page title
$title, // menu title (will be hidden)
$capability,
'freemius',
array( self::class, '_debug_page_render' )
);
if ( ! empty( $hook ) ) {
add_action( ( fs_is_network_admin() ? 'network_' : '' ) . 'admin_menu', function () {
remove_menu_page( 'freemius' );
}, 999 );
}
}

if ( ! empty( $hook ) ) {
Expand Down Expand Up @@ -472,6 +478,8 @@ public static function load_required_static() {
self::class,
'_add_debug_section',
) );
// Ensure $title is never null after current screen is set (prevents PHP 8.1+ strip_tags(null) deprecation).
add_action( 'current_screen', array( self::class, '_ensure_admin_title_on_current_screen' ), 1, 1 );
}

add_action( "wp_ajax_fs_toggle_debug_mode", array( self::class, '_toggle_debug_mode' ) );
Expand All @@ -492,6 +500,27 @@ public static function register_hooks() {
add_action( 'fs_debug_turn_off_logging_hook', array( self::class, '_turn_off_debug_mode' ) );
}

/**
* Ensure the global $title is a string once the current screen exists.
* This prevents core admin-header.php from calling strip_tags(null) on PHP 8.1+.
*
* @param WP_Screen $screen
* @since 2.12.2.3
*/
public static function _ensure_admin_title_on_current_screen( $screen ) {
// $title is a core global used by admin-header.php.
global $title;
if ( null === $title ) {
// Try to derive a proper title from the screen; otherwise default to empty string.
if ( is_object( $screen ) && method_exists( $screen, 'get_title' ) ) {
$derived = $screen->get_title();
$title = is_string( $derived ) ? $derived : '';
} else {
$title = '';
}
}
}

/**
* @author Daniele Alessandra (@danielealessandra)
*
Expand Down
133 changes: 127 additions & 6 deletions start.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,133 @@
return;
}

/**
* Freemius SDK Version.
*
* @var string
*/
$this_sdk_version = '2.12.2';
/**
* Freemius SDK Version.
*
* @var string
*/
$this_sdk_version = '2.12.2';

// Define SDK version constant once, early, so it's available for deferred paths too.
if ( ! defined( 'WP_FS__SDK_VERSION' ) ) {
define( 'WP_FS__SDK_VERSION', $this_sdk_version );
}

/**
* Allow skipping the early "latest SDK loader" but still support safe, late initialization.
*
* When FS__SKIP_LATEST_SDK_LOADING is true, we avoid the early loader that may run before WP is fully bootstrapped
* (common in Bedrock/Altis when Composer autoload runs too early). Instead of disabling the SDK, we provide a
* deferring shim for fs_dynamic_init() that initializes the SDK as soon as the WordPress plugin API is available.
*
* @since 2.12.2.2
*/
if ( defined( 'FS__SKIP_LATEST_SDK_LOADING' ) && FS__SKIP_LATEST_SDK_LOADING ) {
// Queue for modules waiting for late init.
if ( ! isset( $GLOBALS['fs__deferred_modules'] ) || ! is_array( $GLOBALS['fs__deferred_modules'] ) ) {
$GLOBALS['fs__deferred_modules'] = array();
}

if ( ! function_exists( 'fs__process_deferred_freemius_inits' ) ) {
/**
* Process deferred Freemius initializations.
* Loads the SDK core and initializes all queued modules.
*/
function fs__process_deferred_freemius_inits() {
if ( class_exists( 'Freemius' ) ) {
// SDK already loaded.
} else {
// Load SDK core files directly, bypassing start.php to avoid recursion/early loader.
// Preload essentials to satisfy functions used by config.php (e.g., fs_get_ip()).
$fs_sdk_dir = dirname( __FILE__ );
$fs_essential = $fs_sdk_dir . '/includes/fs-essential-functions.php';
if ( file_exists( $fs_essential ) ) {
require_once $fs_essential;
}
$fs_ip = $fs_sdk_dir . '/includes/fs-ip.php';
if ( file_exists( $fs_ip ) ) {
require_once $fs_ip;
}
require_once $fs_sdk_dir . '/require.php';
}

if ( isset( $GLOBALS['fs__deferred_modules'] ) && is_array( $GLOBALS['fs__deferred_modules'] ) ) {
foreach ( $GLOBALS['fs__deferred_modules'] as $module ) {
if ( is_array( $module ) && isset( $module['id'], $module['slug'] ) ) {
try {
$fs = Freemius::instance( $module['id'], $module['slug'], true );
$fs->dynamic_init( $module );
} catch ( Exception $e ) {
// Swallow to avoid breaking host; log if WP_DEBUG is true.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[Freemius] Deferred init failed: ' . $e->getMessage() );
}
}
}
}
// Clear the queue.
$GLOBALS['fs__deferred_modules'] = array();
}
}
}

if ( ! function_exists( 'fs_dynamic_init' ) ) {
/**
* Deferring shim for fs_dynamic_init().
*
* If the WordPress plugin API is already available, initialize immediately.
* Otherwise, queue the module and ensure processing happens on 'plugins_loaded'.
*
* @param array $module
* @return Freemius|null
*/
function fs_dynamic_init( $module = array() ) {
// If WP plugin API is ready, initialize now.
if ( function_exists( 'add_action' ) ) {
if ( ! class_exists( 'Freemius' ) ) {
// Preload essentials to satisfy functions used by config.php (e.g., fs_get_ip()).
$fs_sdk_dir = dirname( __FILE__ );
$fs_essential = $fs_sdk_dir . '/includes/fs-essential-functions.php';
if ( file_exists( $fs_essential ) ) {
require_once $fs_essential;
}
$fs_ip = $fs_sdk_dir . '/includes/fs-ip.php';
if ( file_exists( $fs_ip ) ) {
require_once $fs_ip;
}
require_once $fs_sdk_dir . '/require.php';
}
if ( class_exists( 'Freemius' ) && is_array( $module ) && isset( $module['id'], $module['slug'] ) ) {
$fs = Freemius::instance( $module['id'], $module['slug'], true );
$fs->dynamic_init( $module );
return $fs;
}
}

// Otherwise, defer until plugins_loaded.
if ( ! isset( $GLOBALS['fs__deferred_modules'] ) || ! is_array( $GLOBALS['fs__deferred_modules'] ) ) {
$GLOBALS['fs__deferred_modules'] = array();
}
$GLOBALS['fs__deferred_modules'][] = $module;

// If WP hooks are available, schedule processing (idempotent).
if ( function_exists( 'add_action' ) ) {
add_action( 'plugins_loaded', 'fs__process_deferred_freemius_inits', 1 );
}

return null;
}
}

// If WP hooks are already available at this point (e.g., start.php was included late),
// schedule immediate processing of any queued modules.
if ( function_exists( 'add_action' ) ) {
add_action( 'plugins_loaded', 'fs__process_deferred_freemius_inits', 1 );
}

// Important: return to skip the early loader below.
return;
}

#region SDK Selection Logic --------------------------------------------------------------------

Expand Down
51 changes: 34 additions & 17 deletions templates/debug.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@

global $fs_active_plugins;

// Derive SDK version from the constant only. Avoid referencing $fs_active_plugins here because it may be null
// during deferred initialization and we only need a display value for the heading.
$sdk_version = defined('WP_FS__SDK_VERSION') ? (string) WP_FS__SDK_VERSION : '';

// Normalize plugins list for the SDK versions table.
$fs_plugins = array();
if ( is_object( $fs_active_plugins )
&& isset( $fs_active_plugins->plugins )
&& is_array( $fs_active_plugins->plugins ) ) {
$fs_plugins = $fs_active_plugins->plugins;
}

$fs_options = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true );

$off_text = fs_text_x_inline( 'Off', 'as turned off' );
Expand All @@ -26,7 +38,7 @@

$auto_off_timestamp = wp_next_scheduled( 'fs_debug_turn_off_logging_hook' ) * 1000;
?>
<h1><?php echo fs_text_inline( 'Freemius Debug' ) . ' - ' . fs_text_inline( 'SDK' ) . ' v.' . $fs_active_plugins->newest->version ?></h1>
<h1><?php echo fs_text_inline( 'Freemius Debug' ) . ' - ' . fs_text_inline( 'SDK' ) . ' v.' . esc_html( $sdk_version ) ?></h1>
<div>
<!-- Debugging Switch -->
<span class="fs-switch-label"><?php fs_esc_html_echo_x_inline( 'Debugging', 'as code debugging' ) ?></span>
Expand Down Expand Up @@ -304,7 +316,7 @@ function stopCountdownManually() {
</tr>
</thead>
<tbody>
<?php foreach ( $fs_active_plugins->plugins as $sdk_path => $data ) : ?>
<?php foreach ( $fs_plugins as $sdk_path => $data ) : ?>
<?php $is_active = ( WP_FS__SDK_VERSION == $data->version ) ?>
<tr<?php if ( $is_active ) {
echo ' style="background: #E6FFE6; font-weight: bold"';
Expand Down Expand Up @@ -372,17 +384,24 @@ function stopCountdownManually() {
<?php
$fs = null;
if ( $is_active ) {
$fs = freemius( $data->id );

$active_modules_by_id[ $data->id ] = true;
if ( function_exists( 'freemius' ) ) {
$fs = freemius( $data->id );
} elseif ( class_exists( 'Freemius' ) ) {
// Fallback for deferred loads where the helper function may not be defined yet.
// We have $slug from the loop key.
$fs = Freemius::instance( $data->id, $slug, true );
}
if ( is_object( $fs ) ) {
$active_modules_by_id[ $data->id ] = true;
}
}
?>
<tr<?php if ( $alternate ) { echo ' class="alternate" '; } ?><?php if ( $is_active ) {
$has_api_connectivity = $fs->has_api_connectivity();
$has_api_connectivity = is_object( $fs ) ? $fs->has_api_connectivity() : null;

if ( true === $has_api_connectivity && $fs->is_on() ) {
if ( true === $has_api_connectivity && is_object( $fs ) && $fs->is_on() ) {
echo ' style="background: #E6FFE6; font-weight: bold"';
} else if ( false === $has_api_connectivity || ! $fs->is_on() ) {
} else if ( false === $has_api_connectivity || ( is_object( $fs ) && ! $fs->is_on() ) ) {
echo ' style="background: #ffd0d0; font-weight: bold"';
}
} ?>>
Expand All @@ -400,13 +419,11 @@ function stopCountdownManually() {
fs_text_x_inline( 'No requests yet', 'API connectivity state is unknown' ) )
);
} ?></td>
<td<?php if ( $is_active && ! $fs->is_on() ) {
<td<?php if ( $is_active && is_object( $fs ) && ! $fs->is_on() ) {
echo ' style="color: red; text-transform: uppercase;"';
} ?>><?php if ( $is_active ) {
echo esc_html( $fs->is_on() ?
$on_text :
$off_text
);
$is_on = ( is_object( $fs ) && $fs->is_on() );
echo esc_html( $is_on ? $on_text : $off_text );
} ?></td>
<td><?php echo $data->file ?></td>
<td><?php echo $data->public_key ?></td>
Expand All @@ -427,7 +444,7 @@ function stopCountdownManually() {
<?php endif ?>
<td>
<?php if ( $is_active ) : ?>
<?php if ( $fs->has_trial_plan() ) : ?>
<?php if ( is_object( $fs ) && $fs->has_trial_plan() ) : ?>
<form action="" method="POST">
<input type="hidden" name="fs_action" value="simulate_trial">
<input type="hidden" name="module_id" value="<?php echo $fs->get_id() ?>">
Expand All @@ -436,10 +453,10 @@ function stopCountdownManually() {
<button type="submit" class="button button-primary simulate-trial"><?php fs_esc_html_echo_inline( 'Simulate Trial Promotion' ) ?></button>
</form>
<?php endif ?>
<?php if ( $fs->is_registered() ) : ?>
<?php if ( is_object( $fs ) && $fs->is_registered() ) : ?>
<a class="button" href="<?php echo $fs->get_account_url() ?>"><?php fs_esc_html_echo_inline( 'Account', 'account' ) ?></a>
<?php endif ?>
<?php if ( fs_is_network_admin() && ! $fs->is_network_upgrade_mode() ) : ?>
<?php if ( fs_is_network_admin() && is_object( $fs ) && ! $fs->is_network_upgrade_mode() ) : ?>
<form action="" method="POST">
<input type="hidden" name="fs_action" value="simulate_network_upgrade">
<input type="hidden" name="module_id" value="<?php echo $fs->get_id() ?>">
Expand Down Expand Up @@ -948,4 +965,4 @@ class="dashicons dashicons-download"></i> <?php fs_esc_html_echo_inline( 'Downlo

$( '.fs-debug-table-toggle-button:last' ).click();
} );
</script>
</script>
Loading