<?php
###############################################################################
# LookupManager.php
#
# @author Anil Kumar <akumar@codepunch.com>
# @link   https://codepunch.com
#
############################################################################### 

namespace 	CodePunch\LU;
use 		CodePunch\Base\Util as UTIL;
use			CodePunch\Base\Text as TEXT;
use			CodePunch\DB\Audit as AUDIT;

###############################################################################

class LookupManager  extends ConnectionManager {
	
	###########################################################################
	
	const DOMAIN_RECORDS		= 1;
	const ROOT_DNS				= 2;
	const HTTP_WEBSITE			= 4;
	const US_TM					= 8;
	const ALEXA_DATA			= 16;
	const PING_RESPONSE			= 32;
	const SSL_CERTS				= 64;
	const IP_WHOIS				= 128;
	const AUTH_DOMAIN_RECORDS	= 256;
	const SUB_DOMAINS			= 512;
	const GOOGLE_INDEX			= 1024;
	
	const LU_MINUTE				= 0;
	const LU_HOUR				= 1;
	const LU_DAY				= 2;
	const LU_WEEK				= 3;
	const LU_MONTH				= 4;
	const LU_ALWAYS				= 5;
	
	const LUQ_WAIT				= 0;
	const LUQ_TIMEWAIT			= 1;
	const LUQ_COMPLETE			= 2;
	const LUQ_FAIL				= 3;
	const LUQ_BLOCKED			= 4;
	const LUQ_UNSUPPORTED		= 5;
	
	const SD_USER_ROWS			= 0;
	const SD_AUTO_DNS_ROWS		= 1;
	const SD_AUTO_SSL_ROWS		= 2;
	const SD_TXT_ROWS			= 3;
	const SD_MAN_DNS_ROWS		= 4;
	const SD_MAN_SSL_ROWS		= 5;
	const SD_WEBSHOT_ROWS		= 6;
	const SD_COOKIE_ROWS		= 7;
	const SD_MAN_MISC_ROWS		= 10;
	
	###########################################################################
	
	public function __construct($auth=null) {
		parent::__construct($auth);
	}
	
	###########################################################################

	public function DomainLookup(&$ludata)
	{
		$did = $ludata['sid'];
		$db = $this->getAuthentication()->getDatabase();
		$domain = $db->getDomainName($did);
		$ludata['api_profile'] = $db->findOneOf($db->getDomainTableName(), "sid", $did, "api_profile");
		if($domain !== false) {
			$parts = $this->splitDomain($domain);
			if($parts['lus'] == 0) {
				$whois = new \CodePunch\LU\Whois($this);
				if($ludata['lutype'] == self::DOMAIN_RECORDS) {
					$ludata['server'] = $parts['server'];
					return $whois->Whois($ludata);
				}
				else if($ludata['lutype'] == self::AUTH_DOMAIN_RECORDS)
					return $whois->SecondaryWhois($ludata);
			}
			else if($parts['lus'] == 1) {
				$rdap = new \CodePunch\LU\RDAP($this);
				if($ludata['lutype'] == self::DOMAIN_RECORDS) {
					$ludata['server'] = $parts['rdap'];
					return $rdap->registryRDAP($ludata);
				}
				else if($ludata['lutype'] == self::AUTH_DOMAIN_RECORDS) {
					$secondary_rdap_server = $db->findOneOf($db->getDomainTableName(), "sid", $did, "secondary_rdap_server");
					if($secondary_rdap_server === false || $secondary_rdap_server == "" ||
						($secondary_rdap_server !== false && substr($secondary_rdap_server, 0, 1) == "!")) {
						$whois = new \CodePunch\LU\Whois($this);
						return $whois->SecondaryWhois($ludata);
					}
					else
						return $rdap->registrarRDAP($ludata);
				}
			}
		}
		$ludata['status'] = self::LUQ_UNSUPPORTED;
		return 0;
	}

	###########################################################################
		
	public function Lookup(&$ludata) 
	{
		$status = false;
		$lucount = -1;
		switch($ludata['lutype']) {
			case self::DOMAIN_RECORDS:
				//$whois = new \CodePunch\LU\Whois($this);
				//$lucount = $whois->Whois($ludata);
				//break;
			case self::AUTH_DOMAIN_RECORDS:
				//$whois = new \CodePunch\LU\Whois($this);
				//$lucount = $whois->SecondaryWhois($ludata);
				$lucount = $this->DomainLookup($ludata);
				break;
			case self::ROOT_DNS:
				$dns = new \CodePunch\LU\DNS($this);
				$lucount = $dns->Root($ludata);
				break;
			case self::SUB_DOMAINS:
				$dns = new \CodePunch\LU\DNS($this);
				$lucount = $dns->SubDomain($ludata);
				break;
			case self::SSL_CERTS:
				$ssl = new \CodePunch\LU\SSL($this);
				$lucount = $ssl->SSL($ludata);
				break;
			case self::PING_RESPONSE:
				$ping = new \CodePunch\LU\Ping($this);
				$lucount = $ping->Ping($ludata);
				break;
			case self::HTTP_WEBSITE:
				$http = new \CodePunch\LU\HTTP($this);
				$lucount = $http->Lookup($ludata);
				break;
			case self::IP_WHOIS:
				$http = new \CodePunch\LU\IPWhois($this);
				$lucount = $http->lookup($ludata);
				break;
			case self::ALEXA_DATA:
				$http = new \CodePunch\LU\Alexa($this);
				$lucount = $http->lookup($ludata);
				break;
			case self::GOOGLE_INDEX:
				$http = new \CodePunch\LU\GoogleIndex($this);
				$lucount = $http->lookup($ludata);
				break;
		}
		
		if($lucount == -1) {
			// Lookup Not supported
			$logger = new \CodePunch\Base\CPLogger();
			$logger->error(TEXT::get("luq_invalid", $ludata['lutype']));
			$lucount = 0;
			$ludata['status'] = self::LUQ_UNSUPPORTED;
		}
		return $lucount;
	}
	
	###############################################################################
	
	public function removeFromLookupQueue($lutype)
	{
		$db = $this->getAuthentication()->getDatabase();
		if($lutype == 0)
			return $db->deleteFromTable($db->getLookupQueueTableName(), "1=1", array());
		else
			return $db->deleteFromTable($db->getLookupQueueTableName(), "lutype=?", array($lutype));
	}
	
	###############################################################################
	
	public function removeLookup($ludata)
	{
		$db = $this->getAuthentication()->getDatabase();
		if(isset($ludata['id']) && $db) 
			return $db->deleteFromTable($db->getLookupQueueTableName(), "id=?", array($ludata['id']));
		return false;
	}
	
	###############################################################################
	
	public function reInsertLookup(&$ludata, $status=\CodePunch\LU\LookupManager::LUQ_FAIL)
	{
		$db = $this->getAuthentication()->getDatabase();
		$luqtable = $db->getLookupQueueTableName();
		unset($ludata['id']);
		unset($ludata['registrar_abuse_email']);
		$ludata['created_on'] = date("Y-m-d H:i:s");
		$ludata['last_update'] = date("Y-m-d H:i:s");
		$ludata['failcount']++;
		$ludata['status'] = $status;
		$luq = $ludata;
		unset($luq['api_profile']);
		$count = $db->getRowCount($luqtable, "sid=? AND lutype=?", array($luq['sid'], $luq['lutype']));
		if($count !== false && !$count)
			$status = $db->insertIntoTable($luqtable, $luq);
	}
	
	###########################################################################

	public function resetDomainData($domain)
	{
		$db = $this->getAuthentication()->getDatabase();
		$resetarray = array(
		'registry_expiry'=>null, 'registrar_expiry'=>null, 'created_on'=>null, 'last_update'=>null,
		'registrar'=>'', 'status'=>'', 'availability'=>'',
		'ns1'=>'', 'ns2'=>'', 'ns3'=>'', 'ns4'=>'', 'ns5'=>'', 'ns6'=>'', 'ns7'=>'', 'ns8'=>'',
		'owner'=>'', 'organization'=>'', 'address'=>'', 'owner_country'=>'',
		'admin_phone'=>null,
		'admin_email'=>'', 'tech_email'=>'', 'billing_email'=>'', 'registrant_email'=>'', 'r_h_disp'=>null,
		'registrar_whois'=>''
		);
		$resetarray['domain'] = $domain;
		$db->updateDomainTable($resetarray);
	}

	###############################################################################
	// Uses the TLD table to find applicable TLD, whois server and subdomain.

	public function splitDomain($domain)
	{
		$tld = "";
		$server = "";
		$rdapserver = "";
		$lusequence = 1;  // June 2025
		$dnscopyns = 0;

		$db = $this->getAuthentication()->getDatabase();
		$platform = $db->connection->getDatabasePlatform();
		$lengthExpression = $platform->getLengthExpression('tld');
		$tlds = $db->getFromTable("*", $db->getTLDsTableName(), "", array(), $lengthExpression, "DESC");
		foreach($tlds as $info) {
			$info = array_change_key_case($info, CASE_LOWER);
			$thistld = $info['tld'];
			$server = trim($info['server'], " -");
			$rdapserver = $info['rdap_server'];
			$lusequence = 1; //$info['lu_sequence'];  // June 2025
			$dnscopyns = $info['dnscopyns'];
			$lookfor = "." . $thistld;
			$domaintail = mb_substr($domain, -mb_strlen($lookfor));
			if(!strcasecmp($domaintail, $lookfor)) {
				$tld = ltrim($thistld, ".");
				break;
			}
			else {
				$tld = "";
				$server = "";
				$rdapserver = "";
				$lusequence = 0;
				$dnscopyns = 0;
			}
		}

		$nowhois = false;
		if(($server == "" || $tld == "") && $lusequence == 0) {
			$info = $this->findWhoisServer($domain);
			$tld = $info['tld'];
			$server = $info['server'];
			if($server == "") {
				$lusequence = 1;
				$nowhois = true;
			}
		}

		if(($rdapserver == "" || $tld == "") && $lusequence == 1) {
			$info = $this->findRDAPServer($domain, $tld);
			$tld = $info['tld'];
			$rdapserver = $info['server'];
		}

		// If set to RDAP and no RDAP servers, reset to Whois if
		// Whois is available.
		if($lusequence == 1 && $rdapserver == "" && !$nowhois) {
			if($server == "" || $tld == "") {
				$info = $this->findWhoisServer($domain);
				$tld = $info['tld'];
				$server = $info['server'];
			}
			$lusequence = $nowhois ? 1 : 0;
		}

		// If no RDAP or Whois Server, setup the TLD to copy NS data from DNS.
		if($server == "" && $rdapserver == "" && $dnscopyns == 0) {
			if($tld == "") {
				$tld = strrchr($domain, ".");
				if($tld !== false && count($nsrecords))
					$tld = substr($tld, 1);
			}
			if($tld != "") {
				$logger = new \CodePunch\Base\CPLogger();
				$logs = "No whois server or RDAP server configured for the TLD '$tld'. Name server data will be copied to the domain table from DNS records.";
				$logger->info($logs);
				$db->insertOrUpdateTable($db->getTLDsTableName(), array('dnscopyns'=>'1'), "tld", $tld);
				AUDIT::add($db, AUDIT::GENERIC_ACTION, $logs);
			}
		}

		$domainhead = mb_substr($domain, 0, mb_strlen($domain)-strlen($tld)-1);
		$domain_parts = explode(".", $domainhead);
		$name = array_pop($domain_parts);
		$subdomain  = implode(".", $domain_parts);
		return array('name'=>$name, 'tld'=>$tld, 'subdomain'=>$subdomain, 'server'=>$server, 'domain'=>$domain, 'rdap'=>$rdapserver, 'lus'=>$lusequence);
	}

	###############################################################################

	public function findRDAPServer($domain_name, $tld="")
	{
		if($tld == "") {
			$tld = $domain_name;
			$parts = explode(".", $domain_name);
			if(count($parts) > 1)
				$tld = $parts[count($parts)-1];
		}
		$rdapserver = \CodePunch\LU\RDAP::findForTLD($tld);
		$db = $this->getAuthentication()->getDatabase();
		if($rdapserver != "" && $rdapserver != "unknown") {
			$server = $db->getRDAPServerForTLD($tld);
			if($server === false || $server == "")
				$db->setRDAPServerForTLD($tld, $rdapserver);
			else
				$rdapserver = "";
		}
		return array('server' => $rdapserver, 'tld' => $tld);
	}

	###############################################################################

	public function findWhoisServer($domain_name, $tld="", $timeout=20)
	{
		$wserver = "";
		$registry = "";
		if($tld == "") {
			$tld = $domain_name;
			$parts = explode(".", $domain_name);
			if(count($parts) > 1)
				$tld = $parts[count($parts)-1];
		}
		$ascii_name = UTIL::idn_convert($tld);
		$url = "https://www.iana.org/domains/root/db/$ascii_name.html";
		$options  = array('http' => array('user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'));
		$context  = stream_context_create($options);
		$result = @file_get_contents($url, false, $context);
		if($result !== false) {
			$p1 = strip_tags(UTIL::get_string_between($result, "<b>URL for registration services:</b>", "\n"));
			$registry = trim($p1);
			$p2 = strip_tags(UTIL::get_string_between($result, "<b>WHOIS Server:</b>", "\n"));
			$wserver = trim($p2);
		}
		if($wserver == "") {
			$tserver = "whois.nic." . $tld;
			if(\CodePunch\LU\Whois::hasWhoisPort($tserver, $timeout))
				$wserver = $tserver;
		}
		if($wserver == "") {
			$tserver = "whois.dns." . $tld;
			if(\CodePunch\LU\Whois::hasWhoisPort($tserver, $timeout))
				$wserver = $tserver;
		}
		$db = $this->getAuthentication()->getDatabase();
		if($wserver != "" && $wserver != "unknown") {
			$server = $db->getWhoisServerForTLD($tld);
			if($server === false || $server == "")
				$db->setWhoisServerForTLD($tld, $wserver);
			else
				$wserver = "";
		}

		return array('server' => $wserver, 'registry' => $registry, 'tld' => $tld);
	}

	###############################################################################

	public static function getLastLookupTimeField($lookup_type)
	{
		switch($lookup_type)
		{
			case self::DOMAIN_RECORDS:
				return "primary_whois_checked_at";
			case self::AUTH_DOMAIN_RECORDS:
				return "secondary_whois_checked_at";
			case self::ROOT_DNS:
				return "rootdns_checked_at";
			case self::HTTP_WEBSITE:
				return "home_page_checked_at";
			case self::PING_RESPONSE:
				return 'ping_checked_at';
			case self::ALEXA_DATA:
				return 'alexa_checked_at';
			case self::SSL_CERTS:
				return 'ssl_checked_at';
			case self::IP_WHOIS:
				return 'ip_whois_at';
			case self::SUB_DOMAINS:
				return 'subdomains_checked_at';
			case self::GOOGLE_INDEX:
				return "google_index_checked_at";
			default:
				return "";
		}
	}
	
	###############################################################################
	
	public function addMultiLookups($sid, $lut, $mingap = 1440)
	{
		$count = 0;
		$db = $this->getAuthentication()->getDatabase();
		if(!$db->isWriteProtected($sid)) {
			$luts = array(self::DOMAIN_RECORDS, self::AUTH_DOMAIN_RECORDS, self::ROOT_DNS,
						self::HTTP_WEBSITE, self::PING_RESPONSE, //self::ALEXA_DATA,
						self::SSL_CERTS, self::IP_WHOIS, self::SUB_DOMAINS, 
						//self::GOOGLE_INDEX
						);
			foreach($luts as $l) {
				if($lut&$l)
					$count += $this->addToLookupQueue($sid, $l, $mingap);
			}
		}
		return $count;
	}
	
	###############################################################################
	
	public function getLookupTypes($lutype)
	{
		$luts = array(self::DOMAIN_RECORDS, self::AUTH_DOMAIN_RECORDS, self::ROOT_DNS,
					self::HTTP_WEBSITE, self::PING_RESPONSE, //self::ALEXA_DATA,
					self::SSL_CERTS, self::IP_WHOIS, self::SUB_DOMAINS, 
					//self::GOOGLE_INDEX
					);
		$lutsarray = array();
		foreach($luts as $lut) {
			if($lutype&$lut)
				$lutsarray[] = $lut;
		}
		return $lutsarray;
	}

	
	###############################################################################
	// Add Lookup
	// See if a lookup is required.
	// See if the lookup entry already exists.
	// If No, add

	public function addToLookupQueue($sid, $lookup_type, $min_gap=1440)
	{	
		// Don't do unsupported lookups.
		if($lookup_type == self::ALEXA_DATA || $lookup_type == self::GOOGLE_INDEX)
			return false;
		
		$db = $this->getAuthentication()->getDatabase();
		$domain = $db->getDomainName($sid);
		if($domain !== false && $domain != "") {
			$last_lookup_time_field = $this->getLastLookupTimeField($lookup_type);
			if($last_lookup_time_field != "") {
				$lookup_required = true;
				$next_lookup = $db->findOneOf($db->getDomainTableName(), "sid", $sid, $last_lookup_time_field);
				if($next_lookup !== false)
				{
					$next_lookup = strtotime($next_lookup) + $min_gap*60; 
					$current_time = time();
					if($next_lookup > $current_time)
						$lookup_required = false;
				}
				if($lookup_required)
					return $this->addLookup($sid, $lookup_type);
			}
		}
		return false;
	}
	
	###############################################################################

	public function addLookup($sid, $lut, $server = null)
	{
		$db = $this->getAuthentication()->getDatabase();
		$luqtable = $db->getLookupQueueTableName();
		//if($lut == self::DOMAIN_RECORDS)
		//	$count = $db->getRowCount($luqtable, "sid=? AND (lutype=? OR lutype=?)", array($sid, $lut, self::AUTH_DOMAIN_RECORDS));   
		//else
		$count = $db->getRowCount($luqtable, "sid=? AND lutype=?", array($sid, $lut));   
		if($count !== false && !$count)
		{
			$wait = self::LUQ_WAIT;
			$timenow = date("Y-m-d H:i:s");
			$serverid = ($server == null) ? $this->getAuthentication()->getServerID() : $server;
			return $db->insertIntoTable($luqtable, array('sid'=>$sid, 'lutype'=>$lut, 'status'=>$wait, 'server'=> $serverid, 'first_created'=>$timenow, 'created_on'=>$timenow));
		}
		return false;
	}
	
	###############################################################################
	
	public static function getLookupTypeLabel($lutype)
	{
		switch($lutype)
		{
			case self::DOMAIN_RECORDS:
				return TEXT::get("lut_whois");
			case self::AUTH_DOMAIN_RECORDS:
				return TEXT::get("lut_secondary_whois");
			case self::ROOT_DNS:
				return TEXT::get("lut_rootdns");
			case self::HTTP_WEBSITE:
				return TEXT::get("lut_http");
			case self::PING_RESPONSE:
				return TEXT::get("lut_ping");
			case self::ALEXA_DATA:
				return TEXT::get("lut_alexa");
			case self::SSL_CERTS:
				return TEXT::get("lut_ssl");
			case self::IP_WHOIS:
				return TEXT::get("lut_ipwhois");
			case self::SUB_DOMAINS:
				return TEXT::get("lut_extdns");
			case self::GOOGLE_INDEX:
				return TEXT::get("lut_google_index");
			case 0:
				return TEXT::get("lut_all");
			default:
				return TEXT::get("lut_unknown");
		}
	}
	
	###############################################################################
	
	public function addDefaultLookups($sid)
	{
		$status = array();
		$db = $this->getAuthentication()->getDatabase();
		if(!$db->isWriteProtected($sid)) {
			$setup = new \CodePunch\Config\Settings($this->getAuthentication());
			$lookups = $setup->getOption('default_lookup_types', "dw,ip");
			$luts = explode(",", $lookups);
			foreach($luts as $luw)
			{
				$luw = strtolower(trim($luw));
				//if($luw == "alexa")
				//	$status[] = $this->addToLookupQueue($sid, self::ALEXA_DATA);
				//else 
				if($luw == "dw")
					$status[] = $this->addToLookupQueue($sid, self::DOMAIN_RECORDS);
				else if($luw == "ip")
					$status[] = $this->addToLookupQueue($sid, self::ROOT_DNS);
				else if($luw == "ipw")
					$status[] = $this->addToLookupQueue($sid, self::IP_WHOIS);
				else if($luw == "http")
					$status[] = $this->addToLookupQueue($sid, self::HTTP_WEBSITE);
				else if($luw == "ping")
					$status[] = $this->addToLookupQueue($sid, self::PING_RESPONSE);
				else if($luw == "ssl")
					$status[] = $this->addToLookupQueue($sid, self::SSL_CERTS);
				else if($luw == "dns")
					$status[] = $this->addToLookupQueue($sid, self::SUB_DOMAINS);
				//else if($luw == "gi")
				//	$status[] = $this->addToLookupQueue($sid, self::GOOGLE_INDEX);
			}
		}
		return $status;
	}
	
	###############################################################################
	
	public function findDomainsToLookup($lookup_type, $cids, $maxcount, $mingap)
	{
		$db = $this->getAuthentication()->getDatabase();
		$lutimefield = $this->getLastLookupTimeField($lookup_type);
		
		$nextlookup = time() - ($mingap*60);
		// Find domains not ever looked up.
		//$lutimefield IS NULL OR $lutimefield < 
	}
	
	###############################################################################
	
	public function getWhoisFromRegistrar($domain, $apiprofile)
	{
		$ci = $this->getAuthentication()->getDatabase()->getRegistrarAPIClass($apiprofile, $this->getAuthentication());
		if($ci['class'] != "" && count($ci['params'])) {
			$regapi = new $ci['class']($this, ...$ci['params']);
			$r = $regapi->whois($domain);
			if($r['status'] == 'ok') 
				return $r['data'];
			else {
				$logger = new \CodePunch\Base\CPLogger();
				$logger->error($r['error']);
			}
		}
		return false;
	}
	
	###############################################################################
	
	public function queueDomains($lutype, $lufreq, $lunits, $query="", $params="", $maxrows=180)
	{
		$query = ($query == null) ? "" : $query;
		
		// No lookups for this obvious case, otherwise all domains
		// will continuously get queued
		if($lunits == self::LU_ALWAYS && $query == "")
			return 0;
		
		$timefield = "d." . self::getLastLookupTimeField($lutype);
		
		if($params != "" && !is_array($params))
			$params = explode(",", $params);
		else
			$params = array();
		
		$extraquery = "";
		if($lufreq) {
			$extraquery = "($timefield  < ?)";
			$refresh = self::refreshIntervalInMinutes($lufreq, $lunits);
			$compdate = date("Y-m-d H:i:s", time() - ($refresh*60));
			$params[] = $compdate;
		}
		
		// Do lookups of "never looked-up before" domains if the query is not set
		if($query == "") {
			if($extraquery != "")
				$extraquery .= " OR ($timefield is NULL)";
			else
				$extraquery = "($timefield is NULL)";
		}
		
		// Combine queries.
		if($query != "" && $extraquery != "")
			$query = "($query) AND ($extraquery)";
		else if($query == "" && $extraquery != "")
			$query = $extraquery;
		
		if($query != "")
			$query = "($query) AND (write_protect != '1' OR write_protect IS NULL)";
		else 
			$query = "(write_protect != '1' OR write_protect IS NULL)";
		
		$select = "sid,domain";
		if(stristr($query, "s.") !== false)
			$select .= ",hid";
		
		// Find domains to add to queue.
		$processed = 0;
		$db = $this->getAuthentication()->getDatabase();
		$data = $db->getDomainReportRows($query, $params, $select, $timefield, "DESC", 0, $maxrows*2);
		foreach($data as $d) {
			$d = array_change_key_case($d, CASE_LOWER);
			if($this->addToLookupQueue($d['sid'], $lutype, 0))
				$processed++;
			if($processed >= $maxrows)
				break;
		} 
		return $processed;
	}
	
	###############################################################################
	
	private function makeLookupQueue()
	{
		$processed = 0;
		$fp = UTIL::get_lock_file("queue");
		if($fp) {
			$db = $this->getAuthentication()->getDatabase();
			$table = $db->getLookupSchedulerTableName();
			$rows = $db->getFromTable("*", $table);
			$added = 0;
			if($rows !== false && is_array($rows) && count($rows)) {
				foreach($rows as $row) {
					$row = array_change_key_case($row, CASE_LOWER);
					if($row['enabled'] > 0) {
						$dc = 0;
						$luts = $this->getLookupTypes($row['lutype']);
						foreach($luts as $lut) {
							$dc += $this->queueDomains($lut, $row['frequency'], $row['lunits'], $row['query'], $row['params']);
						}
						$processed += $dc;
						if($dc)
							$db->updateTable($table, array('last_runat'=>date("Y-m-d H:i:s")), "id=?", array($row['id']));
					}
				}
			}
			$setup = new \CodePunch\Config\Settings($this->getAuthentication());
			$setup->setOption("lookup_scheduler_run", date("Y-m-d H:i:s"));
			flock($fp, LOCK_UN);
			fclose($fp);
		}
		else {
			UTIL::debug_cli_print("unable to get lock file for queue management.\n");
		}
		return $processed;
	}
	
	###############################################################################
	
	public static function refreshIntervalInMinutes($frequency, $units)
	{
		switch($units) {
			case self::LU_MINUTE:
				return $frequency;
			case self::LU_HOUR:
				return $frequency * 60;
			case self::LU_DAY:
				return $frequency * 60 * 24;
			case self::LU_WEEK:
				return $frequency * 60 * 24 * 7;
			case self::LU_MONTH:
				return $frequency * 60 * 24 * 30;
		}
		return 0;
	}
	
	###############################################################################
	
	public static function getQueueEntries($auth, $responce, $mode=0)
	{
		$db = $auth->getDatabase();
		$luqtable = $db->getLookupQueueTableName();
		$priority = array(self::DOMAIN_RECORDS, self::ROOT_DNS, self::AUTH_DOMAIN_RECORDS);
		$pri = implode(",", $priority);
		if(!count($priority))
			$mode = 2;
		$rows = false;
		while(1) {
			if($mode == 0) 
				$rows = $db->getFromTable("*", $luqtable, "lutype IN ($pri)", array(), "created_on", "ASC", array($responce['start'], $responce['maxrows']));
			else if($mode == 1) 
				$rows = $db->getFromTable("*", $luqtable, "lutype NOT IN ($pri)", array(), "created_on", "ASC", array($responce['start'], $responce['maxrows']));
			else if($mode == 2) 
				$rows = $db->getFromTable("*", $luqtable, "", array(), "created_on", "ASC", array($responce['start'], $responce['maxrows']));
			else
				break;
			if($rows !== false && is_array($rows) && count($rows))
				break;
			$mode++;
		}
		return $rows;
	}
	
	###############################################################################
	
	public static function run($auth, &$responce)
	{
		$lookupManager = new \CodePunch\LU\LookupManager($auth);
		$setup = new \CodePunch\Config\Settings($auth);
		if(UTIL::is_cli()) {
			// Run Lookup Scheduler once every hour.
			// Lookup Scheduler is also activated whenever there is free time
			// after the lookup queue is processed. So this one hour gap
			// is not an issue.
			$lastschrun = $setup->getOption("lookup_scheduler_run", "");
			if($lastschrun != "" && UTIL::is_a_date($lastschrun)) {
				if((time() - strtotime($lastschrun)) > 3600) {
					$processed = $lookupManager->makeLookupQueue();
					if($processed > 0 && $processed !== false) {
						$responce['type'] |= \CodePunch\Config\Cron::LUSCH_PROCESSING;
						$responce['time'] = $auth->getElapsedTime();
						return true;
					}
				}
			}
			$lookupManager->sendPendingAlerts();
		}
		
		// Lock makeLookupQueue
		$fp = UTIL::get_lock_file("queue");
		//

		$db = $auth->getDatabase();
		$luqtable = $db->getLookupQueueTableName();
		$timeup = false;
		$mode = 0;
		$validcount = 0;
		while(!$timeup && $mode < 3 && $validcount < ($responce['maxrows']/2)) {
			$rows = self::getQueueEntries($auth, $responce, $mode);
			if($rows !== false && is_array($rows) && count($rows)) {
				foreach($rows as &$row) {
					$row = array_change_key_case($row, CASE_LOWER);
					$lookupManager->removeLookup($row);
					$count = $lookupManager->Lookup($row);
					//UTIL::debug_print($row);
					if($row['status'] != \CodePunch\LU\LookupManager::LUQ_COMPLETE && $row['status'] != \CodePunch\LU\LookupManager::LUQ_UNSUPPORTED)
						$lookupManager->reInsertLookup($row, \CodePunch\LU\LookupManager::LUQ_TIMEWAIT);
					else
						$validcount++;
					$responce['attempted']++;
					if($count) { 
						$responce['processed'] += $count;
						$lutype = $lookupManager->getLookupTypeLabel($row['lutype']);
						$row['lookup'] = self::getLookupTypeLabel($row['lutype']);
						$row['domain'] = $db->getDomainName($row['sid']);
						$responce['rows'][] = $row;
					}
					if($auth->getElapsedTime() > $responce['maxseconds']) {
						$timeup = true;
						break;
					}
				}
			}
			else
				break;
			$mode++;
		}

		// Unlock makeLookupQueue
		if($fp) {
			flock($fp, LOCK_UN);
			fclose($fp);
		}
		//

		if($responce['attempted'] > 0)
			$responce['type'] |= \CodePunch\Config\Cron::QUEUE_PROCESSING;		
		if($responce['attempted'] == 0 || $auth->getElapsedTime() < ($responce['maxseconds']/2)) {
			$processed = $lookupManager->makeLookupQueue();
			if($processed > 0 && $processed !== false)
				$responce['type'] |= \CodePunch\Config\Cron::LUSCH_PROCESSING;
			else if($auth->getElapsedTime() < 4){ 
				// If everything else is done in 4 seconds or less
				$tasks = new \CodePunch\Config\Tasks($auth);
				$tasks->periodic($lookupManager);
				$responce['type'] |= \CodePunch\Config\Cron::TASK_PROCESSING;
			}
		}
		$responce['time'] = $auth->getElapsedTime();
		return true;
	}
	
	###############################################################################
	
	public function updateDomainTable($lutype, $ddata)
	{
		$db = $this->getAuthentication()->getDatabase();
		$keys = array_keys($ddata);
		
		$sid = 0;
		if(UTIL::in_array_casei("sid", $keys))
			$sid = $ddata['sid'];
		if($sid == 0 && UTIL::in_array_casei("domain", $keys))
			$sid = $db->getDomainID($ddata['domain']);
		if($sid == 0 && UTIL::in_array_casei("ascii_domain", $keys))
			$sid = $db->$sid = $this->findOneOf($this->getDomainTableName(), "ascii_domain", $ddata['ascii_domain'], "sid");
		
		if($sid != 0) {

			if(method_exists("\CodePunch\LU\Hooks", "onDomainDataUpdate"))
				call_user_func(array("\CodePunch\LU\Hooks", "onDomainDataUpdate"), $this->getAuthentication(), $db->getDomainName($sid), $lutype, $ddata);

			switch($lutype) {
				case self::DOMAIN_RECORDS:
					if(isset($ddata['registry_whois']))
						$db->updateDataHistory($sid, self::DOMAIN_RECORDS, $ddata['registry_whois']);
					break;
				case self::AUTH_DOMAIN_RECORDS:
					if(isset($ddata['registrar_whois'])) {
						$db->updateDataHistory($sid, self::AUTH_DOMAIN_RECORDS, $ddata['registrar_whois']);
						$ddata = $db->analyseRegistrarDomainData($sid, $ddata);
					}
					break;
				case self::ROOT_DNS:
					if(isset($ddata['ip']))
						$db->updateDataHistory($sid, self::ROOT_DNS, ip2long($ddata['ip']));
					break;
				case self::HTTP_WEBSITE:
					break;
				case self::ALEXA_DATA:
					if(isset($ddata['alexa_rank']))
						$db->updateDataHistory($sid, self::ALEXA_DATA, $ddata['alexa_rank']);
					break;
				case self::PING_RESPONSE:
					break;
				case self::SSL_CERTS:
					break;
				case self::IP_WHOIS:
					break;
				case self::SUB_DOMAINS:
					break;
				case self::GOOGLE_INDEX:
					if(isset($ddata['google_index_count']))
						$db->updateDataHistory($sid, self::GOOGLE_INDEX, $ddata['google_index_count']);
					break;
			}
		}
		return $db->updateDomainTable($ddata);
	}
	
	###############################################################################
	
	public function monitorDNSData($sid, $domain, $subdomain, &$data_array, $rtype)
	{
		if(method_exists("\CodePunch\LU\Hooks", "onDNSDataMonitor"))
			call_user_func(array("\CodePunch\LU\Hooks", "onDNSDataMonitor"), $this->getAuthentication(), $domain, $subdomain, $data_array);
		
		$logs = array();
		$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		if($setup->getBoolean("dns_monitor_enabled", false)) {		
			$alert_new_dns_records = $setup->getBoolean("alert_new_dns_entries", false);
			if($subdomain == "")
				$subdomain = "@";
			
			$db = $this->getAuthentication()->getDatabase();
			//$sid = $db->getDomainID($domain);
			
			$new_data = array();
			foreach($data_array as $dnsdata) {
				$type = strtolower(UTIL::get_from_array($dnsdata['type'], ""));
				if($type == "ns")
					$new_data[$type][] = UTIL::get_from_array($dnsdata['target'], "");
				else if($type == "cname")
					$new_data[$type][] = UTIL::get_from_array($dnsdata['target'], "");
				else if($type == "a")
					$new_data[$type][] = UTIL::get_from_array($dnsdata['ip'], "");
				else if($type == "txt")
					$new_data[$type][] = UTIL::get_from_array($dnsdata['txt'], "");
				else if($type == "mx")
					$new_data[$type][] = UTIL::get_from_array($dnsdata['pri'], "") . " " . UTIL::get_from_array($dnsdata['target'], "");
			}
			
			$dns_monitor_defaults = array('ns','mx','cname','a', 'txt');
			$dns_monitor_records = $dns_monitor_defaults;
			$dnsrecords = $setup->getOption("dns_monitor_records", "");
			if($dnsrecords != "") {
				$dns_monitor_records = array_filter(explode(",", $dnsrecords));
				if(!count($dns_monitor_records))
					$dns_monitor_records = $dns_monitor_defaults;
			}

			$all_old_data = array();
			$allrows = $db->getFromTable("record_value", $db->getSubdomainTableName(), 
						"auto_added=? AND sid=? AND subdomain=? AND LOWER(record_type)=?",
						array(self::SD_AUTO_DNS_ROWS, $sid, $subdomain, strtolower($rtype)));
			if($allrows !== false && is_array($allrows)) {
				$index = 0;
				$pdata = array();
				foreach($allrows as $row) {
					$row = $db->fetchRow($allrows, $index++);
					$pdata[strtolower($rtype)][] = $row['record_value'];
				}
				foreach($pdata as $key=>$value) {
					if(!isset($new_data[$key])) {
						$newlogs = $this->getDNSLog($domain, $subdomain, $pdata[$key], array(), strtoupper($key));
						$logs = array_merge($logs, $newlogs);
					}
				}
				$all_old_data[strtolower($rtype)] = $pdata;
			}

			foreach($new_data as $key=>$value) {
				if(!UTIL::in_array_casei($key, $dns_monitor_records))
					continue;
				$old_data = $all_old_data[strtolower($key)];
				$dolog = true;
				if(!isset($old_data[$key])) {
					$mainrow = $db->getFromTable("hid", $db->getSubdomainTableName(), "sid=? AND subdomain=? AND LOWER(record_type)=? AND auto_added=?", array($sid, $subdomain, strtolower($rtype), \CodePunch\LU\LookupManager::SD_MAN_DNS_ROWS));
					//if($alert_new_dns_records)
					if($mainrow !== false && is_array($mainrow) && count($mainrow))
						$old_data[$key] = array();
					else
						$dolog = false;
				}
				if($dolog) {
					$newlogs = $this->getDNSLog($domain, $subdomain, $old_data[$key], $new_data[$key], strtoupper($key));
					$logs = array_merge($logs, $newlogs);
				}
			}
		}
		return $logs;
	}
	
	###############################################################################
	
	public function getDNSLog($domain, $subdomain, $old_data, $new_data, $recordtype)
	{
		$db = $this->getAuthentication()->getDatabase();
		$logs = array();
		$auditlogs = array();
		$commondata = array();
		foreach($old_data as $value) {
			if(UTIL::in_array_casei($value, $new_data))
				$commondata[] = $value;
		}
		$thistime = date("Y-m-d H:i:s");
		$new_data = array_values(array_filter(array_udiff($new_data, $commondata, 'strcasecmp')));
		$old_data = array_values(array_filter(array_udiff($old_data, $commondata, 'strcasecmp')));
		if(count($old_data)) {
			$logs[] = "$thistime $recordtype Record(s) " . implode(", ", $old_data) . " deleted for $subdomain.$domain";
			$auditlogs[] = "$recordtype Record(s) " . implode(", ", $old_data) . " deleted for $subdomain.$domain";
		}
		if(count($new_data)) {
			$logs[] = "$thistime New $recordtype Record(s) " . implode(", ", $new_data) . " found for $subdomain.$domain";
			$auditlogs[] = "New $recordtype Record(s) " . implode(", ", $new_data) . " found for $subdomain.$domain";
		}
		if(count($auditlogs)) {
			AUDIT::add($db, AUDIT::DNS_RECORD_CHANGE, $auditlogs);
			UTIL::debug_cli_print($logs);
		}
		return $logs;
	}
	
	###############################################################################

	public function getAlertEmailDelay($setup=null)
	{
		if($setup == null)
			$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		$dealymin = intval($setup->getOption("email_alerts_delay", "5"));
		if($dealymin < 0 || $dealymin > 60)
			$dealymin = 10;
		return $dealymin;
	}

	###############################################################################
	
	public function emailDNSAlerts($difflog)
	{
		if(count($difflog)) {
			$setup = new \CodePunch\Config\Settings($this->getAuthentication());
			$emaildnsalerts = $setup->getBoolean("email_dns_alerts", false);
			if($emaildnsalerts) {
				$doemail = true;
				$delaymin = $this->getAlertEmailDelay($setup);
				if($delaymin > 0) {
					$setup->addPendingAlerts(implode("\n", $difflog), "DNS");
					$doemail = false;
				}
				if($doemail) {
					$alertdata = $setup->getDNSAlert($difflog, "DNS");
					return $this->sendAlertEmail($alertdata['body'], $alertdata['subject'], $alertdata['emailname']);
				}
			}
		}
		return false;
	}
	
	###############################################################################

	public function sendPendingAlerts()
	{
		$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		$delaymin = $this->getAlertEmailDelay($setup);
		$count = 0;
		$alerttypes = array("DNS", "Ping");
		foreach($alerttypes as $name) {
			$oldalerts = $setup->getPendingAlerts($delaymin, $name);
			if($oldalerts !== false) {
				$body = $oldalerts['body'];
				$subject = $oldalerts['subject'];
				$emailname = $oldalerts['emailname'];
				if($this->sendAlertEmail($body, $subject, $emailname))
					$count++;
			}
		}
		return $count;
	}

	###############################################################################

	public function sendAlertEmail($alertmsg, $subject="DNS Alert", $recipientname = "dns_alert_recipients")
	{
		$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		$recipients = $setup->getOption($recipientname, "");
		if($recipients == "")
			$recipients = $setup->getOption("email_recipients", "");
		if($recipients == "") {
			$logger = new \CodePunch\Base\CPLogger();
			$logger->error("No email recipients found for mailing $subject");
		}
		else {
			$mailto = "Recipients: " . implode(", ", explode("\n", $recipients));
			$mailer = new \CodePunch\Base\Mailer($this->getAuthentication());
			$recipients = $mailer->formatEmailRecipients($recipients);
			$status = $mailer->send($recipients, $subject, is_array($alertmsg) ? implode("\n\n", $alertmsg) : $alertmsg);
			$db = $this->getAuthentication()->getDatabase();
			if($status) 
				AUDIT::add($db, AUDIT::REPORT_EMAILED, $subject, $mailto);
			else
				AUDIT::add($db, AUDIT::EMAIL_ATTEMPT_FAIL, $subject, $mailto);
			return $status;
		}
		return false;
	}

	###############################################################################

	public function emailPingAlerts($did, $host, $status)
	{
		if($host != "") {
			$db = $this->getAuthentication()->getDatabase();
			$setup = new \CodePunch\Config\Settings($this->getAuthentication());
			$emailalerts = $setup->getOption("email_ping_alerts", 0);
			if($emailalerts == 2) {
				// Alert on status change.
				// Find previous status, If previous status == current, set $emailalerts to 0;
				$oldstatus = $db->findOneOf($db->getDomainTableName(), "sid", $did, "pingtime");
				if($oldstatus !== false) {
					if(($oldstatus == -1 && $status == -1) || ($oldstatus >= 0 && $status >= 0))
						$emailalerts = 0;
				}
				if($oldstatus === false)
					$emailalerts = 0;
				$msg = $status == -1 ? "Site Alert: Unable to access $host" : "Site Alert: $host is now up";
				if($emailalerts)
					AUDIT::add($db, AUDIT::PING_STATUS_CHANGE, $msg);
				else if($status == -1)
					AUDIT::add($db, AUDIT::PING_ATTEMPT_FAIL, $msg);
			}
			else if($emailalerts == 1 && $status == -1) {
				$msg = "Site Alert: Unable to access $host";
				AUDIT::add($db, AUDIT::PING_ATTEMPT_FAIL, $msg);
			}
			if($emailalerts && isset($msg)) {
				$doemail = true;
				$delaymin = $this->getAlertEmailDelay($setup);
				if($delaymin > 0) {
					$setup->addPendingAlerts($msg, "Ping");
					$doemail = false;
				}
				if($doemail)
					return $this->sendAlertEmail($msg, "Site/URL Alert", "dns_alert_recipients");
			}
		}
		return false;
	}

	###############################################################################

	public function emailHttpAlerts($did, $pagedata, $hpdata) {
		$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		$emailalerts = $setup->getOption("email_http_alerts", 0);
		if($emailalerts > 0) {
			$db = $this->getAuthentication()->getDatabase();
			if($emailalerts == 2) {
				$oldhttpstatus = $db->findOneOf($db->getDomainTableName(), "sid", $did, "home_page_status");
				if($oldhttpstatus !== false) {
					$httpstatus = $hpdata['home_page_status'];
					if(strcasecmp($oldhttpstatus, $httpstatus)) {
						if(stripos($oldhttpstatus, "Connection timed out after ") !== false && stripos($httpstatus, "Connection timed out after ") !== false) {
						}
						else {
							$msg = "HTTP Status Changed; New: " . $hpdata['home_page_url'] . ": " . $httpstatus . ", Old: " . $oldhttpstatus;
							AUDIT::add($db, AUDIT::HTTP_STATUS_CHANGE, $msg);
						}
					}
				}
			}
			if($emailalerts == 1) {
				$httpstatus = $hpdata['home_page_status'];
				if(stripos($httpstatus, "404") !== false || stripos($httpstatus, "/1.1 200") === false || stripos($httpstatus, "Could not resolve host:") !== false) {
					$msg = "HTTP Status Alert: " . $hpdata['home_page_url'] . ": " . $httpstatus;
					AUDIT::add($db, AUDIT::HTTP_STATUS_FAIL, $msg);
				}
			}
			if($emailalerts > 0 && isset($msg)) {
				$doemail = true;
				$delaymin = $this->getAlertEmailDelay($setup);
				if($delaymin > 0) {
					$setup->addPendingAlerts($msg, "Ping");
					$doemail = false;
				}
				if($doemail)
					$this->sendAlertEmail($msg, "Site/URL Alert", "dns_alert_recipients");
			}
		}
	}

	###############################################################################

	public function getStatus()
	{
		$db = $this->getAuthentication()->getDatabase();
		$setup = new \CodePunch\Config\Settings($this->getAuthentication());
		$servertable = $db->getInstalledServersTableName();
		$server_name = $this->getAuthentication()->getServerID();
		$rows = $db->getFromTable("*", $servertable, "name=?", array($server_name));
		return $db->fetchRow($rows, 0);
	}
}

###############################################################################
