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; } }