1136 lines
33 KiB
PHP
1136 lines
33 KiB
PHP
<?php
|
|
/**
|
|
* License management service.
|
|
*
|
|
* @package PopupMaker
|
|
* @copyright Copyright (c) 2024, Code Atlantic LLC
|
|
*/
|
|
|
|
namespace PopupMaker\Services;
|
|
|
|
use PopupMaker\Base\Service;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* Pro license management.
|
|
*
|
|
* NOTE: For wordpress.org admins: This is only used if:
|
|
* - The user explicitly entered a Pro license key.
|
|
*
|
|
* @package PopupMaker
|
|
*/
|
|
class License extends Service {
|
|
|
|
/**
|
|
* EDD API URL.
|
|
*
|
|
* @var string
|
|
*/
|
|
const API_URL = 'https://wppopupmaker.com/edd-sl-api/';
|
|
|
|
/**
|
|
* Item ID.
|
|
*
|
|
* @var int
|
|
*/
|
|
const ID = 480187;
|
|
|
|
/**
|
|
* Option key.
|
|
*
|
|
* @var string
|
|
*/
|
|
const OPTION_KEY = 'popup_maker_license';
|
|
|
|
/**
|
|
* Settings key.
|
|
*
|
|
* @var string
|
|
*/
|
|
const SETTINGS_KEY = 'popup_maker_pro_license_key';
|
|
|
|
/**
|
|
* Loss delay transient.
|
|
*
|
|
* @var string
|
|
*/
|
|
const DELAY_TRANSIENT = 'popup_maker_license_loss_delay';
|
|
|
|
/**
|
|
* Loss check transient.
|
|
*
|
|
* @var string
|
|
*/
|
|
const CHECK_TRANSIENT = 'popup_maker_license_loss_check';
|
|
|
|
/**
|
|
* License data
|
|
*
|
|
* @var array{key:string|null,status:array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null,auto_activation?:array{enabled:bool,first_activated:string|null,last_seen:string|null,key_missing_since:string|null,key_hash:string|null}}|null
|
|
*/
|
|
private $license_data;
|
|
|
|
/**
|
|
* Initialize license management.
|
|
*
|
|
* @param \PopupMaker\Plugin\Core $container Plugin container.
|
|
*/
|
|
public function __construct( $container ) {
|
|
parent::__construct( $container );
|
|
|
|
$this->register_hooks();
|
|
}
|
|
|
|
/**
|
|
* Register hooks.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_hooks() {
|
|
add_action( 'init', [ $this, 'autoregister' ] );
|
|
add_action( 'popup_maker_license_status_check', [ $this, 'refresh_license_status' ] );
|
|
add_action( 'admin_init', [ $this, 'schedule_crons' ] );
|
|
add_filter( 'pum_settings_editor_args', [ $this, 'filter_settings_editor_args' ] );
|
|
}
|
|
|
|
/**
|
|
* Autoregister license with simplified security approach.
|
|
*
|
|
* For auto-activated licenses:
|
|
* - Never stores the actual key in database (uses placeholder)
|
|
* - Simple detection and setup without complex monitoring
|
|
* - Self-healing: automatically corrects DB state if needed
|
|
* - Detects loss of auto-activation for debugging
|
|
*
|
|
* @return void
|
|
*/
|
|
public function autoregister() {
|
|
$constant_key = defined( 'POPUP_MAKER_PRO_LICENSE' ) && '' !== POPUP_MAKER_PRO_LICENSE ? POPUP_MAKER_PRO_LICENSE : false;
|
|
$license_data = $this->get_license_data();
|
|
|
|
// Handle new auto-activation setup.
|
|
if ( $constant_key ) {
|
|
// Clear any existing delay transients when key is detected.
|
|
$this->clear_transients();
|
|
|
|
$this->ensure_auto_activation_setup( $constant_key );
|
|
} else {
|
|
$is_auto_activated = isset( $license_data['auto_activation']['enabled'] );
|
|
$constant_lost = ! empty( $license_data['auto_activation']['constant_lost_at'] );
|
|
|
|
// If auto-activation is enabled and constant is not lost, handle loss.
|
|
if ( $is_auto_activated && ! $constant_lost ) {
|
|
$this->handle_auto_activation_loss_with_delay();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear transients.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function clear_transients(): void {
|
|
delete_transient( self::DELAY_TRANSIENT );
|
|
delete_transient( self::CHECK_TRANSIENT );
|
|
}
|
|
|
|
/**
|
|
* Handle loss of auto-activation constant with transient-based delay.
|
|
*
|
|
* Uses simple transient system to delay loss detection by 5 minutes,
|
|
* preventing false positives during plugin updates and WordPress operations.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function handle_auto_activation_loss_with_delay(): void {
|
|
// If delay transient exists, we're still waiting - do nothing
|
|
if ( get_transient( self::DELAY_TRANSIENT ) ) {
|
|
return;
|
|
}
|
|
|
|
// If check transient doesn't exist, start the delay period (first detection)
|
|
if ( ! get_transient( self::CHECK_TRANSIENT ) ) {
|
|
set_transient( self::DELAY_TRANSIENT, true, 10 );
|
|
set_transient( self::CHECK_TRANSIENT, true, 0 ); // Non-expiring transient
|
|
return;
|
|
}
|
|
|
|
// Delay expired and we still don't have the constant - mark as lost
|
|
$this->handle_auto_activation_loss();
|
|
|
|
// Clean up the check transient
|
|
delete_transient( self::CHECK_TRANSIENT );
|
|
}
|
|
|
|
/**
|
|
* Ensure auto-activation is properly set up with simplified approach.
|
|
*
|
|
* @param string $key The license key from constant.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function ensure_auto_activation_setup( string $key ): void {
|
|
$license_data = $this->get_license_data();
|
|
$db_key = isset( $license_data['key'] ) ? $license_data['key'] : '';
|
|
|
|
// Self-healing: ensure DB has placeholder, not real key
|
|
if ( '***AUTO***' !== $db_key || empty( $license_data['auto_activation']['enabled'] ) ) {
|
|
$license_data = [
|
|
'key' => '***AUTO***',
|
|
'status' => $license_data['status'] ?? null, // Preserve existing status
|
|
'auto_activation' => [
|
|
'enabled' => true,
|
|
],
|
|
];
|
|
|
|
$this->update_license_data( $license_data );
|
|
|
|
// Activate using the constant key if not already active
|
|
if ( ! $this->is_license_active() ) {
|
|
try {
|
|
$this->maybe_activate_license( $key );
|
|
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
|
} catch ( \Exception $e ) {
|
|
// Do nothing on activation failure.
|
|
}
|
|
}
|
|
} elseif ( ! empty( $license_data['auto_activation']['constant_lost_at'] ) ) {
|
|
// Constant was restored - clear the loss timestamp and grace period
|
|
unset( $license_data['auto_activation']['constant_lost_at'] );
|
|
$this->update_license_data( $license_data );
|
|
|
|
// Clear delay transients since constant is back
|
|
$this->clear_transients();
|
|
|
|
// Log restoration for debugging
|
|
\PopupMaker\logging()->info( 'Auto-activation constant POPUP_MAKER_PRO_LICENSE was restored at ' . gmdate( 'Y-m-d H:i:s' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle loss of auto-activation constant.
|
|
*
|
|
* Tracks when auto-activation is lost for debugging and monitoring.
|
|
* Updates the license data with loss information and clears transients.
|
|
* Also clears license status to reflect the loss in UI.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function handle_auto_activation_loss(): void {
|
|
$license_data = $this->get_license_data();
|
|
|
|
// Mark as lost and clear license status so UI reflects the change
|
|
$license_data['auto_activation']['constant_lost_at'] = gmdate( 'Y-m-d H:i:s' );
|
|
|
|
// Mark as lost and clear license status so UI reflects the change
|
|
$license_data['auto_activation']['enabled'] = false;
|
|
|
|
// Clear the key and status so UI shows inactive.
|
|
$license_data['key'] = '';
|
|
$license_data['status'] = null; // Clear cached status so UI shows inactive
|
|
|
|
// Update the license data.
|
|
$this->update_license_data( $license_data );
|
|
|
|
// Clear the check transient since we've now marked it as lost
|
|
$this->clear_transients();
|
|
|
|
$current_time = gmdate( 'Y-m-d H:i:s' );
|
|
|
|
// Log for debugging
|
|
\PopupMaker\logging()->warning( 'Auto-activation constant POPUP_MAKER_PRO_LICENSE was removed at ' . $current_time );
|
|
}
|
|
|
|
/**
|
|
* Schedule cron jobs.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function schedule_crons() {
|
|
if ( ! wp_next_scheduled( 'popup_maker_license_status_check' ) ) {
|
|
wp_schedule_event( time(), 'weekly', 'popup_maker_license_status_check' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get license data.
|
|
*
|
|
* @return array{key:string|null,status:array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null,auto_activation?:array{enabled:bool,constant_lost_at?:string|null}}
|
|
*/
|
|
public function get_license_data() {
|
|
if ( ! isset( $this->license_data ) ) {
|
|
$this->license_data = \get_option( self::OPTION_KEY, [
|
|
'key' => '',
|
|
'status' => null,
|
|
'auto_activation' => null,
|
|
] );
|
|
}
|
|
|
|
return $this->license_data;
|
|
}
|
|
|
|
/**
|
|
* Update license data.
|
|
*
|
|
* @param array{key:string|null,status:array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null,auto_activation?:array{enabled:bool,constant_lost_at?:string|null}} $license_data License data.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function update_license_data( array $license_data ): bool {
|
|
if ( \update_option( self::OPTION_KEY, $license_data ) ) {
|
|
$this->license_data = $license_data;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get license key for display purposes.
|
|
*
|
|
* For auto-activated licenses, this always returns the constant value
|
|
* or placeholder - never exposes real keys from database.
|
|
*
|
|
* @uses \PopupMaker\Services\License::get_license_data() For source of truth.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_license_key(): string {
|
|
$license_data = $this->get_license_data();
|
|
|
|
// Handle auto-activated licenses securely
|
|
if ( ! empty( $license_data['auto_activation']['enabled'] ) ) {
|
|
// For auto-activated, always return constant or placeholder
|
|
return defined( 'POPUP_MAKER_PRO_LICENSE' ) && ! empty( POPUP_MAKER_PRO_LICENSE )
|
|
? POPUP_MAKER_PRO_LICENSE
|
|
: $license_data['key']; // Will be '***AUTO***' placeholder
|
|
}
|
|
|
|
// Return regular stored key
|
|
return ! empty( $license_data['key'] ) ? $license_data['key'] : '';
|
|
}
|
|
|
|
/**
|
|
* Get raw license key for internal API calls only.
|
|
*
|
|
* This method should NEVER be used for display purposes.
|
|
* For auto-activated licenses, it always retrieves from the constant.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
private function get_raw_license_key(): ?string {
|
|
$license_data = $this->get_license_data();
|
|
|
|
// For auto-activated licenses, always use the constant
|
|
if ( ! empty( $license_data['auto_activation']['enabled'] ) ) {
|
|
return defined( 'POPUP_MAKER_PRO_LICENSE' ) && ! empty( POPUP_MAKER_PRO_LICENSE ) ? POPUP_MAKER_PRO_LICENSE : null;
|
|
}
|
|
|
|
// For regular licenses, use stored key
|
|
return ! empty( $license_data['key'] ) ? $license_data['key'] : null;
|
|
}
|
|
|
|
/**
|
|
* Get license status.
|
|
*
|
|
* @uses \PopupMaker\Services\License::get_license_data() For source of truth.
|
|
*
|
|
* @param bool $refresh Whether to refresh license status.
|
|
*
|
|
* @return array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null
|
|
*/
|
|
public function get_license_status_data( ?bool $refresh = false ) {
|
|
if ( $refresh ) {
|
|
$this->refresh_license_status();
|
|
}
|
|
|
|
$license_data = $this->get_license_data();
|
|
|
|
$license_status = isset( $license_data['status'] ) ? wp_parse_args( $license_data['status'], [
|
|
'success' => false, // bool.
|
|
'license' => 'invalid', // valid or invalid.
|
|
'item_id' => self::ID, // int or false.
|
|
'item_name' => '', // string.
|
|
'license_limit' => 1, // int. 0 = unlimited.
|
|
'site_count' => 0,
|
|
'expires' => '', // date or 'lifetime'.
|
|
'activations_left' => 0, // int or 'unlimited'.
|
|
'checksum' => '', // string.
|
|
'payment_id' => 0, // int.
|
|
'customer_name' => '', // string.
|
|
'customer_email' => '', // string.
|
|
'price_id' => 0, // string or int.
|
|
] ) : null;
|
|
|
|
return $license_status;
|
|
}
|
|
|
|
/**
|
|
* Get license status.
|
|
*
|
|
* @return 'empty'|'inactive'|'expired'|'error'|'valid'
|
|
*/
|
|
public function get_license_status(): string {
|
|
$license_key = $this->get_license_key();
|
|
$status_data = $this->get_license_status_data();
|
|
|
|
if ( empty( $license_key ) || empty( $status_data ) ) {
|
|
return 'empty';
|
|
}
|
|
|
|
$status = 'inactive';
|
|
|
|
// activate_license 'invalid' on anything other than valid, so if there was an error capture it
|
|
if ( false === $status_data['success'] ) {
|
|
$error = isset( $status_data['error'] ) ? $status_data['error'] : $status;
|
|
|
|
switch ( $error ) {
|
|
case 'expired':
|
|
$status = 'expired';
|
|
break;
|
|
case 'revoked':
|
|
case 'missing':
|
|
case 'invalid':
|
|
case 'site_inactive':
|
|
case 'item_name_mismatch':
|
|
case 'no_activations_left':
|
|
case 'license_not_activable':
|
|
default:
|
|
$status = 'error';
|
|
break;
|
|
}
|
|
} elseif ( 'valid' === $status_data['license'] ) {
|
|
$status = 'valid';
|
|
} else {
|
|
$status = 'deactivated';
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Get license level.
|
|
*
|
|
* Only used in pro version.
|
|
*
|
|
* @return int Integer representing license level.
|
|
*/
|
|
public function get_license_level(): int {
|
|
$license_status = $this->get_license_status_data();
|
|
|
|
if ( empty( $license_status ) ) {
|
|
return -1;
|
|
}
|
|
|
|
$price_id = isset( $license_status['price_id'] ) ? $license_status['price_id'] : null;
|
|
|
|
switch ( $price_id ) {
|
|
default:
|
|
return -1;
|
|
|
|
case false:
|
|
case 0:
|
|
return 0;
|
|
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
return absint( $price_id );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get license tier (pro or pro_plus).
|
|
*
|
|
* @return string 'pro' or 'pro_plus' based on license data.
|
|
*/
|
|
public function get_license_tier(): string {
|
|
$license_status = $this->get_license_status_data();
|
|
|
|
if ( empty( $license_status ) ) {
|
|
return 'pro';
|
|
}
|
|
|
|
// Check if license_tier is explicitly set in the license data.
|
|
if ( ! empty( $license_status['license_tier'] ) ) {
|
|
return in_array( $license_status['license_tier'], [ 'pro', 'pro_plus' ], true )
|
|
? $license_status['license_tier']
|
|
: 'pro';
|
|
}
|
|
|
|
// Fallback to 'pro' if not specified.
|
|
return 'pro';
|
|
}
|
|
|
|
/**
|
|
* Update license key.
|
|
*
|
|
* Side Effects:
|
|
* - Update License Data
|
|
* - Reset License Status
|
|
* - Fire action: popup_maker_license_key_changed
|
|
*
|
|
* @param string $key License key.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function update_license_key( string $key ): bool {
|
|
$license_data = $this->get_license_data();
|
|
|
|
$new_key = trim( $key );
|
|
$old_key = isset( $license_data['key'] ) ? $license_data['key'] : '';
|
|
|
|
if ( $old_key === $new_key ) {
|
|
return false;
|
|
}
|
|
|
|
$updated = $this->update_license_data( [
|
|
'key' => $new_key,
|
|
'status' => null,
|
|
] );
|
|
|
|
if ( $updated ) {
|
|
\PUM_Utils_Options::update( self::SETTINGS_KEY, $new_key );
|
|
|
|
/**
|
|
* Fires when license key is changed.
|
|
*
|
|
* @param string $key License key.
|
|
* @param string $old_key Old license key.
|
|
*/
|
|
\do_action( 'popup_maker_license_key_changed', $new_key, $old_key );
|
|
}
|
|
|
|
return $updated;
|
|
}
|
|
|
|
/**
|
|
* Update license status.
|
|
*
|
|
* @param array<string,mixed> $license_status License status data.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function update_license_status( array $license_status ): bool {
|
|
$license_data = $this->get_license_data();
|
|
|
|
$previous_status = isset( $license_data['status'] ) ? $license_data['status'] : [];
|
|
|
|
if ( ! empty( $license_status['error'] ) ) {
|
|
$license_status['error_message'] = $this->get_license_error_message( $license_status );
|
|
} else {
|
|
unset( $license_status['error_message'] );
|
|
unset( $license_status['error'] );
|
|
}
|
|
|
|
$license_data['status'] = $license_status;
|
|
|
|
if ( $this->update_license_data( $license_data ) ) {
|
|
/**
|
|
* Fires when license status is updated.
|
|
*
|
|
* @param array $license_status License status data.
|
|
* @param array $previous_status Previous license status data.
|
|
*/
|
|
\do_action( 'popup_maker_license_status_updated', $license_status, $previous_status );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get license expiration from license status data.
|
|
*
|
|
* @param bool $as_datetime Whether to return as DateTime object.
|
|
*
|
|
* @return string|null|\DateTime
|
|
*/
|
|
public function get_license_expiration( ?bool $as_datetime = false ) {
|
|
$status_data = $this->get_license_status_data();
|
|
|
|
if ( empty( $status_data ) ) {
|
|
return null;
|
|
}
|
|
|
|
$expiration = isset( $status_data['expires'] ) ? $status_data['expires'] : null;
|
|
|
|
return $as_datetime ?
|
|
\DateTime::createFromFormat( 'Y-m-d H:i:s', $expiration ) :
|
|
$expiration;
|
|
}
|
|
|
|
/**
|
|
* Fetch license status from remote server.
|
|
* This is a blocking request.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function refresh_license_status(): bool {
|
|
$key = $this->get_raw_license_key();
|
|
|
|
if ( empty( $key ) ) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$status = $this->check_license_status();
|
|
} catch ( \Exception $e ) {
|
|
$status = null;
|
|
}
|
|
|
|
return $this->update_license_status( $status );
|
|
}
|
|
|
|
/**
|
|
* Call the API.
|
|
*
|
|
* @param string $action The action to call.
|
|
* @param array|null $params The parameters to pass to the API.
|
|
*
|
|
* @return array|null
|
|
*
|
|
* @throws \Exception If there is an error.
|
|
*/
|
|
private function api_call( string $action, ?array $params = null ) {
|
|
$key = $this->get_raw_license_key();
|
|
|
|
if ( empty( $key ) ) {
|
|
return null;
|
|
}
|
|
|
|
$api_params = [
|
|
'edd_action' => $action,
|
|
'license' => $key,
|
|
'item_id' => self::ID,
|
|
'item_name' => rawurlencode( 'Popup Maker Pro' ),
|
|
'url' => home_url(),
|
|
'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production',
|
|
];
|
|
|
|
// Call the custom API.
|
|
$response = wp_remote_post(
|
|
self::API_URL,
|
|
array_merge( $params ?? [], [
|
|
'timeout' => 15,
|
|
'sslverify' => ! in_array( wp_get_environment_type(), [ 'local', 'development' ], true ),
|
|
'body' => $api_params,
|
|
] )
|
|
);
|
|
|
|
// Make sure the response came back okay.
|
|
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
|
if ( is_wp_error( $response ) ) {
|
|
$message = $response->get_error_message();
|
|
} else {
|
|
$message = __( 'An error occurred, please try again.', 'popup-maker' );
|
|
}
|
|
|
|
throw new \Exception( esc_html( $message ) );
|
|
}
|
|
|
|
$license_status = json_decode( wp_remote_retrieve_body( $response ), true );
|
|
|
|
if ( empty( $license_status ) ) {
|
|
return null;
|
|
}
|
|
|
|
// No structure validation Fix: Validate expected fields exist and types match
|
|
|
|
return $license_status;
|
|
}
|
|
|
|
/**
|
|
* Get license status from remote server.
|
|
*
|
|
* Side Effects:
|
|
* - API Call: Check License Status
|
|
*
|
|
* @return array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null
|
|
*
|
|
* @throws \Exception If there is an error.
|
|
*/
|
|
private function check_license_status() {
|
|
return $this->api_call( 'check_license' );
|
|
}
|
|
|
|
/**
|
|
* Activate license.
|
|
*
|
|
* Side Effects:
|
|
* - API Call: Check License Status
|
|
* - Update Pro Activation Date
|
|
* - Update License Status
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \Exception If there is an error.
|
|
*/
|
|
public function activate_license(): bool {
|
|
$license_status = $this->api_call( 'activate_license' );
|
|
|
|
// Bail early if the license status is empty.
|
|
if ( empty( $license_status ) ) {
|
|
return false;
|
|
}
|
|
|
|
$this->update_license_status( $license_status );
|
|
|
|
if ( $this->is_license_active() ) {
|
|
if ( ! \get_option( 'popup_maker_pro_activation_date', false ) ) {
|
|
\update_option( 'popup_maker_pro_activation_date', time() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires when license is activated.
|
|
*
|
|
* @param array $license_status License status data.
|
|
*/
|
|
\do_action( 'popup_maker_license_activated', $license_status );
|
|
|
|
return $this->is_license_active();
|
|
}
|
|
|
|
/**
|
|
* Deactivate license.
|
|
*
|
|
* Side Effects:
|
|
* - API Call: Deactivate License
|
|
* - Update License Status
|
|
*
|
|
* @return bool
|
|
*
|
|
* @throws \Exception If there is an error.
|
|
*/
|
|
public function deactivate_license(): bool {
|
|
$license_status = $this->api_call( 'deactivate_license' );
|
|
|
|
$this->update_license_status( $license_status );
|
|
|
|
if ( empty( $license_status ) ) {
|
|
return false;
|
|
}
|
|
|
|
$succeeded = 'deactivated' === $license_status['license'];
|
|
|
|
/**
|
|
* Fires when license is activated.
|
|
*
|
|
* @param array $license_status License status data.
|
|
* @param bool $succeeded Whether the license was deactivated successfully.
|
|
*/
|
|
\do_action( 'popup_maker_license_deactivated', $license_status, $succeeded );
|
|
|
|
return $succeeded;
|
|
}
|
|
|
|
/**
|
|
* Convert license error to human readable message.
|
|
*
|
|
* @param array{success:bool,license:'invalid'|'valid',item_id:int|false,item_name:string,license_limit:int,site_count:int,expires:string,activations_left:int,checksum:string,payment_id:int,customer_name:string,customer_email:string,price_id:string|int,error?:'no_activations_left'|'license_not_activable'|'missing'|'invalid'|'expired'|'revoked'|'item_name_mismatch'|'site_inactive'|'no_activations_left'|string|null,error_message?:string}|null $license_status License status data.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_license_error_message( ?array $license_status = null ): string {
|
|
if ( empty( $license_status ) ) {
|
|
$license_status = $this->get_license_status_data();
|
|
}
|
|
|
|
if ( empty( $license_status['error'] ) ) {
|
|
return '';
|
|
}
|
|
|
|
switch ( $license_status['error'] ) {
|
|
case 'expired':
|
|
$message = sprintf(
|
|
/* translators: the license key expiration date */
|
|
__( 'Your license key expired on %s.', 'popup-maker' ),
|
|
date_i18n( \get_option( 'date_format' ), strtotime( $license_status['expires'], time() ) )
|
|
);
|
|
break;
|
|
|
|
case 'disabled':
|
|
case 'revoked':
|
|
$message = __( 'Your license key has been disabled.', 'popup-maker' );
|
|
break;
|
|
|
|
case 'missing':
|
|
$message = __( 'Invalid license.', 'popup-maker' );
|
|
break;
|
|
|
|
case 'invalid':
|
|
case 'site_inactive':
|
|
$message = __( 'Your license is not active for this URL.', 'popup-maker' );
|
|
break;
|
|
|
|
case 'item_name_mismatch':
|
|
$message = sprintf(
|
|
/* translators: the plugin name */
|
|
__( 'This appears to be an invalid license key for %s.', 'popup-maker' ),
|
|
__( 'Popup Maker', 'popup-maker' )
|
|
);
|
|
break;
|
|
|
|
case 'no_activations_left':
|
|
$message = __( 'Your license key has reached its activation limit.', 'popup-maker' );
|
|
break;
|
|
|
|
default:
|
|
$message = __( 'An error occurred, please try again.', 'popup-maker' );
|
|
break;
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Remove license.
|
|
*
|
|
* Caution: Destructive, this will remove the license key and status.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function remove_license(): bool {
|
|
try {
|
|
// Might be an invalid but used key, so we want to deactivate it. without checking if active.
|
|
$this->deactivate_license();
|
|
|
|
if ( \delete_option( self::OPTION_KEY ) ) {
|
|
// Delete the old license key option & status as well.
|
|
\PUM_Utils_Options::delete( self::SETTINGS_KEY );
|
|
delete_option( 'popup_maker_pro_license_active' );
|
|
|
|
/**
|
|
* Fires when license is removed.
|
|
*/
|
|
\do_action( 'popup_maker_license_removed' );
|
|
|
|
return true;
|
|
}
|
|
} catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
|
// Do nothing.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Safely update license key.
|
|
*
|
|
* Side effect:
|
|
* - This will remove the license if the key is empty.
|
|
* - This will update the license key if its different from the current key.
|
|
* - Auto-activated licenses cannot be modified via this method.
|
|
*
|
|
* @param string $key License key.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function maybe_update_license_key( string $key ): bool {
|
|
// Prevent modification of auto-activated licenses
|
|
$license_data = $this->get_license_data();
|
|
if ( ! empty( $license_data['auto_activation']['enabled'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( empty( $key ) ) {
|
|
$this->remove_license();
|
|
return true;
|
|
} else {
|
|
// If the key is starred, get the original key.
|
|
if ( strpos( $key, '*' ) !== false ) {
|
|
$key = $this->get_license_key();
|
|
}
|
|
|
|
return $this->update_license_key( $key );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Safely attempt to activate the license.
|
|
*
|
|
* Proper external handlers with error control.
|
|
*
|
|
* @param string $key License key.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function maybe_activate_license( ?string $key = null ): bool {
|
|
try {
|
|
// Update the license key separately to avoid race condition.
|
|
if ( $key ) {
|
|
$this->maybe_update_license_key( $key );
|
|
}
|
|
|
|
// Activate the license.
|
|
return $this->activate_license();
|
|
} catch ( \Exception $e ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if license is active.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_license_active() {
|
|
return 'valid' === $this->get_license_status();
|
|
}
|
|
|
|
/**
|
|
* Check if license is auto-activated via POPUP_MAKER_PRO_LICENSE constant.
|
|
*
|
|
* Uses database flag for reliable detection even when constant is removed.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_auto_activated(): bool {
|
|
$license_data = $this->get_license_data();
|
|
return ! empty( $license_data['auto_activation']['enabled'] );
|
|
}
|
|
|
|
/**
|
|
* Get auto-activation info for debugging.
|
|
*
|
|
* @return array{enabled:bool,constant_present:bool,db_key:string,constant_lost_at:string|null,status:string}|null
|
|
*/
|
|
public function get_auto_activation_info(): ?array {
|
|
$license_data = $this->get_license_data();
|
|
|
|
if ( ! isset( $license_data['auto_activation']['enabled'] ) ) {
|
|
return null;
|
|
}
|
|
|
|
$constant_key = defined( 'POPUP_MAKER_PRO_LICENSE' ) && ! empty( POPUP_MAKER_PRO_LICENSE ) ? POPUP_MAKER_PRO_LICENSE : null;
|
|
$constant_present = null !== $constant_key;
|
|
$constant_lost_at = $license_data['auto_activation']['constant_lost_at'] ?? null;
|
|
|
|
// Determine status
|
|
$status = 'unknown';
|
|
if ( $constant_present ) {
|
|
$status = $constant_lost_at ? 'restored' : 'active';
|
|
} elseif ( $constant_lost_at ) {
|
|
$status = 'lost';
|
|
} else {
|
|
$status = 'missing';
|
|
}
|
|
|
|
return [
|
|
'enabled' => true,
|
|
'constant_present' => $constant_present,
|
|
'db_key' => $license_data['key'] ?? '',
|
|
'constant_lost_at' => $constant_lost_at,
|
|
'status' => $status,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Generate connection information for Pro upgrade.
|
|
*
|
|
* This method creates the necessary connection data for upgrading to Pro
|
|
* when Pro is not already installed.
|
|
*
|
|
* @return array{url:string,back_url:string}|null Returns connection info or null if conditions not met.
|
|
*/
|
|
public function generate_connect_info(): ?array {
|
|
// Only generate connect info if license is active and Pro is not installed.
|
|
if ( ! $this->is_license_active() || $this->container->is_pro_installed() ) {
|
|
return null;
|
|
}
|
|
|
|
$license_key = $this->get_license_key();
|
|
if ( empty( $license_key ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Use the Connect service to generate connection info.
|
|
$connect_service = $this->container->get( 'connect' );
|
|
return $connect_service->get_connect_info( $license_key );
|
|
}
|
|
|
|
/**
|
|
* Validate license for Pro upgrade workflow.
|
|
*
|
|
* Determines if the current license state allows for Pro upgrade.
|
|
*
|
|
* @return array{valid:bool,can_upgrade:bool,reason:string}
|
|
*/
|
|
public function validate_for_upgrade(): array {
|
|
$license_key = $this->get_license_key();
|
|
$license_status = $this->get_license_status();
|
|
$is_pro_installed = $this->container->is_pro_installed();
|
|
|
|
// No license key provided.
|
|
if ( empty( $license_key ) ) {
|
|
return [
|
|
'valid' => false,
|
|
'can_upgrade' => false,
|
|
'reason' => 'no_license_key',
|
|
];
|
|
}
|
|
|
|
// License is not active.
|
|
if ( 'valid' !== $license_status ) {
|
|
return [
|
|
'valid' => false,
|
|
'can_upgrade' => false,
|
|
'reason' => "license_{$license_status}",
|
|
];
|
|
}
|
|
|
|
// Pro is already installed.
|
|
if ( $is_pro_installed ) {
|
|
return [
|
|
'valid' => true,
|
|
'can_upgrade' => false,
|
|
'reason' => 'pro_already_installed',
|
|
];
|
|
}
|
|
|
|
// License is valid and Pro is not installed - can upgrade.
|
|
return [
|
|
'valid' => true,
|
|
'can_upgrade' => true,
|
|
'reason' => 'ready_for_upgrade',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Filter settings editor args.
|
|
*
|
|
* Add the license key value to the settings editor args, since its not actually stored in options array.
|
|
*
|
|
* @param mixed $args Settings editor args.
|
|
* @return mixed Settings editor args.
|
|
*/
|
|
public function filter_settings_editor_args( $args ) {
|
|
$value = isset( $args['current_values'][ self::SETTINGS_KEY ] ) ? $args['current_values'][ self::SETTINGS_KEY ] : '';
|
|
|
|
try {
|
|
$license_service = \PopupMaker\plugin( 'license' );
|
|
$license_status = $license_service->get_license_status_data();
|
|
|
|
// Get the comprehensive license status from service
|
|
$actual_status = $license_service->get_license_status();
|
|
|
|
// Get the actual license key (either from form input or service)
|
|
// $license_key = ! empty( $value ) ? trim( $value ) : $license_service->get_license_key();
|
|
$license_key = $license_service->get_license_key();
|
|
|
|
$license_key = self::star_key( $license_key );
|
|
|
|
// Use the mapping function to get proper status and classes
|
|
$status_mapping = $this->map_license_status( $actual_status );
|
|
|
|
// Get the license tier (pro or pro_plus).
|
|
$license_tier = $license_service->get_license_tier();
|
|
|
|
// Get Pro installation status for template conditionals.
|
|
$is_pro_installed = $this->container->is_pro_installed();
|
|
$is_pro_active = $this->container->is_pro_active();
|
|
$pro_version = $this->container->get_pro_version();
|
|
|
|
// Check for auto-activation status messages
|
|
$messages = ! empty( $license_status['error_message'] ) ? [ $license_status['error_message'] ] : [];
|
|
|
|
// Add auto-activation disconnect message if applicable
|
|
$auto_info = $license_service->get_auto_activation_info();
|
|
if ( ! empty( $auto_info['constant_lost_at'] ) ) {
|
|
$lost_date = $auto_info['constant_lost_at'];
|
|
$messages[] = sprintf(
|
|
/* translators: the date and time of the auto-activation disconnect */
|
|
__( 'Auto-activation was disconnected on %s. The POPUP_MAKER_PRO_LICENSE constant was removed from your configuration.', 'popup-maker' ),
|
|
date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $lost_date ) )
|
|
);
|
|
}
|
|
|
|
// Pass anything we want to the template here.
|
|
$args['current_values'][ self::SETTINGS_KEY ] = [
|
|
'key' => $license_key,
|
|
'status' => $status_mapping['status'],
|
|
'messages' => $messages,
|
|
'expires' => $license_service->get_license_expiration(),
|
|
'classes' => $status_mapping['classes'],
|
|
'license_tier' => $license_tier,
|
|
'is_pro_installed' => $is_pro_installed,
|
|
'is_pro_active' => $is_pro_active,
|
|
'pro_version' => $pro_version,
|
|
];
|
|
} catch ( \Exception $e ) {
|
|
$args['current_values'][ self::SETTINGS_KEY ] = [
|
|
'key' => $this->star_key( trim( $value ) ),
|
|
'status' => 'invalid',
|
|
/* translators: %s is the error message */
|
|
'messages' => [ sprintf( __( 'Error loading license status: %s', 'popup-maker' ), $e->getMessage() ) ],
|
|
'expires' => '',
|
|
'classes' => 'pum-license-invalid',
|
|
'license_tier' => 'pro', // Default to pro on error.
|
|
'is_pro_installed' => $this->container->is_pro_installed(),
|
|
'is_pro_active' => $this->container->is_pro_active(),
|
|
'pro_version' => $this->container->get_pro_version(),
|
|
];
|
|
}
|
|
|
|
return $args;
|
|
}
|
|
|
|
/**
|
|
* Map license status to template data.
|
|
*
|
|
* @param string $status License status from service.
|
|
* @return array Template data for license status.
|
|
*/
|
|
public function map_license_status( string $status ): array {
|
|
switch ( $status ) {
|
|
case 'valid':
|
|
return [
|
|
'status' => 'valid',
|
|
'classes' => 'pum-license-valid',
|
|
];
|
|
case 'deactivated':
|
|
return [
|
|
'status' => 'deactivated',
|
|
'classes' => 'pum-license-deactivated',
|
|
];
|
|
case 'expired':
|
|
return [
|
|
'status' => 'expired',
|
|
'classes' => 'pum-license-expired',
|
|
];
|
|
case 'empty':
|
|
return [
|
|
'status' => 'empty',
|
|
'classes' => 'pum-license-empty',
|
|
];
|
|
case 'error':
|
|
default:
|
|
return [
|
|
'status' => 'invalid',
|
|
'classes' => 'pum-license-invalid',
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Star the key.
|
|
*
|
|
* @param string $key The key to star.
|
|
*
|
|
* @return string The starred key.
|
|
*/
|
|
public static function star_key( string $key ): string {
|
|
if ( empty( $key ) ) {
|
|
return '';
|
|
}
|
|
|
|
return substr( $key, 0, 3 ) . str_repeat( '*', max( 0, strlen( $key ) - 6 ) ) . substr( $key, -3 );
|
|
}
|
|
}
|