http://modulesgarden.com
* CONTACT -> contact@modulesgarden.com
* AUTHOR -> MODULESGARDEN
* contact@modulesgarden.com
*
*
*
* This software is furnished under a license and may be used and copied
* only in accordance with the terms of such license and with the
* inclusion of the above copyright notice. This software or any other
* copies thereof may not be provided or otherwise made available to any
* other person. No title to and ownership of the software is hereby
* transferred.
*
*
* ******************************************************************** */
namespace MGModule\DNSManager2\mgLibs\custom\dns\utils;
use MGModule\DNSManager2\models\custom\reverse\Repository;
use MGModule\DNSManager2\mgLibs\custom\dns\record\Record;
use MGModule\DNSManager2\mgLibs\custom\dns\utils\ReverseDNSHelper;
use MGModule\DNSManager2\mgLibs\exceptions\RegistrarDNSException;
use MGModule\DNSManager2\mgLibs\MySQL as mysql;
use MGModule\DNSManager2\mgLibs\MySQL\query;
use MGModule\DNSManager2\addon;
use MGModule\DNSManager2\mgLibs\custom\manager\ClientHelper;
use MGModule\DNSManager2\models\custom\zone\Zone;
use MGModule\DNSManager2\models\custom\zone\ZoneTypeEnum;
use MGModule\DNSManager2 as main;
/**
* registrar helper for DNS Management
* Please make sure that "mocks" folder is readable
* Please also make sure that "function" folder is writable. It is necessairy to create dynamic files with specific DNS Functions
*/
class CustomDnsRegistrarHelper
{
public $domainId = null;
public $moduleName = null;
private $package = null;
/**
* Current Module Parametrers for given domain
* @var Array
*/
protected $moduleParams = array();
protected $availableTypes = array(
'A' => array(
'name' => 'A',
'ttl' => 14400
),
'AAAA' => array(
'name' => 'AAAA',
'ttl' => 14400
),
'MX' => array(
'name' => 'MX',
'ttl' => 14400
),
'CNAME' => array(
'name' => 'CNAME',
'ttl' => 14400
),
'TXT' => array(
'name' => 'SPF',
'ttl' => 14400,
'alias' => 'SPF'
),
'TXT' => array(
'name' => 'TXT',
'ttl' => 14400,
'alias' => 'SPF'
)
);
const REGISTRAR_DOMAIN_ID_NOT_EXISTS = 100;
const REGISTRAR_ZONE_NOT_FOUND = 101;
const REGISTRAR_DOMAIN_DETAILS_NOT_FOUND = 102;
const REGISTRAR_DOMAIN_NAME_NOT_FOUND = 103;
const REGISTRAR_MODULE_NAME_NOT_FOUND = 104;
const REGISTRAR_USER_ID_NOT_EXISTS = 105;
static public $MSGS_LIST = array(
100 => "Domain ID cannot be empty. Please provide correct Domain ID in %s",
101 => "Unable to find zone for provided domain. Create a new zone first or contact your administrator",
102 => "Unable to get Domain Details Information in %s",
103 => "No Domain name has been provided for %s",
104 => "No Module Name has been found for %s",
105 => "Unable to get User ID in %s. Probably session has expired",
);
public final function __construct($domainId = null)
{
$this->domainId = $domainId;
$this->getPackage();
}
public function setDomainId($domainId)
{
$this->domainId = $domainId;
}
public function getDNSRecords($domainId)
{
$params = $this->getModuleParams();
addon::I(FALSE, $params);
$helper = new ClientHelper($_SESSION['uid']);
$zone = new Zone($this->getZoneIdForDomain($domainId));
$dns = array();
if(!$helper->isZoneOwnedByClient($zone))
{
return array(
'error' => main\mgLibs\lang::T('you_cannot_edit_this_zone_because_it_is_not_belongs_to_you')
);
}
$module = $zone->getModule();
$vars['records'] = $module->getRecords();
$vars['records'] = array_merge($vars['records'], $this->getPTRRecords($zone));
foreach($vars['records'] as &$record)
{
$record->encode();
if(!isset($this->availableTypes[$record['type']]))
{
continue;
}
$priority = null;
switch($record['type'])
{
case 'MX':
$address = $record['rdata']->exchange;
$priority = $record['rdata']->preference;
break;
case 'CNAME':
$address = $record['rdata']['cname'];
break;
case 'A':
$address = $record['rdata']['address'];
break;
case 'AAAA':
$address = $record['rdata']['address'];
break;
case 'TXT':
$address = $record['rdata']['txtdata'];
break;
default:
}
$dns[] = array(
"hostname" => $record['name'],
"line" => $record['line'],
"type" => $record['type'],
"address" => $address,
"priority" => $priority,
"name" => $record['name'],
"class" => $record['class'],
"ttl" => $record['ttl'],
"rdlength" => $record['rdlength'],
"rdata" => $record["rdata"]
);
}
return $dns;
}
/**
*
* @param type $record
* @return type
*/
private function convertRecord($record)
{
//applying additional parameters to match DNS Manager spec
$record['ttl'] = $this->availableTypes[trim(strtoupper($record['type']))]['ttl'];
$record['name'] = $record['hostname'];
if($record['type'] == "MX")
{
$record['exchange'] = $record['address'];
$record['preference'] = (int) $record['priority'];
$record['rdata']['exchange'] = $record['address'];
$record['rdata']['preference'] = (int) $record['priority'];
}
if($record['type'] == "CNAME")
{
$record['rdata']['cname'] = $record['address'];
}
if($record['type'] == "TXT")
{
$record['rdata']['txtdata'] = $record['address'];
}
return $record;
}
private function transformRecords()
{
$records = $_POST['dnsrecordhost'];
$recordsType = $_POST['dnsrecordtype'];
$recordsAddress = $_POST['dnsrecordaddress'];
$recordsPriority = $_POST['dnsrecordpriority'];
$results = array();
foreach($records as $key => $value)
{
$results[] = array(
'hostname' => $value,
'name' => $value,
'type' => $recordsType[$key],
'address' => $recordsAddress[$key],
'priority' => $recordsPriority[$key],
'ttl' => $this->availableTypes[trim(strtoupper($recordsType[$key]))]['ttl'],
'rdata' => array(
'hostname' => $value,
'name' => $value,
'type' => $recordsType[$key],
'address' => $recordsAddress[$key],
'priority' => $recordsPriority[$key],
'ttl' => $this->availableTypes[trim(strtoupper($recordsType[$key]))]['ttl'],
),
);
}
return $results;
}
/**
* This function is used to check if record type which is going to be created is allowed and available
* @param String $type Name of the DNS record Type to check if is allowed to create
* @see Registrar::availableTypes for available types
* @return boolean True is returned is allowed - false otherwise
*/
private function checkTypeIfAvailable($type = null)
{
if(empty($type))
{
return false;
}
foreach ($this->availableTypes as $key)
{
if ($key['name'] == trim(strtoupper($type)))
{
return true;
}
}
return false;
}
/**
* Function used to save or delete DNS record with provided details
* @param type $domainId Id of the domain for whitch records would be deleted or created
* @return Array . Void otherwise
* @throws \Exception Array with error key is returend in case of failure. in that case an exception is thrown
*/
public function saveDNSRecords($domainId = null)
{
if (empty($domainId))
{
$domainId = $this->domainId;
}
$zoneLoggerManager = new main\mgLibs\custom\helpers\ZoneLogger\Manager($_SESSION['uid']);
$error = array();
$params = $this->getModuleParams();
addon::I(FALSE, $params);
$helper = new ClientHelper($_SESSION['uid']);
$zone = new Zone($this->getZoneIdForDomain($domainId));
//getting den for
$newDNSCollection = $this->transformRecords();
$oldDNSCollection = $this->getDNSRecords($domainId);
$limits = $this->checkPackageLimits($newDNSCollection);
if($limits)
{
throw new \Exception($limits, 0);
}
if (!$helper->isZoneOwnedByClient($zone))
{
throw new \Exception(main\mgLibs\lang::T('you_cannot_edit_this_zone_because_it_is_not_belongs_to_you'), 0);
}
$module = $zone->getModule();
foreach ($newDNSCollection as $newKey => $newValue)
{
$newValue = $this->convertRecord($newValue);
//if such a key does not exists it could be a new one
if (!array_key_exists($newKey, $oldDNSCollection))
{
if (empty($newValue['hostname']) && empty($newValue['address']))
{
continue;
}
//if type is not allowd - return error
if (!$this->checkTypeIfAvailable($newValue['type']))
{
$error[] = $newValue['type'] . ' is not supported. Available types: A, AAAA, MX, CNAME, SPF(txt).';
continue;
}
try
{
$record = $this->createRecordFromData($newValue);
$module->addRecord($record);
$zoneLoggerManager->logAddRecordToZone($zone, $record);
}
catch(\Exception $ex)
{
$error[] = $ex->getMessage();
}
continue;
}
//if there are no difference - do nothing
if($newValue['hostname'] == $oldDNSCollection[$newKey]['hostname']
&& $newValue['address'] == $oldDNSCollection[$newKey]['address']
&& ($newValue['priority'] == $oldDNSCollection[$newKey]['priority'] || !is_numeric($newValue['priority'])))
{
continue;
}
//if there are discrepencies in a host name or IP address remove old record and add new one
//or remove only those in which both hostname and address are empty
try
{
$module->deleteRecord($record = $this->createRecordFromData($oldDNSCollection[$newKey]));
$zoneLoggerManager->logRemoveRecordFromZone($record);
//remove record and if values was empty - do nothing else
if (empty($newValue['hostname']) && empty($newValue['address']))
{
continue;
}
}
catch(\Exception $ex)
{
$error[] = $ex->getMessage();
continue;
}
try
{
$module->addRecord($record = $this->createRecordFromData($newValue));
}
catch(\Exception $ex)
{
$error[] = $ex->getMessage();
//rollbacking
$module->addRecord($record = $this->createRecordFromData($oldDNSCollection[$newKey]));
}
$zoneLoggerManager->logAddRecordToZone($zone, $record);
}
if(!empty($error))
{
throw new \Exception(implode(".
\n", $error), 1);
}
}
/**
* Function used to create record from form data
* @param Array $input Collection of form parameters sent by WHMCS while new DNS record creation
* @return MGModule\DNSManager2\mgLibs\custom\dns\record\Record
*/
private function createRecordFromData($input)
{
$record = Record::tryToCreateFromArray($input);
$record->rdata->setDataFromArray($input['rdata']);
return $record;
}
/**
* Function used to get Current Domain ID
* @return integer ID of the domain is used
*/
protected function getDomainId()
{
return (int)$this->domainId;
}
private function getPTRRecords($zone)
{
$out = array();
foreach(Repository::factory()->from($zone->getServer()->id, $zone->name)->get() as $data)
{
$ptr = new Record();
$ptr->name = ReverseDNSHelper::getLastOctetFromIPv4($data->ip) . '.' . $data->name;
$ptr->type = 'PTR';
$ptr->ttl = $data->ttl;
$ptr->ip = $data->ip;
$ptr->createRDATAObject('PTR');
$ptr->rdata->setFirstProperty(empty($data->sub) ? $data->from : $data->sub . '.' . $data->from);
$out[] = $ptr;
}
return $out;
}
private function getZoneIdForDomain($domainId = null)
{
if (empty($domainId))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_DOMAIN_ID_NOT_EXISTS);
}
$clientid = $_SESSION['uid'];
$details = query::query("
SELECT CONCAT('#', tbldomains.id, ' ', tbldomains.domain) AS group_name,
:type `type`,
tbldomains.id AS relid,
dns_manager2_zone.id AS zone_id,
dns_manager2_zone.name AS zone_name,
dns_manager2_zone.status AS zone_status
FROM tbldomains
LEFT JOIN dns_manager2_zone ON dns_manager2_zone.type = :type2
AND dns_manager2_zone.relid = tbldomains.id
AND dns_manager2_zone.clientid = :clientid
WHERE tbldomains.status IN ('Active')
AND tbldomains.id = :domainid
AND tbldomains.userid = :clientid2", array(
'type' => ZoneTypeEnum::DOMAIN,
'type2' => ZoneTypeEnum::DOMAIN,
'clientid' => $clientid,
'clientid2' => $clientid,
'domainid' => $domainId
))->fetch();
if(empty($details))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND], __FUNCTION__), self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND);
}
if(empty($details['zone_id']))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_ZONE_NOT_FOUND], __FUNCTION__), self::REGISTRAR_ZONE_NOT_FOUND);
}
return $details['zone_id'];
}
/**
* This function is used to get Current Module Name from DB
* @return String Module Name is returned
* @throws RegistrarDNSException An exception is thrown if no domainId has been set or nothign has been found in DB
*/
public function getModuleName()
{
if(empty($this->domainId))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_DOMAIN_ID_NOT_EXISTS);
}
if(!isset($_SESSION['uid']))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_USER_ID_NOT_EXISTS], __FUNCTION__), self::REGISTRAR_USER_ID_NOT_EXISTS);
}
$clientId = $_SESSION['uid'];
$details = query::query("
SELECT tbldomains.id AS domainId,
tbldomains.registrar AS registrar
FROM tbldomains
WHERE tbldomains.status IN ('Active')
AND tbldomains.id = :domainId
AND tbldomains.userid = :clientId", array(
'domainId' => $this->domainId,
'clientId' => $clientId,
))->fetch();
if(empty($details))
{
throw new RegistrarDNSException(sprintf(self::$MSGS_LIST[self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND], __FUNCTION__), self::REGISTRAR_DOMAIN_DETAILS_NOT_FOUND);
}
$this->moduleName = $details['registrar'];
return $details['registrar'];
}
/**
* This function is used to create dynamic file with DNS mamagement functions in it
* Generasted file is later included so the registrar could recognize a set of function so DNS Management section could be activated
* @param String $currentPath Current path location of the DNS Manager hook file
* @return String Name of the file to be included is returned. void in case of errors
*/
public function creatreFunctionsFile($currentPath)
{
$sourceLocation = $currentPath.DS.'storage'.DS.'registrar'.DS.'mocks'.DS.'DNSMockFunction.txt';
$moduleName = $this->getModuleName();
$locaction = $currentPath.DS.'storage'.DS.'registrar'.DS.'functions'.DS;
$file = $locaction . $moduleName . "_DNSFunctions.php";
if(!file_exists($file))
{
if(!is_readable($currentPath.DS.'storage'.DS.'registrar'.DS.'mocks'.DS))
{
throw new \Exception(
main\mgLibs\lang::T('Directory').': '.
$currentPath . '/storage/registrar/mocks '.
main\mgLibs\lang::T('is_not_readable')
);
}
$fileContent = str_replace("%module_name%", $moduleName, file_get_contents($sourceLocation));
if(!is_writable($locaction))
{
throw new \Exception(
main\mgLibs\lang::T('Directory').': '.
$locaction.
main\mgLibs\lang::T('is_not_writable')
);
}
file_put_contents($file, $fileContent);
}
return $file;
}
/**
* This function is used to get Parameters saved for currently used module
* @param String $module Name of the module we want to find parameters for
* @return Array Collection of parameted is returned
* @throws RegistrarDNSException An Exception is thrown in case of Errors
*/
public function getModuleParams($module = null)
{
if (empty($module))
{
$module = $this->getModuleName();
}
if (empty($module))
{
throw new RegistrarDNSException();
}
$q = query::query(
"SELECT * FROM `tblregistrars` WHERE `registrar`= :registrar ", array(
'registrar' => $module
))->fetchAll();
$params = array();
foreach ($q as $row)
{
$params[$row['setting']] = decrypt($row['value']);
}
return $params;
}
public function getTld()
{
$tld = false;
$domain = \MGModule\DNSManager2\models\whmcs\domains\domain::factory($this->domainId);
if($domain)
{
$name = $domain->domain;
$pos = mb_stripos($name, '.');
$tld = mb_substr($name, $pos);
}
return $tld;
}
public function getPackage()
{
$tldExtension = $this->getTld();
$tld = main\models\whmcs\domains\pricing\domainpricing::getExtensionId($tldExtension); // id
if($tld)
{
$dnsRegistrar = main\models\custom\package\registrar\Repository::factory()
->byTld($tld)->one();
if($dnsRegistrar)
{
$this->package = new main\models\custom\package\Package($dnsRegistrar->packageid);
}
}
}
public function isPackageActive()
{
if($this->package)
{
if($this->package->status == "1" && $this->checkRegistrar())
{
return true;
}
}
return false;
}
public function checkPackageLimits($newDNSCollection)
{
//if(!$this->isPackageActive())
//{
// main\mgLibs\lang::T('');
//}
$counts = $this->countRecords($newDNSCollection);
$totalLimit = $this->getPackageTotalRecordsLimit();
if((int)$totalLimit < $counts['all'])
{
return main\mgLibs\lang::T('recordLimitExceded');
}
$recordsLimits = $this->getPackageRecordsLimits();
foreach($counts as $rName => $rCount)
{
if($rName == 'all')
{
continue;
}
if((int)$recordsLimits[$rName] < $rCount)
{
return main\mgLibs\lang::T('youCannotAddSpecificRecords')
.' '.$rName.' '.main\mgLibs\lang::T('limitExceded');
}
}
}
public function countRecords($newDNSCollection)
{
$counts = array('all' => 0);
foreach($newDNSCollection as $record)
{
if($record['hostname'] !== '')
{
$counts['all']++;
if($counts[$record['type']])
{
$counts[$record['type']]++;
}
else
{
$counts[$record['type']] = 1;
}
}
}
return $counts;
}
private function getPackageRecordsLimits()
{
$allowedRecords = main\models\custom\package\setting\Repository::factory()
->byPackageID($this->package->id)
->byKey(main\models\custom\package\setting\PackageSettingEnum::ALLOWED_RECORD_TYPES)
->one();
$result = $allowedRecords ? unserialize($allowedRecords->value) : false;
return $result;
}
private function getPackageTotalRecordsLimit()
{
$limit = main\models\custom\package\item\Repository::factory()
->byPackageID($this->package->id)
->byType(main\models\custom\package\item\PackageItemTypeEnum::DOMAIN)
->byRelID($this->domainId)
->one();
return $limit->limit;
}
public function checkRegistrar()
{
$reg = mysql\query::query("SELECT registrar FROM tbldomains WHERE id = :domainID ",
array('domainID' => $this->domainId))->fetchColumn("registrar");
$path = str_replace( DS.'addons'.DS.'DNSManager2'.DS.'mgLibs'.DS.'custom'.DS.'dns'.DS.'utils',
DS.'registrars'.DS.$reg.DS.$reg.'.php', __DIR__ );
if(file_exists($path))
{
if (!function_exists("getRegistrarConfigOptions")) {
require ROOTDIR . "/includes/registrarfunctions.php";
}
require_once $path;
if(!function_exists($reg.'_GetDNS') && !function_exists($reg.'_SaveDNS'))
{
return true;
}
}
return false;
}
}