<?php

use YahnisElsts\PluginUpdateChecker\v5p6\Plugin\UpdateChecker;

class Wslm_BasicPluginLicensingUI {
	const AUTO_LICENSE_CRON_ACTION = 'wslm_auto_activate_license-';
	const ACTIVATION_FAILURE_FLAG = 'wslm_auto_activation_failed-';

	private $licenseManager;
	/**
	 * @var UpdateChecker
	 */
	private $updateChecker;
	private $pluginFile;
	private $slug;
	private $requiredCapability = 'update_plugins';

	private $triedLicenseKey = null;
	/**
	 * @var Wslm_ProductLicense
	 */
	private $triedLicense = null;
	private $currentTab = 'current-license';
	private $tabs = array();

	private $keyConstant = null;
	private $maxAutoActivationAttempts = 8;

	/** @var bool Whether to display the site token (if any) in the licensing window. */
	protected $tokenDisplayEnabled = false;

	public function __construct(Wslm_LicenseManagerClient $licenseManager, $pluginFile, $updateChecker = null, $keyConstant = null) {
		$this->licenseManager = $licenseManager;
		$this->pluginFile = $pluginFile;
		$this->slug = $this->licenseManager->getProductSlug();
		$this->keyConstant = $keyConstant;
		$this->updateChecker = $updateChecker;

		$this->tabs = array(
			'current-license' => array(
				'caption'  => 'Current License',
				'callback' => array($this, 'tabCurrentLicense'),
			),
			'manage-sites'    => array(
				'caption'  => 'Manage Sites',
				'callback' => array($this, 'tabManageSites'),
			),
		);

		//Turning on the DISALLOW_FILE_MODS constant disables the "update_plugins" capability,
		//so we need to use something else in that case.
		if ( defined('DISALLOW_FILE_MODS') && constant('DISALLOW_FILE_MODS') ) {
			if ( is_multisite() ) {
				$this->requiredCapability = 'manage_network_plugins';
			} else {
				$this->requiredCapability = 'activate_plugins';
			}
		}

		$basename = plugin_basename($this->pluginFile);
		add_filter(
			'plugin_action_links_' . $basename,
			array($this, 'addLicenseActionLink')
		);
		add_filter(
			'network_admin_plugin_action_links_' . $basename,
			array($this, 'addLicenseActionLink')
		);

		add_action('wp_ajax_' . $this->getAjaxActionName(), array($this, 'printUi'));

		add_action('after_plugin_row_' . $basename, array($this, 'printPluginRowNotice'), 10, 0);

		if ( isset($this->updateChecker) ) {
			add_filter('upgrader_pre_download', array($this, 'authorizePluginUpdate'), 10, 3);
		}

		add_action('all_admin_notices', array($this, 'autoActivateLicense'));
		add_action(self::AUTO_LICENSE_CRON_ACTION . $this->slug, array($this, 'autoActivateLicense'));
		add_action('wslm_license_activated-' . $this->slug, array($this, 'clearActivationFailureFlag'), 10, 0);
	}

	public function addLicenseActionLink($links) {
		if ( $this->currentUserCanManageLicense() ) {
			$links['licenses'] = $this->makeLicenseLink();
		}
		return $links;
	}

	private function currentUserCanManageLicense() {
		return apply_filters(
			'wslm_current_user_can_manage_license-' . $this->slug,
			current_user_can($this->requiredCapability)
		);
	}

	private function makeLicenseLink($linkText = 'License') {
		return sprintf(
			'<a href="%s" class="thickbox" title="%s">%s</a>',
			esc_attr(add_query_arg(
				array('TB_iframe' => true,),
				$this->getLicensingPageUrl()
			)),
			esc_attr($this->getPageTitle()),
			apply_filters('wslm_action_link_text-' . $this->slug, $linkText)
		);
	}

	private function getLicensingPageUrl() {
		$url = add_query_arg(
			array(
				'action'   => $this->getAjaxActionName(),
				'_wpnonce' => wp_create_nonce('show_license'), //Assumes the default license action = "show_license".
			),
			admin_url('admin-ajax.php')
		);
		return $url;
	}

	private function getAjaxActionName() {
		return 'show_license_ui-' . $this->slug;
	}

	private function getPageTitle() {
		return apply_filters('wslm_license_ui_title-' . $this->slug, 'Manage Licenses');
	}

	public function printUi() {
		if ( !$this->currentUserCanManageLicense() ) {
			wp_die("You don't have sufficient permissions to manage licenses for this product.");
		}

		$action = isset($_REQUEST['license_action']) ? strval($_REQUEST['license_action']) : '';
		if ( empty($action) ) {
			$action = 'show_license';
		}
		check_admin_referer($action);

		$this->triedLicenseKey = isset($_POST['license_key']) ? trim(strval($_POST['license_key'])) : $this->licenseManager->getLicenseKey();

		if ( isset($_REQUEST['tab']) && is_string($_REQUEST['tab']) && array_key_exists($_REQUEST['tab'], $this->tabs) ) {
			$this->currentTab = $_REQUEST['tab'];
		}

		//We run some core hooks later to load admin CSS and other dependencies. Some plugins that
		//use those hooks will crash if they encounter a "fake" admin page without a screen object,
		//causing the license page to be blank. Let's set up a screen to avoid that.
		set_current_screen('wslm-' . $this->slug . '-licensing_ui');

		$this->printHeader();
		$this->dispatchAction($action);
		$this->printLogo();
		$this->printTabList();
		?>
		<div class="wrap" id="wslm-section-holder">
			<?php
			foreach ($this->tabs as $id => $tab) {
				printf(
					'<div id="section-%1$s" class="wslm-section%2$s">',
					esc_attr($id),
					($this->currentTab !== $id) ? ' hidden' : ''
				);
				call_user_func($tab['callback']);
				echo '</div>';
			}
			?>
		</div> <!-- #wslm-section-holder -->
		<?php

		exit();
	}

	private function dispatchAction($action) {
		do_action('wslm_ui_action-' . $action . '-' . $this->slug);
		$method = 'action' . str_replace(' ', '', ucwords(str_replace('_', ' ', $action)));
		if ( method_exists($this, $method) ) {
			$this->$method();
		} else {
			$this->printNotice(
				sprintf('Unknown action "%s"', htmlentities($action)),
				'error'
			);
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection Used by dispatchAction(). */
	private function actionShowLicense() {
		//Don't need to do anything special in this case, I think.
		//Maybe request the site list if we have a license key.
		$this->licenseManager->checkForLicenseUpdates();
		$this->triedLicenseKey = $this->licenseManager->getLicenseKey();
		$this->triedLicense = $this->licenseManager->getLicense();
	}

	/** @noinspection PhpUnusedPrivateMethodInspection Used by dispatchAction(). */
	private function actionLicenseThisSite() {
		if ( empty($this->triedLicenseKey) ) {
			$this->printNotice('The license key must not be empty.', 'error');
			return;
		}
		$result = $this->licenseManager->licenseThisSite($this->triedLicenseKey);
		if ( is_wp_error($result) ) {
			$this->printError($result);
		} else {
			$this->printNotice('Success! This site is now licensed.');

			//Print any notices or warnings, like "you can't receive updates, please renew".
			if ( isset($result['notice']) ) {
				$this->printNotice($result['notice']['message'], $result['notice']['class']);
			}
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection Used by dispatchAction(). */
	private function actionUnlicenseThisSite() {
		$result = $this->licenseManager->unlicenseThisSite();
		if ( is_wp_error($result) ) {
			$this->printError($result);
		} else {
			$this->printNotice('Success! The existing license has been removed from this site.');
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection Used by dispatchAction(). */
	private function actionUnlicenseOtherSite() {
		$this->currentTab = 'manage-sites';

		$siteUrl = isset($_POST['site_url']) ? strval($_POST['site_url']) : '';
		if ( empty($siteUrl) || empty($this->triedLicenseKey) ) {
			$this->printNotice('Please specify both the site URL and license key.', 'error');
			return;
		}

		$result = $this->licenseManager->unlicenseSite($siteUrl, $this->triedLicenseKey);
		if ( is_wp_error($result) ) {
			$this->printError($result);
			$this->triedLicense = $result->get_error_data('license');
		} else {
			$this->printNotice(
				'Success! This license key is no longer associated with ' . htmlentities($siteUrl)
			);
			$this->triedLicense = $result;
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection Used by dispatchAction(). */
	private function actionShowLicensedSites() {
		$this->currentTab = 'manage-sites';
		if ( empty($this->triedLicenseKey) ) {
			$this->printNotice('License key must not be empty.', 'error');
			return;
		}

		$result = $this->licenseManager->requestLicenseDetails($this->triedLicenseKey);
		if ( is_wp_error($result) ) {
			$this->printError($result);
		} else {
			$this->triedLicense = $result;
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection */
	private function tabCurrentLicense() {
		//Display license information
		$currentLicense = $this->licenseManager->getLicense();
		echo '<h3>Current License</h3>';

		if ( $currentLicense->isValid() ) {
			if ( !$currentLicense->canReceiveProductUpdates() ) {
				$this->printLicenseDetails(
					'Valid License (updates disabled)',
					'This site is currently licensed. However, your access to updates and support has expired.
					 Please consider renewing your license.',
					$currentLicense
				);
			} else {
				$this->printLicenseDetails(
					'Valid License',
					'This site is currently licensed and qualifies for automatic upgrades &amp; support for this product.
				     If you no longer wish to use this product on this site you can remove the license.',
					$currentLicense
				);
			}

			?>
			<form method="post" action="<?php echo esc_attr($this->getLicensingPageUrl()); ?>">
				<input type="hidden" name="license_action" value="unlicense_this_site"/>
				<?php wp_nonce_field('unlicense_this_site'); ?>
				<?php submit_button('Remove License', 'secondary', 'submit', false); ?>
			</form>
			<?php
			$this->printLicenseKeyForm(
				'Change License Key',
				'Want to use a different license key? Enter it below.',
				'Change Key',
				'secondary'
			);
		} else {
			if ( $currentLicense->getStatus() === 'no_license_yet' ) {
				$this->printLicenseDetails(
					'No License Yet',
					'This site is currently not licensed. Please enter your license key below.'
				);
				$this->printLicenseKeyForm();
			} else {
				$this->printLicenseDetails(
					'Invalid license (' . htmlentities($currentLicense->getStatus()) . ')',
					'The current license is not valid. Please enter a valid license key below.',
					$currentLicense
				);
				$this->printLicenseKeyForm();
			}
		}
	}

	/**
	 * @param string $status
	 * @param string $message
	 * @param Wslm_ProductLicense $currentLicense
	 */
	private function printLicenseDetails($status, $message = '', $currentLicense = null) {
		$currentKey = $this->licenseManager->getLicenseKey();
		$currentToken = $this->licenseManager->getSiteToken();
		?>
		<p>
			<span class="license-status">
				<label>Status:</label> <?php echo $status; ?>
			</span>
		</p>

		<?php
		if ( !empty($currentKey) ) {
			?><p><label>License key:</label> <?php echo htmlentities($currentKey); ?></p><?php
		}
		if ( !empty($currentToken) && $this->tokenDisplayEnabled ) {
			?><p><label>Site token:</label> <?php echo htmlentities($currentToken); ?></p><?php
		}

		$expiresOn = isset($currentLicense) ? $currentLicense->get('expires_on') : null;
		if ( $expiresOn ) {
			$formattedDate = date_i18n(get_option('date_format'), strtotime($expiresOn));
			?><p>
			<label>Expires:</label>
			<span title="<?php echo esc_attr($expiresOn); ?>"><?php echo $formattedDate ?></span>
			</p>
			<?php
		}

		do_action('wslm_license_ui_details-' . $this->slug, $currentKey, $currentToken, $currentLicense);

		if ( !empty($message) ) {
			echo '<p>', $message, '</p>';
		}
	}

	/** @noinspection PhpUnusedPrivateMethodInspection */
	private function tabManageSites() {
		if ( isset($this->triedLicense, $this->triedLicense->sites) ) {
			$devSiteTooltip = 'This site was detected as a development/staging/local site.';
			if ( isset($this->triedLicense['max_sites']) && ($this->triedLicense['max_sites'] > 0) ) {
				$devSiteTooltip .= ' It does not count against your license\'s site limit.';
			}
			?>
			<h3>Sites Associated With License Key "<?php echo esc_html($this->triedLicenseKey); ?>"</h3>
			<?php
			if ( !empty($this->triedLicense->sites) ):
				?>
				<table class="widefat">
					<?php
					foreach ($this->triedLicense->sites as $site):
						$displaySiteUrl = preg_replace('#^(https?:)?//#', '', $site->site_url);
						$isDevSite = isset($site->is_dev_site) && $site->is_dev_site;
						?>
						<tr>
							<td>
								<?php echo esc_html($displaySiteUrl); ?><br>
								Token: <?php echo esc_html($site->token); ?>
							</td>
							<td class="wslm-dev-flag-col">
								<?php
								if ( $isDevSite ) {
									printf(
										'<span class="wslm-dev-site-label" title="%s">dev</span>',
										esc_attr($devSiteTooltip)
									);
								}
								?>
							</td>
							<td style="vertical-align: middle; width: 11em;">
								<form method="post" action="<?php echo esc_url($this->getLicensingPageUrl()); ?>">
									<input type="hidden" name="site_url"
									       value="<?php echo esc_attr($site->site_url); ?>"/>
									<input type="hidden" name="license_key"
									       value="<?php echo esc_attr($this->triedLicenseKey); ?>"/>
									<input type="hidden" name="license_action" value="unlicense_other_site"/>
									<?php wp_nonce_field('unlicense_other_site'); ?>
									<?php submit_button('Remove License', 'secondary', 'submit', false); ?>
								</form>
							</td>
						</tr>
					<?php
					endforeach;
					?>
				</table>
			<?php
			else:
				?>
				There are currently no sites using this license key.
			<?php
			endif;

		} else {
			$this->printLicenseKeyForm(
				'',
				'To view sites currently associated with a license, enter your license key below.',
				'Show Licensed Sites',
				'primary',
				'show_licensed_sites'
			);
		}
	}

	private function printLicenseKeyForm(
		$formCaption = 'Enter a License Key',
		$formDescription = '',
		$buttonTitle = 'Activate Key',
		$buttonType = 'primary',
		$licenseAction = 'license_this_site'
	) {
		?>
		<h3><?php echo $formCaption; ?></h3>
		<?php
		if ( !empty($formDescription) ) {
			echo '<p>', $formDescription, '</p>';
		}
		?>
		<form method="post" action="<?php echo esc_attr($this->getLicensingPageUrl()); ?>">
			<input type="hidden" name="license_action" value="<?php echo esc_attr($licenseAction); ?>"/>
			<?php wp_nonce_field($licenseAction); ?>
			<!--suppress HtmlFormInputWithoutLabel -->
			<input type="text" name="license_key" size="36"/>
			<?php submit_button($buttonTitle, $buttonType, 'submit', false); ?>
		</form>
		<?php
	}

	private function printError(WP_Error $error) {
		foreach ($error->get_error_codes() as $code) {
			foreach ($error->get_error_messages($code) as $message) {
				if ( !empty($message) ) {
					$this->printNotice(
						$message . "\n<br>Error code: <code>" . htmlentities($code) . '</code>',
						'error'
					);
				}
			}
		}
	}

	private function printNotice($message, $class = 'updated') {
		printf('<div class="notice %s"><p>%s</p></div>', esc_attr($class), $message);
	}

	private function printHeader() {
		$bodyClasses = ['wp-admin', 'wp-core-ui', 'iframe', 'no-js'];

		//Since WP 7.0, the "admin-color-xyz" class must be added to ensure that the CSS variables for
		//the admin color scheme are set correctly.
		global $wp_version;
		if ( isset($wp_version) && is_string($wp_version) && version_compare($wp_version, '7.0-alpha', '<') ) {
			$defaultAdminColorScheme = 'fresh';
		} else {
			$defaultAdminColorScheme = 'modern';
		}
		$bodyClasses[] .= ' admin-color-' . sanitize_html_class(get_user_option('admin_color'), $defaultAdminColorScheme);

		?>
		<!DOCTYPE html>
		<!--[if IE 8]>
		<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" <?php do_action('admin_xml_ns'); ?> <?php language_attributes(); ?>>
		<![endif]-->
		<!--[if !(IE 8) ]><!-->
		<html xmlns="http://www.w3.org/1999/xhtml" <?php
		do_action('admin_xml_ns');
		echo ' '; //An explicit space in case the action outputs something without a trailing space.
		language_attributes(); ?>>
		<!--<![endif]-->
		<head>
			<meta http-equiv="Content-Type" content="<?php
			bloginfo('html_type');
			echo '; charset', '=', get_option('blog_charset');
			?>"/>
			<title><?php echo esc_html($this->getPageTitle()); ?></title>
			<?php
			wp_admin_css();
			wp_admin_css('buttons');
			wp_admin_css('colors');

			wp_enqueue_style(
				'wslm-basic-ui',
				plugins_url('/ui.css', __FILE__),
				array(),
				'20260512'
			);
			wp_enqueue_script('jquery');

			do_action('admin_print_styles');
			do_action('admin_print_scripts');
			do_action('admin_head');
			?>
		</head>
		<body class="<?php echo esc_attr(implode(' ', $bodyClasses)); ?>" id="licensing-information">
		<script type="text/javascript">
			//<![CDATA[
			(function () {
				var c = document.body.className;
				c = c.replace(/no-js/, 'js');
				document.body.className = c;
			})();
			//]]>
		</script>
		<?php
	}

	private function printLogo() {
		//Logo (optional)
		echo '<div id="wslm-product-logo">';
		do_action('wslm_license_ui_logo-' . $this->slug);
		echo '</div>';
	}

	private function printTabList() {
		?>
		<div id="plugin-information-header">
			<ul id="sidemenu">
				<?php
				$baseTabUrl = remove_query_arg('tab');
				foreach ($this->tabs as $name => $tab) {
					printf(
						'<li><a name="%s" href="%s"%s>%s</a></li>',
						esc_attr($name),
						esc_attr(add_query_arg('tab', $name, $baseTabUrl)),
						($name === $this->currentTab) ? ' class="current"' : '',
						$tab['caption']
					);
				}
				?>
			</ul>
		</div>

		<script type="text/javascript">
			jQuery(function ($) {
				var tabSelector = jQuery('#sidemenu');

				function selectTab(tab) {
					//Flip the tab
					tabSelector.find('a.current').removeClass('current');
					tabSelector.find('a[name="' + tab + '"]').addClass('current');
					//Flip the content.
					$('#wslm-section-holder').find('div.wslm-section').hide(); //Hide 'em all
					$('#section-' + tab).show();
				}

				selectTab('<?php echo esc_js($this->currentTab); ?>');
				tabSelector.find('a').click(function () {
					var tab = $(this).attr('name');
					selectTab(tab);
					return false;
				});
			});
		</script>
		<?php
	}

	public function printPluginRowNotice() {
		//If there's anything wrong with the plugin's license, output a notice under the plugin row in "Plugins".
		$license = $this->licenseManager->getLicense();
		if ( !$this->currentUserCanManageLicense() || ($license->getStatus() === 'valid') ) {
			return;
		}

		$renewalUrl = $license->get('renewal_url');

		$messages = array(
			'no_license_yet' => "License is not set yet. Please enter your license key to enable automatic updates.",
			'expired'        => sprintf(
				'Your access to updates has expired. You can continue using the plugin, but you\'ll need to %1$srenew your license%2$s to receive updates and bug fixes.',
				$renewalUrl ? '<a href="' . esc_attr($renewalUrl) . '">' : '',
				$renewalUrl ? '</a>' : ''
			),
			'not_found'      => 'The current license key or site token is invalid.',
			'wrong_site'     => 'Please re-enter your license key. This is necessary because the site URL has changed.',
		);
		$status = $license->getStatus();
		$notice = isset($messages[$status]) ? $messages[$status] : 'The current license is invalid.';

		$licenseLink = $this->makeLicenseLink(apply_filters(
			'wslm_plugin_row_link_text-' . $this->slug,
			'Enter License Key'
		));
		$showLicenseLink = ($status !== 'expired');

		//WP 4.6+ uses different styles for the update row. We use an inverted condition here because some buggy
		//plugins overwrite $wp_version. This way the default is to assume it's WP 4.6 or higher.
		$isWP46orHigher = !(isset($GLOBALS['wp_version']) && version_compare($GLOBALS['wp_version'], '4.5.9', '<='));

		$messageClasses = array('update-message');
		if ( $isWP46orHigher ) {
			$messageClasses = array_merge($messageClasses, array('notice', 'inline', 'notice-warning', 'notice-alt'));
		}

		?>
		<tr class="plugin-update-tr">
			<td class="plugin-update colspanchange" colspan="3">
				<div class="<?php echo esc_attr(implode(' ', $messageClasses)); ?>">
					<?php
					if ( $isWP46orHigher ) {
						echo '<p>';
					}

					if ( $showLicenseLink ) {
						echo $licenseLink, ' | ';
					}
					echo $notice;

					if ( $isWP46orHigher ) {
						echo '</p>';
					}
					?>
				</div>
			</td>
		</tr>
		<?php
	}


	/**
	 * Abort update if the user doesn't have a valid key or the key has expired (not eligible for updates).
	 *
	 * @param bool|WP_Error $result
	 * @param string $package Update download URL. Typically points to a .zip file.
	 * @param WP_Upgrader $upgrader
	 * @return bool|WP_Error
	 */
	function authorizePluginUpdate(
		                                                  $result,
		/** @noinspection PhpUnusedParameterInspection */ $package,
		                                                  $upgrader
	) {
		//Sanity check.
		if ( !isset($upgrader, $upgrader->skin) ) {
			return $result;
		}

		$license = $this->licenseManager->getLicense();
		if ( $license->canReceiveProductUpdates() || !$this->updateChecker->isPluginBeingUpgraded($upgrader) ) {
			return $result;
		}

		if ( $license->getStatus() === 'expired' ) {
			//Reload the license in case the user just renewed and is retrying the update.
			$this->licenseManager->checkForLicenseUpdates();
			$license = $this->licenseManager->getLicense();
			if ( $license->canReceiveProductUpdates() ) {
				return $result;
			}
		}

		$status = $license->getStatus();
		$messages = array(
			'no_license_yet' => "Please enter your license key to enable plugin updates.",
			'expired'        => sprintf(
				'Your access to %s updates has expired. Please renew your license.',
				apply_filters('wslm_product_name-' . $this->slug, $this->slug)
			),
		);

		$result = new WP_Error(
			'wslm_update_not_available',
			isset($messages[$status]) ? $messages[$status] : 'Update not available. Please (re)enter your license key.',
			'[' . $status . ']'
		);

		//This bit is important. At least in WP 4.3, the return value will be lost or replaced with a generic
		//"download failed" error unless you also set it on the upgrader skin.
		$upgrader->skin->set_result($result);

		return $result;
	}

	public function autoActivateLicense() {
		$doingCron = defined('DOING_CRON') && DOING_CRON;
		if ( !$this->currentUserCanManageLicense() && !$doingCron ) {
			return;
		}

		$license = $this->licenseManager->getLicense();
		if ( $license->isValid() ) {
			return;
		}

		$failureFlag = self::ACTIVATION_FAILURE_FLAG . $this->slug;
		$state = get_site_option($failureFlag, null);
		if ( !is_array($state) || !isset($state['failures']) ) {
			$state = array(
				'failures'        => $state ? 1 : 0,
				'lastAttemptTime' => 0,
			);
		}

		if ( ($state['failures'] > $this->maxAutoActivationAttempts) ) {
			return;
		}

		$elapsedTime = time() - $state['lastAttemptTime'];
		$desiredDelay = $this->calculateLicenseActivationDelay($state['failures']);
		if ( $elapsedTime < $desiredDelay ) {
			return;
		}

		$state['failures']++;
		$state['lastAttemptTime'] = time();
		update_site_option($failureFlag, $state);

		$result = null;
		$tokenHistory = $this->licenseManager->getTokenHistory();
		if ( !empty($this->keyConstant) && defined($this->keyConstant) ) {
			//Attempt to activate the license key that's defined in wp-config.php.
			$result = $this->licenseManager->licenseThisSite(constant($this->keyConstant));
		} else if ( !empty($tokenHistory) ) {
			//Check if there's a known token that matches the current site URL. Try to activate that token.
			$siteUrl = $this->licenseManager->getSiteUrl();
			$reversedTokenHistory = array_reverse($tokenHistory, true); //Put the most recent first.
			$possibleToken = array_search($siteUrl, $reversedTokenHistory);

			//Backwards compatibility: If we find nothing, try again with "http://" instead of "//".
			//Previous versions of the plugin normalized all site URLs to use "http://".
			if ( empty($possibleToken) ) {
				/** @noinspection HttpUrlsUsage */
				$possibleToken = array_search(preg_replace('#^//#', 'http://', $siteUrl), $reversedTokenHistory);
			}

			if ( !empty($possibleToken) ) {
				$result = $this->licenseManager->licenseThisSiteByToken($possibleToken);
			}
		}

		if ( is_wp_error($result) ) {
			$productName = apply_filters('wslm_product_name-' . $this->slug, $this->slug);
			if ( is_admin() && !$doingCron ) {
				printf(
					'<div class="error">
                        <p>
                            %1$s tried to automatically activate your license, but it didn\'t work.<br/>
                            Error: <code>%2$s [%3$s]</code>
                        </p>
                        <p>Please go to the <a href="%4$s">Plugins</a> page and enter your license key.</p>
                    </div>',
					$productName,
					$result->get_error_message(),
					$result->get_error_code(),
					is_multisite() ? network_admin_url('plugins.php') : admin_url('plugins.php')
				);
			}

			$plainError = sprintf(
				'%1$s failed to automatically activate a license. Error: %2$s [%3$s]',
				$productName,
				$result->get_error_message(),
				$result->get_error_code()
			);
			error_log($plainError);

			//Try again later, but only if we have a license key.
			if (
				($state['failures'] < $this->maxAutoActivationAttempts)
				&& !empty($this->keyConstant)
				&& defined($this->keyConstant)
			) {
				wp_schedule_single_event(
					time() + $this->calculateLicenseActivationDelay($state['failures']) + 1,
					self::AUTO_LICENSE_CRON_ACTION . $this->slug,
					array($state['failures'] + 1)
				);
			}
		} else if ( $result instanceof Wslm_ProductLicense ) {
			//Success! Don't output anything, just proceed as normal.
			$this->clearActivationFailureFlag();
		}
	}

	/**
	 * Calculate the minimum delay after the Nth automatic license
	 * activation attempt.
	 *
	 * @param int $attempt
	 * @return float|int Delay in seconds.
	 */
	private function calculateLicenseActivationDelay($attempt) {
		if ( $attempt < 1 ) {
			return 0;
		}

		$minDelay = 20;
		$maxDelay = 600;
		$growthLimit = 4; //Stop increasing the delay after this many attempts.

		$fraction = (min($attempt - 1, $growthLimit) / ($growthLimit));

		$desiredDelay = $minDelay + round(($maxDelay - $minDelay) * $fraction);
		if ( $desiredDelay < $minDelay ) {
			$desiredDelay = $minDelay;
		}
		return $desiredDelay;
	}

	public function clearActivationFailureFlag() {
		delete_site_option(self::ACTIVATION_FAILURE_FLAG . $this->slug);
	}
}