File: //home/dfwparty/www/wp-content/plugins/ws-form-pro/includes/class-ws-form-licensing.php
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Manages plugin licensing
*/
// Include EDD software licensing plugin updater
if(!class_exists('EDD_SL_Plugin_Updater_WS_Form')) {
// edd_sl_api_request_verify_ssl is set to false in this version of the file
include('licensing/edd/EDD_SL_Plugin_Updater_WS_Form.php');
}
class WS_Form_Licensing {
const LICENSE_ENDPOINT = 'https://wsform.com/';
const LICENSE_TRANSIENT_ID = 'wsf-license-status';
const LICENSE_TRANSIENT_EXPIRY = 86400; // 1 Day
private $item_id;
private $prefix;
private $name;
private $version;
private $author;
private $path;
private $tab_license;
private $test_mode;
private $edd_updater = null;
public function __construct($item_id, $action_id = '', $name = WS_FORM_NAME_PRESENTABLE, $version = WS_FORM_VERSION, $author = WS_FORM_AUTHOR, $path = false, $test_mode = false) {
$this->item_id = $item_id;
$this->prefix = (($action_id != '') ? sprintf('action_%s_', $action_id) : '');
$this->name = $name;
$this->version = $version;
$this->author = $author;
$this->path = ($path === false) ? WS_FORM_PLUGIN_ROOT_FILE : $path;
$this->tab_license = ($action_id != '') ? sprintf('action_%s', $action_id) : 'license';
$this->test_mode = $test_mode;
// Resets done in test mode
if($this->test_mode) {
set_site_transient('update_plugins', null);
}
// License constants
add_filter('wsf_option_get', array($this, 'option_get'), 10, 2);
// Register the updater on 'init' so available updates are detected in all contexts (admin, WP-Cron, REST/background). Add-ons still register this on 'admin_init', but the guard in updater() prevents double initialization.
add_action('init', array($this, 'updater'));
}
// License constants
public function option_get($value, $key) {
// Check for license related key
if(strpos($key, 'license_key') !== false) {
// Build license constant (e.g. WSF_LICENSE_KEY)
$license_constant = sprintf('WSF_%s', strtoupper($key));
// If defined, return constant instead
if(defined($license_constant)) {
return trim(constant($license_constant));
}
}
return $value;
}
// Updater
public function updater() {
// Only initialize once per instance (may be called on both 'init' and 'admin_init')
if($this->edd_updater !== null) { return; }
// Build updater args
$args = array(
'version' => $this->version,
'license' => self::license_key(),
'item_id' => $this->item_id,
'author' => $this->author,
'beta' => false
);
// Test mode
if($this->test_mode) { $args['test_mode'] = true; };
self::log(sprintf(
'updater(): initializing EDD updater. endpoint=%s, version=%s, item_id=%s, license=%s, activated=%s, path=%s, is_admin=%s',
self::LICENSE_ENDPOINT,
$this->version,
$this->item_id,
self::log_mask_license($args['license']),
self::license_activated() ? 'true' : 'false',
$this->path,
is_admin() ? 'true' : 'false'
));
// Create new updater instance
$this->edd_updater = new EDD_SL_Plugin_Updater_WS_Form(self::LICENSE_ENDPOINT, $this->path, $args);
}
// Transient check
public function transient_check() {
// Auto activate if license defined in wp-config.php
if(is_admin()) {
// Get constant license key
$license_key_constant = self::maybe_license_key_constant();
// Build option key
$option_key = sprintf('%slicense_key_attempt', $this->prefix);
// Get option value
$option_value = WS_Form_Common::option_get($option_key);
// Check to see if activation has been attempted
if($license_key_constant !== false) {
// Create hashed version of license key
$license_key_constant_hash = self::get_license_key_constant_hash();
// Check to see if activation of this license key has been attempted
if(
($license_key_constant_hash !== false) &&
($option_value != $license_key_constant_hash)
) {
self::log(sprintf('Constant license key detected (%s), attempting silent activation.', self::get_license_constant()));
// Attempt to activate silently
if(self::activate($license_key_constant, true)) {
// Set hash so this is only tried once
WS_Form_Common::option_set($option_key, $license_key_constant_hash);
self::log('Silent activation via constant succeeded. Attempt hash stored.');
} else {
self::log('Silent activation via constant failed.');
}
}
} else {
// If constant license key has been removed and attempt was made to activate it, remove key so WS Form will attempt to reactivate if the constant is added again. Product will automatically be unlicensed once transient check occurs.
if($option_value != '') {
self::log(sprintf('Constant %s no longer defined but attempt hash exists. Clearing hash and deactivating license.', self::get_license_constant()));
// Remove hash
WS_Form_Common::option_remove($option_key);
// Attempt to deactivate silently
WS_Form_Common::option_set($this->prefix . 'license_activated', false);
self::log('license_activated set to false: constant key removed.');
// Reset license expiry
self::reset_license_expiry();
}
}
}
// Do not run if remote checks should be disabled
if(self::do_remote_checks()) {
// Check license transient
try {
// Get license transient value
$license_transient = get_transient($this->prefix . self::LICENSE_TRANSIENT_ID);
} catch (Exception $e) {
self::log(sprintf('Transient read failed (DB error): %s. Aborting transient check.', $e->getMessage()));
// DB error occurred (e.g. DB lock) so lets stop the transient check
return false;
}
if($license_transient === false) {
self::log('License transient expired or missing. Running remote license check.');
// Check license
if(!self::check()) {
self::log('Remote license check failed (network error or invalid response). Aborting transient check.');
// Network issue occured so lets stop the transient check
return false;
}
// Store license status in case we want to pull it from the transient
$license_status = array(
'key' => self::license_key(),
'activated' => self::license_activated(),
'expires' => WS_Form_Common::option_get($this->prefix . 'license_expires', ''),
'time' => time()
);
self::log(sprintf('Transient refreshed. activated=%s', $license_status['activated'] ? 'true' : 'false'));
// Set transient
set_transient($this->prefix . self::LICENSE_TRANSIENT_ID, $license_status, self::LICENSE_TRANSIENT_EXPIRY);
}
}
// Add nag action
if(is_admin()) {
add_action('wsf_nag', array($this, 'nag'));
}
}
// Activate
public function activate($license_key, $silent = false) {
// Override with license constant if defined
$license_key = self::maybe_license_key_constant($license_key);
self::log(sprintf('activate() called. silent=%s', $silent ? 'true' : 'false'), $license_key);
// Get site URL (scheme and host only)
$site_url = self::get_site_url();
// Stop self checking
if(self::LICENSE_ENDPOINT == trailingslashit($site_url)) {
self::log('activate() aborted: LICENSE_ENDPOINT matches site URL (self-check prevention).');
return false;
}
// API parameters
$api_params = array(
'edd_action' => 'activate_license',
'license' => $license_key,
'item_id' => $this->item_id,
'url' => $site_url
);
// Build args
$args = array(
'body' => $api_params,
'user-agent' => WS_Form_Common::get_request_user_agent(),
'timeout' => WS_Form_Common::get_request_timeout(),
'sslverify' => WS_Form_Common::get_request_sslverify(),
);
// Log request
self::log_request('activate', $api_params, $args);
// Call the EDD license endpoint
$response = WS_Form_Common::wp_remote_post_clean(self::LICENSE_ENDPOINT, $args);
// Log response
self::log_response('activate', $response);
// Check for errors
if(is_wp_error($response) || (200 !== wp_remote_retrieve_response_code($response))) {
if(is_wp_error($response)) {
return self::error($response->get_error_message(), $silent);
} else {
return self::error(__('An error occurred, please try again.', 'ws-form'), $silent);
}
}
// Decode license data
$license_data = json_decode(wp_remote_retrieve_body($response));
// Check returned license data
if(
is_null($license_data) ||
!isset($license_data->license) ||
!isset($license_data->success)
) {
self::log('activate() failed: invalid or empty response body.');
return false;
}
if(!$license_data->success) {
self::log(sprintf('activate() failed: success=false, error=%s', isset($license_data->error) ? $license_data->error : 'unknown'));
// Set license key deactivated
WS_Form_Common::option_set($this->prefix . 'license_activated', false);
self::log('license_activated set to false: activation returned success=false.');
// Reset license expiry
self::reset_license_expiry();
switch ($license_data->error) {
case 'expired' :
return self::error(
sprintf(
/* translators: %s: License key expiry date */
__('Your license key expired on %s.', 'ws-form'),
get_date_from_gmt(
$license_data->expires,
get_option('date_format')
)
),
$silent
);
case 'disabled' :
case 'revoked' :
return self::error(__('Your license key has been disabled.', 'ws-form'), $silent);
case 'missing' :
return self::error(__('Invalid license.', 'ws-form'), $silent);
case 'invalid' :
case 'site_inactive' :
return self::error(__('Your license is not active for this URL.', 'ws-form'), $silent);
case 'license_not_activable' :
return self::error(__('License not activable. You are entering the license key for the package you purchased rather than the license key for the plugin itself.', 'ws-form'), $silent);
case 'item_name_mismatch' :
return self::error(
sprintf(
/* translators: %s: Plugin name */
__('This appears to be an invalid license key for %s.', 'ws-form'),
$this->name
),
$silent
);
case 'no_activations_left' :
return self::error(__('Your license key has reached its activation limit.', 'ws-form'), $silent);
case 'invalid_item_id' :
return self::error(__('Invalid item ID.', 'ws-form'), $silent);
case 'missing_item_id' :
return self::error(__('License activation failed because the product ID was missing.', 'ws-form'), $silent);
case 'missing_url' :
return self::error(__('License activation failed because no site URL was provided.', 'ws-form'), $silent);
case 'bundle_activation_not_allowed' :
return self::error(__('This license key belongs to a bundle and cannot be used to activate individual plugins. Please enter the license key for this specific WS Form product.', 'ws-form'), $silent);
case 'key_mismatch' :
return self::error(__('The provided license key does not match this product.', 'ws-form'), $silent);
default:
// If the EDD API returned a message string, include it to help debugging
if(!empty($license_data->error) && is_string($license_data->error)) {
return self::error(
sprintf(
/* translators: %s: Licensing error message */
__('License activation failed: %s', 'ws-form'),
esc_html($license_data->error)
),
$silent
);
}
// Generic fallback
return self::error(__('An error occurred, please try again.', 'ws-form'), $silent);
}
}
// Set license key activated
WS_Form_Common::option_set($this->prefix . 'license_activated', true);
self::log('license_activated set to true: activation succeeded.');
// Set license expiry
self::set_license_expiry($license_data);
// Show success message
if(!$silent) {
WS_Form_Common::admin_message_push(__('License key successfully activated', 'ws-form'));
}
self::log('activate() completed successfully.');
return true;
}
// Deactivate
public function deactivate($license_key, $silent = false) {
self::log(sprintf('deactivate() called. silent=%s', $silent ? 'true' : 'false'));
// Disable account deactivation if a license constant is defined
$license_key_constant = self::maybe_license_key_constant();
if($license_key_constant !== false) {
self::log(sprintf('deactivate() aborted: constant %s is defined.', self::get_license_constant()));
return false;
}
// Get site URL (scheme and host only)
$site_url = self::get_site_url();
// Stop self checking
if(self::LICENSE_ENDPOINT == trailingslashit($site_url)) {
self::log('deactivate() aborted: LICENSE_ENDPOINT matches site URL (self-check prevention).');
return false;
}
// API parameters
$api_params = array(
'edd_action' => 'deactivate_license',
'license' => $license_key,
'item_id' => $this->item_id,
'url' => $site_url
);
// Build args
$args = array(
'body' => $api_params,
'user-agent' => WS_Form_Common::get_request_user_agent(),
'timeout' => WS_Form_Common::get_request_timeout(),
'sslverify' => WS_Form_Common::get_request_sslverify()
);
// Log request
self::log_request('deactivate', $api_params, $args);
// Call the EDD
$response = WS_Form_Common::wp_remote_post_clean(self::LICENSE_ENDPOINT, $args);
// Log response
self::log_response('deactivate', $response);
// Check for errors
if(is_wp_error($response) || (200 !== wp_remote_retrieve_response_code($response))) {
if(is_wp_error($response)) {
return self::error($response->get_error_message(), $silent);
} else {
return self::error(__('An error occurred, please try again.', 'ws-form'), $silent);
}
}
// Decode license data
$license_data = json_decode(wp_remote_retrieve_body($response));
// Check returned license data
if(
is_null($license_data) ||
!isset($license_data->license)
) {
self::log('deactivate() failed: invalid or empty response body.');
return false;
}
if(!$license_data->success) {
return self::error(__('Your license key deactivation failed. If you have changed hostnames you can disregard this message.', 'ws-form'), $silent);
}
// Set license key deactivated
WS_Form_Common::option_set($this->prefix . 'license_activated', false);
self::log('license_activated set to false: deactivation succeeded.');
// Reset license expiry
self::reset_license_expiry();
// Show success message
if(!$silent) {
WS_Form_Common::admin_message_push(__('License key successfully deactivated', 'ws-form'));
}
self::log('deactivate() completed successfully.');
return true;
}
// Check license
public function check() {
self::log('check() called. Running remote license check.');
// Get site URL (scheme and host only)
$site_url = self::get_site_url();
// Stop self checking
if(self::LICENSE_ENDPOINT == trailingslashit($site_url)) {
self::log('check() aborted: LICENSE_ENDPOINT matches site URL (self-check prevention).');
return false;
}
// API parameters
$api_params = array(
'edd_action' => 'check_license',
'license' => self::license_key(),
'item_id' => $this->item_id,
'url' => $site_url
);
// Build args
$args = array(
'body' => $api_params,
'user-agent' => WS_Form_Common::get_request_user_agent(),
'timeout' => WS_Form_Common::get_request_timeout(),
'sslverify' => WS_Form_Common::get_request_sslverify()
);
// Log request
self::log_request('check', $api_params, $args);
// Call the custom API.
$response = WS_Form_Common::wp_remote_post_clean(self::LICENSE_ENDPOINT, $args);
// Log response
self::log_response('check', $response);
// Check for network errors
if(
is_wp_error($response) ||
(200 !== wp_remote_retrieve_response_code($response))
) {
if(is_wp_error($response)) {
self::log(sprintf('check() failed: HTTP 0, %s', $response->get_error_message()));
} else {
self::log(sprintf('check() failed: HTTP %s', wp_remote_retrieve_response_code($response)));
}
return false;
}
// Get response body
$response_body = wp_remote_retrieve_body($response);
// Decode license data
try {
$license_data = json_decode($response_body);
} catch (Exception $e) {
self::log(sprintf('check() failed: JSON decode error: %s', $e->getMessage()));
// JSON decode error
return false;
}
// Check returned license data
if(
is_null($license_data) ||
!isset($license_data->success) ||
!isset($license_data->license)
) {
self::log('check() failed: invalid or missing fields in response.');
// License data response error
return false;
}
if($license_data->success) {
if($license_data->license === 'valid') {
self::log('check() result: valid.');
self::set_license_expiry($license_data);
} else {
self::log(sprintf('check() result: success=true but license status is "%s". Setting license_activated to false.', $license_data->license));
// Set license key invalid
WS_Form_Common::option_set($this->prefix . 'license_activated', false);
self::log('license_activated set to false: check() returned non-valid license status.');
self::reset_license_expiry();
}
} else {
self::log('check() result: success=false.');
}
return true;
}
// Get license constant
public function maybe_license_key_constant($license_key = false) {
// Build license constant (e.g. WSF_LICENSE_KEY)
$license_constant = self::get_license_constant();
// If defined, set value to license constant
if(defined($license_constant)) {
return trim(constant($license_constant));
}
return $license_key;
}
// Get license constant
public function get_license_constant() {
return sprintf('WSF_%sLICENSE_KEY', strtoupper($this->prefix));
}
// Get license constant hash
public function get_license_key_constant_hash() {
$license_key_constant = self::maybe_license_key_constant();
if($license_key_constant === false) { return false; }
return substr(md5($license_key_constant), -6);
}
// Get license key
public function license_key() {
// Override with license constant if defined
return self::maybe_license_key_constant(WS_Form_Common::option_get($this->prefix . 'license_key', ''));
}
// Is this plugin activated?
public function license_activated() {
return WS_Form_Common::option_get($this->prefix . 'license_activated', false);
}
// Should remote license checks be performed?
public function do_remote_checks() {
// License key must be set
// License key must be activated
// Only check if in admin
return !empty(self::license_key()) && self::license_activated() && is_admin();
}
// Get license status as string
public function license_status() {
if(self::license_activated()) {
$license_expires = WS_Form_Common::option_get($this->prefix . 'license_expires', '');
return 'Licensed' . (($license_expires != '') ? ' (Expires: ' . gmdate(get_option('date_format'), $license_expires) . ')' : '');
} else {
return 'Unlicensed';
}
}
// Set license expiry
public function set_license_expiry($license_data) {
if(!is_object($license_data)) { self::reset_license_expiry(); return false; }
if(!isset($license_data->expires)) { self::reset_license_expiry(); return false; }
$license_expires_string = $license_data->expires;
if($license_expires_string !== 'lifetime') {
$license_expires = strtotime($license_expires_string);
if($license_expires === false) { self::reset_license_expiry(); return false; }
}
self::log(sprintf('License expiry set to: %s', $license_expires_string));
WS_Form_Common::option_set($this->prefix . 'license_expires', $license_expires);
}
// Reset license expiry
public function reset_license_expiry() {
self::log('reset_license_expiry() called: license_expires cleared.');
WS_Form_Common::option_set($this->prefix . 'license_expires', '');
}
// Nag
public function nag() {
// Check to see if license is activated
if(!self::license_activated()) {
// If license is not activated, show a nag
WS_Form_Common::admin_message_push(
sprintf(
/* translators: %1$s: Plugin name, %2$s: Opening anchor tag, %3$s: Closing anchor tag */
__('%1$s is not licensed. Please enter your license key in %2$ssettings%3$s to receive software updates and support.', 'ws-form'),
$this->name,
'<a href="' . esc_url( WS_Form_Common::get_admin_url( 'ws-form-settings', false, 'tab=' . $this->tab_license ) ) . '">',
'</a>'
),
'notice-warning',
false,
true
);
}
}
// License key processing error
public function error($message, $silent = false) {
self::log(sprintf('error() called: "%s", silent=%s', $message, $silent ? 'true' : 'false'));
// Error
if(!$silent) {
// Set license key deactivated
WS_Form_Common::option_set($this->prefix . 'license_activated', false);
self::log('license_activated set to false: error() called in non-silent mode.');
// Reset license expiry
self::reset_license_expiry();
WS_Form_Common::admin_message_push($message, 'notice-error');
}
return false;
}
// Get site URL (scheme and host only, no path)
public function get_site_url() {
$parsed = wp_parse_url(home_url());
return $parsed['scheme'] . '://' . $parsed['host'];
}
// Log licensing event
private function log($message, $license_key = '') {
// Check if logging is enabled via filter
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
if(!apply_filters('wsf_licensing_log', false)) { return; }
// Build caller context from backtrace (skip log() itself)
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Required when doing license debugging
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$caller = isset($backtrace[1]['function']) ? $backtrace[1]['function'] : 'unknown';
// Use passed key if provided, otherwise fall back to current license key
$key_suffix = substr($license_key !== '' ? $license_key : self::license_key(), -6);
// Write entry to the shared licensing log
self::log_write($message, array(
'item' => $this->item_id,
'prefix' => ($this->prefix !== '') ? rtrim($this->prefix, '_') : 'core',
'key' => '...' . $key_suffix,
'caller' => $caller
));
}
// Write a message to the licensing log
// Shared by WS_Form_Licensing and EDD_SL_Plugin_Updater_WS_Form so all licensing and update activity is captured in a single file
public static function log_write($message, $context = array()) {
// Check if logging is enabled via filter
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
if(!apply_filters('wsf_licensing_log', false)) { return; }
try {
// Build default log path
$log_path = 'licensing';
// Allow log path to be overridden
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
$log_path = apply_filters('wsf_licensing_log_path', $log_path);
// Get upload directory, creating it if needed
$upload_dir = WS_Form_Common::upload_dir_create($log_path);
if($upload_dir['error']) { return; }
$log_file = trailingslashit($upload_dir['dir']) . 'licensing.log';
// Build context string (e.g. [item:123] [caller:check])
$context_string = '';
foreach($context as $context_key => $context_value) {
$context_string .= sprintf('[%s:%s] ', $context_key, $context_value);
}
// Build log entry
$entry = sprintf(
"[%s] [%s] %s%s\n",
gmdate('Y-m-d H:i:s'),
home_url(),
$context_string,
$message
);
// Append to log file, silently fail if not possible
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
file_put_contents($log_file, $entry, FILE_APPEND | LOCK_EX);
} catch (Exception $e) {
// Intentionally silent - logging must never break a live site
}
}
// Mask a license key for logging (returns last 6 characters only)
public static function log_mask_license($license_key) {
if(empty($license_key)) { return '(none)'; }
return '...' . substr($license_key, -6);
}
// Log an outgoing licensing server request (license key masked)
public function log_request($caller, $api_params, $args) {
// Check if logging is enabled via filter
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
if(!apply_filters('wsf_licensing_log', false)) { return; }
// Mask license key in body
$body = $api_params;
if(isset($body['license'])) { $body['license'] = self::log_mask_license($body['license']); }
self::log(sprintf(
'%s() request: POST %s edd_action=%s timeout=%s sslverify=%s body=%s',
$caller,
self::LICENSE_ENDPOINT,
isset($api_params['edd_action']) ? $api_params['edd_action'] : '',
isset($args['timeout']) ? $args['timeout'] : '',
isset($args['sslverify']) ? ($args['sslverify'] ? 'true' : 'false') : '',
wp_json_encode($body)
));
}
// Log a licensing server response (raw code and body)
public function log_response($caller, $response) {
// Check if logging is enabled via filter
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
if(!apply_filters('wsf_licensing_log', false)) { return; }
if(is_wp_error($response)) {
self::log(sprintf('%s() response: WP_Error: %s', $caller, $response->get_error_message()));
} else {
self::log(sprintf(
'%s() response: code=%s body=%s',
$caller,
wp_remote_retrieve_response_code($response),
wp_remote_retrieve_body($response)
));
}
}
}