client = $client;
$hash = md5( $this->client->getSlug() . $this->client->getProductId() . home_url() );
$this->option_key = 'absp_service_api_' . $hash . '_manage_license';
$this->data_key = $this->client->getSlug() . '-license';
$this->schedule_hook = $this->client->getSlug() . '_license_check_event';
// Load the license.
$this->get_license();
$this->set_activation_required_message(
/* translators: %s: Plugin Name */
sprintf( esc_html__( 'Active %s license to get professional support and automatic update from your WordPress dashboard.', 'absolute-addons' ), '' . esc_html( $this->client->getName() ) . '' )
);
}
public function set_page_url( $url ) {
$this->page_url = esc_url_raw( $url );
return $this;
}
public function get_page_url() {
if ( null !== $this->page_url ) {
return $this->page_url;
}
return admin_url( 'admin.php?page=' . $this->menu_args['menu_slug'] );
}
public function set_activation_required_message( $message ) {
$this->activation_required = $message;
return $this;
}
public function use_custom_style() {
$this->use_custom_style = true;
return $this;
}
private function updating_license( $status ) {
$status = (bool) $status;
// Set initial flag.
$this->updating_license = $status;
if ( $status ) {
set_transient( $this->option_key . '_is_updating_license', 'yes', 20 );
} else {
delete_transient( $this->option_key . '_is_updating_license' );
}
return $this; // Method Chain.
}
private function is_updating_license() {
return $this->updating_license || 'yes' === get_transient( $this->option_key . '_is_updating_license' );
}
/**
* Initialize License.
*
* @return void
*/
public function init() {
add_action( 'init', [ $this, 'handle_license_page_form' ] );
if ( null === $this->menu_args ) {
$this->set_menu_args();
}
// Run hook to check license status daily.
add_action( $this->schedule_hook, [ $this, 'check_license_status' ] );
$this->userCapability = $this->menu_args['capability'];
$this->is_license_page = isset( $_GET['page'] ) && $_GET['page'] === $this->menu_args['menu_slug']; // phpcs:ignore
add_action( 'plugin_action_links_' . $this->client->getBasename(), [ $this, 'plugin_action_links' ] );
add_action( 'admin_notices', [ $this, '__admin_notices' ] );
// Activation/Deactivation hooks.
$this->activation_deactivation();
// Check the validity and save the state. (check after cron scheduled)
$this->is_valid();
// Set Did Init Flag
$this->did_init = true;
}
/**
* Expose the License Key.
*
* @return void|string
*/
public function get_key() {
$this->get_license();
return $this->license['key'];
}
/**
* Display Admin Notices.
*
* @return void
*/
public function __admin_notices() {
if ( ! current_user_can( $this->userCapability ) ) {
return;
}
if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL === true ) {
$host = wp_parse_url( $this->client->getServiceEndpoint( 'license' ), PHP_URL_HOST );
if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) || ( defined( 'WP_ACCESSIBLE_HOSTS' ) && false === stristr( WP_ACCESSIBLE_HOSTS, $host ) ) ) {
?>
'. esc_html__( 'Warning!', 'absolute-addons' ).'',
esc_html( $this->client->getName() ),
'' . esc_html( $host ) . '',
'WP_ACCESSIBLE_HOSTS'
);
?>
inactive_license_notice();
if ( ! empty( $this->error ) ) {
?>
error ) ); ?>
success ) ) {
?>
success ) ); ?>
is_license_page && ! $this->is_valid() && ! $this->is_updating_license() ) {
?>
get_page_url();
if ( ! empty( $age_url ) ) {
$links[] = sprintf( '%s', esc_url( $age_url ), esc_html( $this->menu_args['menu_title'] ) );
}
return $links;
}
/**
* Check license.
*
* @return array
*/
public function check() {
return $this->request( 'status', $this->license );
}
/**
* Check Plugin Update.
*
* @return array
*/
public function check_update() {
return $this->request( 'update', $this->license );
}
/**
* Get Plugin data.
*
* @return array {
* Plugin Information
* @type bool $success API response status
* @type string $api_call_execution_time API Man (Rest Response) Execution Time
* @type array $data {
* Plugin Data ( API Man.)
* @type array $package {
* @type int $product_id API Man Product ID
* }
* @type array $info {
* @type string $id Plugin Id
* @type string $name Plugin Name
* @type string $author Author Name
* @type string $author_profile Author Profile URL
* @type string $slug Plugin Slug
* @type string $plugin Plugin main file path
* @type string $new_version New Version String
* @type string $url Plugin URL
* @type string $package Plugin update download URL
* @type string $icons Plugin Icons
* @type string $banners Plugin Banners
* @type string $banner_rtl RTL Version of Plugin Banners
* @type string $upgrade_notice Upgrade Notice
* @type string $requires Minimum WordPress Version
* @type string $requires_php Minimum PHP Version
* @type string $tested Tested upto WordPress Version
* @type array $compatibility Compatibility information (API Man sends string)
* @type array $contributors Plugin Contributors List (if available)
* @type array $ratings Plugin Rating (if available)
* @type float $num_ratings Plugin Rating (if available)
* @type string $last_updated Last updated Date
* @type string $homepage Plugin Home Page URL
* @type array $sections {
* Plugin Description Sections
* @type string $description Plugin Description
* @type string $changelog Change LOG
* }
* @type mixed $author_block_count
* @type mixed $author_block_rating
* }
* }
* }
*/
public function get_information() {
return $this->request( 'information', $this->license );
}
/**
* Active a license.
*
* @param array $license license data.
* @return array
*/
public function activate( $license ) {
return $this->request( 'activate', $license );
}
/**
* Deactivate current license.
*
* @return array
*/
public function deactivate() {
return $this->request( 'deactivate', $this->license );
}
/**
* Send common request.
*
* @param string $action request action.
* @param array $license license data.
*
* @return array
*/
protected function request( $action, $license = [] ) {
// WC-AM Valid Actions and response data types.
$actions = [
'activate' => 'json',
'deactivate' => 'json',
'status' => 'json',
'information' => 'json',
'update' => 'json',
'plugininformation' => 'serialize', // serialize option doesn't provide success status.
'pluginupdatecheck' => 'serialize',
];
if ( ! in_array( $action, array_keys( $actions ) ) ) {
return [
'success' => false,
'error' => __( 'Invalid Request Action.', 'absolute-addons' ),
];
}
// parse license data
$license = wp_parse_args( $license, $this->get_license() );
// validate license data.
if ( ! $this->validate_license_data( $license ) ) {
return [
'success' => false,
'error' => __( 'Invalid/Empty License Data.', 'absolute-addons' ),
];
}
/** @noinspection HttpUrlsUsage */
$params = [
'object' => str_ireplace( [ 'http://', 'https://' ], '', home_url() ),
'api_key' => $license['key'],
'instance' => $license['instance'],
'product_id' => $license['product_id'],
'plugin_name' => $this->client->getBasename(),
'wc_am_action' => $action,
];
$response = $this->client->request( [ 'body' => $params, 'server' => 'license', 'blocking' => true ] );
if ( ! is_wp_error( $response ) ) {
$response = wp_remote_retrieve_body( $response );
if ( 'json' == $actions[ $action ] ) {
$response = json_decode( $response, true );
} else {
$response = maybe_unserialize( $response );
if( is_string( $response ) && 0 === strpos( $response, '{' ) || 0 === strpos( $response, '[' ) ) {
// WC-AM returns error with json.
$response = json_decode( $response, true );
} else {
return [
'success' => true,
'data' => $response,
// For sanity.
'api_call_execution_time' => '',
];
}
}
if ( empty( $response ) || ! isset( $response['success'] ) || ( isset( $response['success'] ) && ! $response['success'] ) ) {
return [
'success' => false,
'code' => isset( $response['code'] ) ? $response['code'] : 'UNKNOWN ERROR',
'error' => isset( $response['error'] ) ? $response['error'] : __( 'Unknown error occurred, Please try again.', 'absolute-addons' ),
'data' => isset( $response['data'] ) ? $response['data'] : '',
];
}
return $response;
} else {
return [
'success' => false,
'error' => $response->get_error_message(),
'code' => $response->get_error_code(),
'data' => $response->get_error_data( $response->get_error_code() ),
];
}
}
public function set_menu_args( $args = [] ) {
$defaults = [
'type' => 'menu', // Can be: menu, options, submenu.
'page_title' => __( 'Manage License', 'absolute-addons' ),
'menu_title' => __( 'Manage License', 'absolute-addons' ),
'capability' => 'manage_options',
'menu_slug' => $this->client->getSlug() . '-manage-license',
'icon_url' => '',
'position' => null,
'parent_slug' => '',
];
$this->menu_args = wp_parse_args( $args, $defaults );
return $this;
}
/**
* Add settings page for license.
*
* @param array $args settings for rendering the menu.
*
* @return $this
*/
public function add_settings_page() {
if ( $this->did_init ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1. Class Method. */
__( '%s Should be called before License::init()', 'absolute-addons'),
'' . __METHOD__ . ''
),
'1.0.0'
);
return $this;
}
if ( ! in_array( $this->menu_args['type'], [ 'menu', 'options', 'submenu' ] ) ) {
if ( empty( $this->menu_args['parent_slug'] ) ) {
$this->menu_args['type'] = 'menu';
}
}
if ( 'submenu' == $this->menu_args['type'] && empty( $this->menu_args['parent_slug'] ) ) {
$this->menu_args['type'] = 'options';
}
add_action( 'admin_menu', [ $this, '__admin_menu' ], 999 );
return $this;
}
/**
* Admin Menu hook.
*
* @return void
*/
public function __admin_menu() {
switch ( $this->menu_args['type'] ) {
case 'submenu':
$this->add_submenu_page();
break;
case 'options':
$this->add_options_page();
break;
case 'menu':
default:
$this->add_menu_page();
break;
}
}
/**
* License menu output.
*
* @return void
*/
public function render_menu_page() {
?>
render_license_page(); ?>
licenses_style();
$action = ( isset( $this->license['status'] ) && 'active' == $this->license['status'] ) ? 'deactivate' : 'activate';
?>
client->getSlug() . '_license_section' ); ?>
render_page_header(); ?>
activation_required ) {
echo wp_kses_post( wpautop( $this->activation_required ) );
}
?>
render_activation_form( $action ); ?>
render_license_info(); ?>
client->getSlug() . '_license_section' ); ?>
label information for variable product. So user can select from the dropdown.
*
*/
public function set_product_data( $data = [] ) {
if ( ! empty( $data ) ) {
$this->product_data = $data;
}
return $this;
}
/**
* License form submit.
*
* @return void
*/
public function handle_license_page_form() {
$check_key = $this->client->getSlug() . '-check-license';
if ( isset( $_GET[ $check_key ] ) && wp_verify_nonce( sanitize_text_field( $_GET[ $check_key ] ), $this->client->getSlug() ) ) {
$this->check_license_status();
wp_safe_redirect( $this->get_page_url() );
die();
}
if ( isset( $_POST[ $this->data_key ], $_POST[ $this->data_key ]['_action'] ) ) {
check_admin_referer( $this->data_key );
switch ( $_POST[ $this->data_key ]['_action'] ) {
case 'activate':
$this->activate_client_license( array_map( 'sanitize_text_field', $_POST[ $this->data_key ] ) );
break;
case 'deactivate':
$this->deactivate_client_license();
break;
default:
break;
}
}
}
/**
* Check license status on schedule.
* Check and update license status on db.
*
* @return void
*/
public function check_license_status() {
$this->updating_license( true );
// Get current license data.
$license = $this->get_license();
if ( $license ) {
// check license.
$response = $this->check();
if ( isset( $response['success'] ) && $response['success'] ) {
// Update license status.
$license = wp_parse_args( $this->parse_license_from_request( $response ), $license );
} else {
// Don't reset the key.
// keep it, if the user renew subscription update the status and reactivate the plugin.
$license = wp_parse_args(
[
//'key' => '',
'status' => 'inactive',
//'instance' => '',
//'product_id' => empty( $this->client->getProductId() ) ? $this->client->getProductId() : $this->client->getName(), // product id.
'remaining' => 0,
'activations' => 0,
'limit' => 0,
'unlimited' => false,
'expires' => 0,
],
$license
);
}
// Update the license state & and save in db.
$this->set_license( $license );
}
$this->updating_license( false );
}
/**
* Check this is a valid license.
*
* @param array $license Optional. A license instance to check.
* Default blank.
* If blank it will test against license from database..
*
* @return bool
*/
public function is_valid( $license = [] ) {
if ( null !== $this->is_valid_license ) {
return $this->is_valid_license;
}
// Load the license if already not loaded.
if ( empty( $license ) ) {
$license = $this->get_license();
}
if ( isset( $license['key'], $license['instance'], $license['product_id'], $license['status'] ) && 'active' === $license['status'] ) {
$this->is_valid_license = $this->validate_license_signature();
} else {
$this->is_valid_license = false;
}
return $this->is_valid_license;
}
/**
* Validate license data for request.
*
* @param array $license license data.
*
* @return bool
*/
public function validate_license_data( $license = [] ) {
$license = $this->parse_license_data( $license );
return (
! empty( $license['key'] ) &&
! empty( $license['instance'] ) &&
! empty( $license['product_id'] )
);
}
/**
* Read WooCommerce API Manager Data, Convert to new license format and save in db.
*
* @param bool $override override current settings.
* @return bool
*/
public function migrate_license_from_wc_am( $override = false ) {
// phpcs:disable
/*// WC AM data structure.
[
'_data' => [ 'api_key', 'activation_email', ], // api key & email
'_product_id' => '', // product title or name
'_instance' => '', // instance key unique id
'_activated' => '', // activation status => Activated|Deactivated
'_deactivate_checkbox' => '', // deactivation check box state > On|Off
];*/
// phpcs:enable
// Check if already migrated || override.
if ( 1 == get_option( $this->option_key . '_wc_am_migrated', false ) && ! $override ) {
return false;
}
// Is already migrated.
// WC API Manager data prefix.
$wcAmPrefix = str_ireplace( [ ' ', '_', '&', '?' ], '_', strtolower( $this->client->getName() ) );
$license = [
'key' => '',
'status' => 'deactivate', // activate.
'instance' => '', // max len 190.
];
// Get key.
$data = get_option( $wcAmPrefix . '_data', false );
if ( $data && isset( $data['api_key'] ) ) {
$license['key'] = $data['api_key'];
}
if ( $data && isset( $data['product_id'] ) ) {
$license['product_id'] = $data['product_id'];
}
// Is found?.
if ( ! isset( $license['product_id'] ) ) {
$license['product_id'] = get_option( $wcAmPrefix . '_product_id', '' );
}
// Iinstance id.
$license['instance'] = get_option( $wcAmPrefix . '_instance', '' );
// Activation status.
$data = get_option( $wcAmPrefix . '_activated', false );
if ( $data ) {
$license['status'] = strtolower( $data ) === 'activated' ? 'active' : 'inactive'; // Deactivated.
}
// Save license.
$this->set_license( $license );
$this->check_license_status();
update_option( $this->option_key . '_wc_am_migrated', 1, false );
return true;
}
/**
* Styles for licenses page.
*
* @return void
*/
private function licenses_style() {
?>
client->getProductId() );
?>
license['status'] ? 'active' : 'inactive';
$remaining = $this->license['remaining'];
?>
license['updated_at'] ); ?>
render_license_expire_datetime(); ?>
license['unlimited'] ) { ?>
';
$unlimited .= '';
$unlimited .= '';
$unlimited .= '' . esc_html__( 'Unlimited', 'absolute-addons' ) . '';
printf(
/* translators: 1: Remaining activation, 2: Total activation (limit) */
esc_html__( '%1$d out of %2$s', 'absolute-addons' ),
esc_attr( $this->license['activations'] ),
$unlimited
);
?>
license['limit'] ) );
?>
license['status'] ) {
$expires = __( 'N/A', 'absolute-addons' );
$expiring = true;
} elseif ( empty( $this->license['expires'] ) ) {
$expires = __( 'N/A', 'absolute-addons' );
$expiring = false;
} else {
$time_diff = $this->license['expires'] - current_time( 'timestamp', true );
if ( $time_diff > 0 && $time_diff < WEEK_IN_SECONDS ) {
// translators: placeholder is human time diff (e.g. "3 weeks")
$expires = sprintf( __( 'In %s', 'absolute-addons' ), human_time_diff( current_time( 'timestamp', true ), $this->license['expires'] ) );
$expiring = true;
} elseif ( $time_diff < 0 && absint( $time_diff ) < WEEK_IN_SECONDS ) {
// translators: placeholder is human time diff (e.g. "3 weeks")
$expires = sprintf( __( '%s ago', 'absolute-addons' ), human_time_diff( current_time( 'timestamp', true ), $this->license['expires'] ) );
$expiring = true;
} else {
$zone = wp_timezone();
$expires = date_create( '@' . $this->license['expires'], $zone );
$expires = wp_date( 'F j, Y H:i (T)', $expires->getTimestamp(), $zone );
$expiring = false;
}
}
?>
header_icon_url = esc_url_raw( $url );
return $this;
}
public function remove_header() {
$this->remove_header = true;
return $this;
}
/**
* Card header.
*
* @return void
*/
private function render_page_header() {
if ( $this->remove_header ) {
return;
}
?>
header_icon_url ) { ?>
client->getName()
);
?>
updating_license( true );
if ( empty( $postData['license_key'] ) ) {
$this->error = __('The license key field is required.', 'absolute-addons' );
return;
}
$license = $this->get_license();
$updateKey = $this->validate_license_data( $license ); // Check if it's a change request.
if ( $updateKey ) {
$deactivate = $this->deactivate(); // deactivate first.
if ( ! $deactivate['success'] ) {
$check = $this->check(); // Check api status.
if ( $check['success'] && isset( $check['activated'] ) && $check['activated'] ) {
if ( $deactivate['error'] ) {
$this->error = $deactivate['error'];
} else {
$this->error = __( 'Unknown error occurred.', 'absolute-addons' );
}
return;
}
}
}
// Set new license info.
$license['key'] = $postData['license_key'];
$license['product_id'] = $postData['product_id'];
if ( empty( $license['instance'] ) ) {
$license['instance'] = $this->generateInstanceId();
}
// Activate The License.
$response = $this->activate( $license );
if ( ! $response['success'] ) {
if ( $response['error'] ) {
$this->error = $response['error'];
} else {
$this->error = __( 'Unknown error occurred.', 'absolute-addons' );
}
return;
}
// Don't reset the key.
// keep it, so if the user renew subscription update the status and reactivate the plugin.
// Update license status.
$license = wp_parse_args( $this->parse_license_from_request( $response ), $license );
$this->set_license( $license );
if ( ! $updateKey ) {
$this->success = __( 'License activated successfully.', 'absolute-addons' );
} else {
$this->success = __( 'License updated successfully.', 'absolute-addons' );
}
$this->schedule_cron_event();
$this->updating_license( false );
}
/**
* deactivate client license.
*
* @return void
*/
private function deactivate_client_license() {
if ( ! isset( $this->license['key'] ) || empty( $this->license['key'] ) ) {
$this->error = __( 'License key not found.', 'absolute-addons' );
} else {
$response = $this->deactivate();
if ( ! $response['success'] ) {
// check api status.
$check = $this->check();
if ( $check['success'] && isset( $check['activated'] ) && $check['activated'] ) {
if ( $response['error'] ) {
$this->error = $response['error'];
} else {
$this->error = __( 'Unknown error occurred.', 'absolute-addons' );
}
}
}
}
$this->clear_scheduler();
// Keep the instance key for reference.
$this->set_license( [ 'instance' => $this->license['instance'] ] );
$this->success = __( 'License deactivated successfully.', 'absolute-addons' );
}
/**
* Add license menu page.
*
* @return void
*/
private function add_menu_page() {
add_menu_page(
$this->menu_args['page_title'],
$this->menu_args['menu_title'],
$this->menu_args['capability'],
$this->menu_args['menu_slug'],
[ $this, 'render_menu_page' ],
$this->menu_args['icon_url'],
$this->menu_args['position']
);
}
/**
* Add submenu page.
*
* @return void
*/
private function add_submenu_page() {
add_submenu_page(
$this->menu_args['parent_slug'],
$this->menu_args['page_title'],
$this->menu_args['menu_title'],
$this->menu_args['capability'],
$this->menu_args['menu_slug'],
[ $this, 'render_menu_page' ]
);
}
/**
* Add submenu page.
*
* @return void
*/
private function add_options_page() {
add_options_page(
$this->menu_args['page_title'],
$this->menu_args['menu_title'],
$this->menu_args['capability'],
$this->menu_args['menu_slug'],
[ $this, 'render_menu_page' ]
);
}
/**
* Schedule daily license checker event.
*
* @return void
*/
public function schedule_cron_event() {
if ( ! wp_next_scheduled( $this->schedule_hook ) ) {
wp_schedule_event( time(), 'daily', $this->schedule_hook );
wp_schedule_single_event( time() + 20, $this->schedule_hook );
}
}
/**
* Clear any scheduled hook.
*
* @return void
*/
public function clear_scheduler() {
wp_clear_scheduled_hook( $this->schedule_hook );
}
/**
* Register Activation And Deactivation Hooks.
*
* @return void
*/
private function activation_deactivation() {
switch ( $this->client->getType() ) {
case 'plugin':
register_activation_hook( $this->client->getBasename(), [ $this, 'schedule_cron_event' ] );
register_deactivation_hook( $this->client->getBasename(), [ $this, 'project_deactivation' ] );
add_action( 'activated_plugin', [ $this, 'redirect_to_license_page' ], 999, 2 );
break;
case 'theme':
add_action( 'switch_theme', [ $this, 'project_deactivation' ] );
add_action( 'after_switch_theme', [ $this, 'schedule_cron_event' ] );
add_action( 'after_switch_theme', [ $this, 'redirect_to_license_page' ], 999, 2 );
break;
}
}
/**
* Project Deactivation Callback.
*
* @return void
*/
public function project_deactivation() {
$this->get_license();
$this->deactivate_client_license();
}
/**
* Redirect to the license activation page after plugin/theme is activated.
*
* @TODO make option for the plugin/theme (which is using this lib) can alter this method with their custom function.
*
* @param string $param1 Plugin: base file|Theme: old theme name.
* @param bool|WP_Theme $param2 Plugin: network wide activation status|Theme: WP_Theme instance of the old theme.
*
* @return void
*/
public function redirect_to_license_page( $param1, $param2 = null ) {
$canRedirect = false;
if ( 'plugin' == $this->client->getType() ) {
$canRedirect = ( $param1 == $this->client->getBasename() );
}
if ( 'theme' == $this->client->getType() ) {
$canRedirect = ( ! get_option( 'theme_switched_via_customizer' ) );
}
if ( $canRedirect ) {
wp_safe_redirect( $this->get_page_url() );
die();
}
}
/**
* Form action URL.
*
* @return void
*/
private function formActionUrl( $echo = true ) {
if ( $echo ) {
echo esc_url( $this->get_page_url() );
}
return $this->get_page_url();
}
/**
* Get input license key.
*
* @param array $license license data.
* @return string
*/
private function get_input_license_value( $license ) {
// phpcs:disable
// if ( 'deactivate' != $action ) return '';
// $key_length = strlen( $license['key'] );
// return str_pad( substr( $license['key'], 0, $key_length / 2 ), $key_length, '*' );
// phpcs:enable
return isset( $license['key'] ) ? $license['key'] : '';
}
private function license_signature_payload() {
/**
* Once cron runs it updates the next schedule before running
* the scheduled hook, so when we call the update_license_signature
* method we get the new time stamp here (wp_next_scheduled).
*/
$payload = $this->client->getSlug() . '||' . wp_next_scheduled( $this->schedule_hook );
return $payload . '||' . implode( '||', array_values( $this->license ) );
}
private function update_license_signature() {
if ( $this->license && is_array( $this->license ) ) {
$payload = $this->license_signature_payload();
$signature = hash_hmac( 'sha1', $payload, $this->hash( $payload ) );
update_option( $this->option_key . '_signature', $signature, false );
}
}
private function get_license_signature() {
remove_all_filters( "pre_option_{$this->option_key}_signature" );
return get_option( $this->option_key . '_signature', false );
}
private function validate_license_signature() {
$license_signature = $this->get_license_signature();
if ( ! $license_signature ) {
return false;
}
// Validate hash.
$payload = $this->license_signature_payload();
$signature = hash_hmac( 'sha1', $payload, $this->hash( $payload ) );
return hash_equals( $signature, $license_signature );
}
/**
* Update License Data.
* Call this method without license data will deactivate the license (set empty data).
*
* @param array $license {
* Optional. License Data.
* @type string $key The License Key.
* @type string $status Activation Status.
* @type int $remaining Remaining Activation.
* @type int $activation_limit Number of activation allowed for the license key.
* @type int $expires Number of day remaining before the license expires.
* }
* @return bool False if value was not updated and true if value was updated.
*/
private function set_license( $license = [] ) {
// Parse & sanitize.
$this->license = $this->parse_license_data( $license );
$this->license['updated_at'] = current_time( 'mysql' );
// Update license signature.
$this->update_license_signature();
// Update in db.
return update_option( $this->option_key, $this->license, false );
}
/**
* Get Plugin/Theme License.
*
* @return array {
* Optional. License Data.
* @type string $key The License Key.
* @type string $status Activation Status.
* @type int $remaining Remaining Activation.
* @type int $activation_limit Number of activation allowed for the license key.
* @type int $expires Number of day remaining before the license expires.
* }
*/
public function get_license() {
if ( null !== $this->license ) {
return $this->license;
}
// No hooks please...
remove_all_filters( "pre_option_{$this->option_key}" );
$this->license = get_option( $this->option_key, false );
// Initialize blank inactive license data.
if ( false === $this->license ) {
$this->set_license();
}
$this->license = $this->parse_license_data( $this->license );
return $this->license;
}
/**
* Parse License data.
*
* @param array $data license data.
*
* @return array
*/
private function parse_license_data( $data = [] ) {
$defaults = [
'key' => '', // License key.
'status' => 'inactive', // Current status.
'instance' => '', // Instance unique id.
// product id.
'product_id' => empty( $this->client->getProductId() ) ? $this->client->getProductId() : $this->client->getName(),
'remaining' => 0, // Remaining activation.
'activations' => 0, // Total activation.
'limit' => 0, //Activation limit.
'unlimited' => false, // Is unlimited activation.
'expires' => 0, // Expires set this to a unix timestamp [GMT].
'updated_at' => 0, // Expires set this to a unix timestamp [GMT].
];
// Parse.
$data = wp_parse_args( $data, $defaults );
$license = [];
$updated_at = strtotime( $data['updated_at'] );
if ( $updated_at ) {
$updated_at = gmdate( 'Y-m-d H:i:s', $updated_at );
}
// Sanitize data.
$license['key'] = sanitize_text_field( $data['key'] );
$license['status'] = strtolower( $data['status'] ) === 'active' ? 'active' : 'inactive';
$license['instance'] = sanitize_text_field( $data['instance'] );
$license['product_id'] = sanitize_text_field( $data['product_id'] ); // Product id can be string too.
$license['remaining'] = absint( $data['remaining'] );
$license['activations'] = absint( $data['activations'] );
$license['limit'] = absint( $data['limit'] );
$license['unlimited'] = (bool) $data['unlimited'];
$license['expires'] = absint( $data['expires'] );
$license['updated_at'] = $updated_at;
return $license;
}
/**
* @param array $response
*
* @return array
*/
private function parse_license_from_request( $response ) {
// WC API-Manager don't send back the remaining date. We have tricked it to do so.
$status = 'inactive';
if ( isset( $response['activated'] ) && $response['activated'] ) {
$status = 'active';
} elseif (
// Legacy API Manager < 2.0, and API Manager PHP Library <= 1.2.
isset( $response['status_check'] ) && 'active' == $response['status_check']
) {
$status = 'active';
}
return [
'status' => $status,
'remaining' => isset( $response['data'], $response['data']['activations_remaining'] ) ? $response['data']['activations_remaining'] : 0,
'activations' => isset( $response['data'], $response['data']['total_activations'] ) ? $response['data']['total_activations'] : 0,
'limit' => isset( $response['data'], $response['data']['total_activations_purchased'] ) ? $response['data']['total_activations_purchased'] : 0,
'unlimited' => isset( $response['data'], $response['data']['unlimited_activations'] ) && $response['data']['unlimited_activations'],
'expires' => isset( $response['data'], $response['data']['expires'] ) ? $response['data']['expires'] : 0,
];
}
/**
* Generate a random Instance ID.
*
* WC-AM requires an instance id, with maximum length of 190 (instance VARCHAR(190) NOT NULL)
*
* @return string
*/
private function generateInstanceId() {
if ( ! function_exists( 'wp_generate_password' ) ) {
require_once ABSPATH . WPINC . '/pluggable.php';
}
return sha1( wp_generate_password( 12, false ) . microtime( true ) . home_url() );
}
/**
* Gets a form of `wp_hash()` specific to the plugin using license service.
*
* We cannot use `wp_hash()` because it is defined in `pluggable.php` which is not loaded until after plugins are loaded,
* which is too late to verify the recovery mode cookie.
*
* This tries to use the `AUTH` salts first, but if they aren't valid specific salts will be generated and stored.
*
* @param string $data Data to hash.
* @return string|false The hashed $data, or false on failure.
*
* @see wp_hash()
*/
private function hash( $data ) {
if ( ! defined( 'AUTH_KEY' ) || AUTH_KEY === 'put your unique phrase here' ) {
$auth_key = get_site_option( $this->client->getSlug() . '_license_auth_key' );
if ( ! $auth_key ) {
if ( ! function_exists( 'wp_generate_password' ) ) {
require_once ABSPATH . WPINC . '/pluggable.php';
}
$auth_key = wp_generate_password( 64, true, true );
update_site_option( $this->client->getSlug() . '_license_auth_key', $auth_key );
}
} else {
$auth_key = AUTH_KEY;
}
if ( ! defined( 'AUTH_SALT' ) || AUTH_SALT === 'put your unique phrase here' || AUTH_SALT === $auth_key ) {
$auth_salt = get_site_option( $this->client->getSlug() . '_license_auth_salt' );
if ( ! $auth_salt ) {
if ( ! function_exists( 'wp_generate_password' ) ) {
require_once ABSPATH . WPINC . '/pluggable.php';
}
$auth_salt = wp_generate_password( 64, true, true );
update_site_option( $this->client->getSlug() . '_license_auth_salt', $auth_salt );
}
} else {
$auth_salt = AUTH_SALT;
}
return hash_hmac( 'sha1', $data, $auth_key . $auth_salt );
}
}
// End of file class-license.php.