licenseCheckAction, [ $this, 'checkLicense' ] ); add_action( $this->licenseCheckExpiredAction, [ $this, 'checkLicense' ] ); $this->options = aioseo()->options; $this->internalOptions = aioseo()->internalOptions; $this->sensitiveOptions = aioseo()->sensitiveOptions; } /** * Activates the license from the AIOSEO_LICENSE_KEY constant if defined and different from the stored key. * * Deferred to admin_init so the full aioseo() container (actionScheduler, etc.) is ready. * * @since 4.9.6.2 * * @return void */ public function maybeActivateFromConstant() { if ( ! defined( 'AIOSEO_LICENSE_KEY' ) || empty( AIOSEO_LICENSE_KEY ) ) { return; } if ( AIOSEO_LICENSE_KEY === $this->sensitiveOptions->get( $this->sensitiveKeyName ) ) { return; } $this->sensitiveOptions->set( $this->sensitiveKeyName, AIOSEO_LICENSE_KEY ); aioseo()->actionScheduler->unschedule( $this->licenseCheckAction ); $this->activateManual(); } /** * Schedules the recurring daily license check via Action Scheduler. * * @since 4.9.5.2 * * @return void */ public function scheduleLicenseCheck() { if ( ! $this->sensitiveOptions->hasValue( $this->sensitiveKeyName ) ) { aioseo()->actionScheduler->unschedule( $this->licenseCheckAction ); aioseo()->actionScheduler->unschedule( $this->licenseCheckExpiredAction ); return; } aioseo()->actionScheduler->scheduleRecurrent( $this->licenseCheckAction, DAY_IN_SECONDS, DAY_IN_SECONDS ); } /** * Validate plugin notifications for expired licenses. * * @since 4.1.2 * * @return void */ private function validateNotifications() { $notification = Models\Notification::getNotificationByName( 'license-expired' ); if ( $this->isExpired() ) { if ( $notification->exists() ) { // Force the notice to reappear always. $notification->dismissed = false; $notification->save(); return; } // Let user know we've found an error. Models\Notification::addNotification( [ 'slug' => uniqid(), 'notification_name' => 'license-expired', 'title' => __( 'Your License is expired and your SEO is at risk!', 'aioseo-pro' ), 'content' => sprintf( // Translators: 1 - "Pro", 2 - The plugin name ("All in One SEO"), 3 - Opening bold tag, 4 - Closing bold tag. __( 'An active license is needed to use any of the %1$s features of %2$s, including %3$sSearch Statistics, Link Assistant, Redirection Manager, Video and News sitemaps%4$s and more. It also provides access to new features & addons, plugin updates (including security improvements), and our world class support!', 'aioseo-pro' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded 'Pro', AIOSEO_PLUGIN_SHORT_NAME, '', '' ), 'type' => 'error', 'level' => [ 'all' ], 'button1_label' => __( 'Renew Now', 'aioseo-pro' ), 'button1_action' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'account/downloads/', 'admin-notice', 'renew-now' ), 'button2_label' => __( 'Learn More', 'aioseo-pro' ), 'button2_action' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'docs/how-to-renew-your-aioseo-license/', 'admin-notice', 'learn-more' ), 'start' => gmdate( 'Y-m-d H:i:s' ) ] ); return; } if ( $notification->exists() ) { Models\Notification::deleteNotificationByName( 'license-expired' ); } } /** * 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 4.0.0 * @version 4.8.1 Moved bulk of the logic to activateHelper(). * @version 4.9.5.2 Renamed from activate() to activateManual(). * * @return bool Whether or not it was activated. */ public function activateManual() { $licenseKey = $this->sensitiveOptions->get( $this->sensitiveKeyName ); if ( empty( $licenseKey ) ) { return false; } return $this->activateHelper( 'activate', $licenseKey ); } /** * Activates the license from an automated/background context, e.g. a scheduled check. * Rate-limited to one request per hour to prevent flooding the licensing server. * * @since 4.9.5.2 * * @return bool Whether or not it was activated. */ public function activateProgrammatic() { if ( aioseo()->core->cache->get( 'license_programmatic_activation' ) ) { return false; } aioseo()->core->cache->update( 'license_programmatic_activation', true, HOUR_IN_SECONDS ); return $this->activateManual(); } /** * Activate the license by OTP. * * @since 4.8.1 * * @param string $otp The one-time password to activate with. * @return bool Whether or not it was activated. */ public function activateWithOtp( $otp ) { if ( empty( $otp ) ) { return false; } return $this->activateHelper( 'activate/otp', $otp ); } /** * Activates the plugin by license key or OTP. * This is a helper function for activate() and activateWithOtp(). * * @since 4.8.1 * * @param string $key The license key to validate. * @return bool Whether or not it was activated. */ private function activateHelper( $route, $key ) { $this->internalOptions->internal->license->reset( [ 'expires', 'expired', 'invalid', 'disabled', 'activationsError', 'connectionError', 'requestError', 'level', 'addons', 'features', 'counts', 'upgradeUrl' ] ); if ( empty( $route ) || empty( $key ) ) { return false; } $site = aioseo()->helpers->getSite(); $domains = [ 'domain' => $site->domain, 'path' => $site->path ]; $response = $this->sendLicenseRequest( $route, $key, [ $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 ( 'domain-disabled' === $response->error ) { $this->internalOptions->internal->license->domainDisabled = 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 ) ) { return false; } $this->internalOptions->internal->license->level = $response->level; $this->internalOptions->internal->license->addons = wp_json_encode( $response->addons ); $this->internalOptions->internal->license->expires = strtotime( $response->expires ); $this->internalOptions->internal->license->features = wp_json_encode( $response->features ); // Store activation counts if provided. if ( ! empty( $response->counts ) ) { $this->internalOptions->internal->license->counts = wp_json_encode( $response->counts ); } // Store upgrade URL if provided. if ( ! empty( $response->upgradeUrl ) ) { $this->internalOptions->internal->license->upgradeUrl = $response->upgradeUrl; } // Store the license key if provided. if ( ! empty( $response->key ) ) { $this->sensitiveOptions->set( $this->sensitiveKeyName, $response->key ); } if ( $this->isActive() ) { aioseo()->seoChecklist->completeCheck( 'licenseActivated' ); } else { aioseo()->seoChecklist->uncompleteCheck( 'licenseActivated' ); } return true; } /** * Deactivate the license key. * * @since 4.0.0 * * @return boolean Whether or not it was deactivated. */ public function deactivate() { $licenseKey = aioseo()->sensitiveOptions->get( 'licenseKey' ); if ( empty( $licenseKey ) ) { return false; } $site = aioseo()->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', 'addons', 'features', 'counts', 'upgradeUrl' ] ); // Update the AI credits. aioseo()->sensitiveOptions->set( 'aiAccessToken', '' ); aioseo()->ai->getAccessToken( true ); aioseo()->seoChecklist->uncompleteCheck( 'licenseActivated' ); return true; } /** * Output any notices generated by the class. * * @since 4.0.0 * * @param bool $belowH2 */ public function notices( $belowH2 = false ) { // Double check we're actually in the admin before outputting anything. if ( ! is_admin() ) { return; } // Grab the option and output any nag dealing with license keys. $isActive = $this->isActive(); $expired = $this->internalOptions->internal->license->expired; $invalid = $this->internalOptions->internal->license->invalid; $disabled = $this->internalOptions->internal->license->disabled; $belowH2 = $belowH2 ? 'below-h2' : ''; // If there is no license key, output nag about ensuring key is set for automatic updates. if ( ! $isActive ) { ?>

', esc_url( add_query_arg( [ 'page' => 'aioseo-settings' ], is_network_admin() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ) ) ) ), esc_html( AIOSEO_PLUGIN_NAME ), '' ), [ 'a' => [ 'href' => [], ], 'strong' => [] ] ) ?>

helpers->utmUrl( AIOSEO_MARKETING_URL . 'account/downloads/', 'admin-notice', 'renew-now' ); $learnMoreUrl = aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'docs/how-to-renew-your-aioseo-license/', 'admin-notice', 'learn-more' ); ?>

', '' ); // phpcs:ignore Generic.Files.LineLength.MaxExceeded ?>

 

.

isActive() || is_network_admin() ) { return; } $this->outputUpdateRowNotice(); } /** * Outputs the update row notice. * * @since 4.2.5 * * @return void */ protected function outputUpdateRowNotice() { echo '
' . sprintf( // Translators: 1 - Opening HTML bold tag, 2 - Closing HTML bold tag, 3 - The plugin name ("All in One SEO"). esc_html__( 'A %1$svalid license key%2$s is required to download updates for %3$s.', 'aioseo-pro' ), '', '', esc_html( AIOSEO_PLUGIN_NAME ) ) . ''; } /** * Add row to Plugins page with licensing information, if license key is invalid or not found. * * @since 4.0.0 * * @return void */ public function pluginRowNotice() { if ( $this->isActive() || is_network_admin() ) { return; } $this->outputPluginRowNotice(); } /** * Outputs the plugin row notice. * * @since 4.2.5 * * @return void */ protected function outputPluginRowNotice() { $message = esc_html__( 'has not been entered', 'aioseo-pro' ); $pre = sprintf( // Translators: 1 - Opening HTML link tag, 2 - Closing HTML link tag. ' %1$sClick here to enter one now!%2$s', '', '' ); $licenseKey = aioseo()->sensitiveOptions->get( 'licenseKey' ); if ( ! empty( $licenseKey ) ) { $pre = ''; if ( $this->isExpired() ) { $message = esc_html__( 'is expired', 'aioseo-pro' ); } if ( $this->isDisabled() ) { $message = esc_html__( 'is disabled', 'aioseo-pro' ); } if ( $this->isInvalid() ) { $message = esc_html__( 'is invalid', 'aioseo-pro' ); } } // Translators: 1 - HTML Line break tags, 2 - "Pro", 3 - Opening bold tag, 4 - Closing bold tag, 5 - HTML Line break tags. $end = sprintf( esc_html__( 'and your SEO is at risk!%1$sAn active license is needed to use any of the %2$s features including: %3$sSearch Statistics, Link Assistant, Redirection Manager, Video and News sitemaps%4$s and more. It also provides access to new features & addons, plugin updates (including security improvements), and our world class support! %5$s', 'aioseo-pro' ), $pre . '

', 'Pro', '', '', '

' ); // phpcs:ignore Generic.Files.LineLength.MaxExceeded echo '

' . sprintf( '%1$s %2$s %3$s %4$s', esc_html__( 'Your', 'aioseo-pro' ), esc_html__( 'license key', 'aioseo-pro' ), $message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $end // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ) . '

' . esc_html__( 'Manage Licenses', 'aioseo-pro' ) . ' | ' . esc_html__( 'Purchase one now.', 'aioseo-pro' ) . '

'; } /** * Get the URL to check licenses. * * @since 4.0.0 * * @return string The URL. */ public function getUrl() { if ( defined( 'AIOSEO_LICENSING_URL' ) ) { return AIOSEO_LICENSING_URL; } return $this->baseUrl; } /** * Checks to see if the current license is expired. * * @since 4.0.0 * * @return bool True if expired, false if not. */ public function isExpired() { $networkIsExpired = $this->isNetworkLicensed() && aioseo()->networkLicense->isExpired(); if ( ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return $networkIsExpired; } if ( $this->internalOptions->internal->license->expired ) { return true; } $expires = $this->internalOptions->internal->license->expires; return 0 !== $expires && $expires < time(); } /** * Checks to see if the current license is disabled. * * @return bool True if disabled, false if not. */ public function isDisabled() { $networkIsDisabled = $this->isNetworkLicensed() && aioseo()->networkLicense->isDisabled(); if ( ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return $networkIsDisabled; } return $this->internalOptions->internal->license->disabled; } /** * Checks to see if the current license is invalid. * * @since 4.0.0 * * @return bool True if invalid, false if not. */ public function isInvalid() { $networkIsInvalid = $this->isNetworkLicensed() && aioseo()->networkLicense->isInvalid(); if ( ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return $networkIsInvalid; } return $this->internalOptions->internal->license->invalid; } /** * Checks to see if the current license is disabled. * * @since 4.0.0 * * @return bool True if disabled, false if not. */ public function isActive() { $networkIsActive = $this->isNetworkLicensed() && aioseo()->networkLicense->isActive(); if ( ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return $networkIsActive; } return ! $this->isExpired() && ! $this->isDisabled() && ! $this->isInvalid(); } /** * Gets the license key. * * @since 4.4.3 * * @return string The license key. */ public function getLicenseKey() { // Subsite activated licenses take precedence over network activated licenses. if ( aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return aioseo()->sensitiveOptions->get( 'licenseKey' ); } // If no subsite license is found, check for a network license. if ( $this->isNetworkLicensed() ) { return aioseo()->networkSensitiveOptions->get( 'networkLicenseKey' ); } return ''; } /** * Get the license level for the activated license. * * @since 4.0.0 * * @return string The license level. */ public function getLicenseLevel() { $networkLicenseLevel = $this->isNetworkLicensed() ? aioseo()->networkLicense->getLicenseLevel() : 'Unknown'; if ( ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ) { return $networkLicenseLevel; } return $this->internalOptions->internal->license->level; } /** * Get the license features for the activated license. * * @since 4.2.4 * * @param string $type The feature type. * @return array The license features. */ public function getLicenseFeatures( $type = '' ) { $features = $this->isNetworkLicensed() && ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ? aioseo()->internalNetworkOptions->internal->license->features : $this->internalOptions->internal->license->features; $allFeatures = json_decode( $features, true ) ?: []; if ( ! empty( $type ) ) { $allFeatures = ! empty( $allFeatures[ $type ] ) ? $allFeatures[ $type ] : []; } return $allFeatures; } /** * Get the core feature for the activated license. * * @since 4.2.5 * * @param string $sectionSlug The section name. * @param string $feature The feature name. * @return bool The license has access to a core feature. */ public function hasCoreFeature( $sectionSlug, $feature = '' ) { $coreFeatures = $this->getLicenseFeatures( 'core' ); foreach ( $coreFeatures as $section => $features ) { if ( $sectionSlug !== $section ) { continue; } if ( empty( $feature ) ) { return true; } foreach ( $features as $featureName ) { if ( $featureName === $feature || false !== stripos( $featureName, "$feature:" ) ) { return true; } } } return false; } /** * Returns the value for a core feature. * * @since 4.0.0 * * @param string $sectionSlug The section name. * @param string $feature The feature name. * @return mixed The feature value. */ public function getCoreFeatureValue( $sectionSlug, $feature ) { return $this->getFeatureValue( 'core', $sectionSlug, $feature ); } /** * Get the addon feature for the activated license. * * @since 4.2.4 * * @param string $addonName The addon name. * @param string $feature The feature name. * @return bool The license has access to an addon feature. */ public function hasAddonFeature( $addonName, $feature ) { $addons = $this->getLicenseFeatures( 'addons' ); foreach ( $addons as $addon => $features ) { if ( $addon === $addonName && in_array( $feature, $features, true ) ) { return true; } } return false; } /** * Returns the value for an addon feature. * * @since 4.0.0 * * @param string $sectionSlug The section name. * @param string $feature The feature name. * @return mixed The feature value. */ public function getAddonFeatureValue( $sectionSlug, $feature ) { return $this->getFeatureValue( 'addons', $sectionSlug, $feature ); } /** * Checks whether a given addon can be used with the current license plan. * * @since 4.0.0 * * @param string $addonName The addon name. * @return boolean Whether the addon can be used. */ public function isAddonAllowed( $addonName ) { $addons = $this->isNetworkLicensed() && ! aioseo()->sensitiveOptions->hasValue( 'licenseKey' ) ? aioseo()->internalNetworkOptions->internal->license->addons : $this->internalOptions->internal->license->addons; if ( is_string( $addons ) ) { $addons = json_decode( $addons ); } if ( empty( $addons ) ) { return false; } return in_array( $addonName, $addons, true ); } /** * Checks if the license data needs to be reset. * * @since 4.0.0 * * @return bool True if a reset is needed, false if not. */ private function needsReset() { if ( aioseo()->sensitiveOptions->hasValue( '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; } /** * Send the license request. * * @since 4.2.5 * * @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 An array of domains to activate or deactivate. * @return object|null The JSON response as an object. */ public function sendLicenseRequest( $type, $licenseKey, $domains ) { $payload = [ 'sku' => 'all-in-one-seo-pack-pro', 'version' => AIOSEO_VERSION, 'license' => $licenseKey, 'domains' => $domains, 'php_version' => PHP_VERSION, 'wp_version' => get_bloginfo( 'version' ) ]; $response = aioseo()->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 4.2.5 * * @return bool True if licensed at the network level and not licensed locally. */ public function isNetworkLicensed() { if ( ! aioseo()->networkLicense ) { return false; } return aioseo()->networkLicense->isActive(); } /** * Returns the value for a feature. * Helper method form getCoreFeatureValue() and getAddonFeatureValue(). * * @since 4.0.0 * * @param string $type The feature type. * @param string $sectionSlug The section name. * @param string $feature The feature name. * @return mixed The feature value. */ private function getFeatureValue( $type, $sectionSlug, $feature ) { $licenseFeatures = $this->getLicenseFeatures( $type ); foreach ( $licenseFeatures as $section => $features ) { if ( $sectionSlug !== $section ) { continue; } foreach ( $features as $featureName ) { if ( $featureName !== $feature && false === stripos( $featureName, "$feature:" ) ) { continue; } $parts = explode( ':', $featureName ); if ( ! isset( $parts[1] ) ) { continue; } return $parts[1]; } } return null; } /** * Validates the stored license and resets license data if no key is present. * Callback for both the recurring daily check and the expired-license retry action. * * @since 4.9.5.2 * * @return void */ public function checkLicense() { if ( ! $this->sensitiveOptions->hasValue( $this->sensitiveKeyName ) ) { if ( $this->needsReset() ) { $this->internalOptions->internal->license->reset( [ 'expires', 'expired', 'invalid', 'disabled', 'activationsError', 'connectionError', 'requestError', 'level', 'addons', 'features' ] ); } return; } $success = $this->activateProgrammatic(); $this->validateNotifications(); // If the license is expired, schedule a single action to check again in 4 hours. if ( ! $success && $this->internalOptions->internal->license->expired ) { aioseo()->actionScheduler->scheduleSingle( $this->licenseCheckExpiredAction, 4 * HOUR_IN_SECONDS ); return; } aioseo()->actionScheduler->unschedule( $this->licenseCheckExpiredAction ); } }