518 lines
12 KiB
PHP
518 lines
12 KiB
PHP
<?php
|
|
namespace AIOSEO\BrokenLinkChecker\Admin;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
use AIOSEO\BrokenLinkChecker\Models;
|
|
|
|
/**
|
|
* Handles license update/removal and related notices.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class License {
|
|
/**
|
|
* The base URL for the licensing API.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
private $baseUrl = 'https://blc-licensing.aioseo.com/v1/';
|
|
|
|
/**
|
|
* The action name for the periodic license check.
|
|
*
|
|
* @since 1.2.9
|
|
*
|
|
* @var string
|
|
*/
|
|
public $actionName = 'aioseo_blc_license_check';
|
|
|
|
/**
|
|
* Options class instance.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var \AIOSEO\BrokenLinkChecker\Options\Options
|
|
*/
|
|
protected $options = null;
|
|
|
|
/**
|
|
* InternalOptions class instance.
|
|
*
|
|
* @since 1.0.0
|
|
*t
|
|
* @var \AIOSEO\BrokenLinkChecker\Options\InternalOptions
|
|
*/
|
|
protected $internalOptions = null;
|
|
|
|
/**
|
|
* Class constructor.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function __construct() {
|
|
$this->internalOptions = aioseoBrokenLinkChecker()->internalOptions;
|
|
|
|
add_action( 'init', [ $this, 'scheduleLicenseCheck' ], 3 );
|
|
add_action( $this->actionName, [ $this, 'checkLicense' ] );
|
|
}
|
|
|
|
/**
|
|
* Schedules the license check as a recurring Action Scheduler action.
|
|
*
|
|
* @since 1.2.9
|
|
*
|
|
* @return void
|
|
*/
|
|
public function scheduleLicenseCheck() {
|
|
if ( ! $this->internalOptions->internal->license->licenseKey ) {
|
|
return;
|
|
}
|
|
|
|
aioseoBrokenLinkChecker()->actionScheduler->scheduleRecurrent( $this->actionName, DAY_IN_SECONDS, DAY_IN_SECONDS );
|
|
}
|
|
|
|
/**
|
|
* Validates the stored license and resets license data if no key is present.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkLicense() {
|
|
if ( ! $this->internalOptions->internal->license->licenseKey ) {
|
|
if ( $this->needsReset() ) {
|
|
$this->internalOptions->internal->license->reset(
|
|
[
|
|
'expires',
|
|
'expired',
|
|
'invalid',
|
|
'disabled',
|
|
'activationsError',
|
|
'connectionError',
|
|
'requestError',
|
|
'level'
|
|
]
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$this->activateProgrammatic();
|
|
}
|
|
|
|
/**
|
|
* Activates the license in response to a direct user action, e.g. connecting their account in the UI.
|
|
* Unlike activateProgrammatic(), this is never rate-limited so the user gets immediate feedback.
|
|
*
|
|
* @since 1.2.9
|
|
*
|
|
* @return bool Whether or not the license was activated.
|
|
*/
|
|
public function activateManual() {
|
|
return $this->activate();
|
|
}
|
|
|
|
/**
|
|
* Activates the license from an automated/background context, e.g. a scheduled check or a quota-change triggered update.
|
|
* Rate-limited to one request per hour to prevent flooding the licensing server.
|
|
*
|
|
* @since 1.2.9
|
|
*
|
|
* @return bool Whether or not the license was activated.
|
|
*/
|
|
public function activateProgrammatic() {
|
|
if ( aioseoBrokenLinkChecker()->core->cache->get( 'license_programmatic_activation' ) ) {
|
|
return false;
|
|
}
|
|
|
|
aioseoBrokenLinkChecker()->core->cache->update( 'license_programmatic_activation', true, HOUR_IN_SECONDS );
|
|
|
|
return $this->activate();
|
|
}
|
|
|
|
/**
|
|
* Activate/validate the license.
|
|
* This method should never be called directly. Use activateManual() or activateProgrammatic() instead.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether or not it was activated.
|
|
*/
|
|
private function activate() {
|
|
$this->internalOptions->internal->license->reset(
|
|
[
|
|
'expires',
|
|
'expired',
|
|
'invalid',
|
|
'disabled',
|
|
'activationsError',
|
|
'connectionError',
|
|
'requestError',
|
|
'level'
|
|
]
|
|
);
|
|
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return false;
|
|
}
|
|
|
|
$site = aioseoBrokenLinkChecker()->helpers->getSite();
|
|
$domains = [
|
|
'domain' => $site->domain,
|
|
'path' => $site->path
|
|
];
|
|
|
|
$response = $this->sendLicenseRequest( 'activate', $licenseKey, [ $domains ] );
|
|
|
|
if ( empty( $response ) ) {
|
|
// Something bad happened, error unknown.
|
|
$this->internalOptions->internal->license->connectionError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $response->error ) ) {
|
|
if ( 'missing-key-or-domain' === $response->error ) {
|
|
$this->internalOptions->internal->license->requestError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'missing-license' === $response->error ) {
|
|
$this->internalOptions->internal->license->invalid = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'disabled' === $response->error ) {
|
|
$this->internalOptions->internal->license->disabled = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'activations' === $response->error ) {
|
|
$this->internalOptions->internal->license->activationsError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'expired' === $response->error ) {
|
|
$this->internalOptions->internal->license->expires = strtotime( $response->expires );
|
|
$this->internalOptions->internal->license->expired = true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Something bad happened, error unknown.
|
|
if ( empty( $response->success ) || empty( $response->level ) || ! isset( $response->broken_links_count ) ) {
|
|
return false;
|
|
}
|
|
|
|
$oldQuota = $this->internalOptions->internal->license->quota;
|
|
|
|
$this->internalOptions->internal->license->level = $response->level;
|
|
$this->internalOptions->internal->license->expires = strtotime( $response->expires );
|
|
$this->internalOptions->internal->license->quota = intval( $response->broken_links_count );
|
|
|
|
// Set the remaining quota if it's never been set or if the user's plan has changed.
|
|
if (
|
|
! $this->internalOptions->internal->license->quotaRemaining ||
|
|
( intval( $response->broken_links_count ) !== (int) $oldQuota )
|
|
) {
|
|
$this->internalOptions->internal->license->quotaRemaining = intval( $response->broken_links_count );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Deactivate the license key.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether or not it was deactivated.
|
|
*/
|
|
public function deactivate() {
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return false;
|
|
}
|
|
|
|
$site = aioseoBrokenLinkChecker()->helpers->getSite();
|
|
$domains = [
|
|
'domain' => $site->domain,
|
|
'path' => $site->path
|
|
];
|
|
|
|
$response = $this->sendLicenseRequest( 'deactivate', $licenseKey, [ $domains ] );
|
|
|
|
if ( empty( $response ) ) {
|
|
// Something bad happened, error unknown.
|
|
$this->internalOptions->internal->license->connectionError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $response->error ) ) {
|
|
if ( 'missing-key-or-domain' === $response->error || 'not-activated' === $response->error ) {
|
|
$this->internalOptions->internal->license->requestError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'missing-license' === $response->error ) {
|
|
$this->internalOptions->internal->license->invalid = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( 'disabled' === $response->error ) {
|
|
$this->internalOptions->internal->license->disabled = true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$this->internalOptions->internal->license->reset(
|
|
[
|
|
'expires',
|
|
'expired',
|
|
'invalid',
|
|
'disabled',
|
|
'activationsError',
|
|
'connectionError',
|
|
'requestError',
|
|
'level'
|
|
]
|
|
);
|
|
|
|
// Cancel all Link Status scans.
|
|
as_unschedule_all_actions( aioseoBrokenLinkChecker()->main->linkStatus->actionName );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the URL to check licenses.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return string The URL.
|
|
*/
|
|
public function getUrl() {
|
|
if ( defined( 'AIOSEO_BROKEN_LINK_CHECKER_LICENSING_URL' ) ) {
|
|
return AIOSEO_BROKEN_LINK_CHECKER_LICENSING_URL;
|
|
}
|
|
|
|
return $this->baseUrl;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the current license is expired.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the license is expired.
|
|
*/
|
|
public function isExpired() {
|
|
$networkIsExpired = false;
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return $networkIsExpired;
|
|
}
|
|
|
|
$expired = $this->internalOptions->internal->license->expired || $this->internalOptions->internal->license->expires < time();
|
|
if ( $expired ) {
|
|
$didActivationAttempt = $this->maybeReactivateExpiredLicense();
|
|
|
|
// If we tried to activate the license again, start over. Otherwise, return true.
|
|
return $didActivationAttempt ? $this->isExpired() : true;
|
|
}
|
|
|
|
$expires = $this->internalOptions->internal->license->expires;
|
|
|
|
return 0 !== $expires && $expires < time();
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the current license is disabled.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the license is disabled.
|
|
*/
|
|
public function isDisabled() {
|
|
$networkIsDisabled = false;
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return $networkIsDisabled;
|
|
}
|
|
|
|
return $this->internalOptions->internal->license->disabled;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the current license is invalid.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the license is invalid.
|
|
*/
|
|
public function isInvalid() {
|
|
$networkIsInvalid = false;
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return $networkIsInvalid;
|
|
}
|
|
|
|
return $this->internalOptions->internal->license->invalid;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the current license is active.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the license is active.
|
|
*/
|
|
public function isActive() {
|
|
$networkIsActive = false;
|
|
$licenseKey = $this->internalOptions->internal->license->licenseKey;
|
|
if ( empty( $licenseKey ) ) {
|
|
return $networkIsActive;
|
|
}
|
|
|
|
return ! $this->isExpired() && ! $this->isDisabled() && ! $this->isInvalid();
|
|
}
|
|
|
|
/**
|
|
* Get the license level for the activated license.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return string The license level.
|
|
*/
|
|
public function getLicenseLevel() {
|
|
return $this->internalOptions->internal->license->level;
|
|
}
|
|
|
|
/**
|
|
* Checks if the license data needs to be reset.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the license data needs to be reet.
|
|
*/
|
|
private function needsReset() {
|
|
if ( ! empty( $this->internalOptions->internal->license->licenseKey ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $this->internalOptions->internal->license->level ) {
|
|
return true;
|
|
}
|
|
|
|
if ( $this->internalOptions->internal->license->invalid ) {
|
|
return true;
|
|
}
|
|
|
|
if ( $this->internalOptions->internal->license->disabled ) {
|
|
return true;
|
|
}
|
|
|
|
$expired = $this->internalOptions->internal->license->expired;
|
|
if ( $expired ) {
|
|
return true;
|
|
}
|
|
|
|
$expires = $this->internalOptions->internal->license->expires;
|
|
|
|
return 0 !== $expires;
|
|
}
|
|
|
|
/**
|
|
* Sends the license request.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $type The type of request, either activate or deactivate.
|
|
* @param string $licenseKey The license key we are using for this request.
|
|
* @param array $domains List of domains to activate or deactivate.
|
|
* @return Object|null The JSON response as an object.
|
|
*/
|
|
public function sendLicenseRequest( $type, $licenseKey, $domains ) {
|
|
$payload = [
|
|
'sku' => 'aioseo-broken-link-checker',
|
|
'version' => AIOSEO_BROKEN_LINK_CHECKER_VERSION,
|
|
'php_version' => PHP_VERSION,
|
|
'license' => $licenseKey,
|
|
'domains' => $domains,
|
|
'wp_version' => get_bloginfo( 'version' )
|
|
];
|
|
|
|
$response = aioseoBrokenLinkChecker()->helpers->wpRemotePost( $this->getUrl() . $type . '/', [
|
|
'timeout' => 20,
|
|
'body' => wp_json_encode( $payload )
|
|
] );
|
|
$responseBody = wp_remote_retrieve_body( $response );
|
|
|
|
return ! empty( $responseBody ) ? json_decode( $responseBody ) : null;
|
|
}
|
|
|
|
/**
|
|
* Checks if the current site is licensed at the network level.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool Whether the site is licensed at the network level.
|
|
*/
|
|
public function isNetworkLicensed() {
|
|
if ( ! property_exists( aioseoBrokenLinkChecker(), 'networkLicense' ) ) {
|
|
return false;
|
|
}
|
|
|
|
return aioseoBrokenLinkChecker()->networkLicense->isActive();
|
|
}
|
|
|
|
/**
|
|
* Whether the current license plan is the free plan.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isFree() {
|
|
return 'free' === strtolower( (string) $this->getLicenseLevel() );
|
|
}
|
|
|
|
/**
|
|
* Checks if the license is expired and attempts to activate it again.
|
|
*
|
|
* @since 1.1.0
|
|
*
|
|
* @return bool True if an attempt was made to activate the license, false if not.
|
|
*/
|
|
private function maybeReactivateExpiredLicense() {
|
|
// If the license is expired, send out a request to check if it's still expired.
|
|
// We cache this for a few hours so we don't spam the server.
|
|
$transientName = 'expired_license_check';
|
|
if ( aioseoBrokenLinkChecker()->core->cache->get( $transientName ) ) {
|
|
return false;
|
|
}
|
|
|
|
$this->activateProgrammatic();
|
|
|
|
aioseoBrokenLinkChecker()->core->cache->update( $transientName, true, 12 * HOUR_IN_SECONDS );
|
|
|
|
return true;
|
|
}
|
|
} |