2026-02-05 17:08:59 +03:00

947 lines
35 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
defined('WPINC') or die(); // No Direct Access
/**
* Class OnecomMarketplace
* Generalized plugin activation logic for any plugin via plugin_slug
*/
#[\AllowDynamicProperties]
class OnecomMarketplace {
const EXPIRATION_TIME_IN_MINUTES = 5; // 5-minute
const PLUGIN_HANDLE = array(
'wp-rocket' => 'wp-rocket/wp-rocket.php',
'rank-math' => 'seo-by-rank-math-pro/rank-math-pro.php',
);
const ADDONS_SLUGS = array(
'wp-rocket' => 'WP_ROCKET',
'rank-math' => 'RANK_MATH'
);
const PLUGIN_SLUGS_NAME = array(
'wp-rocket' => 'WP Rocket',
'rank-math' => 'Rank Math PRO'
);
CONST ITEM_CATEGORY = array(
'wp-rocket' => 'performance',
'rank-math' => 'seo'
);
CONST PLUGIN_SLUGS = array(
'wp-rocket' => 'wp-rocket',
'rank-math' => 'seo-by-rank-math-pro'
);
public array $contact_support_links = array(
'en' => 'https://help.one.com/hc/en-us/requests/new',
'da' => 'https://help.one.com/hc/da/requests/new',
'de' => 'https://help.one.com/hc/de/requests/new',
'es' => 'https://help.one.com/hc/es/requests/new',
'fr' => 'https://help.one.com/hc/fr/requests/new',
'fi' => 'https://help.one.com/hc/fi/requests/new',
'it' => 'https://help.one.com/hc/it/requests/new',
'nl' => 'https://help.one.com/hc/nl/requests/new',
'no' => 'https://help.one.com/hc/no/requests/new',
'pt' => 'https://help.one.com/hc/pt/requests/new',
'sv' => 'https://help.one.com/hc/sv/requests/new'
);
/**
* Constructor is intentionally empty. Initialization is handled via init().
* @note This class is designed for use with WordPress hooks and AJAX actions, so no setup is required here.
*/
public function __construct() {
}
// Initialize actions
public function init(): void
{
add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']);
add_action('wp_ajax_marketplace_plugin_activate', [$this, 'onclickPluginActivate']);
add_action('wp_ajax_marketplace_plugin_activate_reload', [$this, 'onReloadPluginActivateCheck']);
add_action('wp_ajax_marketplace_addon_purchase_check', [$this, 'addonStatusCheck']);
add_action('wp_ajax_marketplace_addon_purchase_check_onload', [$this, 'addonStatusCheckOnLoad']);
//Check addon activation banner on products page reload
add_action('wp_ajax_marketplace_check_activate_banner', [$this, 'checkActivateBannerOnReload']);
//Can use in marketplace service also to check addon purchase status
add_action('wp_ajax_get_addon_purchase_status', [$this, 'isAddonPurchased']);
//disable activation banner on plugin deactivation
add_action('deactivated_plugin', [$this, 'pluginDeactivated'], 10, 2);
}
// Load scripts on relevant admin pages
public function enqueueScripts($hook_suffix): void
{
$allowed_pages = [
'one-com_page_' . MARKETPLACE_PAGE_SLUG,
'one-com_page_' . MARKETPLACE_PRODUCTS_PAGE_SLUG,
];
if (!in_array($hook_suffix, $allowed_pages)) {
return;
}
wp_enqueue_style('oc_mp_style', ONECOM_WP_URL . 'modules/marketplace/assets/css/marketplace.css', [], ONECOM_WP_VERSION);
wp_enqueue_script('oc_mp_script', ONECOM_WP_URL . 'modules/marketplace/assets/js/marketplace.js', ['jquery'], ONECOM_WP_VERSION, true);
// Localize JS with config
wp_localize_script( 'oc_mp_script', 'mp_config', [
'ajaxURL' => admin_url( 'admin-ajax.php' ),
'mp_asset_url' => ONECOM_WP_URL . 'modules/marketplace/assets/images/',
'wp_rocket_buy_url' => OC_WPR_BUY_URL,
'rank_math_buy_url' => OC_RM_PRO_BUY_URL,
'wp_rocket_manage_url' => admin_url('options-general.php?page=wprocket'),
'rank_math_manage_url' => admin_url('admin.php?page=rank-math&view=modules'),
'marketplace_page_slug' => MARKETPLACE_PAGE_SLUG,
'marketplace_products_page_slug' => MARKETPLACE_PRODUCTS_PAGE_SLUG,
'mp_labels'=> array(
'install' => __('ui.notifications.installing', 'onecom-wp'),
'error_installing' => __("Couldnt install plugin.", 'onecom-wp'),
'deactivate' => __('Deactivate', 'onecom-wp'),
'install_success' => __('Plugin installed successfully.', 'onecom-wp'),
'wp_rocket' => __('WP Rocket', 'onecom-wp'),
'rank_math' => __('Rank Math PRO', 'onecom-wp'),
'active' => __('Active', 'onecom-wp'),
'rank-math' => 'seo-by-rank-math-pro',
'wp-rocket' => 'wp-rocket',
'plugin_activated' => array(
'title' => __('Plugin was activated', 'onecom-wp'),
'description' => __('You can start using it.', 'onecom-wp'),
'btn_text' => __('Manage', 'onecom-wp'),
'wp-rocket_link' => $this->getNoticePluginUrl('wp-rocket'),
'rank-math_link' => $this->getNoticePluginUrl('rank-math'),
),
'plugin_installed' => array(
'title' => __('Plugin was installed.', 'onecom-wp'),
'description' => __('Activate it now to start using it.', 'onecom-wp'),
'btn_text' => __('Activate', 'onecom-wp'),
'wp-rocket_link' => $this->getNoticePluginUrl('wp-rocket'),
'rank-math_link' => $this->getNoticePluginUrl('rank-math'),
),
),
] );
}
/**
* Retrieves the admin URL for the settings page of a specific plugin based on the provided addon slug.
*
* @param string $addon_slug The identifier slug of the addon/plugin to retrieve the URL for.
* @return string|null The admin URL for the plugin settings page, or null if the slug does not match a known plugin.
*/
public function getNoticePluginUrl(string $addon_slug): ?string
{
$url = '';
if($addon_slug === 'wp-rocket'){
$url = admin_url('options-general.php?page=wprocket');
}
if($addon_slug === 'rank-math'){
$url = admin_url('admin.php?page=rank-math&view=modules');
}
return $url;
}
/**
* Generates and returns HTML for a success notice indicating that a plugin was activated.
*
* @param string $addon_slug The slug of the add-on/plugin that was activated.
* @return string The HTML for the success notice, including a message, manage button, and close button.
*/
public function getActivatedNotice(string $addon_slug): string
{
$manageUrl = $this->getNoticePluginUrl($addon_slug);
$pluginName = self::PLUGIN_SLUGS_NAME[$addon_slug] ?? '';
$page = isset($_POST['page']) ? sanitize_text_field($_POST['page']) : '';
$hiddenClass = '';
//Below is for onecom-marketplace-products page success banner
if($page === 'onecom-marketplace-products'){
$title = __('{name} is active', 'onecom-wp');
$notice_title = str_replace('{name}', $pluginName, $title);
$description = __('{name} was successfully activated on this installation.', 'onecom-wp');
$notice_description = str_replace('{name}', $pluginName, $description);
$button_key = __('Go to {name}', 'onecom-wp');
$button_text = str_replace('{name}', $pluginName, $button_key);
$button_event = 'ocwp_ocmp_go_to_' . str_replace('-', '_', $addon_slug). '_clicked_event';
$hiddenClass = 'class="gv-hidden"';
} else {
$title = __('ui.notifications.pluginActivated', 'onecom-wp');
$notice_title = str_replace('{0}', $pluginName, $title);
$description = __('ui.notifications.manageInMyProducts', 'onecom-wp');
$notice_description = str_replace('{0}', $pluginName, $description);
$button_text = __('Get started', 'onecom-wp');
$button_event = 'ocwp_ocmp_get_started_' . str_replace('-', '_', $addon_slug). '_clicked_event';
}
ob_start();
?>
<!-- Notice success -->
<div class="gv-notice gv-notice-success gv-p-lg gv-max-mob-pt-lg gv-mb-0 gv-mt-lg wpr-notice gv-w-full">
<img class="gv-notice-icon" src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/success.svg" alt="<?php echo __('Success', 'onecom-wp'); ?>"/>
<div class="gv-notice-content">
<div class="gv-notice-title"><?php echo $notice_title;?></div>
<p><?php echo $notice_description; ?></p>
</div>
<a href="<?php echo $manageUrl;?>"
class="gv-action gv-button gv-button-neutral <?php echo $button_event;?>" target="_blank"><span class="gv-pr-sm gv-text-on-default "><?php echo $button_text;?></span>
<img src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/black-arrow.svg" height="15px" width="15px" <?php echo $hiddenClass;?> alt="<?php echo $button_text; ?>"/>
</a>
<button class="gv-notice-close">
<img src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/close.svg" height="24px" width="24px" alt="<?php echo __('Close', 'onecom-wp'); ?>"/>
</button>
</div>
<!-- Notice success End -->
<div class="gv-hidden mp-primary-manage-button">
<a href="<?php echo $manageUrl;?>"
class="gv-action gv-button gv-button-primary" target="_blank"><span class="gv-pr-sm"><?php echo __('ui.labels.manage', 'onecom-wp');?></span>
<img src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/white-arrow.svg" height="15px" width="15px" alt="<?php echo __('ui.labels.manage', 'onecom-wp'); ?>"/>
</a>
</div>
<?php
return ob_get_clean();
}
/**
* Check if a plugin is installed by its slug
* @param $plugin_slug
* @return bool
*/
public function isPluginInstalled($plugin_slug): bool {
wp_clean_plugins_cache();
$plugins = get_plugins();
$plugin_handle = self::PLUGIN_HANDLE[$plugin_slug] ?? '';
return array_key_exists( $plugin_handle, $plugins );
}
/**
* Plugin activates on the button click
* @return void
*/
public function onclickPluginActivate(): void
{
$plugin_slug = sanitize_text_field($_POST['plugin_slug']);
//override for seo-by-rank-math-pro activation if needed
if($plugin_slug === 'seo-by-rank-math-pro'){
$plugin_slug = 'rank-math';
}
$transient_key = "{$plugin_slug}_activation_button_clicked_at";
set_site_transient($transient_key, current_time('timestamp'), self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS);
$this->activateWpPlugin($plugin_slug);
}
/**
* Addon status check on the button click
* @return void
*/
public function addonStatusCheck(): void
{
$plugin_slug = sanitize_text_field($_POST['plugin_slug']);
$transient_key = "{$plugin_slug}_select_button_clicked_at";
set_site_transient($transient_key, current_time('timestamp'), self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS);
$this->checkAddonPurchaseStatus($plugin_slug);
}
/**
* Check addon status on page load
* @return void
*/
public function addonStatusCheckOnLoad(): void
{
$plugin_slug = sanitize_text_field($_POST['plugin_slug']);
$this->checkAddonPurchaseStatus($plugin_slug);
}
/**
* Check if activate banner should be shown on products page reload
* This checks if addon is purchased but activation is not in progress
* @return void
*/
public function checkActivateBannerOnReload(): void
{
$plugin_slug = sanitize_text_field($_POST['plugin_slug']);
//override for seo-by-rank-math-pro activation if needed
if($plugin_slug === 'seo-by-rank-math-pro'){
$plugin_slug = 'rank-math';
}
$option_name = 'plugin_deactivated_' . sanitize_key($plugin_slug);
//Return if plugin already deactivated manually
if(get_option($option_name)){
wp_send_json([
'status' => 'plugin_deactivated_manually',
'addon_slug' => $plugin_slug,
'show_banner' => false
]);
}
$plugin_handle = self::PLUGIN_HANDLE[$plugin_slug] ?? '';
// Check if plugin is already active
if ($plugin_handle && function_exists('is_plugin_active') && is_plugin_active($plugin_handle)) {
wp_send_json([
'status' => 'plugin_already_active',
'addon_slug' => $plugin_slug,
'show_banner' => false
]);
}
// Check if activation is in progress
$activation_button_clicked = get_site_transient("{$plugin_slug}_activation_button_clicked_at");
$activation_start_at = get_site_transient("{$plugin_slug}_activation_start_at");
$pp_activation_start = get_site_transient("{$plugin_slug}-pp-activation-start-at");
$activation_in_progress = $activation_button_clicked || $activation_start_at || $pp_activation_start;
if ($activation_in_progress) {
error_log("[MP_ONECOM_PLUGIN] Activation in progress for {$plugin_slug}, not showing activate banner");
wp_send_json([
'status' => 'activation_in_progress',
'addon_slug' => $plugin_slug,
'show_banner' => false
]);
}
// Get addon purchase status and createdAt
$addonStatus = $this->isAddonPurchased($plugin_slug, true);
// Check if addon is purchased
if ($addonStatus['is_active']) {
$createdAt = $addonStatus['addon_info']['data']['createdAt'] ?? null;
if ($createdAt) {
$createdAtTimestamp = strtotime($createdAt);
$currentTimestamp = current_time('timestamp');
$daysSincePurchase = ($currentTimestamp - $createdAtTimestamp) / DAY_IN_SECONDS;
// Only show banner if purchased within last 30 days
if ($daysSincePurchase > 30) {
error_log("[MP_ONECOM_PLUGIN] Addon purchased more than 30 days ago for {$plugin_slug}, not showing activate banner");
wp_send_json([
'status' => 'purchase_too_old',
'addon_slug' => $plugin_slug,
'show_banner' => false,
'createdAt' => $createdAt
]);
}
//If createdAt is less than 1 month from today, show activate banner
error_log("[MP_ONECOM_PLUGIN] Addon purchased but not activated for {$plugin_slug}, showing activate banner");
wp_send_json([
'status' => 'show_activate_banner',
'addon_slug' => $plugin_slug,
'show_banner' => true,
'banner_html' => $this->getActivateBanner($plugin_slug),
'createdAt' => $createdAt
]);
}
}
// Default case - don't show banner
wp_send_json([
'status' => 'addon_not_purchased',
'addon_slug' => $plugin_slug,
'show_banner' => false
]);
}
/**
* Check plugin activation on reload
* @return void
*/
public function onReloadPluginActivateCheck(): void
{
$plugin_slug = sanitize_text_field($_POST['plugin_slug']);
//override for seo-by-rank-math-pro activation if needed
if($plugin_slug === 'seo-by-rank-math-pro'){
$plugin_slug = 'rank-math';
}
$this->activateWpPlugin($plugin_slug);
}
/**
* Call WP API Provisioner for plugin install/activation (generic for any plugin slug)
*/
public function callWpApiProvisioner($plugin_slug): string
{
if (!$this->isAddonPurchased($plugin_slug)) {
error_log('[MP_ONECOM_PLUGIN] addon_not_subscribed, skipping WP API Provisioner call from marketplace: ' . $plugin_slug);
return 'addon_not_subscribed';
}
error_log("[MP_ONECOM_PLUGIN] Request plugin activation from marketplace: $plugin_slug");
$pp_start_at = "$plugin_slug-pp-activation-start-at";
$start_time = get_site_transient($pp_start_at);
if ($start_time) {
error_log("[MP_ONECOM_PLUGIN] The provisioning request has already been sent; skipping the re-request: " . $plugin_slug);
return 'already_in_queue';
}
$subdomain = OCPushStats::get_subdomain();
$domain = OCPushStats::get_domain();
error_log("[MP_ONECOM_PLUGIN] Calling WP API Provisioner for plugin from marketplace ".MIDDLEWARE_URL.": $subdomain.$domain" . $plugin_slug);
if (function_exists('is_cluster_domain') && is_cluster_domain()) {
error_log("[MP_ONECOM_PLUGIN] Calling WP API Provisioner for cluster domain");
$url = MIDDLEWARE_URL . '/plugin-provisioner/cluster';
} else {
error_log("[MP_ONECOM_PLUGIN] Calling WP API Provisioner for legacy domain");
$url = function_exists('onecom_query_check') ? onecom_query_check(MIDDLEWARE_URL . '/plugin-provisioner') : MIDDLEWARE_URL . '/plugin-provisioner';
}
add_filter('http_request_args', 'oc_add_http_headers', 10, 2);
wp_remote_post(
$url,
array(
'body' => json_encode(array(
'subdomain' => $subdomain,
'domain' => $domain,
'addon_slug' => $plugin_slug
))
)
);
remove_filter('http_request_args', 'oc_add_http_headers');
set_site_transient($pp_start_at, current_time('timestamp'), self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS);
return 'added_to_queue';
}
/**
* Validate addon purchase status
* @param $addon_info
* @param $addon_const
* @return bool
*/
public function validateAddonPurchase($addon_info, $addon_const): bool
{
return
is_array($addon_info) &&
array_key_exists('success', $addon_info) &&
$addon_info['success'] &&
array_key_exists('data', $addon_info) &&
array_key_exists('source', $addon_info['data']) &&
$addon_info['data']['source'] === 'PURCHASED' &&
array_key_exists('product', $addon_info['data']) &&
$addon_info['data']['product'] === $addon_const
;
}
/**
* Check if addon is purchased for any plugin slug
* @param string $plugin_slug
* @return bool
*/
public function isAddonPurchased(string $plugin_slug, $getAddonInfo = false): bool | array
{
// Allow override via AJAX request
$isMPCall = false;
//Added handle for ajax call to check addon purchase status from marketplace service
if(isset($_POST['addon_purchase_check']) && $_POST['addon_purchase_check'] === 'true' && isset($_POST['addon_slug'])){
$plugin_slug = sanitize_text_field($_POST['addon_slug']);
//override for seo-by-rank-math-pro activation if needed
if($plugin_slug === 'seo-by-rank-math-pro'){
$plugin_slug = 'rank-math';
}
//Ensure plugin slug should be valid
$isMPCall = true;
}
// Fetch info for the given plugin slug
$addon_const = self::ADDONS_SLUGS[$plugin_slug] ?? '';
$addon_info = $this->getAddonInfo($plugin_slug, true);
//Send ajax response if called from marketplace service
if ($isMPCall) {
wp_send_json([
'is_purchased' => $this->validateAddonPurchase($addon_info, $addon_const),
'addon_slug' => $plugin_slug
]);
}
if($getAddonInfo){
return [
'is_active' => $this->validateAddonPurchase($addon_info, $addon_const),
'addon_info' => $addon_info
];
}
return $this->validateAddonPurchase($addon_info, $addon_const);
}
/**
* Get addon info for a plugin slug (should be implemented to fetch actual info)
* @param string $plugin_slug
* @param bool $force
* @param string $domain
* @return array
*/
public function getAddonInfo(string $plugin_slug, $force = false, $domain = '' ) {
$addon_slug = self::ADDONS_SLUGS[$plugin_slug] ?? '';
// check transient
$addon_info_transient_key = "{$plugin_slug}_onecom_addon_info";
$addon_info = get_site_transient( $addon_info_transient_key);
if ( ! empty( $addon_info ) && false === $force ) {
return $addon_info;
}
if ( ! $domain ) {
$domain = isset( $_SERVER['ONECOM_DOMAIN_NAME'] ) ? $_SERVER['ONECOM_DOMAIN_NAME'] : false;
}
if ( ! $domain ) {
return array(
'data' => null,
'error' => 'Empty domain',
'success' => false,
);
}
return $this->callWPApiForAddon($addon_slug, $domain, $addon_info_transient_key);
}
/**
* Call WP API for addon info
* @param $addon_slug
* @param $domain
* @param $addon_info_transient_key
* @return array|mixed
*/
public function callWPApiForAddon($addon_slug, $domain, $addon_info_transient_key): mixed
{
$totp = oc_generate_totp();
// headers and api url based on cluster domain or not
if ( is_cluster_domain() ) {
//create a header for a cluster model
$curl_url = MIDDLEWARE_URL . "/features/addon/$addon_slug/status/cluster";
$http_header = array(
'Cache-Control: no-cache',
'X-Onecom-Client-Domain: ' . $domain,
'X-TOTP: ' . $totp,
'cache-control: no-cache',
);
$http_header[] = 'X-ONECOM-CLUSTER-ID: ' . OC_CLUSTER_ID;
$http_header[] = 'X-ONECOM-WEBCONFIG-NAME: ' . $_SERVER['HTTP_X_GROUPONE_WEBCONFIG_NAME'];
} else {
//prepare headers for a domain model
$curl_url = MIDDLEWARE_URL . "/features/addon/$addon_slug/status";
$http_header = array(
'Cache-Control: no-cache',
'X-Onecom-Client-Domain: ' . $domain, //need to use from wp-config if available otherwise use domain parse
'X-TOTP: ' . $totp,
'cache-control: no-cache',
);
}
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $curl_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => $http_header
)
);
$response = curl_exec( $curl );
$response = json_decode( $response, true );
$err = curl_error( $curl );
curl_close( $curl );
if ( $err ) {
return array(
'data' => null,
'error' => __( 'Some error occurred, please reload the page and try again.', 'validator' ),
'success' => false,
);
} else {
// save transient for next calls, & return latest response
set_site_transient( $addon_info_transient_key, $response, 12 * HOUR_IN_SECONDS );
return $response;
}
}
/**
* Activate plugin via WP API Provisioner
* @param $plugin_slug
* @return void
*/
public function activateWpPlugin($plugin_slug): void
{
$start_time_key = "{$plugin_slug}_activation_start_at";
$btn_transient_key = "{$plugin_slug}_activation_button_clicked_at";
$timeout_limit = self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS;
$current_time = current_time('timestamp');
$plugin_handle = self::PLUGIN_HANDLE[$plugin_slug] ?? '';
$install_stats_key = "mp_{$plugin_slug}_install_logged";
//Check if the activation button was clicked
if (!get_site_transient($btn_transient_key)) {
error_log('[MP_ONECOM_PLUGIN] Activation button not clicked, normal reload for plugin: ' . $plugin_slug);
wp_send_json(['status' => 'normal_reload', 'addon_slug' => $plugin_slug]);
}
$send_success = function() use ($install_stats_key, $plugin_slug) {
error_log("[MP_ONECOM_PLUGIN] Plugin activated successfully from Marketplace: {$plugin_slug}");
delete_site_transient( $install_stats_key );
//Success activation stats log
( class_exists( OCPUSHSTATS ) ? \OCPushStats::push_stats_event_themes_and_plugins('activate', self::ITEM_CATEGORY[$plugin_slug], self::PLUGIN_SLUGS[$plugin_slug], 'onecom-marketplace') : '' );
$this->clearActivationQueue($plugin_slug);
wp_send_json([
'status' => 'activated',
'url' => admin_url('plugins.php'),
'btn_text' => __('Go to plugin', 'onecom-wp'),
'addon_slug' => $plugin_slug,
'notice_html' => $this->getActivatedNotice($plugin_slug)
]);
};
//Add only single install stats log
if (
$this->isPluginInstalled($plugin_slug)
&& false === get_site_transient($install_stats_key)
&& set_site_transient($install_stats_key, 1, 5 * MINUTE_IN_SECONDS)
){
( class_exists( OCPUSHSTATS ) ? \OCPushStats::push_stats_event_themes_and_plugins('install', self::ITEM_CATEGORY[$plugin_slug], self::PLUGIN_SLUGS[$plugin_slug], 'onecom-marketplace') : '' );
error_log("[MP_ONECOM_PLUGIN] Install stats logged for: {$plugin_slug}");
}
//More shield check before activation
if ( $plugin_handle && function_exists( 'is_plugin_active' ) && is_plugin_active( $plugin_handle ) ) {
$send_success();
}
$start_time = get_site_transient($start_time_key);
if (!$start_time) {
error_log('[MP_ONECOM_PLUGIN] Starting plugin activation process from marketplace: ' . $plugin_slug);
set_site_transient($start_time_key, $current_time, $timeout_limit);
if ($this->isPluginInstalled($plugin_slug)) {
error_log('[MP_ONECOM_PLUGIN] Plugin was installed activating only from wp-admin'.$plugin_handle .', Slug:'. $plugin_slug);
$result = activate_plugin($plugin_handle);
if (is_wp_error($result)) {
error_log("[MP_ONECOM_PLUGIN] Plugin activation failed from Marketplace: {$plugin_slug}");
$this->clearActivationQueue($plugin_slug);
//Delete install stats transient to allow re-log on next attempt
delete_site_transient( $install_stats_key );
wp_send_json_error([
'status' => 'activation_failed',
'message' => $result->get_error_message(),
'addon_slug' => $plugin_slug,
'try_again_banner' => $this->getTryAgainBanner($plugin_slug)
]);
}
$send_success();
}
//First call to WP API Provisioner for installation/activation
if (!is_plugin_active($plugin_handle)) {
$status = $this->callWpApiProvisioner($plugin_slug);
error_log('[MP_ONECOM_PLUGIN] Provisioner status: ' . $status);
wp_send_json(['status' => $status, 'addon_slug' => $plugin_slug]);
}
}
//Check status every interval
if (($current_time - (int)$start_time) >= $timeout_limit) {
if (is_plugin_active($plugin_handle)) {
$send_success();
}
error_log("[MP_ONECOM_PLUGIN] Plugin not activated and timed out from marketplace: {$plugin_slug}");
$this->clearActivationQueue($plugin_slug);
//Delete install stats transient to allow re-log on next attempt
delete_site_transient( $install_stats_key );
wp_send_json(['status' => 'expired_queue', 'addon_slug' => $plugin_slug, 'try_again_banner' => $this->getTryAgainBanner($plugin_slug)]);
}
error_log("[MP_ONECOM_PLUGIN] Plugin activation in progress: {$plugin_slug}");
wp_send_json(['status' => 'already_in_queue', 'addon_slug' => $plugin_slug]);
}
/**
* Check addon purchase status
* @param $plugin_slug
* @return void
*/
public function checkAddonPurchaseStatus($plugin_slug): void
{
$start_time_key = "{$plugin_slug}_purchase_button_start_at";
$btn_transient_key = "{$plugin_slug}_select_button_clicked_at";
$timeout_limit = self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS;
$current_time = current_time('timestamp');
$plugin_handle = self::PLUGIN_HANDLE[$plugin_slug] ?? '';
if (!get_site_transient($btn_transient_key)) {
wp_send_json(['status' => 'normal_reload', 'addon_slug' => $plugin_slug]);
}
$send_success = function() use ($plugin_slug) {
error_log("[MP_ONECOM_PLUGIN] Addon purchased from Marketplace: {$plugin_slug}");
//Success activation stats log
( class_exists( OCPUSHSTATS ) ? \OCPushStats::push_stats_event_themes_and_plugins('addon_purchased', self::ITEM_CATEGORY[$plugin_slug], self::PLUGIN_SLUGS[$plugin_slug], 'onecom-marketplace') : '' );
$this->clearAddonStatusQueue($plugin_slug);
$this->setTransientForAddonActivation($plugin_slug);
wp_send_json(['status' => 'addon_purchased', 'addon_slug' => $plugin_slug]);
};
//Check if the plugin is already active, a rare case but possible
if (is_plugin_active($plugin_handle)) {
error_log("[MP_ONECOM_PLUGIN] Plugin active during addon status check: {$plugin_slug}, skipping purchase check");
$send_success();
}
$start_time = get_site_transient($start_time_key);
// Case 1: First time select click attempt for purchase
if (!$start_time) {
set_site_transient($start_time_key, $current_time, $timeout_limit);
wp_send_json(['status' => 'added_in_queue', 'addon_slug' => $plugin_slug]);
}
//get addon purchase status, force refresh feature endpoint
//call feature endpoint to get latest addon status
$addon_info = $this->getAddonInfo($plugin_slug,true);
$addon_const = self::ADDONS_SLUGS[$plugin_slug] ?? '';
$addon_purchased = $this->validateAddonPurchase($addon_info, $addon_const);
if ($addon_purchased) {
$send_success();
}
$elapsed_time = $current_time - (int)$start_time;
$time_left = $timeout_limit - $elapsed_time;
// Case 2: Stop polling early if less than 30 seconds left
if ($time_left <= 30) {
if ($addon_purchased) {
$send_success();
}
error_log("[MP_ONECOM_PLUGIN] Polling stopped early (time left: {$time_left}s) for {$plugin_slug}");
$this->clearAddonStatusQueue($plugin_slug);
wp_send_json(['status' => 'expired_queue', 'addon_slug' => $plugin_slug]);
}
// Case 3: Queue expired after timeout
if ($elapsed_time >= $timeout_limit) {
if ($addon_purchased) {
$send_success();
}
error_log("[MP_ONECOM_PLUGIN] Addon not purchased and timed out: {$plugin_slug}");
$this->clearAddonStatusQueue($plugin_slug);
wp_send_json(['status' => 'expired_queue', 'addon_slug' => $plugin_slug]);
}
// Case 4: Queue still in progress
error_log("[MP_ONECOM_PLUGIN] Addon purchase in progress: {$plugin_slug}");
wp_send_json(['status' => 'already_in_queue', 'addon_slug' => $plugin_slug]);
}
/**
* Set transient for addon activation
* @param $plugin_slug
* @return void
*/
public function setTransientForAddonActivation($plugin_slug): void
{
$activation_button_clicked_at = "{$plugin_slug}_activation_button_clicked_at";
$pp_activation_start_at = "{$plugin_slug}-pp-activation-start-at";
$activation_start_at = "{$plugin_slug}_activation_start_at";
$timeout_limit = self::EXPIRATION_TIME_IN_MINUTES * MINUTE_IN_SECONDS;
$current_time = current_time('timestamp');
set_site_transient($activation_start_at, $current_time, $timeout_limit);
set_site_transient($activation_button_clicked_at, $current_time, $timeout_limit);
set_site_transient($pp_activation_start_at, $current_time, $timeout_limit);
}
/**
* Clears the activation queue for a specified plugin.
*
* @param string $plugin_slug The slug of the plugin for which the activation queue should be cleared.
* @return void
*/
public function clearActivationQueue(string $plugin_slug): void
{
delete_site_transient("{$plugin_slug}_activation_start_at");
delete_site_transient("{$plugin_slug}_activation_button_clicked_at");
delete_site_transient("{$plugin_slug}-pp-activation-start-at");
if (function_exists('wp_cache_flush')) {
wp_cache_flush();
}
}
/**
* Clears the addon status queue by removing associated transients for a given plugin and flushing cache if available.
*
* @param string $plugin_slug The slug of the plugin whose addon status queue should be cleared.
* @return void
*/
public function clearAddonStatusQueue(string $plugin_slug): void
{
delete_site_transient("{$plugin_slug}_purchase_button_start_at");
delete_site_transient("{$plugin_slug}_select_button_clicked_at");
if (function_exists('wp_cache_flush')) {
wp_cache_flush();
}
}
public function getTryAgainBanner($plugin_slug): string
{
$notice_title = __('ui.notifications.activationFailedTitle','onecom-wp');
$notice_title = str_replace('{plugin name}', self::PLUGIN_SLUGS_NAME[$plugin_slug] ?? '', $notice_title);
$support_link = $this->get_contact_support_link();
$notice_desc = __('ui.notifications.activationFailedText', 'onecom-wp');
$notice_desc = str_replace('{plugin name}', self::PLUGIN_SLUGS_NAME[$plugin_slug] ?? '', $notice_desc);
$message = sprintf($notice_desc,
'<a class="gv-inline-block" href="'.$support_link.'" target="_blank"><span class="gv-text-on-default" style="text-decoration: underline">',
'</span></a>'
);
ob_start();
?>
<!-- Notice error -->
<div class="gv-notice gv-notice-alert gv-p-lg gv-max-mob-pt-lg gv-mb-0 gv-mt-lg gv-w-full">
<img class="gv-notice-icon" src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/error.svg" />
<div class="gv-notice-content">
<div class="gv-notice-title"><?php echo $notice_title;?></div>
<p class="gv-text-sm"><?php echo $message;?></p>
</div>
<a href="javascript:void(0)"
class="gv-button gv-button-neutral ocwp_ocmp_try_again_<?php echo str_replace('-', '_',$plugin_slug );?>_clicked_event" id="try-again-<?php echo $plugin_slug;?>"><span class="gv-text-on-default"><?php echo __( 'Try again', 'onecom-wp' ); ?></span></a>
<button class="gv-notice-close">
<img src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/close.svg" height="24px" width="24px" />
</button>
</div>
<!-- Notice error End -->
<?php
return ob_get_clean();
}
public function get_contact_support_link(): string {
$locale = explode( '_', get_locale() )[0];
if ( ! array_key_exists( $locale, $this->contact_support_links ) ) {
$locale = 'en';
}
return $this->contact_support_links[ $locale ];
}
public function getActivateBanner($addon_slug): string{
$domain = OCPushStats::get_domain() ?? 'localhost';
$domainBold = "<strong>$domain</strong>";
$title = str_replace('{name}', self::PLUGIN_SLUGS_NAME[$addon_slug], __('Activate {name}', 'onecom-wp'));
if($addon_slug == 'wp-rocket') {
$desc = str_replace(
['{name}', '{domain.one}'],
[self::PLUGIN_SLUGS_NAME[$addon_slug], $domainBold],
__("You have a {name} subscription for {domain.one}, but you still need to activate it for the installation on {domain.one}. Activate the plugin to boost your site's performance.", 'onecom-wp')
);
}
if($addon_slug == 'rank-math') {
$desc = str_replace(
['{name}', '{domain}'],
[self::PLUGIN_SLUGS_NAME[$addon_slug], $domainBold],
__("You have a {name} subscription for {domain}, but you still need to activate it for the installation on {domain}. Activate the plugin to start improving your site's SEO.", 'onecom-wp')
);
}
// Set button classes based on addon slug
$button_class = 'oc-activate-' . $addon_slug . '-btn';
$event_class = 'ocwp_ocmp_activate_' . str_replace('-', '_', $addon_slug) . '_clicked_event';
ob_start();
?>
<!-- Notice Warning -->
<div class="gv-notice gv-notice-info gv-p-lg gv-max-mob-pt-lg gv-mb-0 gv-mt-lg activate-notice-banner" data-addon-slug="<?php echo esc_attr($addon_slug); ?>">
<img class="gv-notice-icon" src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/info.svg" />
<div class="gv-notice-content">
<div class="gv-notice-title"><?php echo $title;?></div>
<p><?php echo $desc; ?></p>
</div>
<a href="javascript:void(0)"
class="gv-button gv-button-neutral <?php echo $event_class; ?> <?php echo $button_class; ?>"><?php echo $title; ?></a>
<button class="gv-notice-close">
<img src="<?php echo ONECOM_WP_URL; ?>modules/marketplace/assets/images/close.svg" height="24px" width="24px" />
</button>
</div>
<!-- Notice Warning End -->
<?php
return ob_get_clean();
}
/**
* Set option on premium plugin deactivation
* @param $plugin
* @param $network_deactivating
* @return void
*/
public function pluginDeactivated($plugin, $network_deactivating): void
{
foreach (self::PLUGIN_HANDLE as $handle => $plugin_file) {
if ($plugin !== $plugin_file) {
continue;
}
$option_name = 'plugin_deactivated_' . sanitize_key($handle);
update_option($option_name, [
'time' => time(),
'network' => (bool) $network_deactivating,
]);
break;
}
}
}