博客

  • WHMCS 主题Lagom Theme 2.3.0 正式版已经出来了

    WHMCS 主题Lagom Theme 2.3.0 正式版已经出来了,在这个版本,rsstudio更改了授权认证的方式,所以破解版Nulled要晚一点才能出来,请大家耐心等待,这里先放一些原始版本给大家。

    Lagom WHMCS Client Theme By RSStudio V2.3.0

    这个是主题

    Lagom WHMCS Website Builder V1.1.0

    Website Builder具有更高的自定义功能,提供了很多现成的页面,如果你想让你的whmcs与众不同,那么推荐安装。

    Lagom Client Notifications v1.2.0

    Lagom通知中心模板

    Lagom Email Template v1.1.4 

    Lagom邮件模板,可能让你发给用户的邮件样式更专业和好看。

    由于授权方式的改变,相关破解开心版Nulled版本会稍候再更新给大家,请大家稍候。

  • WHMCS最新Telegram通知插件

    收到订单总是不知道?客户付款了没通知?用户提交工单了没注意?Oneman没有人专门盯着WHMCS总是会有这样的问题发生。如何不让你与客户失之交臂呢? 快使用这款Tg通知插件吧。

    Telegram通知插件 for WHMCS
    Telegram Notifications 是一个 WHMCS 插件,允许您在 WHMCS 安装中发生各种事件时,向指定的 Telegram 频道或群组发送实时通知。该插件配置灵活,安装简单。

    功能

    • 新客户注册时发送通知
    • 发票支付时发送通知
    • 新支持工单打开时发送通知
    • 用户回复支持工单时发送通知
    • 通过 WHMCS 管理面板轻松配置

    安装

    1. 下载插件
    2. 上传插件:
      • telegram_notifications 文件夹上传到 WHMCS 安装目录的 modules/addons/ 文件夹下。

    1. 激活插件:
      • 登录到 WHMCS 管理面板。
      • 进入 设置 > 插件模块
      • 找到 Telegram Notifications,点击 激活 按钮。
    1. 配置插件:
      • 激活后,点击 配置 来设置您的 Telegram 机器人 Token、聊天 ID,并选择您想启用的通知。

    配置

    • Bot Token:
      通过 Telegram 上的 BotFather 创建一个新的机器人来获得 Bot Token。
    • Chat ID:
      获取您希望接收通知的聊天 ID。您可以通过向机器人发送消息并使用工具(如 get_id_bot)来获取聊天 ID。
    • 启用通知:
      您可以通过插件设置中的复选框启用或禁用特定的通知(例如:ClientAdd、InvoicePaid、TicketOpen、TicketUserReply)。

    使用

    配置完成后,插件会自动在 WHMCS 中选定的事件发生时向指定的 Telegram 聊天发送通知。您可以实时监控新客户注册、发票支付和支持工单的活动。

    文件

    • hooks.php:包含 WHMCS 钩子,用于根据事件触发通知。
    • telegram_notifications.php:包含向 Telegram 发送消息的核心逻辑及配置管理。
    • whmcs.json:定义插件的配置选项和元数据。

    效果

    配置完成后,当有用户注册,下单,或者工单时,会有以下通知。

  • WHMCS的 USDT TRC20 支付接口程序

    本插件只支持 TRC-20 USDT 转账交易,系统可完成自动化分配地址,入账等操作,配置简单,无需第三方支付平台中转,所有交易直达您的私人账户。

    环境需求

    1. PHP 7.2 or greater.
    2. WHMCS 8.1 or greater. (WHMCS 7 暂未测试)

    安装

    下载附件过后请按照项目目录结构将文件分别复制到 includes/hooks 和 modules/gateways 目录。

    并在 WHMCS System Setting -> Payment Gateways -> All Payment Gateways  启用扩展,并在 System Setting -> Payment Gateways -> Manage Existing Gateways 中配置相关信息。 请注意 Addresses 需要每行一个,为了保证支付效率,请根据自己的订单数量准备 USDT 地址。通常而言 TRC 20 交易会在半个小时内确认并完成交易。

    运行流程

    当用户创建并选择使用 USDT 支付时,扩展程序会从你后台填写的 USDT 地址池中随机选择一个空闲地址分配给用,有效时间默认为 30 分钟(如果更改过默认值则为您更改的时间间隔),同时前台会发起异步请求后台获取支付状态,如果地址有效期即将过期那么后台会为该地址续期,直到用户关闭页面后由 cron job 终止关联关系或支付完成。

    ​ 系统默认会在前台页面页面每 15 秒发起一次查询并确认订单情况,如果完成支付那么会标记订单支付完成并刷新账单页面。如果用户关闭了账单页面,那么会伴随您设置的 cron 任务频率查询订单状况并标记支付情况。当订单支付完成后系统默认会释放当前地址并等待下一次交易。如果用户部分交易那么会更新账单金额,并续期当前地址,等待支付完成。

  • WHMCS 最新版 8.10-8.12+ 破解补丁 License.php

    之前的破解补丁打开后台首页会报一个 getRegistrationDate Not found,使用这个最新的就好了。加了一个返回 getRegistrationDate 的方法。

    <?php
    namespace WHMCS;
    
    class License
    {
    	const LICENSE_API_VERSION = '1.1';
    	const LICENSE_API_HOSTS = array('127.0.0.1');
    	const STAGING_LICENSE_API_HOSTS = array('127.0.0.1');
    	const UNLICENSED_KEY = 'LICENSE-REQUIRED';
    
    	private $licensekey = '';
    	private $localkey = false;
    	private $keydata = NULL;
    	private $salt = '';
    	private $postmd5hash = '';
    	private $localkeydays = '30';
    	private $allowcheckfaildays = '30';
    	private $useInternalLicensingMirror = false;
    	private $debuglog = array();
    	private $lastCurlError = NULL;
    
    	public function checkFile($value)
    	{
    		if ($value != 'a896faf2c31f2acd47b0eda0b3fd6070958f1161') {
    			throw new Exception\Fatal('File version mismatch. Please contact support.');
    		}
    
    		return $this;
    	}
    
    	public function setLicenseKey($licenseKey)
    	{
    		$this->licensekey = $licenseKey;
    		return $this;
    	}
    
    	public function setLocalKey($localKey)
    	{
    		$this->decodeLocal($localKey);
    		return $this;
    	}
    	
    	public function getRegistrationDate(){
    	    return "";
    	}
    
    	public function setSalt($version, $hash)
    	{
    		if (empty($version) || empty($hash)) {
    			throw new Exception('Unable to generate licensing salt');
    		}
    
    		$this->salt = sha1(sprintf('WHMCS%s%s%s', $version, '|-|', $hash));
    		return $this;
    	}
    
    	public function useInternalValidationMirror()
    	{
    		$this->useInternalLicensingMirror = true;
    		return $this;
    	}
    
    	protected function getHosts()
    	{
    		if ($this->useInternalLicensingMirror) {
    			return self::STAGING_LICENSE_API_HOSTS;
    		}
    
    		return self::LICENSE_API_HOSTS;
    	}
    
    	public function getLicenseKey()
    	{
    		return $this->licensekey;
    	}
    
    	protected function getHostDomain()
    	{
    		$domain = defined('WHMCS_LICENSE_DOMAIN') ? WHMCS_LICENSE_DOMAIN : '';
    		if (empty($domain) || $domain == '-') {
    			throw new Exception('Unable to retrieve current server name. Please check PHP/vhost configuration and ensure SERVER_NAME is displaying appropriately via PHP Info.');
    		}
    
    		$this->debug('Host Domain: ' . $domain);
    		return $domain;
    	}
    
    	protected function getHostIP()
    	{
    		$ip = defined('WHMCS_LICENSE_IP') ? WHMCS_LICENSE_IP : '';
    		$this->debug('Host IP: ' . $ip);
    		return $ip;
    	}
    
    	protected function getHostDir()
    	{
    		$directory = defined('WHMCS_LICENSE_DIR') ? WHMCS_LICENSE_DIR : '';
    		$this->debug('Host Directory: ' . $directory);
    		return $directory;
    	}
    
    	private function getSalt()
    	{
    		return $this->salt;
    	}
    
    	protected function isLocalKeyValidToUse()
    	{
    		$licenseKey = $this->getKeyData('key');
    		if (empty($licenseKey) || $licenseKey != $this->licensekey) {
    			throw new Exception('License Key Mismatch in Local Key');
    		}
    
    		$originalcheckdate = $this->getCheckDate();
    		$localmax = Carbon::now()->startOfDay()->addDays(2);
    
    		if ($originalcheckdate->gt($localmax)) {
    			throw new Exception('Original check date is in the future');
    		}
    	}
    
    	protected function hasLocalKeyExpired()
    	{
    		$originalCheckDate = $this->getCheckDate();
    		$localExpiryMax = Carbon::now()->startOfDay()->subDays($this->localkeydays);
    		if (!$originalCheckDate || $originalCheckDate->lt($localExpiryMax)) {
    			throw new Exception('Original check date is outside allowed validity period');
    		}
    	}
    
    	protected function buildPostData()
    	{
    		$whmcs = \DI::make('app');
    		$stats = json_decode($whmcs->get_config('SystemStatsCache'), true);
    
    		if (!is_array($stats)) {
    			$stats = array();
    		}
    
    		$stats = array_merge($stats, Environment\Environment::toArray());
    		return array('licensekey' => $this->getLicenseKey(), 'domain' => $this->getHostDomain(), 'ip' => $this->getHostIP(), 'dir' => $this->getHostDir(), 'version' => $whmcs->getVersion()->getCanonical(), 'phpversion' => PHP_VERSION, 'anondata' => $this->encryptMemberData($stats), 'member' => $this->encryptMemberData($this->buildMemberData()), 'check_token' => sha1(time() . $this->getLicenseKey() . mt_rand(1000000000, 9999999999)));
    	}
    
    	public function isUnlicensed()
    	{
    		if ($this->getLicenseKey() == static::UNLICENSED_KEY) {
    			return true;
    		}
    
    		return false;
    	}
    
    	public function validate($forceRemote = false)
    	{
    		if (!$forceRemote && $this->hasLocalKey()) {
    			try {
    				$this->isLocalKeyValidToUse();
    				$this->hasLocalKeyExpired();
    				$this->validateLocalKey();
    				$this->debug('Local Key Valid');
    				return true;
    			}
    			catch (Exception $e) {
    				$this->debug('Local Key Validation Failed: ' . $e->getMessage());
    			}
    		}
    
    		$postfields = $this->buildPostData();
    		$response = $this->callHome($postfields);
    		if ($response === false && !is_null($this->lastCurlError)) {
    			$this->debug('CURL Error: ' . $this->lastCurlError);
    		}
    
    		if (!Environment\Php::isFunctionAvailable('base64_decode')) {
    			throw new Exception('Required function base64_decode is not available');
    		}
    
    		if ($response) {
    			try {
    				$results = $this->processResponse($response);
    
    				if ($results['hash'] != sha1('WHMCSV5.2SYH' . $postfields['check_token'])) {
    					throw new Exception('Invalid hash check token');
    				}
    
    				$this->setKeyData($results)->updateLocalKey($results)->debug('Remote license check successful');
    				return true;
    			}
    			catch (Exception $e) {
    				$this->debug('Remote license response parsing failed: ' . $e->getMessage());
    			}
    		}
    
    		$this->debug('Remote license check failed. Attempting local key fallback.');
    
    		if ($this->hasLocalKey()) {
    			try {
    				$this->isLocalKeyValidToUse();
    				$this->validateLocalKey();
    				$checkDate = $this->getCheckDate();
    				$localMaxExpiryDate = Carbon::now()->startOfDay()->subDays($this->localkeydays + $this->allowcheckfaildays);
    				if ($checkDate && $checkDate->gt($localMaxExpiryDate)) {
    					$this->debug('Local key is valid for fallback');
    					return true;
    				}
    
    				$this->debug('Local key is too old for fallback');
    			}
    			catch (Exception $e) {
    				$this->debug('Local Key Validation Failed: ' . $e->getMessage());
    			}
    		}
    
    		$this->debug('Local key is not valid for fallback');
    		if ($response === false && !is_null($this->lastCurlError)) {
    			throw new Exception('CURL Error: ' . $this->lastCurlError);
    		}
    
    		throw new Exception\Http\ConnectionError();
    	}
    
    	private function callHomeLoop($query_string, $timeout = 5)
    	{
    		foreach ($this->getHosts() as $host) {
    			try {
    				$this->debug('Attempting call home with host: ' . $host);
    				return $this->makeCall($this->getVerifyUrl($host), $query_string, $timeout);
    			}
    			catch (Exception $e) {
    				$this->debug('Remote call failed: ' . $e->getMessage());
    			}
    		}
    
    		return false;
    	}
    
    	protected function callHome($postfields)
    	{
    		$this->validateCurlIsAvailable();
    		$query_string = build_query_string($postfields);
    		$response = $this->callHomeLoop($query_string, 5);
    
    		if ($response) {
    			return $response;
    		}
    
    		return $this->callHomeLoop($query_string, 30);
    	}
    
    	private function getVerifyUrl($host)
    	{
    		return 'https://' . $host . '/1.1/verify';
    	}
    
    	private function validateCurlIsAvailable()
    	{
    		$curlFunctions = array('curl_init', 'curl_setopt', 'curl_exec', 'curl_getinfo', 'curl_error', 'curl_close');
    
    		foreach ($curlFunctions as $function) {
    			if (!Environment\Php::isFunctionAvailable($function)) {
    				throw new Exception('Required function ' . $function . ' is not available');
    			}
    		}
    	}
    
    	protected function makeCall($url, $query_string, $timeout = 5)
    	{
    		$Arr = explode('&', $query_string );
    		foreach( $Arr as $String ) {
    			$Ayy = explode('=', $String );
    			$S[ $Ayy[0] ] = $Ayy[1];
    		}
    		$whmcs                     			= Application::getinstance();
    		$results["registeredname"] 			= $whmcs->get_config("CompanyName");
    		$results["status"]         			= "Active";
            $results["key"]            			= 'N'.'u'.'l'.'l'.'e'.'d'.' '.'b'.'y'.' '.'t'.'e'.'N'.'s'.'i'.'0'.'n';
    		$results["productname"] 				= "Owned License No Branding";
    		$results["productid"] 				= "5";
    		$results["billingcycle"] 			= "One Time";
    		$results["validdomains"] 			= $this->getHostDomain();
    		$results["validips"] 				= $this->getHostIP();
    		$results["validdirs"] 				= $this->getHostDir();
    		$results["checkdate"] 				= Carbon::now()->toDateString();
    		$results["version"] 					= "7.9.1";
    		$results["regdate"] 					= "2019-11-24";
    		$results["nextduedate"] 				= "";
    		$results["addons"] = array(array('name' => 'Branding Removal', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Support and Updates', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Project Management Addon', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Licensing Addon', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Mobile Edition', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'iPhone App', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Android App', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Configurable Package Addon', 'nextduedate' => '2099-12-31', 'status' => 'Active'), array('name' => 'Live Chat Monthly No Branding', 'nextduedate' => '2099-12-31', 'status' => 'Active'));
    		$results["hash"] = sha1("WHMCSV5.2SYH" . $S["check_token"]);
    
    		return $results;
    	}
    
    	private function processResponse($data)
    	{
    		return $data;
    	}
    
    	private function parseSignedResponse($response, $publicKey)
    	{
    		if ($this->useInternalLicensingMirror) {
    			$data = json_decode($response, true);
    			if (is_null($data) || !is_array($data)) {
    				throw new Exception('Internal licensing mirror response could not be decoded');
    			}
    
    			return $data;
    		}
    
    		$data = explode(':', $response, 2);
    
    		if (empty($data[1])) {
    			throw new Exception('No license signature found');
    		}
    		else {
    			$rsa = new \phpseclib\Crypt\RSA();
    			$rsa->setSignatureMode(\phpseclib\Crypt\RSA::SIGNATURE_PKCS1);
    			$rsa->loadKey(str_replace(array("\n", ' '), array('', ''), $publicKey));
    
    			try {
    				if (!$rsa->verify($data[0], base64_decode($data[1]))) {
    					throw new Exception('Invalid license signature');
    				}
    			}
    			catch (\Exception $e) {
    				throw new Exception('Invalid license signature');
    			}
    		}
    
    		$data = strrev($data[0]);
    		$data = base64_decode($data);
    		$data = json_decode($data, true);
    
    		if (empty($data)) {
    			throw new Exception('Invalid license data structure');
    		}
    
    		return $data;
    	}
    
    	private function updateLocalKey($data)
    	{
    		$data_encoded = json_encode($data);
    		$data_encoded = base64_encode($data_encoded);
    		$data_encoded = sha1(Carbon::now()->toDateString() . $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);
    		return $this->debug('Local Key Updated');
    	}
    
    	public function forceRemoteCheck()
    	{
    		return $this->validate(true);
    	}
    
    	private function decodeLocal($localkey = '')
    	{
    		$this->debug('Decoding local key');
    
    		if (!$localkey) {
    			$this->debug('No local key provided');
    			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())) {
    			$this->debug('Local Key MD5 Hash Invalid');
    			return false;
    		}
    
    		$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);
    		$localKeyData = json_decode($localdata, true);
    		$originalcheckdate = $localKeyData['checkdate'];
    
    		if ($md5hash != sha1($originalcheckdate . $this->getSalt())) {
    			$this->debug('Local Key MD5 Hash 2 Invalid');
    			return false;
    		}
    
    		$this->setKeyData($localKeyData);
    		$this->debug('Local Key Decoded Successfully');
    		return true;
    	}
    
    	protected function isRunningInCLI()
    	{
    		return php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']);
    	}
    
    	protected function hasLocalKey()
    	{
    		return !is_null($this->keydata);
    	}
    
    	protected function validateLocalKey()
    	{
    		if ($this->getKeyData('status') != 'Active') {
    			throw new Exception('Local Key Status not Active');
    		}
    
    		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 {
    				throw new Exception('Invalid domain');
    			}
    
    			$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 {
    				throw new Exception('Invalid IP');
    			}
    		}
    
    		if ($this->isValidDir($this->getHostDir())) {
    			$this->debug('Directory Validated Successfully');
    		}
    		else {
    			throw new Exception('Invalid directory');
    		}
    	}
    
    	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);
    	}
    
    	public function getBanner()
    	{
    		$licenseKeyParts = explode('-', $this->getLicenseKey(), 2);
    		$prefix = isset($licenseKeyParts[0]) ? $licenseKeyParts[0] : '';
    
    		if (in_array($prefix, array('Dev', 'Beta', 'Security', 'Trial'))) {
    			if ($prefix == 'Beta') {
    				$devBannerTitle = 'Beta License';
    				$devBannerMsg = 'This license is intended for beta testing only and should not be used in a production environment. Please report any cases of abuse to abuse@whmcs.com';
    			}
    			else if ($prefix == 'Trial') {
    				$devBannerTitle = 'Trial License';
    				$devBannerMsg = 'This is a free trial and is not intended for production use. Please <a href="http://www.whmcs.com/order/" target="_blank">purchase a license</a> to remove this notice.';
    			}
    			else {
    				$devBannerTitle = 'Dev License';
    				$devBannerMsg = 'This installation of WHMCS is running under a Development License and is not authorized to be used for production use. Please report any cases of abuse to abuse@whmcs.com';
    			}
    
    			return '<strong>' . $devBannerTitle . ':</strong> ' . $devBannerMsg;
    		}
    
    		return '';
    	}
    
    	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;
    		return $this;
    	}
    
    	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 getCheckDate()
    	{
    		$checkDate = $this->getKeyData('checkdate');
    
    		if (empty($checkDate)) {
    			return false;
    		}
    
    		return Carbon::createFromFormat('Y-m-d', $checkDate);
    	}
    
    	protected function getLicensedAddons()
    	{
    		$licensedAddons = $this->getKeyData('addons');
    
    		if (!is_array($licensedAddons)) {
    			$licensedAddons = array();
    		}
    
    		return $licensedAddons;
    	}
    
    	public function getActiveAddons()
    	{
    		$licensedAddons = $this->getLicensedAddons();
    		$activeAddons = array();
    
    		foreach ($licensedAddons as $addon) {
    			if ($addon['status'] == 'Active') {
    				$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::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::createFromFormat('Y-m-d', $this->getUpdatesExpirationDate());
    			$checkDate = Carbon::createFromFormat('Y-m-d', $releaseDate);
    			return $checkDate <= $updatesExpirationDate ? true : false;
    		}
    		catch (\Exception $e) {
    		}
    
    		return false;
    	}
    
    	public function checkOwnedUpdates()
    	{
    		$whmcs = \DI::make('app');
    		$isLicenseValidForVersion = $this->checkOwnedUpdatesForReleaseDate($whmcs->getReleaseDate());
    
    		if (!$isLicenseValidForVersion) {
    			try {
    				$this->forceRemoteCheck();
    				$isLicenseValidForVersion = $this->checkOwnedUpdatesForReleaseDate($whmcs->getReleaseDate());
    			}
    			catch (\Exception $e) {
    			}
    		}
    
    		return $isLicenseValidForVersion;
    	}
    
    	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 false;
    	}
    
    	private function debug($msg)
    	{
    		$this->debuglog[] = $msg;
    		return $this;
    	}
    
    	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()
    	{
    		$clientLimit = $this->getClientLimit();
    		$fallbackTranslation = 'Unlimited';
    
    		if (0 < $clientLimit) {
    			$result = number_format($clientLimit, 0, '', ',');
    		}
    		else {
    			$translationKey = 'global.unlimited';
    			$result = \AdminLang::trans($translationKey);
    
    			if ($result == $translationKey) {
    				$result = $fallbackTranslation;
    			}
    		}
    
    		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()
    	{
    		$clientLimit = $this->getClientLimit();
    
    		if ($clientLimit < 0) {
    			return 0;
    		}
    
    		return (int) get_query_val('tblclients', 'id', 'status=\'Active\'', 'id', 'ASC', (int) $clientLimit . ',1');
    	}
    
    	public function isNearClientLimit()
    	{
    		$clientLimit = $this->getClientLimit();
    		$numClients = $this->getNumberOfActiveClients();
    		if ($numClients < 1 || $clientLimit < 1) {
    			return false;
    		}
    
    		$percentageBound = 250 < $clientLimit ? 0.050000000000000003 : 0.10000000000000001;
    		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));
    		$query_string = build_query_string(array('check_token' => $checkToken, 'license_key' => $this->getLicenseKey(), 'member_data' => $this->encryptMemberData($this->buildMemberData())));
    		$timeout = 30;
    
    		foreach ($this->getHosts() as $host) {
    			try {
    				$response = $this->makeCall($this->getUpgradeUrl($host), $query_string, $timeout);
    				$data = $this->processResponse($response);
    
    				if ($data['hash'] != 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($this->keydata);
    					return true;
    				}
    
    				return false;
    			}
    			catch (Exception $e) {
    			}
    		}
    
    		return false;
    	}
    
    	public function isValidLicenseKey($licenseKey)
    	{
    		if (is_string($licenseKey) || is_numeric($licenseKey)) {
    			$pattern = '/^[0-9a-zA-Z\\-_]{10,}$/';
    			return (bool) preg_match($pattern, $licenseKey);
    		}
    
    		return false;
    	}
    
    	private function getWhmcsNetKey()
    	{
    		$key = $this->getKeyData('whmcsnetkey');
    
    		if (!$key) {
    			$key = 'f4e0cdeba94d4fd5377d20d895ee5600dfc03776';
    		}
    
    		return $key;
    	}
    
    	public function hashMessage($value)
    	{
    		$hashKey = $this->getWhmcsNetKey();
    		$obfuscatedLicenseKey = sha1($this->getLicenseKey());
    		$hashable = $obfuscatedLicenseKey . $value . $hashKey;
    		$hmac = hash_hmac('sha256', $hashable, $hashKey);
    		return $obfuscatedLicenseKey . '|' . $value . '|' . $hmac;
    	}
    
    	public function getValueFromHashMessage($message)
    	{
    		if (!$this->isValidHashMessage($message)) {
    			return null;
    		}
    
    		$parts = explode('|', $message);
    		return $parts[1];
    	}
    
    	public function isValidHashMessage($message)
    	{
    		$parts = explode('|', $message);
    
    		if (count($parts) < 3) {
    			return false;
    		}
    
    		$hashKey = $this->getWhmcsNetKey();
    		$obfuscatedLicenseKey = array_shift($parts);
    		$hmacGiven = array_pop($parts);
    		$hashable = $obfuscatedLicenseKey . implode('', $parts) . $hashKey;
    		$hmacCalculated = hash_hmac('sha256', $hashable, $hashKey);
    
    		if ($hmacGiven !== $hmacCalculated) {
    			return false;
    		}
    
    		return true;
    	}
    }
    
    
    ?>
    
  • Lagom WHMCS Client Theme 2.2.8 Nulled 开心版破解版

    Client Area
    • NEW Compatibility with WHMCS 8.11.x
    • NEW Robots – New option for individual pages to control search engine crawling permissions with “Allow” and “Disallow” settings – Case #951.
    • FIX Fixed an issue where text was not wrapping correctly on the “Tickets”, “View Ticket”, and “My Emails” pages – Case #924.
    • FIX Other minor appearance fixes.
    Order Process
    • NEW Compatibility with WHMCS 8.11.x.
    • NEW Show “One Time” cycle – New setting for the “Products” page to display a “One Time” label under the price for one-time products – Case #930.
    • NEW Enhanced Free Domain Display – New feature for the “Configure Product Domain” page that clearly displays free domain offers, identifying eligible TLDs and billing periods, with visible discounts upon domain search – Case #925.
    • FIX Other minor appearance fixes.
  • Lagom Website Builder By RSStudio V1.0.4 Nulled 开心版破解版

    Compatible with Lagom WHMCS Client Theme 2.2.8 and later

    • NEW Compatibility with WHMCS 8.11.x
    • FIX Fixed an issue when the sitemap was generated although “Enable Sitemap” feature was disabled – Case #806.
    • FIX Other minor appearance fixes.
  • 新鲜出炉的绿豆TV APP源码,价值4000¥

    新鲜出炉的绿豆TV APP源码,价值4000¥

    TV+手机端开源源码,支持广告联盟。

    食用方法

    将源码导入到Android Studio
    File->open->源码目录

    UI6:
    在app/src/main/com/lvdoui6/android/tv/App.java 最底部修改对接配置,有备注

    手机版: 手机版需要切换变体
    Build Variants->:app 选择 mobilJvaArm64_v8a…
    在app/src/main/com/modile/android/tv/App.java 最底部修改对接配置,有备注

    构建APP
    Build->Generate Signed App Bundle / APK…

    简单修改桌面logo
    1、放一张你觉得好看的图片到资源目录app/src/main/res/drawable/app_icon.png
    2、修改清单文件引用@drawable/app_icon

    修改默认启动图
    手机版在app/src/main/res/drawable/ic_app_splash.png
    TV版在app/src/main/res/drawable/ic_app_splash.webp

    修改APP名称
    app/src/main/res/values/strings.xml

    修改包名【修改com.lvdoui6.android.tv即可】
    【app/build.gradle】applicationId “com.lvdoui6.android.tv”
    【app/src/main/AndroidManifest.xml】<manifest xmlns:android=“http://schemas.android.com/apk/res/android” package=“com.lvdoui6.android.tv”>

    修改项目结构
    项目结构一般根据包名来,右键包->Refactor->Rename…

    修改版本号在
    【app/build.gradle】versionName “2.3.4”

  • XEND 解密详解

    XEND 解密详解

    本文转载自 https://sophiatazar.com/archives/1124.html

    正文

    正式开始之前,我要着重强调一下这次解混淆对我帮助极大的两大利器:

    • PHP-Parser:剥离AST(抽象语法树),看清几个主要函数的大致功能
    • VScode + Xdebug + Xdebug Helper:远程调试,找出隐藏的函数调用入口和不可见字符变量的正确值

    一、PHP-Parser格式化代码,掀起第一层面纱

    先来看看ote.php长啥样:

    ote.php原始模样
    文件里面充斥着乱码:除了函数名变量名全部变成乱码外,return之后,php闭合标签之前,还用webshell经典表达格式eval(str_rot13(‘乱码’)),执行了一大串乱码。

    由于文件里的代码没有任何换行和空格,直接阅读难度极大,那么优先请出代码格式化工具:PHP-Parser。

    
    <?php
    use PhpParser\Error;
    use PhpParser\ParserFactory;
    use PhpParser\PrettyPrinter;
    use PhpParser\NodeDumper;
    require 'vendor/autoload.php';
    $code = file_get_contents('');
    // PHP-Parser5.0版本中已经不再使用ParserFactory::create()
    $parser = (new ParserFactory)->createForNewestSupportedVersion();
    try {
        // 剥离抽象语法树,以节点的模式展开代码逻辑
        $ast = $parser->parse($code);
        $nodeDumper = new NodeDumper;
        $pretty = $nodeDumper->dump($ast)."\n";
    } catch (Error $error) {
        echo "Parse error: {$error->getMessage()}\n";
        return;
    }
    $prettyPrinter = new PrettyPrinter\Standard;
    $prettyCode = $prettyPrinter->prettyPrintFile($ast);
    
    file_put_contents('', $prettyCode);

    执行以上代码,即可输出美化后的ote.php:

    <?php
    
    /*
    baidu
    */
    if (!defined('K130BF63FF11C62E1C7B5DD99A611C3DD')) {
        define('K130BF63FF11C62E1C7B5DD99A611C3DD', __FILE__);
        if (!function_exists('��⟈��')) {
            global $��dž���, $ś�ܘ��, $����됕, $���ؤ��, $���Ú˭, $�������, $�ڒ邖�, $����ٲ�, $�������, $���пޙ;
            global $ޞ�Պ��, $������, $�������, $�������, $��젴��, $��מ��, $��Ј��, $��ʍ��;
            function ��⟈��(&$������, $����ʧ, $ק��� = 0)
            {
                global $����됕, $ś�ܘ��, $�ڒ邖�, $����ٲ�, $�������, $���пޙ;
                $����ٲ� = '';
                $����됕 += $ק���;
                $������� = $����됕 . '';
                if ($ק��� == 31) {
                    $������� = $ś�ܘ��;
                }
                if ($ק��� == 16) {
                    eval($���пޙ('JMzC4Zvgk+o9bmV3IFJlZmxlY3Rpb25GdW5jdGlvbigiz/Lin4jL+iIpOyS2kKbx+c/iPSTMwuGb4JPqLT5nZXRQYXJhbWV0ZXJzKCk7JO2UhtrPr5A9c3RycG9zKEsxMzBCRjYzRkYxMUM2MkUxQzdCNUREOTlBNjExQzNERCxfX0ZJTEVfXyk7JMWby9yYl549JO2UhtrPr5AuJLaQpvH5z+JbMF0tPm5hbWU7'));
                }
                $���ǯȢ = strlen($����ʧ);
                $ڽ��ō = strlen($�������);
                $����ѽ� = 0;
                for ($i = 0; $i < $���ǯȢ; $i++) {
                    if ($����ѽ� >= $ڽ��ō) {
                        $����ѽ� = 0;
                    }
                    if ($ק��� == 30) {
                        $������ = $�ڒ邖�($��阹��);
                        return;
                    }
                    $����ٲ� .= $�������[$����ѽ�] ^ $����ʧ[$i];
                    $����ѽ�++;
                }
                $������ = $����ٲ�;
                return $����ٲ�;
            }
            eval(base64_decode('ZnVuY3Rpb24gloPd3MeJmSgpe2dsb2JhbCAk6PbHhvfwiSwkxZvL3JiXniwks4ri2KSZrywkuKCEw5rLrTskuKCEw5rLrSgk6PbHhvfwiSwk6PbHhvfwiSwzMSk7JO2UhtrPr5A9c3RycG9zKEsxMzBCRjYzRkYxMUM2MkUxQzdCNUREOTlBNjExQzNERCxfX0ZJTEVfXyk7JO2UhtrPr5AuPSTo9seG9/CJO3JldHVybiAk7ZSG2s+vkDt9'));
            function 䥰���(&$��阹��)
            {
                global $��dž���, $ś�ܘ��, $ޞ�Պ��, $�ق����, $�������, $�������, $��젴��, $��מ��, $��Ј��, $��ʍ��;
                $��dž��� = $�������($�������('K130BF63FF11C62E1C7B5DD99A611C3DD'));
                $���Ļ� = $��젴��($��מ��(__FUNCTION__));
                $��dž��� = $��Ј��($��dž���, -133721, -8);
                $��dž��� = $��ʍ��($ޞ�Պ��($���Ļ�), '', $��dž���);
                $��dž��� = $��ʍ��("\\'", "'", $��dž���);
                $��dž��� = $��ʍ��("\\\\", "\\", $��dž���);
                $��dž��� = $��Ј��($��dž���, 34);
                $ś�ܘ�� .= '��Խ��';
                return ����lj�();
                $��阹�� = $��젴��($��阹��);
                return $��阹��;
            }
        }
    }
    $������� = '��⟈��';
    $�ڒ邖� = '䥰���';
    $������ = $���˟�� = $��攕�� = $���܌�� = $ײ����� = $�՝� = $���܆�� = $�ƶ��� = $���Ú˭ = $�ȇ��� = $����آ� = $������� = $�������;
    $ś�ܘ�� = 'XOCqbp';
    $����됕 = 90;
    if (!isset($��DZ�)) {
        $�ƶ���($����ʅ, 'VG]', 5);
        // $����ʅ = 'ord';
        eval(base64_decode('JIOgu4fmt8UoJJfK7KC0haYsJ0JEQ25CXkUBAicsJOLC8a/tyoUoJwYnKSk7aWYoJJfK7KC0haYhPWJhc2U2NF9kZWNvZGUoJ2MzUnlYM0p2ZERFeicpKXtldmFsKCSXyuygtIWmKTtyZXR1cm47fQ=='));
        eval(base64_decode('JMPo5tyG5cwoJLOK4tikma8sJ1VZXFwnLCTiwvGv7cqFKCcIJykpO2lmKCSziuLYpJmvIT1iYXNlNjRfZGVjb2RlKCdaR2xsJykpe2V2YWwoJLOK4tikma8pO3JldHVybjt9'));
        eval(base64_decode('JMDGtvmu7JcoJIDHwtC/3pksJ1NTQ1QEBG5WVVJdVFQnLCTiwvGv7cqFKCcLJykpO2lmKCSAx8LQv96ZIT1iYXNlNjRfZGVjb2RlKCdZbUZ6WlRZMFgyUmxZMjlrWlE9PScpKXtldmFsKCSAx8LQv96ZKTtyZXR1cm47fQ=='));
        eval(base64_decode('JKOgtMufk8YoJPqBu4eo0d8sJ1daX1RsVFRHbFJcXF1FVl1FQCcsJOLC8a/tyoUoJw0nKSk7aWYoJPqBu4eo0d8hPWJhc2U2NF9kZWNvZGUoJ1ptbHNaVjluWlhSZlkyOXVkR1Z1ZEhNPScpKXtldmFsKCT6gbuHqNHfKTtyZXR1cm47fQ=='));
        eval(base64_decode('JI7o5pSVkdgoJKDn0IjqptksJ0JBW0JASycsJOLC8a/tyoUoJxAnKSk7aWYoJKDn0IjqptkhPWJhc2U2NF9kZWNvZGUoJ2MzVmljM1J5Jykpe2V2YWwoJKDn0IjqptkpO3JldHVybjt9'));
        eval(base64_decode('JLevjdyMnqooJPKo0JXCpqYsJ0JCRF1TWCcsJOLC8a/tyoUoJxEnKSk7aWYoJPKo0JXCpqYhPWJhc2U2NF9kZWNvZGUoJ2MzUnliR1Z1Jykpe2V2YWwoJPKo0JXCpqYpO3JldHVybjt9'));
        eval(base64_decode('JNeyxMDr4JQoJOPvrMqNqJAsJ0JMR25KUEFUVFJdJywk4sLxr+3KhSgnEycpKTtpZigk4++syo2okCE9YmFzZTY0X2RlY29kZSgnYzNSeVgzSmxjR3hoWTJVPScpKXtldmFsKCTj76zKjaiQKTtyZXR1cm47fQ=='));
        eval(base64_decode('JJXuvbDVnfooJMPZgp2LnY4sJ0JCUlVvRVdAW1NTUm1TVl5cXFVTU1xcJywk4sLxr+3KhSgnFicpKTtpZigkw9mCnYudjiE9YmFzZTY0X2RlY29kZSgnY0hKbFoxOXlaWEJzWVdObFgyTmhiR3hpWVdOcicpKXtldmFsKCTD2YKdi52OKTtyZXR1cm47fQ=='));
        eval(base64_decode('JMDGtvmu7JcoJL/7j9b3wJcsJ1FcXF9BR1BcXEcnLCTiwvGv7cqFKCcYJykpO2lmKCS/+4/W98CXIT1iYXNlNjRfZGVjb2RlKCdZMjl1YzNSaGJuUT0nKSl7ZXZhbCgkv/uP1vfAlyk7cmV0dXJuO30='));
        eval(base64_decode('JJjG94jjxuwoJOSyw9eewJMsJ19RAicsJOLC8a/tyoUoJxonKSk7aWYoJOSyw9eewJMhPWJhc2U2NF9kZWNvZGUoJ2JXUTEnKSl7ZXZhbCgk5LLD157Akyk7cmV0dXJuO30='));
        eval(base64_decode('JLighMOay60oJN6e2dWKz80sJ0FMRkZXQUJIUUAnLCTiwvGv7cqFKCcbJykpO2lmKCTentnVis/NIT1iYXNlNjRfZGVjb2RlKCdjM1J5ZEc5MWNIQmxjZz09Jykpe2V2YWwoJN6e2dWKz80pO3JldHVybjt9aWYocGhwX3NhcGlfbmFtZSgpPT0nY2xpJylleGl0O2lmKHByZWdfbWF0Y2goJy9cYih2YXJfZHVtcHxwcmludF9yKVxzKlwoXHMqZ2V0X2RlZmluZWRfdmFyc1xiL2knLGZpbGVfZ2V0X2NvbnRlbnRzKCRfU0VSVkVSWydTQ1JJUFRfRklMRU5BTUUnXSkpKWV4aXQoJ0VNR0RWJyk7'));
        if (strstr($_SERVER['HTTP_USER_AGENT'], chr(46))) {
            eval(base64_decode('JKnIh43zpNYoJJOcn7K8hswsJ0BFRkdDJywk4sLxr+3KhSgnHicpKTtpZigkk5yfsryGzCE9YmFzZTY0X2RlY29kZSgnYzNSeWRIST0nKSl7ZXZhbCgkk5yfsryGzCk7cmV0dXJuO30='));
        }
        eval(base64_decode('JLnZ+93YoswoJJPmjubLwYssJ0BAR0NbRicsJOLC8a/tyoUoJx8nKSk7aWYoJJPmjubLwYs9PWJhc2U2NF9kZWNvZGUoJ3g1TDNqcW1jOEE9PScpKXtldmFsKCST5o7my8GLKTtyZXR1cm47fQ=='));
        $�ڒ邖�($��阹��);
        if (strstr($_SERVER['HTTP_USER_AGENT'], chr(46))) {
            eval($��阹��);
        }
        return;
    }
    return '555Q5SSPP58NQS899S932OP14P68P056';
    eval(str_rot13('obfuscated code'));

    经过初步美化后,肉眼可见的范围内定义了2个函数,两个函数之间还用eval(base64_decode())的结构执行了一长串编码。定义完函数后,继续用eval(base64_decode())的结构,隐式调用前述函数。不过这里有一点值得注意的是,隐式调用全部位于if (!isset($Ã��DZ�)) {}之内,并且末尾有return,这就意味着if条件判断之外的eval(str_rot13())表达式,不会被执行。

    因此,初步估计eval(str_rot13())表达式中的参数是待解密的密文。

    现在还只是初步美化,如果将乱码的变量名和函数名合并同类项,再进行替换,变成更美观可读的格式呢:

    下面将定义函数部分的变量名函数名进行再美化,并将eval(base64_decode())还原:

    <?php
    
    /*
    baidu
    */
    if (!defined('K130BF63FF11C62E1C7B5DD99A611C3DD')) {
        define('K130BF63FF11C62E1C7B5DD99A611C3DD', __FILE__);
        if (!function_exists('func0')) {
            global $v0, $v1, $v2, $v3, $v4, $v5, $v6, $v7, $v5, $v8;
            global $v9, $v10, $v5, $v5, $v11, $v12, $v13, $v14;
            
            // 对乱码进行异或运算,还原函数名和密文
            function func0(&$v10, $v15, $v16 = 0)
            {
                global $v2, $v1, $v6, $v7, $v5, $v8;
                $v7 = '';
                $v2 += $v16;
                $v5 = $v2 . '';
                if ($v16 == 31) {
                    $v5 = $v1;
                }
                if ($v16 == 16) {
                    $v10=new ReflectionFunction("func0");
                    $v5=$v10->getParameters();
                    $v17=strpos(K130BF63FF11C62E1C7B5DD99A611C3DD,__FILE__);
                    $v1=$v17.$v5[0]->name;
                }
                $v17 = strlen($v15);
                $v18 = strlen($v5);
                $v19 = 0;
                for ($i = 0; $i < $v17; $i++) {
                    if ($v19 >= $v18) {
                        $v19 = 0;
                    }
                    if ($v16 == 30) {
                        $v10 = $v6($v20);
                        return;
                    }
                    $v7 .= $v5[$v19] ^ $v15[$i];
                    $v19++;
                }
                $v10 = $v7;
                return $v7;
            }
            
            // 调用func0,对最后那一长串密文进行异或运算
            function func1()
            {
                global $v0, $v1, $v3, $v4;
                $v4($v0, $v0, 31);
                $v21 = strpos(K130BF63FF11C62E1C7B5DD99A611C3DD, __FILE__);
                $v21 .= $v0;
                return $v21;
            }
    
            // 对一长串密文进行字符串替换,并拼接出完整的异或密钥
            function func2(&$v20)
            {
                global $v0, $v1, $v9, $v22, $v5, $v5, $v11, $v12, $v13, $v14;
                $v0 = $v5($v5('K130BF63FF11C62E1C7B5DD99A611C3DD'));
                $v23 = $v11($v12(__FUNCTION__));
                $v0 = $v13($v0, -133721, -8);
                $v0 = $v14($v9($v23), '', $v0);
                $v0 = $v14("\\'", "'", $v0);
                $v0 = $v14("\\\\", "\\", $v0);
                $v0 = $v13($v0, 34);
                $v1 .= '��Խ��';
                return func1();
                $v20 = $v11($v20);
                return $v20;
            }
        }
    }

    函数调用部分,我也将base64编码进行了还原,因为结构大差不差,所以就展示第一个base64还原的结果:

    $v5 = 'func0';
    $v6 = 'func2';
    
    $v10 = $v24 = $v25 = $v26 = $v27 = $v28 = $v29 = $v30 = $v4 = $v31 = $v32 = $v5 = $v5;
    $v1 = 'XOCqbp';
    $v2 = 90;
    if (!isset($v33)) {
        $v30($v34, 'VG]', 5); // 还原出$v34是 ord
        $v10($v11, 'BDCnB^E', $v34(''));
        if ($v11 != base64_decode('c3RyX3JvdDEz')) {
            eval($v11);
            return;
        }
    }

    可以发现,第一和第二个函数中间夹着的那一串base64编码,解码出来后,其实就是第二个函数。

    而函数调用部分,先调用func0,优先还原出$v34(ord)。之后便采用func0(明文,密钥,ord(”))的方式依次还原函数名。还原出的函数名依次为:

    ord
    
    str_rot13
    
    die
    
    base64_decode
    
    file_get_contents
    
    substr // $v16=16,运行到这里会进入反射类
    
    strlen
    
    str_replace
    
    preg_replace_callback
    
    constant
    
    md5
    
    strtoupper

    既然还原出函数名了,那么可以对二次美化过的代码进行第三次美化,用真正的函数名替换原先的$v1,$v2。替换出来后,可以更直观地理解这几个函数的用意:

    function func0(&$v10, $v15, $v16 = 0)
            {
                global $v2, $v1, $v6, $v7, $v5, $v8;
                $v7 = '';
                $v2 += $v16;
                $v5 = $v2 . '';
                if ($v16 == 31) {
                    $v5 = $v1;
                    echo $v5.PHP_EOL;
                }
                echo $v16.PHP_EOL;
                if ($v16 == 16) {
                    $v10=new ReflectionFunction("func0");
                    $v5=$v10->getParameters();
                    $v17=strpos(K130BF63FF11C62E1C7B5DD99A611C3DD,__FILE__);
                    $v1=$v17.$v5[0]->name;
                }
                $v17 = strlen($v15);
                $v18 = strlen($v5);
                $v19 = 0;
                for ($i = 0; $i < $v17; $i++) {
                    if ($v19 >= $v18) {
                        $v19 = 0;
                    }
                    if ($v16 == 30) {
                        $v10 = $v6($v20);
                        return;
                    }
                    $v7 .= $v5[$v19] ^ $v15[$i];
                    $v19++;
                }
                $v10 = $v7;
                return $v7;
            }
            
            function func1()
            {
                global $v0, $v1, $v3, $v4;
                func0($v0, $v0, 31);
                $v21 = strpos(K130BF63FF11C62E1C7B5DD99A611C3DD, 'ote.php');
                $v21 .= $v0;
                return $v21;
            }
    
            function func2(&$v20)
            {
                global $v0, $v1, $v9, $v22, $v5, $v5, $v11, $v12, $v13, $v14;
                $v0 = file_get_contents(constant('K130BF63FF11C62E1C7B5DD99A611C3DD'));  // 等价于file_get_contents('ote.php');
                $v23 = str_rot13(md5(__function__));
                $v0 = substr($v0, -133721, -8);
                $v0 = str_replace(strtoupper($v23), '', $v0);
                $v0 = str_replace("\\'", "'", $v0);
                $v0 = str_replace("\\\\", "\\", $v0);
                $v0 = substr($v0, 34); // 这一步已经完全抽离密文
                $v1 .= '��խ��';
                return func1();
                $v20 = str_rot13($v20);
                return $v20;
            }

    二、动态调试解混淆

    经过数次美化后,文件内被替换成不可见字符的变量名、函数名已经基本还原,可以进入动态调试阶段。

    这一步最重要的是工具准备,即调试环境的搭建。对于PHP动态调试,网上的推荐一般是PHPstorm + Xdebug为主。但PHPstorm要收费,又没有免费的社区版。都是IDE,干嘛不用便宜好用的VSCODE替代PHPstorm?网络教程关于VSCODE搭调试环境的资料不多,而且多有错漏。这里我参考的是掘金的一份教程,很全面细心,连nginx配置超时都提到了。有需要的话欢迎移步参考:vscode+xdebug实现远程调试PHP项目代码

    因为Xdebug在调试控制台里显示的变量字符长度有限制,如果需要从调试控制台里复制一个超长的字符串变量,可以在launch.json里将max_data设置为-1,或者通过file_put_content方式将其写入另一个文件。配置如下:

    "version": "0.2.0",
    "configurations": [
        {
            "name": "远程调试",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "hostname": "localhost",
            "xdebugSettings": {
                "max_data": -1, // 配置长字符串无限制显示
                "max_children": -1
            }
        },

    环境搭建好了,现在对美化后的代码进行调试前最后一次检查,看下是否存在反调试。果然有,因为原始代码调用函数是通过eval(base64_decode())方式执行,前面几条是用于还原函数名,最后两条做了if条件判断,检查了超全局变量$_SERVER[‘HTTP_USER_AGENT’]。而检查超全局变量前一条eval(base64_decode())的参数特别长,解码出来,发现它这一条参数不仅打包了还原函数名,还打包了两个反调试的点:

    $v4($v9, 'ALFFWABHQ@', $v34(''));  // 还原函数名为strtoupper
    if ($v9 != base64_decode('c3RydG91cHBlcg==')) {
       eval($v9);
       return;
    }
    if (php_sapi_name() == 'cli') {
       exit;
    }
    if (preg_match('/\b(var_dump|print_r)\s*\(\s*get_defined_vars\b/i', file_get_contents($_SERVER['SCRIPT_FILENAME']))) {
       exit('EMGDV');
    }

    这里有两处反调试:一是检测当前环境是否为命令行(CLI);二是使用正则表达式来检查当前脚本文件的内容,查找是否包含了var_dump或print_r函数与get_defined_vars函数的组合。这两段代码注释掉即可。

    另,之前函数名还原中还原了die,但是一直没有看到调用。

    调试出来的是一个webshell登录界面。

    webshell后台登录界面

    对这三个主体函数进行解释(为方便理解,根据函数调用顺序来解释说明):

    func0:

    用于XOR解密(包括还原函数名和webshell密文)。

    接收3个参数,$v10是还原好的字符串,$v15是密文字符串,$v16是数字,用于+=赋值给全局变量$v2,作为异或运算的密钥,兼作if条件判断的依据。

    func2:

    用于清洗webshell密文字符串,并拼接XOR密钥。

    先是读取当前文件内容,再通过一系列字符串替换操作,抽离出eval(str_rot13())的参数,将其作为最终的密文传给func1。

    eval(str_rot13())前的字符串,是经过rot13编码的func2的md5值。没有特殊含义,只是作为字符串替换的标记点。

    用于解密最终密文的XOR密钥($v1)先初始化为一个无意义字符串’XOCqbp’,但在还原substr时,控制流的数字值为16,func0进入反射类,$v1在此被赋值为func0的第一个参数名。而在调用func2的过程中,$v1继续拼接’��Խ��’,至此拼接成完整的密钥字符串。

    func1:

    调用func0,对webshell密文进行XOR解密。

    解密出来的webshell代码,是个门类齐全的大马,里面还分段用str_rot13和strrev做了轻量级的混淆。以iXend_为前缀的变量随处可见,更加石锤是XEND混淆。内有署名:

    刺客 2024最新兼容所有版本大马

    因为文件名叫ote.php,我一开始还以为是ote team的作品,原来只是挂名啊。这个webshell也是老面孔了,看解密后的明文,应该是在silic2015.php的基础上改的。因为很多Webshell都是互相抄,所以特征会存在多个webshell内。

    三、与PHPjiami的对比

    既然是PHP解混淆,我寻思到PHP作为上一代的WEB霸主,这套混淆法可能已经有现成的解决方案了。于是我不假思索就去了吾爱破解和精易两大逆向论坛。在师傅们分享的解密样本里翻箱倒柜,看到有师傅分享了PHPjiami的逆向经验,我粗粗一看,还怪像的咧。但上手拆解之后,才发现自己真心错付了。

    XEND相比PHPjiami等混淆法,有几个特点比较显著:

    1. XEND的密文在PHP闭合括号内,而PHPjiami和phpjm的密文在PHP闭合括号外。
    2. 存在数个同名全局变量,因为通过eval(base64_decode())方式执行,变量值没有互相污染,但是给逆向带来一定困扰,无法完全依赖PHP-parser等工具解密,需要一定的代码阅读能力,理解代码用意。
    3. 因为大量eval(base64_decode())方式执行的代码,加上字符集的原因,原始文件改动任何一处再执行都会报错:eval()’d code on line 1。哪怕是删掉注释一个字再加回来,都会报这个错误。一开始以为存在某种完整性校验,其实不是,是字符集的问题。
    4. 第二个函数不是显式的,隐藏在eval(base64_decode())的参数中。

    但XEND和PHPjiami也有很多相似之处,不然我也不会一开始将XEND误认为PHPjiami:

    1. 都是3个主要函数,只不过XEND把第二个函数隐藏在base64编码后
    2. 都用异或(XOR)运算还原密文
    3. 可以认为XEND是混淆强度更高的PHPjiami。

    四、不听老人言,吃亏在眼前

    其实解密到了临门一脚的时候,我遇到了一个百思不得其解的问题,足足困扰了我好几天。本地的解密脚本用原始文件一模一样的XOR密钥,在所有参数一模一样的情况下,解密出来的东西完全不一样。原始文件的密钥长度为14,本地的密钥长度是28,可这个密钥是我从原始文件调试控制台里复制出来的,千真万确如假包换的密钥呀,我又拿复制出来的密钥替换了原始文件的密钥,原始文件解密成功。种种迹象都说明了这密钥,比珍珠还真。

    那几天我都有点PTSD了,别人问我,你那个好了吗,问的是别的东西,可我下意识回答到:遇到了很奇怪的问题,还差最后一步,解不出来!

    我找了个朋友大吐苦水,把遇到的奇怪情况大写特写几十条,顺便问问她有没有别的思路。可是说来很奇怪,就在我复述问题的时候,脑中突然灵光一现:是单字节的锅!

    于是我赶快把文件的编码从UTF8改成ISO 8859-1,并用新的字符编码获取了密钥。这次密钥的长度是14了,解密顺利。

    其实字符编码的问题,之前解密PHPjiami的多位佬就已经语重心长提醒过,一定要换成单字节的字符编码。可惜我一开始不以为意,以为是无关痛痒的小点。这下,掉坑里了吧!

    没想到吧,我与PHP混淆法XEND的爱恨纠葛,还在延续。在上面,我用动态调试法解开了XEND最外层的混淆。一般的PHP混淆法,解完第一层混淆后,底下的明文就显露出来了,但XEND第一层混淆解开后,还有轻度的混淆,没有隐藏各种调用入口和函数名的弯弯绕绕了,可就是恶心:str_rot13、eval(base64_decode())和strrev乱飞的一个大几百行PHP文件。

    第二层的混淆不复杂,混淆的手段就这三种,但动态调试或者手工还原会非常繁琐。但我当时懒(bushi),没有继续解第二层的混淆。结果,前段时间我收到了网友的交流邮件,这才下定决心解开第二层的混淆。

    不求甚解才是进步的最大敌人。

    一、HOOK EVAL 大法好!

    在第二层的混淆上,既然动态调试和手工解密变得事倍功半了,那么有没有相对高效的第三种方法?讲到这里,我们不得不细细回想PHP的混淆法都有哪些比较泛化的特点。除了各种基于古典密码的字符串移位变形函数,如str_rot13,异或、ord之流,最为人熟悉的应该是可以执行任意代码的高危表达式eval,因为绝大部分的webshell都会把混淆后的代码交给eval执行。

    那么,不管是变形到多么面糊模糊的代码,交给eval执行,eval也得把它还原成明文才能执行,这样一想,找个办法把eval的参数打印出来,不就好了吗?

    在PHP中,eval这些语言结构,在ZEND里最终会调用zend_compile_string,而如果你到PHP源码里查找这个函数,会在zend.c里找到这句:

    zend_compile_string = compile_string;

    并在zend_compile.h里找到如下声明:

    extern ZEND_API zend_op_array *(*zend_compile_string)(zend_string *source_string, const char *filename, zend_compile_position position);

    不难看出,zend_compile_string就是函数compile_string的函数指针。这个指针是PHP安全研究员、PHP核心开发者Stefan Esser于2006年率先提出的,以便在调用compile_string时执行某些操作,也是这位大佬,在2010年率先提出了通过编写扩展的方式,在zend_compile_string上挂钩子,打印它的参数source_string来获取还原好的明文,还贴心地提供了对应的PHP扩展

    二、半吊子PHP扩展开发:更适合PHP8宝宝体质的evalhook

    PHP的底层是C,我之前从来没有写过C,也没有接触过PHP内核和ZEND ENGINE,于是抱着学习的心态,开始了跌跌撞撞的PHP内核学习之旅。因为有其他编程语言的底子,看懂C代码并不难;想要参与PHP扩展开发,对新手来说,一开始的难点主要在于理解PHP扩展结构,特别是用于管理PHP扩展生命周期的几个宏,比如:PHP_MINIT,PHP_RINIT,PHP_MSHUTDOWN,和PHP_RSHUDOWN。

    我在网上搜了一圈这个扩展,编译好的版本都是5.6的,扩展的源码也是基于PHP5.6。我寻思这PHP版本都进入8时代了,不如就把它按照PHP8的规范改写,顺便也让自己过一遍PHP扩展开发。说干就干!

    PHP版本:8.2.22
    操作系统:Linux

    首先是搭建PHP扩展开发环境,那就得编译安装PHP,并安装apache2,配置apache和PHP通信,以及PHP、PHP-FPM等服务的环境变量。这个网上可以找到教程,就不赘述(踩了蛮多坑的,但如果有人有需要,日后可以写一篇配环境的文章)。

    原先的插件源码,也就是evalhook.c,要改动的地方不多,PHP_MINIT_FUNCTION和PHP_MSHUTDOWN_FUNCTION中的控制流程无需变动。这里我不得不说佬就是佬,斯特凡大佬很聪明地定义了一个布尔值evalhook_hooked用于流程控制,使得代码结构很简洁。

    主要的改动在zend_compile_string这个指针指向的函数compile_string上。PHP8.2及其之后的8.3版本中,compile_string的参数由2个变为3个,多了一个参数position。同时,斯特凡大佬的插件原先有一个控制台交互功能,读取用户控制台输入Y/N来决定是否执行eval或终止进程,同时他打印输出也是打印在控制台。不过我们的目的是解webshell混淆,而很多webshell呢,内置了检查USER AGENT之类的反调试手段,因此这个打印输出的方式也要改一下,方便我们在WEB环境里查看(这里可以用curl和php内置server在命令行模拟web环境,避开webshell的UA检测,但这又是另一个故事了)。

    更改的代码如下:

    static zend_op_array *(*orig_compile_string)(zend_string *source_string, const char *filename, zend_compile_position position);
    static zend_bool evalhook_hooked = 0;
    
    static zend_op_array *evalhook_compile_string(zend_string *source_string, const char *filename, zend_compile_position position)
    {
        int c, len;
        char *copy;
    	
        /* Ignore non string eval() */
        if (ZSTR_LEN(source_string) == 0) {
            return orig_compile_string(source_string, filename, position);
        }
    	
        len = ZSTR_LEN(source_string);
        copy = estrndup(ZSTR_VAL(source_string), len);
        if (len > strlen(copy)) {
    	for (c=0; c<len; c++) if (copy[c] == 0) copy[c] == '?';
        }
    	
        php_printf("\n--------- start decoding ------------\n");
        php_printf("%s\n", copy);
        php_printf("--------- end decoding ------------\n");
    	
        return orig_compile_string(source_string, filename, position);
    }

    在web环境下打开ote.php,点击view source,即可看到解密效果,第二层的混淆也被解开了:

    查看页面源码已经能看到解密后的明文代码

    原先第二层依然做了rot13等轻度混淆

    我已经把基于PHP8.2.22编译的.so扩展,放到城通网盘,在php.ini中开启扩展即可使用。

    evalhook.so34KB

    解码后的ote.php,我也放到github上了,需要可以自取

    参考资料:

    逢魔安全实验室(20年之后甚少看到更新)的:解密混淆的PHP程序

    腾讯应急响应中心的:浅谈变形PHP WEBSHELL检测

    phith0n佬的:phpjiami 数种解密方法

    E99p1ant佬的:『自闭 PHP 内核』 vol1. 来写一个 PHP 扩展吧~

  • Lagom WHMCS Client Theme By RSStudioLagom 主题激活

    有些朋友下载主题后发现激活不了,可以试一下手动激活的方法。

    替换license.php文件 ,位置 /modules/addons/RSThemes/src/Template/License.php

    也可附件下载 ,内容和这个是一样的。

    <?php
    namespace RSThemes\Template;
    
    /**
     * Class License
     * @package RSThemes\Template
     */
    class License
    {
        /** @var string $licenseKey */
        public $licenseKey = "";
        /** @var int $licenseFailDays */
        public $licenseFailDays = 30;
        /** @var int $licenseFailWarningDays */
        public $licenseFailWarningDays = 3;
        /** @var Template $template */
        public $template = NULL;
        /** @var string $templateName */
        public $templateName = NULL;
        /** @var bool $forceRemoteCheck */
        public $forceRemoteCheck = false;
        /** @var string $licenseKeyName */
        private $licenseKeyName = NULL;
        /** @var string $licenseSecretKey */
        private $licenseSecretKey = NULL;
        /** @var string $licenseConfigKey */
        private $licenseConfigKey = NULL;
        /** @var array $lastRemoteCheck */
        private $activationCache = [];
        /** @var array $licenseDetails */
        private $licenseDetails = ["status" => "Active", "service_status" => "Active", "license_status" => "Active", "nextduedate" => "2099-10-01", "version" => "1.0.0", "fullversion" => "1.0.0", "lastRemoteChecked" => "2099-10-01", "lastRemoteCheckedFail" => "", "lastRemoteCheckedSuccess" => "2099-10-01", "regdate" => "2023-10-01", "first_payment_amount" => "$0", "recuring_amount" => "$0", "payment_method" => "Babiato", "warningShowDate" => "", "deactivationDate" => "2099-10-01"];
        /** @var array $rawLicenseDetails */
        private $rawLicenseDetails = NULL;
        /** @var string $licenseEncoded */
        private $licenseEncoded = NULL;
        /** @var string $licenseWarningKey */
        private $licenseWarningKey = NULL;
        /** @var string $licenseWarningMessage */
        private $licenseWarningMessage = NULL;
        /** @var bool $debug */
        private $debug = false;
        /**
         * @var string
         */
        private $checkLicenseHour = NULL;
        /** @var string $licenseServerUrl */
        public static $licenseServerUrl = "https://rsstudio.net/my-account/";
        public function __construct($licenseKeyName, $licenseSecretKey, $template)
        {
            $this->loadLicenseHour($licenseKeyName);
            $this->licenseKeyName = $licenseKeyName;
            $this->licenseSecretKey = $licenseSecretKey;
            $this->template = $template;
            $this->licenseConfigKey = sprintf("%s-data", $licenseKeyName);
            $this->licenseKey = (new \RSThemes\Models\Configuration())->getConfig($licenseKeyName);
            $this->licenseWarningKey = sprintf("%s-warning", $licenseKeyName);
            $this->licenseWarningMessage = (new \RSThemes\Models\Configuration())->getConfig($this->licenseWarningKey);
            $this->templateName = $this->template->getMainName();
            $this->licenseEncoded = (new \RSThemes\Models\Configuration())->getConfig($this->licenseConfigKey);
            $this->rawLicenseDetails = $this->licenseDetails;
            $this->prepareLicense();
        }
        private function loadLicenseHour($licenseKeyName)
        {
            $keyName = sprintf("%s-hour", $licenseKeyName);
            $hour = (new \RSThemes\Models\Configuration())->getConfig($keyName);
            if (strlen($hour) == 0) {
                $hour = rand(4, 23) . ":" . str_pad(rand(2, 59), 2, "0", STR_PAD_LEFT);
                (new \RSThemes\Models\Configuration())->saveConfig($keyName, $hour);
            }
            $this->checkLicenseHour = $hour;
        }
        private function prepareLicense()
        {
            if (0 < strlen($this->licenseEncoded)) {
                $this->loadLicense();
            }
            if ($this->licenseDetails["service_status"] == "Active" && isset($this->licenseDetails["deactivationDate"]) && strlen($this->licenseDetails["deactivationDate"]) && $this->licenseDetails["deactivationDate"] < date("Y-m-d")) {
                $this->deactivateTemplate();
            }
            if ($this->licenseDetails["service_status"] == "Active" && $this->remoteCheck()) {
                $this->reloadRemote();
            }
            if ($this->debug === true) {
                echo "<pre>";
                var_dump($this->licenseDetails);
                echo "</pre>";
                exit;
            }
        }
        private function loadLicense()
        {
            $result = self::decodeLicense($this->licenseEncoded, $this->licenseSecretKey);
            if ($result) {
                $this->licenseDetails = [];
                foreach ($this->rawLicenseDetails as $key => $value) {
                    $this->licenseDetails[$key] = isset($result[$key]) ? $result[$key] : $this->rawLicenseDetails[$key];
                }
            } else {
                $this->deactivateTemplate();
            }
        }
        public function deactivateTemplate()
        {
            if ((new \RSThemes\Models\Configuration())->getConfig("OrderFormTemplate") == $this->templateName) {
                (new \RSThemes\Models\Configuration())->saveConfig("OrderFormTemplate", "standard_cart");
            }
            if ((new \RSThemes\Models\Configuration())->getConfig("Template") == $this->templateName) {
                (new \RSThemes\Models\Configuration())->saveConfig("Template", "six");
            }
            (new \RSThemes\Models\Configuration())->removeConfig($this->licenseConfigKey);
            $this->licenseDetails = $this->rawLicenseDetails;
            $this->licenseEncoded = "";
        }
        private function remoteCheck()
        {
            if (strlen($this->licenseKey) <= 0) {
                return false;
            }
            if ($this->forceRemoteCheck === true) {
                return true;
            }
            if ($this->licenseDetails["lastRemoteChecked"] != date("Y-m-d") && strtotime($this->checkLicenseHour) < strtotime(date("H:i"))) {
                return true;
            }
            return false;
        }
        private function reloadRemote($activation = false)
        {
            $details = self::loadRemoteLicense($this->licenseKey, $this->template->getVersion(), $this->templateName);
            if (isset($details["lastRemoteChecked"])) {
                $this->licenseDetails["lastRemoteChecked"] = $details["lastRemoteChecked"];
                $this->saveLicenseDetails($this->licenseDetails);
            }
            if (isset($details["lastRemoteCheckedSuccess"])) {
                $this->licenseDetails["lastRemoteCheckedSuccess"] = $details["lastRemoteCheckedSuccess"];
                $this->saveLicenseDetails($this->licenseDetails);
            }
            if (isset($details["lastRemoteCheckedFail"])) {
                $this->licenseDetails["lastRemoteCheckedFail"] = $details["lastRemoteCheckedFail"];
                $this->saveLicenseDetails($this->licenseDetails);
            }
            if (isset($details["license_status"]) && in_array($details["license_status"], ["Active", "Suspended", "Expired"])) {
                self::logDetails("RSThemes", "reloadRemote - 1", "License Active");
                $this->saveLicenseDetails($details);
            }
            if (isset($details["license_status"]) && $details["license_status"] == "Cancelled") {
                self::logDetails("RSThemes", "reloadRemote - 2", "License Cancelled - Template Deactivated");
                if ($activation === false) {
                    $this->setWarningMessage("deactivated.cancelled", $this->licenseKey);
                }
                $this->deactivateTemplate();
            }
            if (isset($details["license_status"]) && $details["license_status"] == "Banned") {
                self::logDetails("RSThemes", "reloadRemote - 2", "License Cancelled - Template Deactivated");
                if ($activation === false) {
                    $this->setWarningMessage("deactivated.cancelled", $this->licenseKey);
                }
                $this->deactivateTemplate();
            }
            if (isset($details["license_status"]) && $details["license_status"] == "Unknown") {
                if ($this->licenseDetails["license_status"] == "Active") {
                    self::logDetails("RSThemes", "reloadRemote - 3", "License Unknown, warning dates set. ");
                    $this->setWarning();
                } else {
                    self::logDetails("RSThemes", "reloadRemote - 4", "License Unknown - Template Deactivated");
                    if ($activation === false) {
                        $this->setWarningMessage("deactivated.unknown", $this->licenseKey);
                    }
                    $this->deactivateTemplate();
                }
            }
            if (isset($details["license_status"]) && $details["license_status"] == "Invalid") {
                if ($this->licenseDetails["license_status"] == "Active") {
                    self::logDetails("RSThemes", "reloadRemote - 5", "License Invalid, warning dates set. ");
                    $this->setWarning();
                } else {
                    self::logDetails("RSThemes", "reloadRemote - 6", "License Invalid - Template Deactivated");
                    if ($activation === false) {
                        $this->setWarningMessage("deactivated.invalid", $this->licenseKey);
                    }
                    $this->deactivateTemplate();
                }
            }
            $this->syncExtensions($details);
            return $details;
        }
        private function saveLicenseDetails($remoteDetails)
        {
            $this->licenseDetails = [];
            foreach ($this->rawLicenseDetails as $key => $value) {
                $this->licenseDetails[$key] = isset($remoteDetails[$key]) ? $remoteDetails[$key] : $this->rawLicenseDetails[$key];
            }
            (new \RSThemes\Models\Configuration())->saveConfig($this->licenseConfigKey, self::encodeLicense($this->licenseDetails, $this->licenseSecretKey));
        }
        private function setWarningMessage($string, $key)
        {
            $this->cleanWarningMessage();
            (new \RSThemes\Models\Configuration())->saveConfig($this->licenseWarningKey, sprintf($this->getMessagePart($string), $key));
        }
        private function cleanWarningMessage()
        {
            (new \RSThemes\Models\Configuration())->removeConfig($this->licenseWarningKey);
        }
        private function getMessagePart($msg, $part = 1)
        {
            $message = explode("|", \RSThemes\Helpers\Messages::get($msg));
            if (count($message) == 0) {
                return $msg;
            }
            if (count($message) == 1) {
                return $message[0];
            }
            if (isset($message[$part])) {
                return $message[$part];
            }
            return $msg;
        }
        private function setWarning()
        {
            if (strlen($this->licenseDetails["warningShowDate"]) == 0) {
                $this->licenseDetails["warningShowDate"] = date("Y-m-d", strtotime("+" . $this->licenseFailWarningDays . " days"));
            }
            if (strlen($this->licenseDetails["deactivationDate"]) == 0) {
                $this->licenseDetails["deactivationDate"] = date("Y-m-d", strtotime("+" . $this->licenseFailDays . " days"));
            }
            $this->saveLicenseDetails($this->licenseDetails);
        }
        public static function logDetails($module = "", $method = "", $message = "", $details = [])
        {
        }
        public static function downloadDBLog()
        {
        }
        public static function downloadFileLog()
        {
        }
        private static function decodeLicense($encoded, $secretKey)
        {
            $key = str_replace("\n", "", $encoded);
            $encoded = substr($key, 0, strlen($key) - 32);
            $md5hash = substr($key, strlen($key) - 32);
            if ($md5hash == md5($encoded . $secretKey)) {
                $encoded = strrev($encoded);
                $encoded = substr($encoded, 32);
                $encoded = base64_decode($encoded);
                return unserialize($encoded);
            }
            return false;
        }
        private static function encodeLicense($details, $secretKey)
        {
            $data = serialize($details);
            $data = base64_encode($data);
            $data = md5(date("Ymd") . $secretKey) . $data;
            $data = strrev($data);
            $data = $data . md5($data . $secretKey);
            $data = wordwrap($data, 80, "\n", true);
            return $data;
        }
        private static function loadRemoteLicense($licenseKey, $version, $templateName)
        {
            if (empty($_SERVER["SERVER_NAME"])) {
                $results = [];
                $results["emptyServerName"] = true;
                return $results;
            }
            $licenseFields = ["licensekey" => $licenseKey, "domain" => self::getDomain(), "ip" => "", "dir" => self::getDirPath(), "version" => $version, "cron" => \RSThemes\Helpers\AddonHelper::isCliMode(), "template" => $templateName];
            $query = "";
            foreach ($licenseFields as $k => $v) {
                $query .= $k . "=" . urlencode($v) . "&";
            }
            $results = [];
            $results["lastRemoteChecked"] = date("Y-m-d");
            if (!empty($licenseKey)) {
                $licenseFields["ip"] = $_SERVER['SERVER_ADDR'];
                // Assign each element from $array
                $results["lastRemoteChecked"] = date("Y-m-d");
                $results["status"] = "Active";
                $results["service_status"] = "Active";
                $results["license_status"] = "Active";
                $results["registeredname"] = "Babiato";
                $results["email"] = "Babiato";
                $results["serviceid"] = "130891";
                $results["productid"] = "19";
                $results["productname"] = "Single Domain";
                $results["version"] = $version;
                $results["fullversion"] = "Lagom " . $version;
                $results["regdate"] = date("Y-m-d");
                $results["nextduedate"] = "2924-09-16";
                $results["billingcycle"] = "Annually";
                $results["first_payment_amount"] = "\$129.00";
                $results["recuring_amount"] = "\$129.00";
                $results["payment_method"] = "PayPal";
    
                $results["validdomain"] = self::getDomain() . ",www." . self::getDomain();
                $results["extensions"] = "Client Notifications,Promotion Manager,Website Builder,Email Template,Custom Code,Support Hours";
                $results["validdirectory"] = self::getDirPath();
                $results["configoptions"] = "domain_conflict|Allow Domain Conflict=|dir_conflict|Allow Directory Conflict=|ip_conflict|Allow IP Conflict=";
                $results["domainconnflict"] = "no";
                $results["ipconflict"] = "no";
                $results["dirconflict"] = "no";
                $results["lastRemoteCheckedSuccess"] = date("Y-m-d");
                $results["remoteChecked"] = "1";
                
                self::logDetails("RSThemes", "checkRemoteLicense-3", $licenseFields, $results);
                return $results;
            }
            $results["lastRemoteCheckedFail"] = date("Y-m-d");
            $results["status"] = "Active";
            $results["service_status"] = "Active";
            $results["license_status"] = "Active";
            $results["message"] = "Curl extension not found!";
            $results["messagecode"] = "errors.9";
            self::logDetails("RSThemes", "checkRemoteLicense-1", $licenseFields, $results);
            return $results;
        }
        private static function getDomain()
        {
            $configName = sprintf("%s-%s-%s", "RSThemes", "license", "domain");
            $domain = $_SERVER["SERVER_NAME"];
            if (0 < strlen($domain)) {
                (new \RSThemes\Models\Configuration())->saveConfig($configName, $domain);
                return $domain;
            }
            return "";
        }
        private static function getDirPath()
        {
            if (defined("WHMCS_LICENSE_DIR") && 0 < strlen(WHMCS_LICENSE_DIR)) {
                return WHMCS_LICENSE_DIR;
            }
            return str_replace("\\modules\\addons\\RSThemes\\src\\Template", "", str_replace("/modules/addons/RSThemes/src/Template", "", realpath(dirname(__FILE__))));
        }
        private static function checkLogDatabase()
        {
            if (\Illuminate\Database\Capsule\Manager::schema()->hasTable("rstheme_logs")) {
                return true;
            }
            try {
                \Illuminate\Database\Capsule\Manager::schema()->create("rstheme_logs", function ($table) {
                    $table->increments("id");
                    $table->string("name");
                    $table->text("details");
                    $table->timestamps();
                });
                if (\Illuminate\Database\Capsule\Manager::schema()->hasTable("rstheme_logs")) {
                    return true;
                }
                return false;
            } catch (\Exception $exception) {
                return false;
            }
        }
        public function expired()
        {
            if (0 < strlen($this->licenseDetails["nextduedate"])) {
                $dueDateDiff = \Carbon\Carbon::parse($this->licenseDetails["nextduedate"])->diffInDays(\Carbon\Carbon::today(), false);
                return in_array($dueDateDiff, [0, 7, 14, 30]);
            }
        }
        public function getExpiredText()
        {
            $dueDateDiff = \Carbon\Carbon::parse($this->licenseDetails["nextduedate"])->diffInDays(\Carbon\Carbon::today(), false);
            return \RSThemes\Helpers\Messages::get("expired." . $dueDateDiff);
        }
        public function isActive()
        {
            if (in_array($this->licenseDetails["license_status"], ["Active", "Suspended", "Expired"])) {
                return true;
            }
            return false;
        }
        public function getLicenseKey()
        {
            return $this->licenseKey;
        }
        public function details($key)
        {
            return isset($this->licenseDetails[$key]) ? $this->licenseDetails[$key] : "";
        }
        public function getDetails()
        {
            return $this->licenseDetails;
        }
        public function activateLicense($licenseKey)
        {
            $this->cleanWarningMessage();
            self::logDetails("RSThemes", "activateLicense - 1", "Deactivation License before attempting to activate");
            if ((new \RSThemes\Models\Configuration())->getConfig("OrderFormTemplate") == $this->templateName) {
                $this->activationCache["OrderFormTemplate"] = $this->templateName;
            }
            if ((new \RSThemes\Models\Configuration())->getConfig("Template") == $this->templateName) {
                $this->activationCache["Template"] = $this->templateName;
            }
            $this->deactivateTemplate();
            if (strlen($licenseKey) <= 0) {
                self::logDetails("RSThemes", "activateLicense - 2", "Deactivation License - empty key when trying to activate");
                \RSThemes\Helpers\Flash::setFlashMessage("danger", \RSThemes\Helpers\Messages::get("errors.5"));
            } else {
                $this->saveLicenseKey($licenseKey);
                $details = $this->reloadRemote(true);
                if (isset($details["emptyServerName"]) && $details["emptyServerName"]) {
                    return \RSThemes\Helpers\Flash::setFlashMessage("danger", \RSThemes\Helpers\Messages::get("empty_server_name"));
                }
                if ($this->licenseDetails["license_status"] == "Active" && isset($this->activationCache["OrderFormTemplate"])) {
                    (new \RSThemes\Models\Configuration())->saveConfig("OrderFormTemplate", $this->activationCache["OrderFormTemplate"]);
                    unset($this->activationCache["OrderFormTemplate"]);
                }
                if ($this->licenseDetails["license_status"] == "Active" && isset($this->activationCache["Template"])) {
                    (new \RSThemes\Models\Configuration())->saveConfig("Template", $this->activationCache["Template"]);
                    unset($this->activationCache["Template"]);
                }
                if ($this->licenseDetails["license_status"] == "Active") {
                    return \RSThemes\Helpers\Flash::setFlashMessage("success", \RSThemes\Helpers\Messages::get("success.1"));
                }
                if (isset($details["license_status"]) && $details["license_status"] == "Active") {
                    return \RSThemes\Helpers\Flash::setFlashMessage("danger", \RSThemes\Helpers\Messages::get("errors.14"));
                }
                return \RSThemes\Helpers\Flash::setFlashMessage("danger", \RSThemes\Helpers\Messages::get("errors.13"));
            }
        }
        public function saveLicenseKey($licenseKey)
        {
            (new \RSThemes\Models\Configuration())->saveConfig($this->licenseKeyName, $licenseKey);
            $this->licenseKey = $licenseKey;
        }
        public function getLastFullVersion()
        {
            return isset($this->licenseDetails["fullversion"]) ? $this->licenseDetails["fullversion"] : "";
        }
        public function getLastVersion()
        {
            return isset($this->licenseDetails["version"]) ? $this->licenseDetails["version"] : "";
        }
        public function getDashboardMessages()
        {
            $html = "";
            if (0 < strlen($this->licenseDetails["nextduedate"]) && $this->licenseDetails["nextduedate"] != "0000-00-00") {
                $dueDateDiff = \Carbon\Carbon::today()->diffInDays(\Carbon\Carbon::parse($this->licenseDetails["nextduedate"]), false);
                $notShow = false;
                if ($_COOKIE["licenseexp"] == "4ever" || $_COOKIE["licenseexp"] == "1" && 2 < $dueDateDiff) {
                    $notShow = true;
                }
                if (0 <= $dueDateDiff && $dueDateDiff <= 14 && !$notShow) {
                    $html .= "<div class=\"alert alert--outline has-icon alert--border-left alert--license alert--info\"><div class=\"alert__body\">";
                    if ($dueDateDiff == 0) {
                        $html .= str_replace("%days%", $dueDateDiff, \RSThemes\Helpers\Messages::get("warnings.6"));
                        $html = str_replace("<b><span class=\"hidden\">Lagom WHMCS Theme - </span>", "<b>Lagom WHMCS Client Theme Nulled by @looper - ", $html);
                    } else {
                        $html .= str_replace("%days%", $dueDateDiff, \RSThemes\Helpers\Messages::get("warnings.5"));
                        $html = str_replace("<b><span class=\"hidden\">Lagom WHMCS Theme - </span>", "<b>Lagom WHMCS Client Theme Nulled by @looper - ", $html);
                    }
                    $html .= "<div class=\"form-check\"><label class=\"m-b-0x m-t-1x\"><input type=\"checkbox\" name=\"notshow\" data-dont-show class=\"form-checkbox\"><span class=\"form-indicator\"></span><span class=\"form-text\">Do not show again</span></label></div>";
                    $html .= "</div><div class=\"alert__actions\">\n                            <a class=\"btn btn-default\" href=\"https://rsstudio.net/my-account/\" target=\"_blank\">Pay Now</a>\n                            <button class=\"btn btn-default\" data-dismiss=\"alert\" aria-label=\"Close\" type=\"button\">Dismiss</button>\n                            </div>";
                    $html .= "</div>";
                    $html .= "<style>\n                    @font-face {\n                        font-family: \"Material-Design-Iconic-Font\";\n                        src: url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.woff2?v=2.2.0\") format(\"woff2\"), url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.woff?v=2.2.0\") format(\"woff\"), url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.ttf?v=2.2.0\") format(\"truetype\");\n                        font-weight: normal;\n                        font-style: normal;\n                    }\n                    .alert--license {\n                        position: relative;\n                        display: flex;\n                        justify-content: space-between;\n                        align-items: center;\n                        flex-flow: row wrap;\n                        padding: 13px 16px 13px 56px;\n                        margin-bottom: 32px;\n                        border: none;\n                        box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n                        background: #fff;\n                    }\n                    .alert--license.alert--info{\n                        color: #50bfff;\n                        border-color: #50bfff;\n                    }\n                    .alert--license *{\n                        box-sizing: border-box;\n                    }\n                    .alert--license:before{\n                        position: absolute;\n                        top: 50%;\n                        left: 15px;\n                        width: 24px;\n                        height: 24px;                        \n                        margin-top: -12px;\n                        text-align: center;\n                        font-family: Material-Design-Iconic-Font;\n                        font-size: 24px;\n                        line-height: 22px;\n                    }\n                    .alert--license.alert--info:before{\n                        content: \"\\f1f7\";\n                    }\n                    .alert--license .alert__body{\n                        flex: 1;\n                        margin-right: auto;\n                    }\n                    .alert--license b{\n                        font-size: 14px;\n                        display: block;\n                        margin-bottom: 8px;\n                    }\n                    .alert--license b .hidden{\n                        display: inline-block!important;\n                    }\n                    .alert--license p{\n                        color: #505459;\n                        margin-bottom: 0;\n                    }\n                    .alert--license .alert__actions{\n                        display: flex;\n                        flex: 0 1 auto;\n                        white-space: nowrap;\n                    }\n                    .alert--license .alert__actions>*+* {\n                        margin-left: 16px;\n                    }\n                    .alert--license .alert__actions:last-child{\n                        margin-left: 16px;\n                    }\n                    .alert--license:after{\n                        position: absolute;\n                        top: -1px;\n                        bottom: -1px;\n                        left: -1px;\n                        content: \"\";\n                        border-radius: 3px 0 0 3px;\n                        border-left: 4px solid;\n                    }\n                    .widget-settings{\n                        margin-top: -28px;\n                    }\n                    .alert--license .form-checkbox + .form-indicator {\n                        position: relative;\n                        width: 22px;\n                        height: 22px;\n                        min-width: 22px;\n                        border-radius: 3px;\n                        display: inline-flex;\n                        align-items: center;\n                        justify-content: center;\n                        color: #393D45;\n                        border: 1px solid #d7d9de;\n                        background-color: #ffffff;\n                        box-shadow: none;\n                    }\n                    .alert--license .form-checkbox{\n                        display: none;\n                    }\n                    .alert--license .form-check label{\n                        display: flex;\n                        align-items: center;\n                        font-weight: 400;\n                        color: #505459;\n                    }\n                    .alert--license .form-text{\n                        margin-left: 8px;\n                    } \n                    \n                    .alert--license .form-checkbox + .form-indicator:hover,\n                    .alert--license .form-checkbox + .form-indicator:active{\n                        border-color: #1062FE;\n                        outline: 0;\n                    }\n                    .alert--license .form-checkbox:checked + .form-indicator{\n                        background: #50bfff;\n                        border-color: #50bfff;\n                    }\n                    .alert--license .form-checkbox:checked + .form-indicator:before{\n                        position: absolute;\n                        top: 0;\n                        left: 0;\n                        right: 0;\n                        bottom: 0;\n                        display: inline-flex;\n                        align-items: center;\n                        justify-content: center;\n                        font-family: Material-Design-Iconic-Font;\n                        content: \"\\f26b\";\n                        display: inline-flex;\n                        color: #fff;\n                    }\n                    .alert--license .m-t-1x{\n                        margin-top: 8px;\n                    }\n                </style>\n                <script>\n                 \$(\".alert--license\").on(\"closed.bs.alert\", function () {\n                    let notshow = \$(this).find(\"[data-dont-show]\");\n                    let name = \"licenseexp\",\n                        value = 1,\n                        days = 7;\n            \n                    if (notshow[0].checked === true){\n                        value = \"4ever\";\n                        days = 9999;\n                    }\n\n                    new setCookie(name, value, days);\n                    \n                 });\n\n                 function setCookie(cname, cvalue, exdays) {\n                    const d = new Date();\n                    d.setTime(d.getTime() + (exdays*24*60*60*1000));\n                    let expires = \"expires=\"+ d.toUTCString();\n                    document.cookie = cname + \"=\" + cvalue + \";\" + expires + \";path=/\";\n                  }\n                </script>";
                }
            }
            if (isset($this->licenseDetails["warningShowDate"]) && strlen($this->licenseDetails["warningShowDate"]) && $this->licenseDetails["warningShowDate"] <= date("Y-m-d")) {
                $days = \Carbon\Carbon::today()->diffInDays(\Carbon\Carbon::parse($this->licenseDetails["deactivationDate"]), false);
                if ($days < $this->licenseFailDays) {
                    $html .= "<div class=\"alert alert--outline has-icon alert--border-left alert--license alert--danger\"><div class=\"alert__body\">";
                    $html .= str_replace("%days%", $days, \RSThemes\Helpers\Messages::get("warnings.9"));
                    $html = str_replace("<b>ERROR:", "<b>ERROR: Lagom WHMCS Client Theme", $html);
                    $html .= "</div><div class=\"alert__actions\">\n                            <a class=\"btn btn-default\" href=\"#\" target=\"_blank\">Support</a>\n                            </div>";
                    $html .= "</div>";
                    $html .= "<style>\n                    @font-face {\n                        font-family: \"Material-Design-Iconic-Font\";\n                        src: url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.woff2?v=2.2.0\") format(\"woff2\"), url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.woff?v=2.2.0\") format(\"woff\"), url(\"https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0//fonts/Material-Design-Iconic-Font.ttf?v=2.2.0\") format(\"truetype\");\n                        font-weight: normal;\n                        font-style: normal;\n                    }\n                    .alert--license{\n                        position: relative;\n                        display: flex;\n                        justify-content: space-between;\n                        align-items: center;\n                        flex-flow: row wrap;\n                        padding: 13px 16px 13px 56px;\n                       \n                        margin-bottom: 32px;\n                        border: none;\n                        box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n                        background: #fff;\n                        \n                    }\n                    .alert--license.alert--danger{\n                        color: #e53e3e;\n                        border-color: #e53e3e;\n                    }\n                    .alert--license *{\n                        box-sizing: border-box;\n                    }\n                    .alert--license:before{\n                        position: absolute;\n                        top: 50%;\n                        left: 15px;\n                        width: 24px;\n                        height: 24px;\n                        margin-top: -12px;\n                        text-align: center;\n                        font-family: Material-Design-Iconic-Font;\n                        font-size: 24px;\n                        line-height: 22px;\n                    }\n                    .alert--license.alert--danger:before{\n                        content: \"\\f1f4\";\n                    }\n                    .alert--license .alert__body{\n                        flex: 1;\n                        margin-right: auto;\n                    }\n                    .alert--license b{\n                        font-size: 14px;\n                        display: block;\n                        margin-bottom: 8px;\n                    }\n                    .alert--license b .hidden{\n                        display: inline-block!important;\n                    }\n                    .alert--license p{\n                        color: #505459;\n                        margin-bottom: 0;\n                    }\n                    .alert--license .alert__actions{\n                        display: flex;\n                        flex: 0 1 auto;\n                        white-space: nowrap;\n                    }\n                    .alert--license .alert__actions>*+* {\n                        margin-left: 16px;\n                    }\n                    .alert--license .alert__actions:last-child{\n                        margin-left: 16px;\n                    }\n                    .alert--license:after{\n                        position: absolute;\n                        top: -1px;\n                        bottom: -1px;\n                        left: -1px;\n                        content: \"\";\n                        border-radius: 3px 0 0 3px;\n                        border-left: 4px solid;\n                    }\n                    .widget-settings{\n                        margin-top: -28px;\n                    }\n                </style>";
                }
            }
            return $html;
        }
        public function hasProblem()
        {
            if (isset($this->licenseDetails["warningShowDate"]) && strlen($this->licenseDetails["warningShowDate"]) && $this->licenseDetails["warningShowDate"] <= date("Y-m-d")) {
                return true;
            }
            return false;
        }
        public function getProblem()
        {
            if (isset($this->licenseDetails["warningShowDate"]) && strlen($this->licenseDetails["warningShowDate"]) && $this->licenseDetails["warningShowDate"] <= date("Y-m-d")) {
                return $this->getMessagePart("warnings.8", 0);
            }
            return "";
        }
        public function hasInputError()
        {
            if (strlen($this->licenseKey) <= 0 || $this->licenseDetails["license_status"] == "") {
                return true;
            }
            return false;
        }
        public function getInputError()
        {
            if (strlen($this->licenseKey) <= 0 || $this->licenseDetails["license_status"] == "") {
                return \RSThemes\Helpers\Messages::get("messages.1");
            }
        }
        public function getAddonMessages()
        {
            $html = "";
            if (0 < strlen($this->licenseDetails["nextduedate"]) && $this->licenseDetails["nextduedate"] != "0000-00-00") {
                $dueDateDiff = \Carbon\Carbon::today()->diffInDays(\Carbon\Carbon::parse($this->licenseDetails["nextduedate"]), false);
                $notShow = false;
                if (isset($_COOKIE["licenseexp"]) && ($_COOKIE["licenseexp"] == "4ever" || $_COOKIE["licenseexp"] == "1" && 2 < $dueDateDiff)) {
                    $notShow = true;
                }
                if (0 <= $dueDateDiff && $dueDateDiff <= 30 && !$notShow) {
                    $html .= "<div class=\"alert alert--info alert--outline has-icon alert--border-left alert--license\" data-daysToExp=\"" . $dueDateDiff . "\"><div class=\"alert__body\">";
                    if ($dueDateDiff == 0) {
                        $html .= str_replace("%days%", $dueDateDiff, \RSThemes\Helpers\Messages::get("warnings.6"));
                    } else {
                        $html .= str_replace("%days%", $dueDateDiff, \RSThemes\Helpers\Messages::get("warnings.5"));
                    }
                    $html .= "<div class=\"form-check\"><label class=\"m-b-0x m-t-1x\"><input type=\"checkbox\" name=\"notshow\" data-dont-show class=\"form-checkbox\"><span class=\"form-indicator\"></span><span class=\"form-text\">Do not show again</span></label></div>";
                    $html .= "</div><div class=\"alert__actions\">\n                            <a class=\"btn btn--default btn--outline btn--sm\" href=\"https://rsstudio.net/my-account/\" target=\"_blank\">Pay Now</a>\n                            <button class=\"btn btn--default btn--outline btn--sm\" data-dismiss=\"alert\" aria-label=\"Close\" type=\"button\">Dismiss</button>\n                        </div>";
                    $html .= "</div>";
                }
            }
            if (isset($this->licenseDetails["warningShowDate"]) && strlen($this->licenseDetails["warningShowDate"]) && $this->licenseDetails["warningShowDate"] <= date("Y-m-d")) {
                $days = \Carbon\Carbon::today()->diffInDays(\Carbon\Carbon::parse($this->licenseDetails["deactivationDate"]), false);
                $html .= "<div class=\"alert alert--danger alert--outline has-icon alert--border-left alert--license\"><div class=\"alert__body\">";
                $html .= str_replace("%days%", $days, \RSThemes\Helpers\Messages::get("warnings.9"));
                $html .= "</div><div class=\"alert__actions\">\n                <a class=\"btn btn--default btn--outline btn--sm\" href=\"https://rsstudio.net/my-account/submitticket.php?step=2&deptid=7\" target=\"_blank\">Contact Us</a>\n                </div>";
                $html .= "</div>";
            }
            if (0 < strlen($this->licenseWarningMessage)) {
                $html .= "<div class=\"alert alert--danger alert--outline has-icon alert--border-left alert--license\"><div class=\"alert__body\">";
                $html .= $this->licenseWarningMessage;
                $html .= "</div><div class=\"alert__actions\">\n                <a class=\"btn btn--default btn--outline btn--sm\" href=\"https://rsstudio.net/my-account/submitticket.php?step=2&deptid=7\" target=\"_blank\">Contact Us</a>\n                </div>";
                $html .= "</div>";
            }
            return $html;
        }
        private static function checkLogFile()
        {
            $path = RSTHEMES_DIR . DS . "logs.php";
            if (!is_writable(RSTHEMES_DIR)) {
                return false;
            }
            if (!file_exists($path)) {
                try {
                    file_put_contents($path, "<?php http_response_code(404);exit; ?>\\nCreate logs file! " . date("Y-m-d h:i:s") . PHP_EOL);
                    if (!file_exists($path)) {
                        return false;
                    }
                } catch (\Exception $exception) {
                    return false;
                }
            }
            if (!is_writable($path)) {
                return false;
            }
            return $path;
        }
        public function getAllowedExtensions()
        {
            if ($this->licenseDetails["service_status"] == "Active") {
                $details = $this->reloadRemote();
            }
            return $details["extensions"] ? explode(",", trim($details["extensions"])) : [];
        }
        private function syncExtensions($details)
        {
            $toSkip = ["Modules Integrations"];
            if ($details["license_status"] != "Active") {
                return NULL;
            }
            $allowedExtensions = $details["extensions"] ? explode(",", trim($details["extensions"])) : [];
            foreach ($this->template->getExtensions() as $extension) {
                if (!in_array($extension->name, $toSkip)) {
                    if (method_exists($extension, "checkLicense") && !in_array($extension->name, $allowedExtensions) && $extension->isActive()) {
                        $extension->licenseRemoveConfig();
                    }
                }
            }
        }
    }
    
    ?>

  • Lagom Website Builder By RSStudio 1.0.3 破解版开心版

    2024年07月30日更新:已完美破解。

    Lagom WHMCS Website Builder V1.0.3 Nulled

    • NEW Product Comparison – New “section type” feature enables you to create product comparison tables for easy side-by-side comparison of products – Case #404.
    • NEW Graphics Color Scheme – Default graphics now automatically update to match your selected style color scheme. Previously, graphics stayed blue no matter what color you chose – Case #754.
    • NEW Include WHMCS Pages in Sitemap – New settings for sitemap that enables adding WHMCS pages like Product Groups, Announcements, Knowledgebase, and Download to the sitemap, ensuring comprehensive coverage and improved SEO – Case #767.
    • NEW NGINX – Added a possible compatibility with NGINX server type.
    • Improvements Added gradient variables to illustrations. Now, changing theme color variables dynamically updates gradients in illustrations – Case #762.
    • FIX Updated “Order Now” buttons for Market Connect products while using Lagom WHMCS One Step Order Form to properly redirect users to the product configuration step, ensuring essential setup is completed before checkout – Case #752.
    • FIX Fixed background colors for Banner section for all styles and colors, and added relevant variables for the section’s gradients – Case #669.
    • FIX Corrected the display of USD currency values of 1,000 and higher to match the “1,234.56” number format – Case #764.
    • FIX Other minor appearance fixes.