<?php
//NULLED BY JONVI
namespace WHMCS;

class License
{
	const LICENSE_API_VERSION = '1.1';
	const LICENSE_API_HOSTS = array('');
	private $licensekey = '';
	private $localkey = '';
	private $keydata = array();
	private $salt = '';
	private $date = '';
	private $localkeydecoded = false;
	private $responsedata = '';
	private $postmd5hash = '';
	private $localkeydays = '10';
	private $allowcheckfaildays = '5';

	private $debuglog = array();
	private $version = 'ba3289e318b73a3b8c2c69102123a8c752a0079b43a627f2ac8a067ee8ad2e9c';
	public function __construct(Application $whmcs)
	{
		$this->licensekey = $whmcs->get_license_key();
		$this->localkey = $whmcs->get_config('License');
		$this->salt = sha1('WHMCS' . $whmcs->get_config('Version') . 'TFB' . $whmcs->get_hash());
		$this->date = date('Ymd');
		$this->decodeLocalOnce();
		if (isset($_GET['forceremote'])) {
			$this->forceRemoteCheck();
			Terminus::getInstance()->doExit();
		}
	}

	public static function getInstance()
	{
		return \DI::make('license');
	}
	public function useInternalLicensingValidation()
	{
		$config = \App::getApplicationConfig();
		return (bool) $config['use_internal_licensing_validation'];
	}

	private function getHosts()
	{
		if ($this->useInternalLicensingValidation()) {
			return array('');
		}
		return self::LICENSE_API_HOSTS;
	}
	public function getLicenseKey()
	{
		return $this->licensekey;
	}
	private function getHostIP()
	{
		return '127.0.0.1';
	}
	private function getHostDomain()
	{
		return 'WHMCS.COM';
	}
	private function getHostDir()
	{
		return '/';
	}
	public function getSalt()
	{
		return $this->salt;
	}
	public function getDate()
	{
		return $this->date;
	}
	public function checkLocalKeyExpiry()
	{
		$originalcheckdate = $this->getKeyData('checkdate');
		$localexpirymax = date('Ymd', mktime(0, 0, 0, date('m'), date('d') - $this->localkeydays, date('Y')));
		if ($originalcheckdate < $localexpirymax) {
			return false;
		}
		$localmax = date('Ymd', mktime(0, 0, 0, date('m'), date('d') + 2, date('Y')));
		if ($localmax < $originalcheckdate) {
			return false;
		}
		return true;
	}

	protected function buildPostData()
	{
		$postfields = array();
		$postfields['licensekey'] = $this->getLicenseKey();
		$postfields['domain'] = $this->getHostDomain();
		$postfields['ip'] = $this->getHostIP();
		$postfields['dir'] = $this->getHostDir();
		$postfields['check_token'] = sha1(time() . $this->getLicenseKey() . mt_rand(1000000000, 9999999999.0));
		$whmcs = \DI::make('app');
		$postfields['version'] = $whmcs->getVersion()->getCanonical();
		$postfields['phpversion'] = PHP_VERSION;
		$stats = json_decode($whmcs->get_config('SystemStatsCache'), true);
		if (!is_array($stats)) {
			$stats = array();
		}
		$postfields['anondata'] = $this->encryptMemberData($stats);
		$postfields['member'] = $this->encryptMemberData($this->buildMemberData());
		return $postfields;
	}

	public function remoteCheck($forceRemote = false)
	{
		try {
			$localkeyvalid = $this->decodeLocalOnce();
			$this->debug('Local Key Valid: ' . $localkeyvalid);
			if ($localkeyvalid) {
				$localkeyvalid = $this->checkLocalKeyExpiry();
				$this->debug('Local Key Expiry: ' . $localkeyvalid);
				if ($localkeyvalid) {
					$localkeyvalid = $this->validateLocalKey();
					$this->debug('Local Key Validation: ' . $localkeyvalid);
				}
			}
			if ( !$localkeyvalid || $forceRemote ) {
				$whmcs                     = Application::getinstance();
                $results["status"]         = "Active";
                $results["key"]            = $this->licensekey;
                $results["registeredname"] = $whmcs->get_config("CompanyName");
                $results["productname"]    = "WHMCS 7.0.2 Nulled By Jonvi";
                $results["productid"]      = "5";
                $results["billingcycle"]   = "One Time";
                $results["validdomains"]   = $this->getHostDomain();
                $results["validips"]       = $this->getHostIP();
                $results["validdirs"]      = $this->getHostDir();
                $results["checkdate"]      = $this->getDate();
                $results["version"]        = $whmcs->getVersion()->getCanonical();
                $results["regdate"]        = "2016-12-02";
                $results["nextduedate"]    = "";
                $this->setKeyData($results);
                $this->updateLocalKey();
			}
			$this->debug('Remote Check Done');
		} catch (Exception $exception) {
			$this->debug(sprintf('License Error: %s', $exception->getMessage()));
			return false;
		}
		return true;
	}
	private function getLocalMaxExpiryDate()
	{
		return date('Ymd', mktime(0, 0, 0, date('m'), date('d') - ($this->localkeydays + $this->allowcheckfaildays), date('Y')));
	}

	private function updateLocalKey()
	{
		$data_encoded = json_encode($this->keydata);
		$data_encoded = base64_encode($data_encoded);
		$data_encoded = sha1($this->getDate() . $this->getSalt()) . $data_encoded;
		$data_encoded = strrev($data_encoded);
		$splpt = strlen($data_encoded) / 2;
		$data_encoded = substr($data_encoded, $splpt) . substr($data_encoded, 0, $splpt);
		$data_encoded = sha1($data_encoded . $this->getSalt()) . $data_encoded . sha1($data_encoded . $this->getSalt() . time());
		$data_encoded = base64_encode($data_encoded);
		$data_encoded = wordwrap($data_encoded, 80, "\n", true);
		\App::self()->set_config('License', $data_encoded);
		$this->debug('Updated Local Key');
	}
	public function forceRemoteCheck()
	{
		$this->remoteCheck(true);
	}
	private function setInvalid($reason = 'Invalid')
	{
		$this->keydata = array('status' => $reason);
	}
	private function decodeLocal()
	{
		$this->debug('Decoding local key');
		$localkey = $this->localkey;
		if (!$localkey) {
			return false;
		}
		$localkey = str_replace("\n", '', $localkey);
		$localkey = base64_decode($localkey);
		$localdata = substr($localkey, 40, -40);
		$md5hash = substr($localkey, 0, 40);
		if ($md5hash == sha1($localdata . $this->getSalt())) {
			$splpt = strlen($localdata) / 2;
			$localdata = substr($localdata, $splpt) . substr($localdata, 0, $splpt);
			$localdata = strrev($localdata);
			$md5hash = substr($localdata, 0, 40);
			$localdata = substr($localdata, 40);
			$localdata = base64_decode($localdata);
			$localkeyresults = json_decode($localdata, true);
			$originalcheckdate = $localkeyresults['checkdate'];
			if ($md5hash == sha1($originalcheckdate . $this->getSalt())) {
				if (isset($localkeyresults['key']) && $localkeyresults['key'] == \App::self()->get_license_key()) {
					$this->debug('Local Key Decode Successful');
					$this->setKeyData($localkeyresults);
				} else {
					$this->debug('License Key Invalid');
				}
			} else {
				$this->debug('Local Key MD5 Hash 2 Invalid');
			}
		} else {
			$this->debug('Local Key MD5 Hash Invalid');
		}
		$this->localkeydecoded = true;
		return $this->getKeyData('status') == 'Active' ? true : false;
	}
	private function decodeLocalOnce()
	{
		if ($this->localkeydecoded) {
			return true;
		}
		return $this->decodeLocal();
	}
	private function isRunningInCLI()
	{
		return php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']);
	}
	private function validateLocalKey()
	{
		if ($this->getKeyData('status') != 'Active') {
			$this->debug('Local Key Status Check Failure');
			return false;
		}
		if ($this->isRunningInCLI()) {
			$this->debug('Running in CLI Mode');
		} else {
			$this->debug('Running in Browser Mode');
			if ($this->isValidDomain($this->getHostDomain())) {
				$this->debug('Domain Validated Successfully');
			} else {
				$this->debug('Local Key Domain Check Failure');
				return false;
			}
			$ip = $this->getHostIP();
			$this->debug('Host IP Address: ' . $ip);
			if (!$ip) {
				$this->debug('IP Could Not Be Determined - Skipping Local Validation of IP');
			} else {
				if (!trim($this->getKeyData('validips'))) {
					$this->debug('No Valid IPs returned by license check - Cloud Based License - Skipping Local Validation of IP');
				} else {
					if ($this->isValidIP($ip)) {
						$this->debug('IP Validated Successfully');
					} else {
						$this->debug('Local Key IP Check Failure');
						return false;
					}
				}
			}
		}
		if ($this->isValidDir($this->getHostDir())) {
			$this->debug('Directory Validated Successfully');
		} else {
			$this->debug('Local Key Directory Check Failure');
			return false;
		}
		return true;
	}
	private function isValidDomain($domain)
	{
		$validdomains = $this->getArrayKeyData('validdomains');
		return in_array($domain, $validdomains);
	}
	private function isValidIP($ip)
	{
		$validips = $this->getArrayKeyData('validips');
		return in_array($ip, $validips);
	}
	private function isValidDir($dir)
	{
		$validdirs = $this->getArrayKeyData('validdirs');
		return in_array($dir, $validdirs);
	}
	private function revokeLocal()
	{
		\App::self()->set_config('License', '');
	}
	public function getKeyData($var)
	{
		return isset($this->keydata[$var]) ? $this->keydata[$var] : '';
	}
	private function setKeyData($data)
	{
		$this->keydata = $data;
	}

	protected function getArrayKeyData($var)
	{
		$listData = array();
		$rawData = $this->getKeyData($var);
		if (is_string($rawData)) {
			$listData = explode(',', $rawData);
			foreach ($listData as $k => $v) {
				if (is_string($v)) {
					$listData[$k] = trim($v);
				} else {
					throw new Exception('Invalid license data structure');
				}
			}
		} else {
			if (!is_null($rawData)) {
				throw new Exception('Invalid license data structure');
			}
		}
		return $listData;
	}
	public function getRegisteredName()
	{
		return $this->getKeyData('registeredname');
	}
	public function getProductName()
	{
		return $this->getKeyData('productname');
	}
	public function getStatus()
	{
		return $this->getKeyData('status');
	}
	public function getSupportAccess()
	{
		return $this->getKeyData('supportaccess');
	}

	protected function getLicensedAddons()
	{

		if (!is_array($licensedAddons)) {

			$licensedAddons = array(
                array(
                    'name' => 'Branding Removal',
                ),
                array(
                    'name' => 'Support and Updates'
                ),
                array(
                    'name' => 'Project Management Addon'
                ),
                array(
                    'name' => 'Licensing Addon'
                ),
                array(
                    'name' => 'Mobile Edition'
                ),
                array(
                    'name' => 'iPhone App'
                ),
                array(
                    'name' => 'Android App'
                ),
                array(
                    'name' => 'Configurable Package Addon'
                ),
                array(
                    'name' => 'Live Chat Monthly No Branding'
                )
            );
		}
		return $licensedAddons;
	}
	public function getActiveAddons()
	{
		$licensedAddons = $this->getLicensedAddons();
		$activeAddons = array();
		foreach ($licensedAddons as $addon) {

				$activeAddons[] = $addon['name'];
		}
		return $activeAddons;
	}
	public function isActiveAddon($addon)
	{
		return in_array($addon, $this->getActiveAddons()) ? true : false;
	}
	public function getExpiryDate($showday = false)
	{
		$expiry = $this->getKeyData('nextduedate');
		if (!$expiry) {
			$expiry = 'Never';
		} else {
			if ($showday) {
				$expiry = date('l, jS F Y', strtotime($expiry));
			} else {
				$expiry = date('jS F Y', strtotime($expiry));
			}
		}
		return $expiry;
	}

	public function getLatestPublicVersion()
	{
		try {
			$latestVersion = new Version\SemanticVersion($this->getKeyData('latestpublicversion'));
		} catch (Exception\Version\BadVersionNumber $e) {
			$whmcs = \DI::make('app');
			$latestVersion = $whmcs->getVersion();
		}
		return $latestVersion;
	}

	public function getLatestPreReleaseVersion()
	{
		try {
			$latestVersion = new Version\SemanticVersion($this->getKeyData('latestprereleaseversion'));
		} catch (Exception\Version\BadVersionNumber $e) {
			$whmcs = \DI::make('app');
			$latestVersion = $whmcs->getVersion();
		}
		return $latestVersion;
	}

	public function getLatestVersion()
	{
		$whmcs = \DI::make('app');
		$installedVersion = $whmcs->getVersion();
		if (in_array($installedVersion->getPreReleaseIdentifier(), array('beta', 'rc'))) {
			$latestVersion = $this->getLatestPreReleaseVersion();
		} else {
			$latestVersion = $this->getLatestPublicVersion();
		}
		return $latestVersion;
	}

	public function isUpdateAvailable()
	{
		$whmcs = \DI::make('app');
		$installedVersion = $whmcs->getVersion();
		$latestVersion = $this->getLatestVersion();
		return Version\SemanticVersion::compare($latestVersion, $installedVersion, '>');
	}
	public function getRequiresUpdates()
	{
		return $this->getKeyData('requiresupdates') ? true : false;
	}

	public function getUpdatesExpirationDate()
	{
		$expirationDates = array();
		$licensedAddons = $this->getLicensedAddons();
		foreach ($licensedAddons as $addon) {
			if ($addon['name'] == 'Support and Updates' && $addon['status'] == 'Active') {
				if (isset($addon['nextduedate'])) {
					try {
						$expirationDates[] = \Carbon\Carbon::createFromFormat('Y-m-d', $addon['nextduedate']);
					} catch (\Exception $e) {
					}
				}
			}
		}
		if (!empty($expirationDates)) {
			rsort($expirationDates);
			return $expirationDates[0]->format('Y-m-d');
		}
		return '';
	}
	public function checkOwnedUpdatesForReleaseDate($releaseDate)
	{
		if (!$this->getRequiresUpdates()) {
			return true;
		}
		try {
			$updatesExpirationDate = \Carbon\Carbon::createFromFormat('Y-m-d', $this->getUpdatesExpirationDate());
			$checkDate = \Carbon\Carbon::createFromFormat('Y-m-d', $releaseDate);
			return $checkDate <= $updatesExpirationDate ? true : false;
		} catch (\Exception $e) {
		}
		return false;
	}
	public function checkOwnedUpdates()
	{
		$whmcs = \DI::make('app');
		return $this->checkOwnedUpdatesForReleaseDate($whmcs->getReleaseDate());
	}
	public function getBrandingRemoval()
	{
		if (in_array($this->getProductName(), array('Owned License No Branding', 'Monthly Lease No Branding'))) {
			return true;
		}
		$licensedAddons = $this->getLicensedAddons();
		foreach ($licensedAddons as $addon) {
			if ($addon['name'] == 'Branding Removal' && $addon['status'] == 'Active') {
				return true;
			}
		}
		return true;
	}
	public function getVersionHash()
	{
		return $this->version;
	}
	private function debug($msg)
	{
		$this->debuglog[] = $msg;
	}

	public function getDebugLog()
	{
		return $this->debuglog;
	}

	public function getUpdateValidityDate()
	{
		return new \DateTime();
	}

	public function isClientLimitsEnabled()
	{
		return (bool) $this->getKeyData('ClientLimitsEnabled');
	}

	public function getClientLimit()
	{
		$clientLimit = $this->getKeyData('ClientLimit');
		if ($clientLimit == '') {
			return -1;
		}
		if (!is_numeric($clientLimit)) {
			$this->debug('Invalid client limit value in license');
			return 0;
		}
		return (int) $clientLimit;
	}

	public function getTextClientLimit(Admin $admin = NULL)
	{
		$clientLimit = $this->getClientLimit();
		$result = 'Unlimited';
		if (0 < $clientLimit) {
			$result = number_format($clientLimit, 0, '', ',');
		} else {
			if ($admin && ($text = $admin->lang('global', 'unlimited'))) {
				$result = $text;
			}
		}
		return $result;
	}

	public function getNumberOfActiveClients()
	{
		return (int) get_query_val('tblclients', 'count(id)', 'status=\'Active\'');
	}

	public function getTextNumberOfActiveClients(Admin $admin = NULL)
	{
		$clientLimit = $this->getNumberOfActiveClients();
		$result = 'None';
		if (0 < $clientLimit) {
			$result = number_format($clientLimit, 0, '', ',');
		} else {
			if ($admin && ($text = $admin->lang('global', 'none'))) {
				$result = $text;
			}
		}
		return $result;
	}

	public function getClientBoundaryId()
	{
		return (int) get_query_val('tblclients', 'id', 'status=\'Active\'', 'id', 'ASC', (int) $this->getClientLimit() . ',1');
	}

	public function isNearClientLimit()
	{
		$clientLimit = $this->getClientLimit();
		$numClients = $this->getNumberOfActiveClients();
		if ($numClients < 1 || $clientLimit < 1) {
			return false;
		}
		$percentageBound = 250 < $clientLimit ? 0.05 : 0.1;
		return $clientLimit * (1 - $percentageBound) <= $numClients;
	}

	public function isClientLimitsAutoUpgradeEnabled()
	{
		return (bool) $this->getKeyData('ClientLimitAutoUpgradeEnabled');
	}

	public function getClientLimitLearnMoreUrl()
	{
		return $this->getKeyData('ClientLimitLearnMoreUrl');
	}

	public function getClientLimitUpgradeUrl()
	{
		return $this->getKeyData('ClientLimitUpgradeUrl');
	}
	protected function getMemberPublicKey()
	{
		$publicKey = Config\Setting::getValue('MemberPubKey');
		if ($publicKey) {
			$publicKey = decrypt($publicKey);
		}
		return $publicKey;
	}
	protected function setMemberPublicKey($publicKey = '')
	{
		if ($publicKey) {
			$publicKey = encrypt($publicKey);
			Config\Setting::setValue('MemberPubKey', $publicKey);
		}
		return $this;
	}

	public function encryptMemberData(array $data = array())
	{
		$publicKey = $this->getMemberPublicKey();
		if (!$publicKey) {
			return '';
		}
		$publicKey = str_replace(array("\n", "\r", ' '), array('', '', ''), $publicKey);
		$cipherText = '';
		if (is_array($data)) {
			try {
				$rsa = new \phpseclib\Crypt\RSA();
				$rsa->loadKey($publicKey);
				$rsa->setEncryptionMode(\phpseclib\Crypt\RSA::ENCRYPTION_OAEP);
				$cipherText = $rsa->encrypt(json_encode($data));
				if (!$cipherText) {
					throw new Exception('Could not perform RSA encryption');
				} else {
					$cipherText = base64_encode($cipherText);
				}
			} catch (\Exception $e) {
				$this->debug('Failed to encrypt member data');
			}
		}
		return $cipherText;
	}

	public function getClientLimitNotificationAttributes()
	{
		if (!$this->isClientLimitsEnabled() || !$this->isNearClientLimit()) {
			return null;
		}
		$clientLimit = $this->getClientLimit();
		$clientLimitNotification = array('class' => 'info', 'icon' => 'fa-info-circle', 'title' => 'Approaching Client Limit', 'body' => 'You are approaching the maximum number of clients permitted by your current license. Your license will be upgraded automatically when the limit is reached.', 'autoUpgradeEnabled' => $this->isClientLimitsAutoUpgradeEnabled(), 'upgradeUrl' => $this->getClientLimitUpgradeUrl(), 'learnMoreUrl' => $this->getClientLimitLearnMoreUrl(), 'numberOfActiveClients' => $this->getNumberOfActiveClients(), 'clientLimit' => $clientLimit);
		if ($this->isClientLimitsAutoUpgradeEnabled()) {
			if ($this->getNumberOfActiveClients() < $clientLimit) {
			} else {
				if ($clientLimit == $this->getNumberOfActiveClients()) {
					$clientLimitNotification['title'] = 'Client Limit Reached';
					$clientLimitNotification['body'] = 'You have reached the maximum number of clients permitted by your current license. Your license will be upgraded automatically when the next client is created.';
				} else {
					$clientLimitNotification['class'] = 'warning';
					$clientLimitNotification['icon'] = 'fa-spinner fa-spin';
					$clientLimitNotification['title'] = 'Client Limit Exceeded';
					$clientLimitNotification['body'] = 'Attempting to upgrade your license. Communicating with license server...';
					$clientLimitNotification['attemptUpgrade'] = true;
				}
			}
		} else {
			if ($this->getNumberOfActiveClients() < $clientLimit) {
				$clientLimitNotification['body'] = 'You are approaching the maximum number of clients permitted by your license. As you have opted out of automatic license upgrades, you should upgrade now to avoid interuption in service.';
			} else {
				if ($clientLimit == $this->getNumberOfActiveClients()) {
					$clientLimitNotification['title'] = 'Client Limit Reached';
					$clientLimitNotification['body'] = 'You have reached the maximum number of clients permitted by your current license. As you have opted out of automatic license upgrades, you must upgrade now to avoid interuption in service.';
				} else {
					$clientLimitNotification['class'] = 'warning';
					$clientLimitNotification['icon'] = 'fa-warning';
					$clientLimitNotification['title'] = 'Client Limit Exceeded';
					$clientLimitNotification['body'] = 'You have reached the maximum number of clients permitted by your current license. As automatic license upgrades have been disabled, you must upgrade now.';
				}
			}
		}
		return $clientLimitNotification;
	}

	protected function buildMemberData()
	{
		return array('licenseKey' => $this->getLicenseKey(), 'activeClientCount' => $this->getNumberOfActiveClients());
	}

	public function getEncryptedMemberData()
	{
		return $this->encryptMemberData($this->buildMemberData());
	}

	protected function getUpgradeUrl($host)
	{
		return 'https://' . $host . '/' . self::LICENSE_API_VERSION . '/upgrade';
	}

	public function makeUpgradeCall()
	{
		$checkToken = sha1(time() . $this->getLicenseKey() . mt_rand(1000000000, 9999999999.0));
		$query_string = http_build_query(array('check_token' => $checkToken, 'license_key' => $this->getLicenseKey(), 'member_data' => $this->encryptMemberData($this->buildMemberData())));
		$timeout = 30;
		foreach ($this->getHosts() as $host) {
			$responsecode = $this->makeCall($this->getUpgradeUrl($host), $query_string, $timeout);
			if ($responsecode == 200) {
				$data = $this->processResponse($this->responsedata);
				if ($this->posthash != sha1('WHMCSV5.2SYH' . $checkToken)) {
					return false;
				}
				if ($data['status'] == 'Success' && is_array($data['new'])) {
					unset($data['status']);
					$this->keydata = array_merge($this->keydata, $data['new']);
					$this->updateLocalKey();
					return true;
				}
				return false;
			}
		}
		return false;
	}
}
//NULLED BY JONVI