andre 4 éve
szülő
commit
b2b4330be2
71 módosított fájl, 3570 hozzáadás és 1325 törlés
  1. 3 2
      modules/addons/DNSManager2/controllers/addon/admin/settings.php
  2. 10 3
      modules/addons/DNSManager2/controllers/addon/admin/tools.php
  3. 15 16
      modules/addons/DNSManager2/controllers/addon/admin/zones.php
  4. 28 24
      modules/addons/DNSManager2/controllers/addon/clientarea/dashboard.php
  5. 1 1
      modules/addons/DNSManager2/hooks.php
  6. 14 4
      modules/addons/DNSManager2/langs/english.php
  7. 8 0
      modules/addons/DNSManager2/mgLibs/custom/TaskManager.php
  8. 30 6
      modules/addons/DNSManager2/mgLibs/custom/dns/Strategy.php
  9. 190 121
      modules/addons/DNSManager2/mgLibs/custom/dns/SubmoduleAbstract.php
  10. 1 1
      modules/addons/DNSManager2/mgLibs/custom/dns/interfaces/SubmoduleInterface.php
  11. 273 0
      modules/addons/DNSManager2/mgLibs/custom/dns/record/RRSet.php
  12. 4 11
      modules/addons/DNSManager2/mgLibs/custom/dns/record/Record.php
  13. 1 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/ALIAS.php
  14. 1 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/ANAME.php
  15. 2 2
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/CNAME.php
  16. 1 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/DNAME.php
  17. 1 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/MX.php
  18. 3 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/NS.php
  19. 17 5
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/RecordTypeAbstract.php
  20. 10 1
      modules/addons/DNSManager2/mgLibs/custom/dns/record/type/SRV.php
  21. 0 21
      modules/addons/DNSManager2/mgLibs/custom/dns/record/validators/NameValidator.php
  22. 700 564
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53.php
  23. 25 1
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53/AWSRoute53API.php
  24. 61 18
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53/AWSRoute53ResponseParseHelper.php
  25. 178 53
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/Bind9.php
  26. 7 3
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/DNS4PSA.php
  27. 3 1
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/DNSMadeEasy.php
  28. 27 2
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/PowerDNS.php
  29. 7 1
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/PowerDNSV4.php
  30. 621 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero.php
  31. 33 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/AFSDBAdapter.php
  32. 27 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/ALIASAdapter.php
  33. 19 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/AbstractRCodeZeroAdapter.php
  34. 35 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/CAAAdapter.php
  35. 28 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/CNAMEAdapter.php
  36. 27 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DNAMEAdapter.php
  37. 34 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DNSKEYAdapter.php
  38. 36 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DSAdapter.php
  39. 33 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/HINFOAdapter.php
  40. 25 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/LOCAdapter.php
  41. 35 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/MINFOAdapter.php
  42. 35 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/MXAdapter.php
  43. 44 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/NAPTRAdapter.php
  44. 28 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/NSAdapter.php
  45. 28 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/PTRAdapter.php
  46. 34 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/RPAdapter.php
  47. 46 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/SOAAdapter.php
  48. 39 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/SRVAdapter.php
  49. 24 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/TLSAAdapter.php
  50. 25 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/TXTAdapter.php
  51. 35 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/URIAdapter.php
  52. 21 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/RRSet.php
  53. 9 0
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/RecordFromServer.php
  54. 17 1
      modules/addons/DNSManager2/mgLibs/custom/dns/submodules/SimpleDNSV8.php
  55. 28 10
      modules/addons/DNSManager2/mgLibs/custom/helpers/ZoneUpdateHelper.php
  56. 1 1
      modules/addons/DNSManager2/mgLibs/custom/manager/LogHelper.php
  57. 20 0
      modules/addons/DNSManager2/mgLibs/custom/manager/RecordHelper.php
  58. 12 4
      modules/addons/DNSManager2/mgLibs/custom/manager/ZoneCreator.php
  59. 0 5
      modules/addons/DNSManager2/mgLibs/custom/task/ImportToFileWHMCS.php
  60. 170 64
      modules/addons/DNSManager2/mgLibs/custom/task/RecordSynchronization.php
  61. 3 3
      modules/addons/DNSManager2/mgLibs/lang.php
  62. 7 1
      modules/addons/DNSManager2/models/whmcs/servers/server.php
  63. 2 2
      modules/addons/DNSManager2/moduleVersion.php
  64. 1 1
      modules/addons/DNSManager2/templates/admin/pages/settings/global.tpl
  65. 1 1
      modules/addons/DNSManager2/templates/clientarea/default/assets/css/layout.css
  66. 1 1
      modules/addons/DNSManager2/templates/clientarea/default/main.tpl
  67. 47 47
      modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-rdns.tpl
  68. 121 121
      modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-record.tpl
  69. 117 117
      modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-zone.tpl
  70. 22 22
      modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/edit-rdns.tpl
  71. 58 58
      modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/set-records.tpl

+ 3 - 2
modules/addons/DNSManager2/controllers/addon/admin/settings.php

@@ -19,6 +19,7 @@ use \MGModule\DNSManager2\models\custom\set;
 use \MGModule\DNSManager2\models\custom\zone;
 use \MGModule\DNSManager2\mgLibs\custom\helpers\ZoneLogger;
 use WHMCS\Database\Capsule as DB;
+use MGModule\DNSManager2\models\custom\task;
 
 class settings extends main\mgLibs\process\abstractController{
     private $set_form = array(
@@ -962,14 +963,14 @@ class settings extends main\mgLibs\process\abstractController{
 
         $migrations = custom\TaskManager::getXTasksObjects(0, 'Migration:main', 0, false);
         foreach($migrations as $migration) {
-            if($migration->getParams('from') == $input['id'] || $migration->getParams('to') == $input['id']) {
+            if($migration->getStatus() != task\TaskStatusEnum::FINISHED && $migration->getParams('from') == $input['id'] || $migration->getParams('to') == $input['id']) {
                 return AjaxResponse::I()->addError('you_cannot_delete_server_with_task')->toArray();
             }
         }
 
         $imports = custom\TaskManager::getXTasksObjects(0, 'Import:main', 0, false);
         foreach($imports as $import) {
-            if($import->getParams('from') == $input['id']) {
+            if($import->getStatus() != task\TaskStatusEnum::FINISHED && $import->getParams('from') == $input['id']) {
                 return AjaxResponse::I()->addError('you_cannot_delete_server_with_task')->toArray();
             }
         }

+ 10 - 3
modules/addons/DNSManager2/controllers/addon/admin/tools.php

@@ -365,10 +365,17 @@ class tools extends main\mgLibs\process\abstractController{
 
         foreach($helper->get() as $task)
         {
-            $userid = $this->getUserIdByHostingDomain($task->data['domain']);
-            if(empty($userid))
+            $hostingDomainUserId = $this->getUserIdByHostingDomain($task->data['domain']);
+            $domainUserId        = $this->getUserIdByDomain($task->data['domain']);
+
+            if( $hostingDomainUserId && $domainUserId )
             {
-                $userid = $this->getUserIdByDomain($task->data['domain']);
+                $itemPriority = custom\manager\GlobalSettingHelper::getSetting(globalsetting\GlobalSettingEnum::IMPORT_PRIORITY);
+                $userid       = (int)$itemPriority === 1 ? $hostingDomainUserId : $domainUserId;
+            }
+            else
+            {
+                $userid = $hostingDomainUserId ?? $domainUserId;
             }
 
             $user = false;

+ 15 - 16
modules/addons/DNSManager2/controllers/addon/admin/zones.php

@@ -92,7 +92,12 @@ class zones extends main\mgLibs\process\abstractController{
         $class = 'MGModule\DNSManager2\mgLibs\custom\dns\record\type\\' . $input['new_record_type'];
         if(class_exists($class)) {
             $zone = new zone\Zone($input['id']);
-            $packageSettings = $zone->getPackage()->getSettings('default_ttl');
+            $package = $zone->getPackage();
+            if(!$package)
+            {
+                return AjaxResponse::I()->addRawError(main\mgLibs\lang::T('package_not_found'))->toArray();
+            }
+            $packageSettings = $package->getSettings('default_ttl');
             $packageSettings = unserialize($packageSettings);
 
             $vars['ttl_default_value'] = $packageSettings[$input['new_record_type']];
@@ -161,27 +166,15 @@ class zones extends main\mgLibs\process\abstractController{
 
             $zoneLoggerManager = new ZoneLoggerManager();
             foreach($input['add_record'] as $counter => $record_data) {
-
                 $record = $this->createRecordFromInput($record_data);
+                //todo przenieść kiedyś do addRecord
+                $module->validateRecord($record);
                 $record->nameToAbsolute($zone->name);
 
-                if($record->validateName() !== true) {
-                    return AjaxResponse::I()->addError($record->validateName())->toArray();
-                }
-
-                try {
-                    $record->rdata->validate();
-                } catch (Exception $e) {
-                    return AjaxResponse::I()->addError($e->getMessage())->toArray();
-                }
-
                 $module->addRecord($record);
                 $zoneLoggerManager->logAddRecordToZone($zone, $record);
             }
 
-
-
-
             EmailNotificationHelper::sendClientNotificationUsingZoneID(DefaultNotifications::GENERAL_ZONE_ALTERED_NOTIFICATION, $input['id'], ['zone_name' => $zone->name]);
 //            LogHelper::addSuccessLogUsingZoneID('Edit Zone', '', $input['id']);
             
@@ -302,7 +295,8 @@ class zones extends main\mgLibs\process\abstractController{
         AjaxResponse::I()->addInfo('record_removed');
         return $this->editZoneJSON($input, $vars);
     }
-    
+
+    //dodac parametr moduł dns
     private function createRecordFromInput($input) {
         $record = Record::tryToCreateFromArray($input);
         $record->rdata->setDataFromArray($input['field']);
@@ -335,6 +329,11 @@ class zones extends main\mgLibs\process\abstractController{
     public function createZoneJSON($input, $vars = array())
     {
         $zone = new zone\Zone($input['id']);
+        if(!$zone->getServer() || !$zone->getServer()->isEnabled())
+        {
+            AjaxResponse::I()->addError("server_disabled_or_not_exists", ['server' => $zone->getServer() ? $zone->getServer()->name : '']);
+            return AjaxResponse::I()->toArray();
+        }
         try
         {
             $module = $zone->getModule();

+ 28 - 24
modules/addons/DNSManager2/controllers/addon/clientarea/dashboard.php

@@ -166,16 +166,6 @@ class dashboard extends main\mgLibs\process\abstractController
             $vars['is_not_domain'] = true;
         }
 
-        $vars['is_ip_required'] = true;
-        $groupMainDomain = custom\helpers\ZoneSettings::getOneDomain($vars['relid'], $vars['type']);
-	    if($groupMainDomain)
-	    {
-            $simulateCreation = new ZoneCreator($groupMainDomain->domain, $vars['type'], $vars['relid'], false, $groupMainDomain->clientID());
-            if ($simulateCreation->server) {
-                $vars['is_ip_required'] = $simulateCreation->server->getModule()->isIPRequired();
-            }
-        }
-
         if($input['type'] == '0' && $input['relid'] == '0')
         {
             $vars['custom_ip'] = 1;
@@ -186,6 +176,11 @@ class dashboard extends main\mgLibs\process\abstractController
             $creator = new ZoneCreator('',  $input['type'], $input['relid']);
         }
 
+        $vars['is_ip_required'] = true;
+        if ($creator->server) {
+            $vars['is_ip_required'] = $creator->server->getModule()->isIPRequired();
+        }
+        
         $defaultSets = $creator->getPackage()->getAdminSets();
         $adminSets   = set\Repository::factory()->adminOnly()->get();
 
@@ -810,16 +805,9 @@ class dashboard extends main\mgLibs\process\abstractController
             } else {
                 $record = $this->createRecordFromInputData($input);
                 $record->encode();
-                $record->nameToAbsolute($zone->name);
-                if($record->validateName() !== true) {
-                    return AjaxResponse::I()->addError($record->validateName())->toArray();
-                }
+                $module->validateRecord($record);
 
-                try {
-                    $record->rdata->validate();
-                } catch (Exception $e) {
-                    return AjaxResponse::I()->addError($e->getMessage())->toArray();
-                }
+                $record->nameToAbsolute($zone->name);
                 $module->addRecord($record);
             }
 
@@ -2262,7 +2250,6 @@ class dashboard extends main\mgLibs\process\abstractController
     {
         $add = false;
         $imported = false;
-
         foreach($input['zone'] as $resultId => $data)
         {
             if($data['import'] != 'on' && !isset($input['justOne']))
@@ -2280,16 +2267,13 @@ class dashboard extends main\mgLibs\process\abstractController
             $task = custom\TaskManager::getTaskObjectByID($result->taskid);
 
             $main_task = $task->getParent();
-
             $result->data['resultid'] = $resultId;
             $result->data['domain']   = custom\helpers\IdnaHelper::idnaDecode($result->data['name']);
             $child = $main_task->addChild('import', $result->data);
-
             $result->data['status'] = 'importing';
             $result->data['taskid'] = $child->obj()->id;
 
             $result->save();
-
             if(isset($input['runNow']))
             {
                 $child->run();
@@ -2315,7 +2299,6 @@ class dashboard extends main\mgLibs\process\abstractController
         {
             AjaxResponse::I()->addError('no_zone_selected_for_import_export');
         }
-
         return AjaxResponse::I()->toArray();
     }
 
@@ -2343,6 +2326,27 @@ class dashboard extends main\mgLibs\process\abstractController
 
         foreach($helper->get() as $task)
         {
+            $relid = $task->data['relid'];
+            if($relid != '0')
+            {
+                try
+                {
+                    switch($task->data['type']) {
+                        case '1':
+                            $status = new main\models\whmcs\domains\domain($relid);
+                            break;
+                        case '2':
+                            $status = new main\models\whmcs\service\service($relid);
+                            break;
+                    }
+                }
+                catch (\Exception $e)
+                {
+                    continue;
+                }
+                if(!$status || $status->status() != 'Active')
+                    continue;
+            }
 
             $vars['data'][] = $this->dataTablesParseRow('import-row',
                 array(

+ 1 - 1
modules/addons/DNSManager2/hooks.php

@@ -277,7 +277,7 @@ function DNSManager2RemoveZones($type, $relid, $clientid)
             }
             $zone->delete();
             main\mgLibs\custom\manager\LogHelper::addSuccessLogUsingZone('Remove Zone', '-', $zone);
-            main\mgLibs\custom\manager\EmailNotificationHelper::sendClientNotificationUsingZone(main\mgLibs\custom\manager\DefaultNotifications::GENERAL_ZONE_REMOVED_NOTIFICATION, $zone);
+            main\mgLibs\custom\manager\EmailNotificationHelper::sendClientNotificationUsingZone(main\mgLibs\custom\manager\DefaultNotifications::GENERAL_ZONE_REMOVED_NOTIFICATION, $zone, ['zone_name' => $zone->name]);
         } 
         catch (Exception $e)
         {

+ 14 - 4
modules/addons/DNSManager2/langs/english.php

@@ -94,6 +94,7 @@ $_LANG['addonAA']['zones']['close']                  = "Close";
 $_LANG['addonAA']['zones']['edit_zone']              = "Edit Zone";
 $_LANG['addonAA']['zones']['save_changes']           = "Save Changes";
 $_LANG['addonAA']['zones']['zone_already_taken']     = "The selected zone is already taken";
+$_LANG['addonAA']['zones']['master_server_disabled'] = "The primary (master) server is disabled: ";
 
 $_LANG['addonAA']['zones']['infos']['zone_created_successfully'] = "The new zone has been created successfully";
 $_LANG['addonAA']['zones']['infos']['set_added_successfully']    = "The selected record has been changed successfully";
@@ -103,8 +104,9 @@ $_LANG['addonAA']['zones']['infos']['zone_created']              = "The selected
 $_LANG['addonAA']['zones']['infos']['zone_removed_from_server']  = "The selected zone has been deleted from the server successfully";
 $_LANG['addonAA']['zones']['infos']['zone_removed_from_whmcs']   = "The selected zone has been removed from WHMCS successfully";
 
-$_LANG['addonAA']['zones']['errors']['not_allowed_ip']           = "You cannot use this IP";
-$_LANG['addonAA']['zones']['errors']['ip_on_blacklist']          = "The selected IP is on the blacklist";
+$_LANG['addonAA']['zones']['errors']['not_allowed_ip']                  = "You cannot use this IP";
+$_LANG['addonAA']['zones']['errors']['ip_on_blacklist']                 = "The selected IP is on the blacklist";
+$_LANG['addonAA']['zones']['errors']['server_disabled_or_not_exists']   = "Server :server: is disabled or doesn't exist.";
 
 $_LANG['addonAA']['zones']['remove_record']  = "Remove Record";
 $_LANG['addonAA']['zones']['add_new_record'] = "Add New Record";
@@ -284,6 +286,9 @@ $_LANG['addonAA']['zones']['record_field_info']['HTTPRED']['title']        = 'If
 $_LANG['addonAA']['zones']['record_field_info']['HTTPRED']['hardlink']     = '0 or 1 any request that is made for this record will have the path removed after the fully qualified domain name portion of the requested URL';
 $_LANG['addonAA']['zones']['record_field_info']['HTTPRED']['redirecttype'] = 'One of \'Hidden Frame Masked\', \'STANDARD - 301\', or \'STANDARD - 302\'';
 $_LANG['addonAA']['zones']['record_field_info']['HTTPRED']['link']         = 'Valid URL';
+
+$_LANG['addonAA']['defaultNameValidationError'] = "Valid name must contain only alphanumeric characters (including language specific), numbers, dots, hyphens and underscore.";
+$_LANG['addonAA']['srvNameValidationError']     = "Name must follow the pattern: _{service}._{protocol}.{domainOptional}, for example: `_sip._tcp.example.com` , `_sip._tls`, `_sip._tls.`. Keep in mind that some servers require SRV record name to end with a dot.";
 //===========================================================================================================
 //===========================================/RECORDS INFO===================================================
 //===========================================================================================================
@@ -496,6 +501,10 @@ $_LANG['addonAA']['tools']['cron_backupper']                    = "Cron Backup S
 $_LANG['addonAA']['tools']['backup_zones']                      = "Backup Zones";
 $_LANG['addonAA']['tools']['delete_backup_after_time']          = "Delete Backup After";
 $_LANG['addonAA']['tools']['automatic_backup_limit']            = "Automatic Backup Limit";
+$_LANG['addonAA']['tools']['terminatedServices']                = 'Terminated Services';
+$_LANG['addonAA']['tools']['cancelledServices']                 = 'Cancelled Services';
+$_LANG['addonAA']['tools']['suspendedServices']                 = 'Suspended Services';
+
 
 $_LANG['addonAA']['tools']['migration']              = "Migration";
 $_LANG['addonAA']['tools']['zones_moved_total']      = "Total Number Of Zones Moved";
@@ -548,6 +557,7 @@ $_LANG['addonAA']['settings']['remove_set_confirmation_body'] = "Are you sure th
 $_LANG['addonAA']['settings']['remove_all_logs_confirmation'] = "Are you sure that you want to remove all logs?";
 
 $_LANG['addonAA']['zones']['cannot_find_class']             = "Cannot find a class";
+$_LANG['addonAA']['zones']['package_not_found']             = "Package for the zone has not been found.";
 $_LANG['addonAA']['zones']['errors']['select_one_at_least'] = "You have to select at least one zone";
 
 $_LANG['addonAA']['settings']['infos']['connection_success']     = "The server connection has been established successfully";
@@ -727,7 +737,7 @@ $_LANG['addonCA']['dashboard']['errors']['you_cant_remove_this_zone_because_it_i
 $_LANG['addonCA']['dashboard']['errors']['you_cant_remove_zone_that_is_locked']                                        = "You cannot remove zone that is locked";
 $_LANG['addonCA']['dashboard']['errors']['you_cant_remove_this_record_because_it_is_not_belongs_to_one_of_your_zones'] = "You cannot remove this record because it does not belong to your zones";
 $_LANG['addonCA']['dashboard']['errors']['invalid_zone_id']                                                            = "The selected zone ID is invalid";
-$_LANG['addonCA']['dashboard']['errors']['invalid_module_config']                                                      = "Either the Module Name or Module Configuration Data fields are empty";
+$_LANG['addonCA']['dashboard']['master_server_disabled']                                                               = "Master server disabled: ";
 $_LANG['addonCA']['dashboard']['infos']['zone_removed_successfully']                                                   = "The selected zone has been removed successfully";
 
 $_LANG['addonCA']['dashboard']['add_record']                           = "Add Record";
@@ -1349,4 +1359,4 @@ $_LANG['addonAA']['settings']['showDnsManagerButton_desc']                    =
 
 $_LANG['addonAA']['cron']['you_cannot_edit_this_zone_because_it_is_terminated_on_server'] = 'You cannot edit this zone because it is terminated on the server';
 
-$_LANG['addonAA']['settings']['do_not_remove_empty_ptr_zones'] = "Don't remove empty PTR Zones";
+$_LANG['addonAA']['settings']['do_not_remove_empty_ptr_zones'] = "Don't remove empty PTR Zones";

+ 8 - 0
modules/addons/DNSManager2/mgLibs/custom/TaskManager.php

@@ -113,4 +113,12 @@ class TaskManager {
     public static function getTaskResultByID($resultid) {
         return new task\result\TaskResult($resultid);
     }
+
+    public static function getTasksByNameAndStatus($taskName, $taskStatus)
+    {
+        $rep = new task\Repository();
+        $rep->byName($taskName);
+        $rep->byStatus($taskStatus);
+        return $rep->get();
+    }
 }

+ 30 - 6
modules/addons/DNSManager2/mgLibs/custom/dns/Strategy.php

@@ -27,9 +27,6 @@ class Strategy
         'terminateZone' => [
             self::AFTER => 'RecordSynchronization:syncTerminateZone'
         ],
-        'getRDNSRecord' => [
-            self::AFTER => 'RecordSynchronization:syncGetRDNSRecord'
-        ],
         'removeRDNS'    => [
             self::AFTER => 'RecordSynchronization:syncRemoveRDNS'
         ],
@@ -47,7 +44,8 @@ class Strategy
 
     public function __call($name, $args)
     {
-        $result = count($args) == 1 ? $this->module->{$name}($args[0]) : $this->module->{$name}($args);
+        $result = call_user_func_array(array($this->module,$name), $args);
+        //$result = count($args) == 1 ? $this->module->{$name}($args[0]) : $this->module->{$name}($args);
         if (isset(self::$events[$name][self::AFTER]))
         {
             $this->addTask($name, $args, self::AFTER);
@@ -68,9 +66,30 @@ class Strategy
     private function addTask($name, $args, $when)
     {
         $packageServerRepo = new \MGModule\DNSManager2\models\custom\package\server\Repository();
-        if ($packageServerRepo->byServerID($this->module->getServer()->id)->get()[0]->isMaster())
+        $packageServer = $packageServerRepo->byServerID($this->module->getServer()->id)->get()[0];
+        if (!$packageServer || !$packageServer->isMaster())
+        {
+            return;
+        }
+        switch ($name)
         {
-            TaskManager::addTask(self::$events[$name][$when], ['submodule' => $this, 'values' => $args, 'zone' => $this->getZone()]);
+            case "deleteRecord":
+            case "editRecord":
+            case "addRecord":
+                if (($this->taskExistsWithStatusStart('deleteRecord', self::AFTER)
+                     || $this->taskExistsWithStatusStart('editRecord', self::AFTER)
+                     || $this->taskExistsWithStatusStart('addRecord', self::AFTER)))
+                {
+                    return;
+                }
+                TaskManager::addTask(self::$events[$name][$when] . "_" . $this->module->getDomain(), ['submodule' => $this, 'values' => $args, 'zone' => $this->getZone()]);
+                break;
+            default:
+                if (!$this->taskExistsWithStatusStart($name, $when))
+                {
+                    TaskManager::addTask(self::$events[$name][$when] . "_" . $this->module->getDomain(), ['submodule' => $this, 'values' => $args, 'zone' => $this->getZone()]);
+                }
+                break;
         }
     }
 
@@ -80,4 +99,9 @@ class Strategy
         return $zoneRepository->byServerID($this->getServer()->id)->byName($this->getDomain())->get()[0];
     }
 
+    private function taskExistsWithStatusStart(string $name, string $when): bool
+    {
+        return count(TaskManager::getTasksByNameAndStatus(self::$events[$name][$when] . "_" . $this->module->getDomain(), 'start')) > 0;
+    }
+
 }

+ 190 - 121
modules/addons/DNSManager2/mgLibs/custom/dns/SubmoduleAbstract.php

@@ -1,6 +1,8 @@
 <?php
 
 namespace MGModule\DNSManager2\mgLibs\custom\dns;
+
+use InvalidArgumentException;
 use MGModule\DNSManager2 as main;
 use MGModule\DNSManager2\mgLibs\custom\dns\utils\IDNAConvert;
 use MGModule\DNSManager2\mgLibs\custom\dns\utils\ReverseDNSHelper;
@@ -11,21 +13,21 @@ use MGModule\DNSManager2\models\custom\server\Server;
 //TODO: optymalizacja dla multiple delete?
 abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInterface, interfaces\SubmoduleInterface
 {
-    protected $hasTTL           =   false;
-    protected $supportRDNS      =   false;
-    protected $requireIP        =   false;
-    protected $supportImport    =   false;
-    protected $supportDNSSEC    =   false;
+    protected $hasTTL = false;
+    protected $supportRDNS = false;
+    protected $requireIP = false;
+    protected $supportImport = false;
+    protected $supportDNSSEC = false;
     protected $isRemoveableDefaultServerRecordsSet = false;
-    
-    protected $availableTypes = array();
-    protected $configFields = array();
-    
+
+    protected $availableTypes = [];
+    protected $configFields = [];
+
     protected $domain = false;
     protected $config;
-    
+
     protected $debug = false;
-    
+
     protected $ip = '';
 
     /**
@@ -37,15 +39,15 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
      * @var main\mgLibs\custom\helpers\ZoneLogger\Manager
      */
     protected $zoneLoggerManager;
-    
+
     public function __construct()
-    { 
-        $this->supportRDNS      =   $this instanceof interfaces\SubmoduleRDNSInterface;
-        $this->requireIP        =   $this instanceof interfaces\SubmoduleIPInterface;
-        $this->hasTTL           =   $this instanceof interfaces\SubmoduleTTLInterface;
-        $this->supportImport    =   $this instanceof interfaces\SubmoduleImportInterface;
-        $this->supportDNSSEC    =   $this instanceof interfaces\SubmoduleDNSSecInterface;
-        $this->isRemoveableDefaultServerRecordsSet    =   $this instanceof interfaces\SubmoduleRemoveDefaultRecords;
+    {
+        $this->supportRDNS                         = $this instanceof interfaces\SubmoduleRDNSInterface;
+        $this->requireIP                           = $this instanceof interfaces\SubmoduleIPInterface;
+        $this->hasTTL                              = $this instanceof interfaces\SubmoduleTTLInterface;
+        $this->supportImport                       = $this instanceof interfaces\SubmoduleImportInterface;
+        $this->supportDNSSEC                       = $this instanceof interfaces\SubmoduleDNSSecInterface;
+        $this->isRemoveableDefaultServerRecordsSet = $this instanceof interfaces\SubmoduleRemoveDefaultRecords;
     }
 
     /**
@@ -67,10 +69,10 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
     public function getNameServers($index = false)
     {
         $nameservers = $this->server->getNameservers($index);
-        $out = [];
+        $out         = [];
         foreach ($nameservers as $nameserver)
         {
-            if('' != $nameserver->name)
+            if ('' != $nameserver->name)
             {
                 $out[] = $nameserver->name;
             }
@@ -80,46 +82,55 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
 
     public function setDomain($domain)
     {
-        $idna = new IDNAConvert();
+        $idna         = new IDNAConvert();
         $this->domain = $idna->encode($domain);
     }
-    
+
     //TODO: podstawowe parsowanie błędóww!
-    
+
     //TODO: sprawdzic specjalne znaki kodowane przez WHMCSa
-    public function setConfiguration($config) {
-        if(isset($config['password']))
+    public function setConfiguration($config)
+    {
+        if (isset($config['password']))
+        {
             $config['password'] = html_entity_decode($config['password']);
-        
+        }
+
         $this->config = $config;
     }
-    
-    public function getConfiguration() {
+
+    public function getConfiguration()
+    {
         return $this->configFields;
     }
-    
-    public function getAvailableRecordTypes() {
+
+    public function getAvailableRecordTypes()
+    {
         $array = $this->availableTypes;
         sort($array);
         return $array;
     }
-    
-    public function isIPRequired() {
+
+    public function isIPRequired()
+    {
         return $this->requireIP;
     }
-    
-    public function isRDNSSupported() {
+
+    public function isRDNSSupported()
+    {
         return $this->supportRDNS;
     }
-    
-    public function isTTLEnabled() {
+
+    public function isTTLEnabled()
+    {
         return $this->hasTTL;
     }
-    
-    public function isImportSupported() {
+
+    public function isImportSupported()
+    {
         return $this->supportImport;
     }
-    
+
     /**
      * Return true if DNSSEC is supported
      * @return bool
@@ -128,36 +139,43 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
     {
         return $this->supportDNSSEC;
     }
-    
-    
-    public function enableDebug() {
+
+
+    public function enableDebug()
+    {
         $this->debug = true;
     }
-    
-    public function disableDebug() {
+
+    public function disableDebug()
+    {
         $this->debug = false;
     }
-    
-    protected function log($message, $type = 'INFO') { //TODO: dodać interfejs + domyślna klasa logera
-        if(!$this->debug)
-            return ;
-        
+
+    protected function log($message, $type = 'INFO')
+    { //TODO: dodać interfejs + domyślna klasa logera
+        if (!$this->debug)
+        {
+            return;
+        }
+
         $class = explode('\\', get_class($this));
-        $path = __DIR__ . DIRECTORY_SEPARATOR . 'submodules' . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . array_pop($class);
-        if(!file_exists($path))
-            @mkdir($path, 0777,true);
+        $path  = __DIR__ . DIRECTORY_SEPARATOR . 'submodules' . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . array_pop($class);
+        if (!file_exists($path))
+        {
+            @mkdir($path, 0777, true);
+        }
         $filepath = $path . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log';
-        $line = date('Y-m-d H:i:s') . "\t" . $type . "\t" . $message . "\r\n"; 
+        $line     = date('Y-m-d H:i:s') . "\t" . $type . "\t" . $message . "\r\n";
         @file_put_contents($filepath, $line, FILE_APPEND);
-    }        
-    
+    }
+
     public function hasRecordSupport($recordType)
     {
-        if(in_array($recordType, $this->availableTypes))
+        if (in_array($recordType, $this->availableTypes))
         {
             return true;
         }
-        
+
         return false;
     }
 
@@ -165,22 +183,26 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
     {
         return $this->disabledPopulateNs;
     }
-    
+
 //===========================================================================================================
 //===========================================STANDARD rDNS===================================================
 //===========================================================================================================
-    protected function getClone($ip) {
+    protected function getClone($ip)
+    {
         $clone = clone $this;
         $clone->setDomain(ReverseDNSHelper::reverseZoneName($ip));
         return $clone;
     }
-    
-    public function getRDNSRecord($ip) {
+
+    public function getRDNSRecord($ip)
+    {
         $clone = $this->getClone($ip);
 
-        foreach($clone->getRecords('PTR') as $record) {
-            if($record->name == ReverseDNSHelper::reverseRecordName($ip) 
-                    || $record->nameToRelative($clone->domain) == ReverseDNSHelper::reverseRecordName($ip)) {
+        foreach ($clone->getRecords('PTR') as $record)
+        {
+            if ($record->name == ReverseDNSHelper::reverseRecordName($ip)
+                || $record->nameToRelative($clone->domain) == ReverseDNSHelper::reverseRecordName($ip))
+            {
 
                 return $record;
             }
@@ -188,53 +210,58 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
         return false;
     }
 
-    public function getRDNSRecordsNumber($ip) {
-        $clone = $this->getClone($ip);
-        $result =  $clone->getRecords('PTR');
+    public function getRDNSRecordsNumber($ip)
+    {
+        $clone  = $this->getClone($ip);
+        $result = $clone->getRecords('PTR');
         return count($result);
     }
-    
-    public function updateRDNS($ip, $ttl = false, $value = false) {
-        if(!$this->isRDNSSupported()) {
+
+    public function updateRDNS($ip, $ttl = false, $value = false)
+    {
+        if (!$this->isRDNSSupported())
+        {
             throw new exceptions\DNSSubmoduleException('rDNS is not supported', SubmoduleExceptionCodes::COMMAND_ERROR);
         }
-        
+
         $clone = $this->getClone($ip);
-        if(!$clone->zoneExists()) {
+        if (!$clone->zoneExists())
+        {
             $clone->activateZone();
             $this->addNSRecordsRDNS($clone);
         }
         $record = $this->getRDNSRecord($ip);
-        if($record === FALSE) {
-            $record = ReverseDNSHelper::createPTRRecord($ip, $ttl, $value?:$this->domain);
+        if ($record === false)
+        {
+            $record = ReverseDNSHelper::createPTRRecord($ip, $ttl, $value ?: $this->domain);
             $clone->addRecord($record);
-            return ;
+            return;
         }
-        
+
         $record->rdata->setFirstProperty($value);
         $clone->editRecord($record);
     }
-    
+
     public function addNSRecordsRDNS($clone)
     {
         $zone = (new main\models\custom\zone\Repository())->byName($this->domain)->one();
-        if($zone)
+        if ($zone)
         {
             $defaultSet = $zone->getPackage()->getDefaultSet();
         }
 
         // if default record set is set for package
-        if($defaultSet)
+        if ($defaultSet)
         {
             $records = $defaultSet->getRecords();
-            foreach($records as $r)
+            foreach ($records as $r)
             {
-                if($r->type === 'NS')
+                if ($r->type === 'NS')
                 {
-                    $record         = new record\Record();
-                    $record->name   = $r->name;
-                    $record->type   = $r->type;
-                    $record->ttl    = $r->ttl;
+                    $record       = new record\Record();
+                    $record->name = $r->name;
+                    $record->type = $r->type;
+                    $record->ttl  = $r->ttl;
                     $record->createRDATAObject('NS');
                     $record->rdata->setFirstProperty($r->rdata['nsdname']);
                     $clone->addRecord($record);
@@ -243,40 +270,43 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
             }
         }
     }
-    
-    public function removeRDNS($ip) {  
 
-        if(!$this->isRDNSSupported()) {
+    public function removeRDNS($ip)
+    {
+
+        if (!$this->isRDNSSupported())
+        {
             throw new exceptions\DNSSubmoduleException('rDNS is not supported', SubmoduleExceptionCodes::COMMAND_ERROR);
         }
-        
-        $clone = $this->getClone($ip);  
 
-        if(!$clone->zoneExists()) {
-            return ;
+        $clone = $this->getClone($ip);
+
+        if (!$clone->zoneExists())
+        {
+            return;
         }
 
         $record = $this->getRDNSRecord($ip);
 
-        if($record)
+        if ($record)
         {
             $clone->deleteRecord($record);
         }
-        
+
         $rdnsNumber = $this->getRDNSRecordsNumber($ip);
-        if($rdnsNumber < 1 && GlobalSettingHelper::getSetting(GlobalSettingEnum::DO_NOT_REMOVE_EMPTY_PTR_ZONES) !== 'on') // 1 cause w just deleted one record
+        if ($rdnsNumber < 1 && GlobalSettingHelper::getSetting(GlobalSettingEnum::DO_NOT_REMOVE_EMPTY_PTR_ZONES) !== 'on') // 1 cause w just deleted one record
         {
             $clone->terminateZone();
         }
     }
-    
+
     public function wipeRecords()
     {
-        $records        = $this->getRecords();
-        $cannotDelete   = array();
-        foreach($records as &$record)
-        {  
-            if($record->type === 'SOA')
+        $records      = $this->getRecords();
+        $cannotDelete = [];
+        foreach ($records as &$record)
+        {
+            if ($record->type === 'SOA')
             {
                 continue;
             }
@@ -284,63 +314,64 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
             try
             {
                 $this->deleteRecord($record);
-                $records    = $this->getRecords();                
-            } 
-            catch (\Exception $ex) 
+                $records = $this->getRecords();
+            }
+            catch (\Exception $ex)
             {
                 $record->name   = $recordName;
                 $cannotDelete[] = $record;
                 continue;
             }
         }
-        
-        return $cannotDelete;     
+
+        return $cannotDelete;
     }
-    
+
     public function wipeRecordsArray(array $wipe)
     {
         $records = $this->getRecords();
-        foreach($wipe as $r)
+        foreach ($wipe as $r)
         {
-            foreach($records as &$record)
+            foreach ($records as &$record)
             {
-                if($r->name == $record->name && $r->type == $record->type && $r->rdata == $record->rdata)
+                if ($r->name == $record->name && $r->type == $record->type && $r->rdata == $record->rdata)
                 {
                     try
                     {
                         $r->line = $record->line;
                         $this->deleteRecord($r);
-                        $records = $this->getRecords();                      
-                    } 
-                    catch (\Exception $ex) 
+                        $records = $this->getRecords();
+                    }
+                    catch (\Exception $ex)
                     {
                         continue;
                     }
                 }
             }
-        }   
+        }
         return;
     }
 //===========================================================================================================
 //===========================================IP DEFAULT======================================================
 //===========================================================================================================
-    public function setIP($ip) {
+    public function setIP($ip)
+    {
         $this->ip = $ip;
     }
-    
+
     public function removeDefaultServerRecordsSet($defaultModuleRecords)
     {
-        if($this->isRemoveableDefaultServerRecordsSet)
+        if ($this->isRemoveableDefaultServerRecordsSet)
         {
             $this->removeDefaultServerRecords($defaultModuleRecords);
         }
     }
-    
+
     public function isRemoveableDefaultServerRecordsSet()
     {
         return $this->isRemoveableDefaultServerRecordsSet;
     }
-    
+
     public function hasCustomEditRecords()
     {
         return $this instanceof interfaces\SubmoduleCustomEditRecords;
@@ -348,7 +379,7 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
 
     public function getLoggerManager()
     {
-        if(!$this->zoneLoggerManager)
+        if (!$this->zoneLoggerManager)
         {
             $this->zoneLoggerManager = new main\mgLibs\custom\helpers\ZoneLogger\Manager($_SESSION['uid']);
         }
@@ -359,4 +390,42 @@ abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInt
     {
         return $this->domain;
     }
+
+    public function validateRecord($record)
+    {
+        foreach ($this->validators() as $validatorName => $validatingFunction)
+        {
+            if (!$validatingFunction($record->{$validatorName}, $record->type))
+            {
+                $langName     = strtolower($record->type) . ucfirst($validatorName) . "ValidationError";
+                $errorMessage = main\mgLibs\lang::absoluteT('addonAA', $langName);
+                $errorMessage = (!$errorMessage || $langName == $errorMessage)
+                    ? main\mgLibs\lang::absoluteT('addonAA', 'defaultNameValidationError')
+                    : $errorMessage;
+
+                throw new InvalidArgumentException($errorMessage);
+            }
+        }
+
+        $record->rdata->validate();
+    }
+
+    protected function validators(): array
+    {
+        return [
+            'name' =>
+                function (string $name, string $type = '')
+                {
+                    $idna = new IDNAConvert();
+                    $name = $name == '@' ? $name : $idna->encode($name);
+                    switch (strtolower($type))
+                    {
+                        case 'srv':
+                            return preg_match("(_\w+\._\w+\.*)", $name) == 1;
+                        default:
+                            return preg_match("/^[@\w.-]+$/", $name) == 1;
+                    }
+                }
+        ];
+    }
 }

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/interfaces/SubmoduleInterface.php

@@ -31,7 +31,7 @@ interface SubmoduleInterface {
     
     /** 
      * @param string $name typy rekordów do pobrania, jeśli nie ustawione to zwraca wszystkie dostępne
-     * @return dns\record\Record|Array 
+     * @return dns\record\Record|array
      * @throws \MGModule\DNSManager2\mgLibs\custom\dns\exceptions\DNSSubmoduleException
      */
     public function getRecords($recordType = false);

+ 273 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/record/RRSet.php

@@ -0,0 +1,273 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\record;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+use ReflectionClass;
+
+class RRSet
+{
+    /** @var string */
+    private $name;
+    /** @var string */
+    private $type;
+    /** @var int */
+    private $ttl;
+    /** @var array */
+    private $records;
+
+    public function __construct( string $name = '', string $type = '', int $ttl = 14400, array $records = [] )
+    {
+        $this->name    = $name;
+        $this->type    = $type;
+        $this->ttl     = $ttl;
+        $this->records = $records;
+    }
+
+    /**
+     * @param Record $record
+     *
+     * @return bool
+     */
+    public function isRecordMatchingRRset( Record $record ): bool
+    {
+        return $record->type === $this->getType() && $record->name === $this->getName();
+    }
+
+    /**
+     * @param RecordTypeAbstract $rdata
+     *
+     * @return bool
+     */
+    public function isRdataMatchingRRset( RecordTypeAbstract $rdata ): bool
+    {
+        $rdataClassName = (new ReflectionClass($rdata))->getShortName();
+        return $rdataClassName === $this->getType();
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEmpty(): bool
+    {
+        return !(bool)$this->records;
+    }
+
+    public function countRecords()
+    {
+        return count($this->getRecords());
+    }
+
+    /**
+     * @param Record $recordToCompare
+     *
+     * @return bool
+     */
+    public function recordExists( Record $recordToCompare ): bool
+    {
+        /** @var Record $record */
+        foreach( $this->getRecords() as $record )
+        {
+            if( $record->line === $recordToCompare->line )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param Record $recordToCompare
+     *
+     * @return bool
+     */
+    public function replaceRecord( Record $recordToCompare ): bool
+    {
+        /** @var Record $record */
+        foreach( $this->records as $index => $record )
+        {
+            if( $record->line === $recordToCompare->line )
+            {
+                $this->records[$index] = $recordToCompare;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param Record $recordToFind
+     *
+     * @return false|int|string
+     */
+    public function findRecord(Record $recordToFind)
+    {
+        /** @var Record $record */
+        foreach( $this->records as $index => $record )
+        {
+            if( $record->line === $recordToFind->line )
+            {
+                return $index;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param RRSet $set
+     *
+     * @return bool
+     */
+    public function compare( RRSet $set ): bool
+    {
+        if( $this->getType() !== $set->getType() )
+        {
+            return false;
+        }
+
+        if( $this->getName() !== $set->getName() )
+        {
+            return false;
+        }
+
+        if( $this->getTtl() !== $set->getTtl() )
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @param RRSet $set
+     *
+     * @return RRSet
+     */
+    public function merge( RRSet $set ): self
+    {
+        if( !$this->compare($set) )
+        {
+            return $this;
+        }
+
+        foreach( $set->getRecords() as $record )
+        {
+            if( !$this->recordExists($record) )
+            {
+                $this->pushRecord($record);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param Record $record
+     *
+     * @return bool
+     */
+    public function pushRecord( Record $record ): bool
+    {
+        if( $this->isRecordMatchingRRset($record) )
+        {
+            $this->records[] = $record;
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param Record $recordToRemove
+     *
+     * @return $this
+     */
+    public function removeRecord( Record $recordToRemove ): self
+    {
+        foreach( $this->records as $index => $record )
+        {
+            if( $record->line === $recordToRemove->line )
+            {
+                unset($this->records[$index]);
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return RRSet
+     */
+    public function setName( string $name ): self
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @param string $type
+     *
+     * @return RRSet
+     */
+    public function setType( string $type ): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecords(): array
+    {
+        return $this->records;
+    }
+
+    /**
+     * @param array $records
+     *
+     * @return RRSet
+     */
+    public function setRecords( array $records ): self
+    {
+        $this->records = $records;
+        return $this;
+    }
+
+    /**
+     * @return int
+     */
+    public function getTtl(): int
+    {
+        return $this->ttl;
+    }
+
+    /**
+     * @param int $ttl
+     *
+     * @return RRSet
+     */
+    public function setTtl( int $ttl ): self
+    {
+        $this->ttl = $ttl;
+        return $this;
+    }
+
+}

+ 4 - 11
modules/addons/DNSManager2/mgLibs/custom/dns/record/Record.php

@@ -124,7 +124,6 @@ class Record implements \ArrayAccess, ArrayPrinter
     public function unify($domain)
     {
         $this->rdata->unify();
-        //name na koncu kropka - strreplace
         $this->name = str_replace("." . $domain . ".", "", $this->name);
 
         //@todo - this should be fixed in submodule!
@@ -188,13 +187,13 @@ class Record implements \ArrayAccess, ArrayPrinter
     public function nameToAbsolute($zoneName, $with_dot = true)
     {
         //Make sure we don't have dot at the end
-        $zoneName = rtrim(IdnaHelper::idnaDecode($zoneName), '.');
+        $zoneName = rtrim(IdnaHelper::idnaDecode(IdnaHelper::idnaEncode($zoneName)), '.');
 
         $this->name = str_replace('@', $zoneName . '.', $this->name);
         //Match string that has :
         //0 or 1 dot at the beggingng (handles subdomains and domains)
         //Domain name with one or more dots at the END of the string
-        $domainRegex = '/\.*' . preg_quote($zoneName) . '\.+$/';
+        $domainRegex = '/\.*' . preg_quote($zoneName,'/') . '\.+$/';
         //Try to remove the domain
         $newRecordName = preg_replace($domainRegex, '', IdnaHelper::idnaDecode($this->name), 1);
         $newRecordName = preg_replace('/\.{2,}/', '.', $newRecordName);
@@ -214,12 +213,12 @@ class Record implements \ArrayAccess, ArrayPrinter
     public function nameToRelative($zoneName)
     {
         //Make sure we don't have dot at the end
-        $zoneName = rtrim(IdnaHelper::idnaDecode($zoneName), '.');
+        $zoneName = rtrim(IdnaHelper::idnaDecode(IdnaHelper::idnaEncode($zoneName)), '.');
 
         //Match string that has :
         //0 or 1 dot at the beggingng (handles subdomains and domains)
         //Domain name with one or more dots at the END of the string
-        $domainRegex = '/\.?' . preg_quote($zoneName) . '\.+$/';
+        $domainRegex = '/\.?' . preg_quote($zoneName,'/') . '\.+$/';
         $newName     = preg_replace($domainRegex, '', IdnaHelper::idnaDecode($this->name), 1);
         $this->name  = $newName ?: '@';
         return $this->name;
@@ -266,10 +265,4 @@ class Record implements \ArrayAccess, ArrayPrinter
         }
         return $out;
     }
-
-
-    public function validateName()
-    {
-        return NameValidator::validateName($this->name, $this->type);
-    }
 }

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/ALIAS.php

@@ -10,6 +10,6 @@ class ALIAS extends RecordTypeAbstract
      * @return bool
      */
     public function targetValidate($target){
-        return filter_var($target,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
+        return is_string($target);
     }
 }

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/ANAME.php

@@ -13,6 +13,6 @@ class ANAME extends RecordTypeAbstract {
      * @return bool
      */
     public function cnameValidate($aname){
-        return (filter_var($aname,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME) || filter_var($aname,FILTER_VALIDATE_IP));
+        return is_string($aname);
     }
 }

+ 2 - 2
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/CNAME.php

@@ -9,11 +9,11 @@ class CNAME extends RecordTypeAbstract {
     public $cname;
 
     /**
-     * @param string $cname Valid ip or domain
+     * @param string $cname Valid string
      * @return bool
      */
     public function cnameValidate($cname){
-        return (filter_var($cname,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME) || filter_var($cname,FILTER_VALIDATE_IP));
+        return is_string($cname);
     }
 
     public function unify()

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/DNAME.php

@@ -13,6 +13,6 @@ class DNAME extends RecordTypeAbstract {
      * @return bool
      */
     public function targetValidate($target){
-        return filter_var($target,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
+        return is_string($target);
     }
 }

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/MX.php

@@ -25,7 +25,7 @@ class MX extends RecordTypeAbstract {
      * @return bool
      */
     public function exchangeValidate($exchange){
-        return filter_var($exchange,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
+        return is_string($exchange);
     }
 
     public function unify()

+ 3 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/NS.php

@@ -2,6 +2,8 @@
 
 namespace MGModule\DNSManager2\mgLibs\custom\dns\record\type;
 
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
 class NS extends RecordTypeAbstract {
     /**
      * A <domain-name> which specifies a host which should be authoritative for the specified class and domain.
@@ -12,7 +14,7 @@ class NS extends RecordTypeAbstract {
      * @return bool
      */
     public function nsdnameValidate($nsdname){
-        return filter_var($nsdname,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
+        return filter_var(IdnaHelper::idnaEncode($nsdname),FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
     }
 
     public function unify()

+ 17 - 5
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/RecordTypeAbstract.php

@@ -24,12 +24,24 @@ abstract class RecordTypeAbstract implements \ArrayAccess {
         if($k !== false)
             $this->$k = $value;
     }
-    
-    public function setDataFromArray($array) {
-        foreach($array as $k => $value) {
+
+    public function setDataFromArray( $array )
+    {
+        foreach( $array as $k => $value )
+        {
             $k = strtolower($k);
-            if(property_exists($this, $k))
-                $this->$k = $value;
+            if( property_exists($this, $k) )
+            {
+                $customMethodName = 'set' . ucfirst($k);
+                if( method_exists($this,$customMethodName ) )
+                {
+                    $this->{$customMethodName}($value);
+                }
+                else
+                {
+                    $this->$k = $value;
+                }
+            }
         }
     }
     

+ 10 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/record/type/SRV.php

@@ -10,6 +10,15 @@ class SRV extends RecordTypeAbstract {
     public $port;
     public $target;
 
+    /**
+     * @param string $value
+     * @return void
+     */
+    public function setTarget( $value )
+    {
+        $this->target = rtrim($value, '.') . '.';
+    }
+
     /**
      * @param int $priority validate number
      * @return bool
@@ -36,6 +45,6 @@ class SRV extends RecordTypeAbstract {
      * @return bool
      */
     public function targetValidate($target){
-        return substr($target, -1) == '.' && filter_var($target,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
+        return filter_var($target,FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
     }
 }

+ 0 - 21
modules/addons/DNSManager2/mgLibs/custom/dns/record/validators/NameValidator.php

@@ -1,21 +0,0 @@
-<?php
-
-namespace MGModule\DNSManager2\mgLibs\custom\dns\record\validators;
-
-class NameValidator
-{
-    const SRV_REGEX = '%(^_\w+\._\w+\.)%';
-
-    const SRV_MESSAGE = "Name must follow pattern: _{service}._{protocol}.{name}, for example: _sip._tcp.example.com";
-    public static function validateName($name, $type)
-    {
-        switch (strtoupper($type)) {
-            case 'SRV':
-                return preg_match( self::SRV_REGEX,$name) ? true : self::SRV_MESSAGE;
-            default:
-                return true;
-
-        }
-    }
-
-}

+ 700 - 564
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53.php

@@ -1,564 +1,700 @@
-<?php
-
-namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
-
-use MGModule\DNSManager2\mgLibs\custom\dns;
-use MGModule\DNSManager2\mgLibs\custom\dns\exceptions\DNSSubmoduleException;
-use MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
-use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53 as AwsRouteHelpers;
-use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53API;
-use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53ResponseInterface;
-
-class AWSRoute53 extends dns\SubmoduleAbstract implements 
-        interfaces\SubmoduleRDNSInterface, 
-        interfaces\SubmoduleTTLInterface, 
-        interfaces\SubmoduleImportInterface,
-        interfaces\SubmoduleRemoveDefaultRecords,
-        interfaces\SubmoduleCustomParseEditZoneInput
-{
-    public $configFields = [
-        'accessKeyId'     => [
-            'friendlyName' => 'Access Key Id',
-            'validators'   => [
-                'required' => 'required'
-            ]
-        ],
-        'secretAccessKey' => [
-            'friendlyName' => 'Secret Access Key',
-            'type'         => 'password',
-            'validators'   => [
-                'required' => 'required'
-            ]
-        ],
-        'region'          => [
-            'friendlyName' => 'Region'
-        ],
-        'delegation_set'  => [
-            'friendlyName' => 'Delegation Set Id',
-            'validators'   => [
-            ]
-        ],
-        'soa_edit'        => [
-            'friendlyName' => 'Allow To Edit SOA Records',
-            'type'         => 'yesno'
-        ],
-        'delete_aws_ns'   => [
-            'friendlyName' => 'Delete AWS NS Records After Zone Creation',
-            'type'         => 'yesno'
-        ],
-        'use_white_label_nameservers' => [
-            'friendlyName' => 'Using White Label Nameservers',
-            'type'         => 'yesno',
-            'help'         => 'Check this box if you are using White Label Nameservers'
-        ],
-    ];
-
-    public $availableTypes = ['A', 'AAAA', 'ALIAS', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SOA', 'SPF', 'SRV', 'TXT'];
-    /** @var AWSRoute53API */
-    private $connection;
-    
-    public function testConnection()
-    {
-        if(!extension_loaded('SimpleXML'))
-        {
-            throw new DNSSubmoduleException('This server requires SimpleXML PHP extension', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
-        }
-        
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->testConnection();
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
-        }
-                
-        return true;
-    }
-
-    public function getNameServers( $index = false )
-    {
-        if($this->config['use_white_label_nameservers'] === 'on')
-        {
-            return (array)parent::getNameServers();
-        }
-        $this->loadConnectionInstance();
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if( $zones->getResponseType() === 'error' )
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if( !$zone )
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        $hostedZone = json_decode(json_encode($this->connection->getHostedZoneById($zone)->getParsedResponseBody()), true);
-        return (array)$hostedZone['DelegationSet']['NameServers']['NameServer'];
-    }
-
-    public function getRecords($recordType = false)
-    {
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if(!$zone)
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        /** @var AWSRoute53ResponseInterface $records */
-        $records = $this->connection->listRecords($zone);
-        if($records->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $availableTypes = $recordType ? [$recordType] : $this->availableTypes;
-        
-        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $availableTypes, $this->config['soa_edit'] === 'on');
-    }
-    
-    public function addRecord(dns\record\Record $record)
-    {
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if(!$zone)
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        $aliasType = $this->gatAliasTypeIfRequired($record);
-        if(!$aliasType)
-        {
-            $rSet = $this->matchSetForRecord($record);
-        }
-
-        if(isset($rSet) && $rSet)
-        {
-            AwsRouteHelpers\AWSRoute53ResponseParseHelper::mergeHostsRecordsForRdata($rSet, $record, $rSet->type);
-
-            return $this->editRecord($rSet);
-        }
-        $record->name = $record->nameToAbsolute($this->domain);
-
-        /** @var AWSRoute53ResponseInterface $aRecord */
-        $aRecord = $this->connection->createRecord($zone, $record, $aliasType);
-        if($aRecord->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }        
-        
-        return true;
-    }
-    
-    public function editRecord(dns\record\Record $record)
-    {  
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if(!$zone)
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $aliasType = $this->gatAliasTypeIfRequired($record);
-
-        /** @var AWSRoute53ResponseInterface $aRecord */
-        $aRecord = $this->connection->updateRecord($zone, $record, $aliasType);
-        if($aRecord->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }        
-        
-        return true;
-    }
-    
-    public function deleteRecord(dns\record\Record $record)
-    {
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if(!$zone)
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $aliasType = $this->gatAliasTypeIfRequired($record);
-        if(!$aliasType)
-        {
-            $rSet = $this->matchSetForRecord($record);
-        }
-
-        if(isset($rSet) && $rSet)
-        {
-            return $this->editRecord($rSet);
-        }
-
-        /** @var AWSRoute53ResponseInterface $aRecord */
-        $aRecord = $this->connection->deleteRecord($zone, $record, $aliasType);
-        if($aRecord->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }        
-        
-        return true;
-    }
-    
-    public function zoneExists()
-    {
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-
-        return $zone ? true : false;
-    }
-    
-    public function activateZone()
-    {
-        $this->loadConnectionInstance();
-        $this->log('ACTIVATE ZONE');
-        if($this->zoneExists())
-        {
-            throw new DNSSubmoduleException('Domain name already exists!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        if(empty($this->domain))
-        {
-            throw new DNSSubmoduleException('Domain name is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
-        }
-
-        /** @var AWSRoute53ResponseInterface $zone */
-        $zone = $this->connection->createZone($this->domain, $this->config['delegation_set']);
-        if($zone->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        return true;
-    }
-   
-    public function terminateZone()
-    {
-        $records = $this->getRecords();
-        foreach($records as $record)
-        {
-            try 
-            {
-                $this->deleteRecord($record);
-            }
-            catch(DNSSubmoduleException $exc)
-            {
-                
-            }
-        }
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($this->domain);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zonesId = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
-        if($zonesId === false)
-        {
-            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        /** @var AWSRoute53ResponseInterface $deleted */
-        $deleted = $this->connection->deleteHostedZone($zonesId);
-        if($deleted->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($deleted->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        return true;
-    }
-    
-    public function getZones()
-    {
-        $this->loadConnectionInstance();
-        $zones = $this->connection->listZones();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::zoneListXmlToArray($zones->getParsedResponseBody());
-    }
-   
-    private function loadConnectionInstance()
-    {
-        if(!$this->connection)
-        {
-            $responseHandler = new AwsRouteHelpers\AWSRoute53Response();
-            $requestHandler = new AwsRouteHelpers\AWSRoute53Request(
-                    $responseHandler,
-                    $this->config['accessKeyId'], 
-                    $this->config['secretAccessKey'],
-                    $this->config['region']                    
-                );
-            
-            $apiHandler = new AwsRouteHelpers\AWSRoute53API($requestHandler);
-            
-            $this->connection = $apiHandler;
-        }
-    }
-    
-    public function updateRDNS($ip, $ttl = false, $value = false)
-    {
-        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
-        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
-        
-        if(!$zoneId)
-        {
-            $zoneId = $this->createRevDnsZone($revDnsZoneName);
-        }
-        
-        $revRecord = dns\utils\ReverseDNSHelper::createPTRRecord($ip, $ttl, $value);
-        $revRecord->name .= '.'.$revDnsZoneName;
-
-        /** @var AWSRoute53ResponseInterface $aRecord */
-        $aRecord = $this->connection->updateRecord($zoneId, $revRecord);
-        if($aRecord->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }        
-        
-        return true;
-    }
-
-    public function removeRDNS($ip)
-    {
-        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
-        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
-        if(!$zoneId)
-        {
-            return true;
-        }
-
-        /** @var AWSRoute53ResponseInterface $records */
-        $records = $this->connection->listRecords($zoneId);
-        if($records->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        $recordPrev = dns\utils\ReverseDNSHelper::reverseRecordName($ip);
-        $recordName = $recordPrev.'.'.$revDnsZoneName;
-        
-        $recordToRemove = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findPtrRecordByName($records->getParsedResponseBody(), $recordName);
-
-        if(!$recordToRemove)
-        {
-            return true;
-        }
-
-        /** @var AWSRoute53ResponseInterface $aRecord */
-        $aRecord = $this->connection->deleteRecord($zoneId, $recordToRemove);
-        if($aRecord->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }        
-        
-        return true;        
-    }
-
-    public function getRDNSRecord($ip)
-    {
-        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
-        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
-        
-        if(!$zoneId)
-        {
-            return [];
-        }
-
-        /** @var AWSRoute53ResponseInterface $records */
-        $records = $this->connection->listRecords($zoneId);
-        if($records->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $this->availableTypes, $this->config['soa_edit'] === 'on');
-    }
-    
-    private function getRevDNSZoneID($zoneName)
-    {
-        $this->loadConnectionInstance();
-
-        /** @var AWSRoute53ResponseInterface $zones */
-        $zones = $this->connection->listZonesByName($zoneName);
-        if($zones->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-        
-        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $zoneName);
-        
-        return $zone ? : false;
-    }
-    
-    private function createRevDnsZone($revDnsZoneName)
-    {
-        /** @var AWSRoute53ResponseInterface $zone */
-        $zone = $this->connection->createZone($revDnsZoneName);
-        if($zone->getResponseType() === 'error')
-        {
-            throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
-        }
-
-        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::getZoneIdFromCreateConfirmation($zone->getParsedResponseBody());
-    }
-    
-    private function gatAliasTypeIfRequired($record)
-    {
-        if($record->type !== 'ALIAS')
-        {
-            return false;
-        }
-        
-        $dNamePos = stripos($record->name, trim($this->domain, '.'));
-        if(!$dNamePos && $dNamePos !== 0)
-        {
-            $record->name .= '.'.$this->domain;
-        }
-        
-        $deafultType = 'A';        
-        
-        $recordsList = $this->getRecords();
-        foreach($recordsList as $rec)
-        {
-            $trimedName = trim($rec->name, '.');
-            if(($rec->name === $record->rdata->target || $trimedName === $record->rdata->target ) && $rec->type === 'AAAA')
-            {
-                return 'AAAA';
-            }
-        }
-        
-        return $deafultType;
-    }
-    
-    public function convertInputFormData(&$input)
-    {
-        AwsRouteHelpers\AWSRoute53ResponseParseHelper::convertInputFormData($input);
-    }
-
-    /**
-     * @param dns\record\Record $record
-     * @return bool|dns\record\Record
-     * @throws DNSSubmoduleException
-     */
-    public function matchSetForRecord( dns\record\Record $record)
-    {
-        $cRecord = clone $record;
-        $allowedTypes = ['MX', 'A', 'AAAA', 'NS'];
-        if(!in_array($cRecord->type, $allowedTypes, true) )
-        {
-            return false;
-        }
-        
-        $basicRdata = $cRecord->rdata->toString();
-        $cRecord->createRDATAObject($cRecord->type);
-        $cRecord->rdata = null;
-        $recordList = $this->getRecords();
-        foreach($recordList as $rec)
-        {
-            if($cRecord->type === $rec->type && trim($cRecord->name, '.') === trim($rec->name, '.'))
-            {
-                if($rec->rdata->toString() === $basicRdata)
-                {
-                    continue;
-                }
-                
-                AwsRouteHelpers\AWSRoute53ResponseParseHelper::mergeHostsRecordsForRdata($cRecord, $rec, $cRecord->type);
-            }
-        }
-
-        return $cRecord->rdata !== null ? $cRecord : false;
-    }
-    
-    public function removeDefaultServerRecords($defaultModuleRecords)
-    {
-        if($this->config['delete_aws_ns'] !== 'on')
-        {
-            return false;
-        }
-
-        $cRecords = $this->getRecords();
-        foreach($cRecords as $cRecord)
-        {
-            if($cRecord->type !== 'NS')
-            {
-                continue;
-            }
-            
-            $found = false;
-            foreach($defaultModuleRecords as $dRecord)
-            {
-                if($this->areRecordsEqual($cRecord, $dRecord))
-                {
-                    $found = true;
-                    break;
-                }
-            }
-            
-            if($found === false)
-            {
-                $this->deleteRecord($cRecord);
-            }
-        }
-        return true;
-    }
-    
-    private function areRecordsEqual( dns\record\Record $rec1, dns\record\Record $rec2)
-    {
-        $recAname = rtrim($rec1->name,'.');
-        $recBname = rtrim($rec2->name,'.');
-
-        return $recAname === $recBname &&
-               $rec1->type === $rec2->type &&
-               $rec1->ttl === $rec2->ttl &&
-               $rec1->rdata->toString() === $rec2->rdata->toString();
-    }
-}
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
+
+use MGModule\DNSManager2\mgLibs\custom\dns;
+use MGModule\DNSManager2\mgLibs\custom\dns\exceptions\DNSSubmoduleException;
+use MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
+use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53 as AwsRouteHelpers;
+use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53API;
+use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53ResponseInterface;
+use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53\AWSRoute53ResponseParseHelper;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class AWSRoute53 extends dns\SubmoduleAbstract implements 
+        interfaces\SubmoduleRDNSInterface, 
+        interfaces\SubmoduleTTLInterface, 
+        interfaces\SubmoduleImportInterface,
+        interfaces\SubmoduleRemoveDefaultRecords,
+        interfaces\SubmoduleCustomParseEditZoneInput
+{
+    public $configFields = [
+        'accessKeyId'     => [
+            'friendlyName' => 'Access Key Id',
+            'validators'   => [
+                'required' => 'required'
+            ]
+        ],
+        'secretAccessKey' => [
+            'friendlyName' => 'Secret Access Key',
+            'type'         => 'password',
+            'validators'   => [
+                'required' => 'required'
+            ]
+        ],
+        'region'          => [
+            'friendlyName' => 'Region'
+        ],
+        'delegation_set'  => [
+            'friendlyName' => 'Delegation Set Id',
+            'validators'   => [
+            ]
+        ],
+        'soa_edit'        => [
+            'friendlyName' => 'Allow To Edit SOA Records',
+            'type'         => 'yesno'
+        ],
+        'delete_aws_ns'   => [
+            'friendlyName' => 'Delete AWS NS Records After Zone Creation',
+            'type'         => 'yesno'
+        ],
+        'use_white_label_nameservers' => [
+            'friendlyName' => 'Using White Label Nameservers',
+            'type'         => 'yesno',
+            'help'         => 'Check this box if you are using White Label Nameservers'
+        ],
+    ];
+
+    public $availableTypes = ['A', 'AAAA', 'ALIAS', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SOA', 'SPF', 'SRV', 'TXT'];
+    /** @var AWSRoute53API */
+    private $connection;
+    
+    public function testConnection()
+    {
+        if(!extension_loaded('SimpleXML'))
+        {
+            throw new DNSSubmoduleException('This server requires SimpleXML PHP extension', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
+        }
+        
+        $this->loadConnectionInstance();
+
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->testConnection();
+        if($zones->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
+        }
+                
+        return true;
+    }
+
+    public function getNameServers( $index = false )
+    {
+        if($this->config['use_white_label_nameservers'] === 'on')
+        {
+            return (array)parent::getNameServers();
+        }
+        $this->loadConnectionInstance();
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->listZonesByName($this->domain);
+        if( $zones->getResponseType() === 'error' )
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
+        if( !$zone )
+        {
+            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        $hostedZone = json_decode(json_encode($this->connection->getHostedZoneById($zone)->getParsedResponseBody()), true);
+        return (array)$hostedZone['DelegationSet']['NameServers']['NameServer'];
+    }
+
+    public function getRecords($recordType = false)
+    {
+        $this->loadConnectionInstance();
+
+        $zone = $this->getZone();
+
+        /** @var AWSRoute53ResponseInterface $records */
+        $records = $this->connection->listRecords($zone);
+        if($records->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        
+        $availableTypes = $recordType ? [$recordType] : $this->availableTypes;
+        
+        $records = AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $availableTypes, $this->config['soa_edit'] === 'on');
+
+        return array_map(function( dns\record\Record $record ) {
+            return $record;
+        }, $records);
+    }
+    
+    public function addRecord(dns\record\Record $record)
+    {
+        $this->loadConnectionInstance();
+
+        $zone = $this->getZone();
+        
+        $rrSets = $this->buildRRSets($this->getRecords());
+
+        //synchronization purposes - modifying record names to match AWS standards - shouldn't change workflow of Route53 module
+        $record->name = $this->prepareNameForSynchro($record->name, $this->domain);
+
+        if($record->type == 'TXT')
+        {
+            $this->explodeTxtRdata($record);
+        }
+
+        if( ($index = $this->findMatchingRecordSet($rrSets, $record)) !== false )
+        {
+            /** @var dns\record\RRSet $rrSet */
+            $rrSet = $rrSets[$index];
+            $rrSet->setTtl($record->ttl);
+            $rrSet->pushRecord($record);
+        }
+        else
+        {
+            $rrSet = new dns\record\RRSet($record->name, $record->type, $record->ttl, [$record]);
+        }
+
+        $response = $this->connection->updateRRSet($rrSet, $zone,$this->gatAliasTypeIfRequired($record));
+        if( $response->getResponseType() === 'error' )
+        {
+            throw new DNSSubmoduleException($response->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $this->resetConnection();
+        return true;
+    }
+    
+    public function editRecord(dns\record\Record $record)
+    {
+        $this->loadConnectionInstance();
+        $record->name = IdnaHelper::idnaEncode($record->name);
+        $zone   = $this->getZone();
+        $rrSets = $this->buildRRSets($this->getRecords());
+
+        //Record RRSet based on line property
+        if( ($recordToDeleteIndex = $this->findRecordInRecordSets($rrSets, $record)) !== false )
+        {
+            //RRSet Changed
+            if( $rrSets[$recordToDeleteIndex]->getName() !== $record->name )
+            {
+                //RRSet exist so we just push
+                if( ($recordToAddIntex = $this->findMatchingRecordSet($rrSets, $record)) !== false )
+                {
+                    $rrsetToAddRecord = $rrSets[$recordToAddIntex];
+                    $rrsetToAddRecord->pushRecord($record);
+                    $rrsetToAddRecord->setTtl($record->ttl);
+                }
+                else
+                {
+                    $rrsetToAddRecord = new dns\record\RRSet($record->name, $record->type, $record->ttl, [$record]);
+                }
+
+                $response = $this->connection->updateRRSet($rrsetToAddRecord,$zone,$this->gatAliasTypeIfRequired($record));
+
+                if($response->getResponseType() === 'error')
+                {
+                    throw new DNSSubmoduleException($response->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+                }
+
+                if( $rrSets[$recordToDeleteIndex]->countRecords() <= 1 )
+                {
+                    $response = $this->connection->removeRRSet($rrSets[$recordToDeleteIndex], $zone,$this->gatAliasTypeIfRequired($record));
+                }
+                else
+                {
+                    $rrSets[$recordToDeleteIndex]->removeRecord($record);
+                    $response = $this->connection->updateRRSet($rrSets[$recordToDeleteIndex], $zone,$this->gatAliasTypeIfRequired($record));
+                }
+            }
+            else
+            {
+                //RRSet Not Changed
+               $rrSets[$recordToDeleteIndex]->replaceRecord($record);
+               $rrSets[$recordToDeleteIndex]->setTtl($record->ttl);
+               $response = $this->connection->updateRRSet($rrSets[$recordToDeleteIndex], $zone,$this->gatAliasTypeIfRequired($record));
+            }
+
+            if($response->getResponseType() === 'error')
+            {
+                throw new DNSSubmoduleException($response->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+            }
+        }
+        else
+        {
+            throw new DNSSubmoduleException('Record Not Found on server', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
+        }
+
+        $this->resetConnection();
+        return true;
+    }
+    
+    public function deleteRecord(dns\record\Record $record)
+    {
+        $record->nameToAbsolute($this->domain);
+        $record->name = IdnaHelper::idnaEncode($record->name);
+        $this->loadConnectionInstance();
+
+        if(strpos($record->name, $this->domain) === false) {
+            $record->name .= '.' . $this->domain;
+        }
+        $zone = $this->getZone();
+
+        $rrSets = $this->buildRRSets($this->getRecords());
+        if( ($index = $this->findMatchingRecordSet($rrSets, $record)) !== false )
+        {
+            if( $rrSets[$index]->countRecords() <= 1 )
+            {
+                $response = $this->connection->removeRRSet($rrSets[$index],$zone,$this->gatAliasTypeIfRequired($record));
+            }
+            else
+            {
+                $rrSets[$index]->removeRecord($record);
+                $response =$this->connection->updateRRSet($rrSets[$index],$zone,$this->gatAliasTypeIfRequired($record));
+            }
+
+            if($response->getResponseType() === 'error')
+            {
+                throw new DNSSubmoduleException($response->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+            }
+        }
+        $this->resetConnection();
+
+        return true;
+    }
+    
+    public function zoneExists()
+    {
+        $this->loadConnectionInstance();
+
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->listZonesByName($this->domain);
+        if($zones->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        
+        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
+
+        return $zone ? true : false;
+    }
+    
+    public function activateZone()
+    {
+        $this->loadConnectionInstance();
+        $this->log('ACTIVATE ZONE');
+        if($this->zoneExists())
+        {
+            throw new DNSSubmoduleException('Domain name already exists!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        if(empty($this->domain))
+        {
+            throw new DNSSubmoduleException('Domain name is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
+        }
+
+        /** @var AWSRoute53ResponseInterface $zone */
+        $zone = $this->connection->createZone($this->domain, $this->config['delegation_set']);
+        if($zone->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $this->resetConnection();
+        return true;
+    }
+   
+    public function terminateZone()
+    {
+        $records = $this->getRecords();
+        foreach($records as $record)
+        {
+            try 
+            {
+                $this->deleteRecord($record);
+            }
+            catch(DNSSubmoduleException $exc)
+            {
+                
+            }
+        }
+        $this->loadConnectionInstance();
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->listZonesByName($this->domain);
+        if($zones->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        
+        $zonesId = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
+        if($zonesId === false)
+        {
+            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        /** @var AWSRoute53ResponseInterface $deleted */
+        $deleted = $this->connection->deleteHostedZone($zonesId);
+        if($deleted->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($deleted->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $this->resetConnection();
+        return true;
+    }
+    
+    public function getZones()
+    {
+        $zones = [];
+        $client = \Aws\Route53\Route53Client::factory([
+            'credentials' => [
+                'key'    => $this->config['accessKeyId'],
+                'secret' => $this->config['secretAccessKey'],
+            ],
+            'version'     => '2013-04-01',
+            'region'      => $this->config['region']
+        ]);
+
+        do
+        {
+            $params = ['MaxItems' => 100];
+
+            if( isset($result) )
+            {
+                $params['Marker'] = $result->get('NextMarker');
+            }
+
+            $result = $client->listHostedZones($params);
+            $zones[]  = array_column($result->get('HostedZones'), 'Name');
+        } while( $result->hasKey('NextMarker') );
+
+        $zones = array_merge(...$zones);
+        return array_combine($zones, array_fill(0,count($zones),''));
+    }
+   
+    private function loadConnectionInstance()
+    {
+        if(!$this->connection)
+        {
+            $responseHandler = new AwsRouteHelpers\AWSRoute53Response();
+            $requestHandler = new AwsRouteHelpers\AWSRoute53Request(
+                    $responseHandler,
+                    $this->config['accessKeyId'], 
+                    $this->config['secretAccessKey'],
+                    $this->config['region']                    
+                );
+            
+            $apiHandler = new AwsRouteHelpers\AWSRoute53API($requestHandler);
+            
+            $this->connection = $apiHandler;
+        }
+    }
+    
+    public function updateRDNS($ip, $ttl = false, $value = false)
+    {
+        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
+        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
+        
+        if(!$zoneId)
+        {
+            $zoneId = $this->createRevDnsZone($revDnsZoneName);
+        }
+        
+        $revRecord = dns\utils\ReverseDNSHelper::createPTRRecord($ip, $ttl, $value);
+        $revRecord->name .= '.'.$revDnsZoneName;
+
+        /** @var AWSRoute53ResponseInterface $aRecord */
+        $aRecord = $this->connection->updateRecord($zoneId, $revRecord);
+        if($aRecord->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }        
+
+        $this->resetConnection();
+
+        return true;
+    }
+
+    public function removeRDNS($ip)
+    {
+        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
+        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
+        if(!$zoneId)
+        {
+            return true;
+        }
+
+        /** @var AWSRoute53ResponseInterface $records */
+        $records = $this->connection->listRecords($zoneId);
+        if($records->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        $recordPrev = dns\utils\ReverseDNSHelper::reverseRecordName($ip);
+        $recordName = $recordPrev.'.'.$revDnsZoneName;
+        
+        $recordToRemove = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findPtrRecordByName($records->getParsedResponseBody(), $recordName);
+
+        if(!$recordToRemove)
+        {
+            return true;
+        }
+
+        /** @var AWSRoute53ResponseInterface $aRecord */
+        $aRecord = $this->connection->deleteRecord($zoneId, $recordToRemove);
+        if($aRecord->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($aRecord->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $this->resetConnection();
+        return true;        
+    }
+
+    public function getRDNSRecord($ip)
+    {
+        $revDnsZoneName = dns\utils\ReverseDNSHelper::reverseZoneName($ip);
+        $zoneId = $this->getRevDNSZoneID($revDnsZoneName);
+        
+        if(!$zoneId)
+        {
+            return [];
+        }
+
+        /** @var AWSRoute53ResponseInterface $records */
+        $records = $this->connection->listRecords($zoneId);
+        if($records->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($records->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $this->resetConnection();
+        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::prepareRecordList($records->getParsedResponseBody(), $this->availableTypes, $this->config['soa_edit'] === 'on');
+    }
+    
+    private function getRevDNSZoneID($zoneName)
+    {
+        $this->loadConnectionInstance();
+
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->listZonesByName($zoneName);
+        if($zones->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        
+        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $zoneName);
+        
+        return $zone ? : false;
+    }
+    
+    private function createRevDnsZone($revDnsZoneName)
+    {
+        /** @var AWSRoute53ResponseInterface $zone */
+        $zone = $this->connection->createZone($revDnsZoneName);
+        if($zone->getResponseType() === 'error')
+        {
+            throw new DNSSubmoduleException($zone->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        return AwsRouteHelpers\AWSRoute53ResponseParseHelper::getZoneIdFromCreateConfirmation($zone->getParsedResponseBody());
+    }
+    
+    private function gatAliasTypeIfRequired($record)
+    {
+        if($record->type !== 'ALIAS')
+        {
+            return false;
+        }
+        
+        $dNamePos = stripos($record->name, trim($this->domain, '.'));
+        if(!$dNamePos && $dNamePos !== 0)
+        {
+            $record->name .= '.'.$this->domain;
+        }
+        
+        $deafultType = 'A';        
+        
+        $recordsList = $this->getRecords();
+        foreach($recordsList as $rec)
+        {
+            $trimedName = trim($rec->name, '.');
+            if(($rec->name === $record->rdata->target || $trimedName === $record->rdata->target ) && $rec->type === 'AAAA')
+            {
+                return 'AAAA';
+            }
+        }
+        
+        return $deafultType;
+    }
+    
+    public function convertInputFormData(&$input)
+    {
+        AwsRouteHelpers\AWSRoute53ResponseParseHelper::convertInputFormData($input);
+    }
+
+    /**
+     * @param dns\record\Record $record
+     * @return bool|dns\record\Record
+     * @throws DNSSubmoduleException
+     */
+    public function matchSetForRecord( dns\record\Record $record)
+    {
+        $cRecord = clone $record;
+        $allowedTypes = ['MX', 'A', 'AAAA', 'NS'];
+        if(!in_array($cRecord->type, $allowedTypes, true) )
+        {
+            return false;
+        }
+        
+        $basicRdata = $cRecord->rdata->toString();
+        $cRecord->createRDATAObject($cRecord->type);
+        $cRecord->rdata = null;
+        $recordList = $this->getRecords();
+        foreach($recordList as $rec)
+        {
+            if($cRecord->type === $rec->type && trim($cRecord->name, '.') === trim($rec->name, '.'))
+            {
+                if($rec->rdata->toString() === $basicRdata)
+                {
+                    continue;
+                }
+                
+                AwsRouteHelpers\AWSRoute53ResponseParseHelper::mergeHostsRecordsForRdata($cRecord, $rec, $cRecord->type);
+            }
+        }
+
+        return $cRecord->rdata !== null ? $cRecord : false;
+    }
+    
+    public function removeDefaultServerRecords($defaultModuleRecords)
+    {
+        if($this->config['delete_aws_ns'] !== 'on')
+        {
+            return false;
+        }
+
+        $cRecords = $this->getRecords();
+        foreach($cRecords as $cRecord)
+        {
+            if($cRecord->type !== 'NS')
+            {
+                continue;
+            }
+            
+            $found = false;
+            foreach($defaultModuleRecords as $dRecord)
+            {
+                if($this->areRecordsEqual($cRecord, $dRecord))
+                {
+                    $found = true;
+                    break;
+                }
+            }
+            
+            if($found === false)
+            {
+                $this->deleteRecord($cRecord);
+            }
+        }
+        return true;
+    }
+    
+    private function areRecordsEqual( dns\record\Record $rec1, dns\record\Record $rec2)
+    {
+        $recAname = rtrim($rec1->name,'.');
+        $recBname = rtrim($rec2->name,'.');
+
+        return $recAname === $recBname &&
+               $rec1->type === $rec2->type &&
+               $rec1->ttl === $rec2->ttl &&
+               $rec1->rdata->toString() === $rec2->rdata->toString();
+    }
+
+    private function getZone()
+    {
+        /** @var AWSRoute53ResponseInterface $zones */
+        $zones = $this->connection->listZonesByName($this->domain);
+        if( $zones->getResponseType() === 'error' )
+        {
+            throw new DNSSubmoduleException($zones->getResponseMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+
+        $zone = AwsRouteHelpers\AWSRoute53ResponseParseHelper::findZoneOnZoneList($zones->getParsedResponseBody(), $this->domain);
+        if( !$zone )
+        {
+            throw new DNSSubmoduleException('Zone name is not valid!', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        return $zone;
+    }
+
+    public function buildRRSets( array $records ): array
+    {
+        $rrsets = [];
+        /** @var dns\record\Record $record */
+        foreach($records as $record)
+        {
+            if( ($index = $this->findMatchingRecordSet($rrsets, $record)) !== false )
+            {
+                $rrsets[$index]->pushRecord($record);
+            }
+            else
+            {
+                $rrsets[] = new dns\record\RRSet($record->name, $record->type, $record->ttl, [$record]);
+            }
+        }
+        return $rrsets;
+    }
+
+    public function findRecordInRecordSets($rrsets,dns\record\Record $record)
+    {
+        /** @var dns\record\RRSet $rrset */
+        foreach($rrsets as $index => $rrset)
+        {
+            if($rrset->recordExists($record))
+            {
+                return $index;
+            }
+        }
+        return false;
+    }
+
+    private function findMatchingRecordSet( array $rrsets, dns\record\Record $record )
+    {
+        /** @var dns\record\RRSet $rrset */
+        foreach($rrsets as $index => $rrset)
+        {
+            if($rrset->isRecordMatchingRRset($record))
+            {
+                return $index;
+            }
+        }
+        return false;
+    }
+
+    private function resetConnection()
+    {
+        $this->connection = null;
+    }
+
+    private function explodeTxtRdata(&$record)
+    {
+        //AWS doesn't alllow TXT rdata record to have more than 255 chars length - need to split it to a few strings
+        $rdataSplitted = str_split(trim($record->rdata->txtdata, '"'), 255);
+        $record->rdata->txtdata = '';
+        foreach ($rdataSplitted as $rdataPart) {
+            $record->rdata->txtdata .= '"' . $rdataPart . '"';
+        }
+    }
+
+    function prepareNameForSynchro($recordName, $domain)
+    {
+        $startsWithDot = substr($recordName, 0, 1) == '.';
+        $endsWithDot   = substr($recordName, -1) == '.';
+
+        if ($startsWithDot)
+        {
+            $recordName = substr($recordName, 1);
+        }
+        if (!$endsWithDot)
+        {
+            $recordName = $recordName . (empty($recordName) ? '' : '.') . $domain . '.';
+        }
+        return IdnaHelper::idnaEncode($recordName);
+    }
+}

+ 25 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53/AWSRoute53API.php

@@ -1,6 +1,7 @@
 <?php
 
 namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\RRSet;
 use MGModule\DNSManager2\mgLibs\custom\dns\submodules\AWSRoute53 as awsRoute53;
 
 class AWSRoute53API
@@ -57,6 +58,20 @@ class AWSRoute53API
         return $this->requestHandler->makeRequest("/2013-04-01".$zoneId."/rrset/", 'POST', __FUNCTION__, $body);
     }
 
+    public function updateRRSet( RRSet $RRSet, $zoneId ,$aliasType = false)
+    {
+        $cleanZoneId = $this->cleanZoneId($zoneId);
+        $body = $this->getRecordBody($cleanZoneId, AWSRoute53ResponseParseHelper::createUnifiedRecordFromRRSet($RRSet), 'UPSERT',$aliasType);
+        return $this->requestHandler->makeRequest("/2013-04-01".$zoneId."/rrset/", 'POST', __FUNCTION__, $body);
+    }
+
+    public function removeRRSet( RRSet $RRSet, $zoneId ,$aliasType = false)
+    {
+        $cleanZoneId = $this->cleanZoneId($zoneId);
+        $body = $this->getRecordBody($cleanZoneId, AWSRoute53ResponseParseHelper::createUnifiedRecordFromRRSet($RRSet), 'DELETE',$aliasType);
+        return $this->requestHandler->makeRequest("/2013-04-01".$zoneId."/rrset/", 'POST', __FUNCTION__, $body);
+    }
+
     public function deleteRecord($zoneId, $record, $aliasType = false)
     {
         $cleanZoneId = $this->cleanZoneId($zoneId);
@@ -93,6 +108,13 @@ class AWSRoute53API
     {
         $recordType = $aliasType ? $aliasType : $record->type;
 
+        $finalRecord        = '';
+
+        foreach( explode(PHP_EOL, $record->rdata->toString()) as $recordNS )
+        {
+            $finalRecord .= $this->getNsResourceBody(str_replace("\t",'',$recordNS));
+        }
+
         $params = array(
             'action' => $action,
             'recordName' => $record->name,
@@ -101,7 +123,7 @@ class AWSRoute53API
             'targetName' => $record->rdata->target,
             'hostedZoneId' => $zoneId,
             'recordValue' =>str_replace("\t", ' ',  $record->rdata->toString()),
-            'nsResourceRecords' => $this->getNsResourceBody(str_replace("\t", ' ',  $record->rdata->toString()))
+            'nsResourceRecords' => $finalRecord
         );
 
         switch($recordType)
@@ -113,6 +135,7 @@ class AWSRoute53API
                 $template = awsRoute53\AWSRoute53XmlRequestHelper::RECORD_NS_REQUEST;
                 break;
             case 'NS':
+            case 'TXT':
                 $template = awsRoute53\AWSRoute53XmlRequestHelper::RECORD_NS_REQUEST;
                 break;
             case 'MX':
@@ -122,6 +145,7 @@ class AWSRoute53API
                 $template = awsRoute53\AWSRoute53XmlRequestHelper::RECORD_REQUEST;
                 break;
         }
+
         $template = $aliasType ? awsRoute53\AWSRoute53XmlRequestHelper::RECORD_ALIAS_REQUEST : $template;
 
         $body = awsRoute53\AWSRoute53XmlRequestHelper::prepareResponseBody($template, $params);

+ 61 - 18
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/AWSRoute53/AWSRoute53ResponseParseHelper.php

@@ -67,24 +67,21 @@ class AWSRoute53ResponseParseHelper
                 switch($rType)
                 {
                     case 'ALIAS':
-                            $record->rdata->fromString(self::parseRDataForALIASRecords($aRecord->AliasTarget)); 
-                            $recordList[] = $record;
+                        $record->rdata->fromString(self::parseRDataForALIASRecords($aRecord->AliasTarget));
+                        $record->line = md5((string)$record->rdata);
+                        $recordList[] = $record;
                         break;
+                    case 'NS':
+                    case 'A':
+                    case 'AAAA':
                     case 'MX':
-                            self::parseMultihostRecords($recordList, $record, $aRecord);
+                    case 'TXT':
+                        self::parseMultihostRecords($recordList, $record, $aRecord);
                         break;
-                    case 'NS':   
-                            self::parseMultihostRecords($recordList, $record, $aRecord); 
-                        break;
-                    case 'A':   
-                            self::parseMultihostRecords($recordList, $record, $aRecord);
-                        break;
-                    case 'AAAA':   
-                            self::parseMultihostRecords($recordList, $record, $aRecord);
-                        break;                    
                     default:
-                            $record->rdata->fromString(self::parseRData($aRecord->ResourceRecords));
-                            $recordList[] = $record;
+                        $record->rdata->fromString(self::parseRData($aRecord->ResourceRecords));
+                        $record->line = md5((string)$record->rdata);
+                        $recordList[] = $record;
                         break;
                 }
             }   
@@ -161,8 +158,8 @@ class AWSRoute53ResponseParseHelper
             $cRecord->createRDATAObject($cRecord->type);
 
             self::parseHostValueToRdata($cRecord, $host->Value->__toString());
-
-            $recordList[] = $cRecord;             
+            $cRecord->line = md5((string)$cRecord->rdata);
+            $recordList[] = $cRecord;
         }   
     }
     
@@ -243,8 +240,54 @@ class AWSRoute53ResponseParseHelper
                     $editRecord['address'] .= PHP_EOL.$addToEdit['address'];
                 break;
         }
-    }   
-    
+    }
+
+    public static function createUnifiedRecordFromRRSet( dns\record\RRSet $RRSet )
+    {
+        $unifiedRecord       = new dns\record\Record;
+        $unifiedRecord->name = $RRSet->getName();
+        $unifiedRecord->ttl  = $RRSet->getTtl();
+        $unifiedRecord->type = $RRSet->getType();
+
+        $unifiedRecord->createRDATAObject($RRSet->getType());
+
+        foreach( $RRSet->getRecords() as $record )
+        {
+            switch( $RRSet->getType() )
+            {
+                case 'MX':
+                    if( $unifiedRecord->rdata->preference )
+                    {
+                        $unifiedRecord->rdata->exchange = $unifiedRecord->rdata->preference . ' ' . $unifiedRecord->rdata->exchange;
+                        $unifiedRecord->rdata->preference = false;
+                    }
+                    $unifiedRecord->rdata->exchange = $unifiedRecord->rdata->exchange ? $unifiedRecord->rdata->exchange . PHP_EOL . $record->rdata->preference . ' ' . $record->rdata->exchange
+                        : $record->rdata->preference . ' ' . $record->rdata->exchange;
+                    $unifiedRecord->rdata->exchange = trim( $unifiedRecord->rdata->exchange);
+                    break;
+                case 'NS':
+                    $unifiedRecord->rdata->nsdname = $unifiedRecord->rdata->nsdname ? $unifiedRecord->rdata->nsdname . PHP_EOL . $record->rdata->nsdname
+                        : $record->rdata->nsdname;
+                    break;
+                case 'A':
+                    $unifiedRecord->rdata->address = $unifiedRecord->rdata->address ? $unifiedRecord->rdata->address . PHP_EOL . $record->rdata->address
+                        : $record->rdata->address;
+                    break;
+                case 'AAAA':
+                    $unifiedRecord->rdata->address = $unifiedRecord->rdata->address ? $unifiedRecord->rdata->address . PHP_EOL . $record->rdata->address
+                        : $record->rdata->address;
+                    break;
+                case 'TXT':
+                    $record->rdata->txtdata = '"' . trim(html_entity_decode($record->rdata->txtdata), '"') . '"';
+                    $unifiedRecord->rdata->txtdata = $unifiedRecord->rdata->txtdata ? $unifiedRecord->rdata->txtdata . PHP_EOL . $record->rdata->txtdata : $record->rdata->txtdata;
+                    break;
+                default:
+                    $unifiedRecord->rdata = $record->rdata;
+            }
+        }
+        return $unifiedRecord;
+    }
+
     public static function mergeHostsRecordsForRdata(&$editRecord, $addToEdit, $type)
     {
         if($editRecord->rdata == null)

+ 178 - 53
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/Bind9.php

@@ -40,9 +40,6 @@ class Bind9 extends dns\SubmoduleAbstract implements
         'password'       => [
             'friendlyName' => 'User Password',
             'type'         => 'password',
-            'validators'   => [
-                'required' => 'required'
-            ],
         ],
         'default_ip'     => [
             'friendlyName' => 'Default IP',
@@ -74,6 +71,15 @@ class Bind9 extends dns\SubmoduleAbstract implements
                 'required' => 'required',
             ]
         ],
+        'reloadService' => [
+            'friendlyName' => 'Reload Service Name',
+            'type'         => 'select',
+            'options' => [
+                'bind'  => 'bind9',
+                'named' => 'named'
+            ],
+            'help'         => 'Required for reloading service after zone activation'
+        ],
         'rname'          => [
             'friendlyName' => 'Admin Email (RNAME)',
             'placeholder'  => 'admin.example.com.',
@@ -124,7 +130,7 @@ class Bind9 extends dns\SubmoduleAbstract implements
         ]
     ];
 
-    public $availableTypes = ['A', 'AAAA', 'CAA', 'DS', 'HINFO', 'NS', 'MX', 'CNAME', 'DNAME', 'TXT', 'SRV', 'AFSDB', 'NAPTR', 'RP', 'SOA'];
+    public $availableTypes = ['A', 'AAAA','AFSDB','ALIAS', 'CAA', 'CNAME', 'DNAME','DS', 'HINFO', 'NS', 'MX',  'TXT', 'SRV', 'NAPTR', 'RP', 'SOA'];
     private $soa = [];
 
     /**
@@ -496,6 +502,7 @@ class Bind9 extends dns\SubmoduleAbstract implements
                 $regexDomain = '/zone\s+"' . preg_quote($domain, '/') . '\.*"\s+.+?\n};/msi';
                 $configFile  = preg_replace($regexDomain, '', $configFile, 1);
                 $this->upload($this->parsePathToConfig(), $configFile);
+                $this->removeFile('/var/cache/bind/db.'.$this->getDomainWithoutDot());
             }
             $this->reloadBind9();
         }
@@ -537,9 +544,10 @@ class Bind9 extends dns\SubmoduleAbstract implements
         $pathToConfig = $this->parsePathToConfig();
         if ( !$this->upload($pathToConfig, $configFile) ) throw new DNSSubmoduleException('We couldn\'t remove zone from config file in path: ' . $pathToConfig);
 
-        $this->reloadZone();
+        $this->synchronizeSlaves($this->getDomainWithoutDot(), 'Terminate');
+        $this->reloadBind9();
 
-        return $this->synchronizeSlaves($this->getDomainWithoutDot(), 'Terminate');
+        return true;
     }
 
     /**
@@ -609,9 +617,20 @@ class Bind9 extends dns\SubmoduleAbstract implements
      */
     private function reloadBind9()
     {
-        if ( !$this->ssh2 ) $this->establishSSH2Connection();
+        if( !$this->ssh2 ) $this->establishSSH2Connection();
+
+        $command = 'systemctl reload ';
 
-        return $this->ssh2->exec('systemctl reload bind9');
+        if( $this->config['reloadService'] === 'named' )
+        {
+            $command .= 'named';
+        }
+        else
+        {
+            $command .= 'bind9';
+        }
+
+        return $this->ssh2->exec($command);
     }
 
     /**
@@ -917,7 +936,9 @@ class Bind9 extends dns\SubmoduleAbstract implements
      */
     private function removeComments( $line )
     {
-        return (($index = strpos($line, ';')) !== false) ? substr($line, 0, $index) : $line;
+        //Replaces everything after ; but only if semicolon is not between quotation marks
+        //I spent on it about 1 hour plz don't remove
+        return preg_replace('/(?:^\s*;|(?:;(?!.+"))).+/', '', $line);
     }
 
     /**
@@ -1108,14 +1129,24 @@ class Bind9 extends dns\SubmoduleAbstract implements
         foreach ( $this->records as $record )
         {
             $record->name = $this->replaceNameToBind9Record($record->name);
+
             $recordArray  = [
                 $record->name,
                 $record->class,
                 $record->ttl,
                 $record->type
             ];
+            $recordRdata =  $record->rdata->toString();
+
+            if( strlen($recordRdata) > 255 )
+            {
+                $recordsArray = array_merge($recordsArray, $this->splitMultilineRecord($recordArray,$recordRdata));
+            }
+            else
+            {
+                $recordsArray[] = implode(' ', $recordArray) . ' ' .$recordRdata;
+            }
 
-            $recordsArray[] = implode(' ', $recordArray) . ' ' . $record->rdata->toString();
         }
 
         return implode("\n", $recordsArray) . "\n";
@@ -1341,7 +1372,18 @@ class Bind9 extends dns\SubmoduleAbstract implements
     {
         $connctIp   = $ip ? : $this->config['hostname'];
         $this->sftp = new SFTP($connctIp);
-        if ( !$this->sftp->login($this->config['username'], $this->config['password']) )
+
+        if ( $this->config['rsa'] )
+        {
+            $auth = new RSA();
+            $auth->loadKey($this->config['rsa']);
+        }
+        else
+        {
+            $auth = $this->config['password'];
+        }
+
+        if ( !$this->sftp->login($this->config['username'], $auth) )
         {
             throw new DNSSubmoduleException('We couldn\'t upload changes to your server check permissions for given account');
         }
@@ -1377,6 +1419,10 @@ class Bind9 extends dns\SubmoduleAbstract implements
             throw new DNSSubmoduleException('You can have only one SOA record in file');
         }
 
+        $lines = $this->trimUnnecessaryBrackets($lines);
+
+        $lines = $this->convertMultilineRecords($lines);
+
         //If filter records types is set we filter any other out
         if ( $recordTypeFilter )
         {
@@ -1384,52 +1430,15 @@ class Bind9 extends dns\SubmoduleAbstract implements
         }
 
         //Everything else in file should be record
-        foreach ( $lines as $nol => $line )
+        foreach( $lines as $nol => $line )
         {
-            if ( !trim($line) ) continue;
-            //Replace @/blank/whitespace in first val for valid domain names
-            $line   = $this->replaceName($line);
-            $values = preg_split('/\s+/', $line);
-
-            $recordType        = $this->getRecordType($values);
-            $indexOfRecordType = $this->getIndexOfRecordType($values, $recordType);
-
-            //If record type is not supported
-            if ( !$recordType )
-            {
-                throw new DNSSubmoduleException('We don\'t support this record type in line: ' . ($nol + 1));
-            }
-
-            $record       = new dns\record\Record();
-            $record->name = $values[0];
+            if( !trim($line) ) continue;
+            $record = $this->buildRecord($line, $nol);
 
-            $record->line       = $nol;
-            $record->type       = $values[$indexOfRecordType];
-            $record->customData = $record->rdlength = '';
-
-            $ttlandclass   = $this->getTTLAndClassFromValues($indexOfRecordType, $values, $this->ttl);
-            $record->class = $ttlandclass['class'];
-            $record->ttl   = $ttlandclass['ttl'];
-            //We split string by whitespace but txt record type has spaces in one of the params so we have to merge them
-            if ( $recordType === 'TXT' )
-            {
-                preg_match('/".+"/', $line, $match);
-                $values[$indexOfRecordType + 1] = str_replace("\t", ' ', $match[0]);
-            }
-
-            $className = 'MGModule\DNSManager2\mgLibs\custom\dns\record\type\\' . $recordType;
-
-            $additionalVars = array_keys(get_class_vars($className));
-
-            $recordTypeClass = new $className;
-
-            foreach ( $additionalVars as $index => $prop )
+            if( $record )
             {
-                $recordTypeClass->$prop = $values[$indexOfRecordType + 1 + $index];
+                $out[] = $record;
             }
-
-            $record->rdata = $recordTypeClass;
-            $out[]         = $record;
         }
 
         return $out;
@@ -1467,4 +1476,120 @@ class Bind9 extends dns\SubmoduleAbstract implements
         }
         return true;
     }
+
+    private function splitMultilineRecord( array $recordArray, string $recordRdata )
+    {
+        $out   = [];
+        $out[] = implode(' ', $recordArray) . ' (';
+
+        foreach( str_split($recordRdata, 200) as $splittedRdata )
+        {
+            $out[] = '"' . trim($splittedRdata, '"') . '"';
+        }
+        $out[count($out) - 1] .= ')';
+
+        return $out;
+    }
+
+
+    private function trimUnnecessaryBrackets( array $lines )
+    {
+        foreach( $lines as &$line )
+        {
+            if( strpos($line, '(') !== false && strpos($line, ')') !== false )
+            {
+                $line = str_replace(['(', ')'], '', $line);
+            }
+        }
+
+        return $lines;
+    }
+
+    private function buildRecord( $line, $nol )
+    {
+        //Replace @/blank/whitespace in first val for valid domain names
+        $line   = $this->replaceName($line);
+        $values = preg_split('/\s+/', $line);
+
+        $recordType        = $this->getRecordType($values);
+        $indexOfRecordType = $this->getIndexOfRecordType($values, $recordType);
+
+        //If record type is not supported
+        if( !$recordType )
+        {
+            throw new DNSSubmoduleException('We don\'t support this record type in line: ' . ($nol + 1));
+        }
+
+        $record       = new dns\record\Record();
+        $record->name = $values[0];
+
+        $record->line       = $nol;
+        $record->type       = $values[$indexOfRecordType];
+        $record->customData = $record->rdlength = '';
+
+        $ttlandclass   = $this->getTTLAndClassFromValues($indexOfRecordType, $values, $this->ttl);
+        $record->class = $ttlandclass['class'];
+        $record->ttl   = $ttlandclass['ttl'];
+        //We split string by whitespace but txt record type has spaces in one of the params so we have to merge them
+        if( $recordType === 'TXT' )
+        {
+            preg_match('/".+"/', $line, $match);
+            $values[$indexOfRecordType + 1] = str_replace("\t", ' ', $match[0]);
+        }
+
+        $className = 'MGModule\DNSManager2\mgLibs\custom\dns\record\type\\' . $recordType;
+
+        $additionalVars = array_keys(get_class_vars($className));
+
+        $recordTypeClass = new $className;
+
+        foreach( $additionalVars as $index => $prop )
+        {
+            $recordTypeClass->$prop = $values[$indexOfRecordType + 1 + $index];
+        }
+
+        $record->rdata = $recordTypeClass;
+        return $record;
+    }
+
+    private function convertMultilineRecords( array $lines )
+    {
+        $joinToLine = false;
+        foreach( $lines as $nol => $line )
+        {
+            //If we are currently joining values and there is no closing bracket just join it
+            if( $joinToLine !== false && strpos($line, ')') === false )
+            {
+                $lines[$joinToLine] .= trim($line);
+
+                unset($lines[$nol]);
+
+                continue;
+            }
+
+            //If there is closing bracket and we are joining values we add trimmed value without closing bracket and end joining
+            if( $joinToLine !== false && strpos($line, ')') !== false )
+            {
+                $lines[$joinToLine] .= trim(str_replace(')', '', $line));
+                $joinToLine         = false;
+
+                unset($lines[$nol]);
+
+                continue;
+            }
+
+            //If there is opening bracket in line and no closing brackets we start joining values
+            if( strpos($line, '(') !== false && strpos($line, ')') === false )
+            {
+                $lines[$nol] = str_replace('(', '', $line);
+                $joinToLine  = $nol;
+            }
+        }
+
+        //Since we join values from multiple lines they are connected with double double quotes
+        //We want to create single record so we have to remove this double quotes
+        return array_map(static function( $line ) {
+            return str_replace('""', '', $line);
+        }, $lines);
+    }
 }

+ 7 - 3
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/DNS4PSA.php

@@ -236,7 +236,11 @@ class DNS4PSA extends dns\SubmoduleAbstract implements interfaces\SubmoduleImpor
                         $data              = new \stdClass();
                         $data->DNSRecordId = $r->DNSRecordId;
                         $fullRecord        = $this->get('GetDNSRecord', $data)->DNSRecord;
-                        $record->name      = "{$fullRecord->service}.{$fullRecord->protocol}.{$fullRecord->host}";
+                        
+                        $service            = '_'.ltrim($fullRecord->service, '_');
+                        $protocol           = '_'.ltrim($fullRecord->protocol, '_');
+                        
+                        $record->name      = "{$service}.{$protocol}.{$fullRecord->host}";
 
                         $record->rdata->setDataFromArray((array)$fullRecord);
 
@@ -278,8 +282,8 @@ class DNS4PSA extends dns\SubmoduleAbstract implements interfaces\SubmoduleImpor
 
                 $exploded = explode('.', $record->name, 3);
                 list($service, $protocol, $name) = $exploded;
-                $dns_zone->service  = $service;
-                $dns_zone->protocol = $protocol;
+                $dns_zone->service  = ltrim($service, '_');
+                $dns_zone->protocol = ltrim($protocol, '_');
                 $record->name       = $name;
                 $dns_zone->host     = $record->nameToAbsolute($this->domain);
 

+ 3 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/DNSMadeEasy.php

@@ -92,6 +92,7 @@ class DNSMadeEasy extends dns\SubmoduleAbstract implements interfaces\SubmoduleT
                     case 'SRV':
                         $record->rdata->setDataFromArray($data);
                         $record->rdata->target = (string)$data['value'];
+                        break;
                     default:
                         $record->rdata->setFirstProperty((string)$data['value']);
                         break;
@@ -128,7 +129,8 @@ class DNSMadeEasy extends dns\SubmoduleAbstract implements interfaces\SubmoduleT
                 $params['value']        = $record->rdata->link;
                 break;
             case 'SRV':
-                $params = array_merge($record->rdata->toArray());
+                $params = array_merge($params, $record->rdata->toArray(false));
+                unset($params['target']);
                 $params['value'] = $record->rdata->target;
                 $params['ttl'] = $record->ttl;
                 $params['type'] = $record->type;

+ 27 - 2
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/PowerDNS.php

@@ -1689,8 +1689,22 @@ class PowerDNS extends dns\SubmoduleAbstract implements
         
         if(!empty($this->config['ssh_public_key']) && !empty($this->config['ssh_private_key'])) 
         {
-            $sshPublicKey=ROOTDIR.DIRECTORY_SEPARATOR.'ssh_keys'.DIRECTORY_SEPARATOR.$this->config['ssh_public_key'];
-            $shhPrivateKey=ROOTDIR.DIRECTORY_SEPARATOR.'ssh_keys'.DIRECTORY_SEPARATOR.$this->config['ssh_private_key'];
+            $sshPublicKey = 
+                $this->config['ssh_public_key'][0] === '/' 
+                    ? $this->config['ssh_public_key'] 
+                    : ROOTDIR.DIRECTORY_SEPARATOR.'ssh_keys'.DIRECTORY_SEPARATOR.$this->config['ssh_public_key'];
+            $shhPrivateKey = 
+                $this->config['ssh_private_key'][0] === '/' 
+                    ? $this->config['ssh_private_key']
+                    : ROOTDIR.DIRECTORY_SEPARATOR.'ssh_keys'.DIRECTORY_SEPARATOR.$this->config['ssh_private_key'];
+            
+            if(!file_get_contents($sshPublicKey)) {
+                throw new exceptions\DNSSubmoduleException('SSH public key file does not exist at path: ' . $sshPublicKey, dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
+            }
+            if(!file_get_contents($shhPrivateKey)) {
+                throw new exceptions\DNSSubmoduleException('SSH private key file does not exist at path: ' . $shhPrivateKey, dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
+            }
+            
             $auth = ssh2_auth_pubkey_file($this->ssh, $this->config['ssh_username'], $sshPublicKey, $shhPrivateKey);
         
             if(!$auth)
@@ -1757,6 +1771,14 @@ class PowerDNS extends dns\SubmoduleAbstract implements
         }
         return $res->numRows() > 0;
     }
+
+    public function validateRecord($record)
+    {
+        if($this->server->getModuleConfiguration()['disable_validation'] !== 'on') {
+            parent::validateRecord($record);
+        }
+    }
+
 }
 
 $messages_file = substr(__DIR__, 0, strpos(__DIR__, '' . DIRECTORY_SEPARATOR . 'includes')) . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'addons' . DIRECTORY_SEPARATOR . 'dns_manager' . DIRECTORY_SEPARATOR . 'powerdns_messages.php';
@@ -1891,3 +1913,6 @@ else
     //2.9.0
     define('ERR_DNS_SUBTYPE', 'This is not a valid subtype. Valid type is 1 or 2 depending on whether the endpoint is an AFS Volume Location Server or DCE Authentication Server.');
 }
+
+
+

+ 7 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/PowerDNSV4.php

@@ -418,6 +418,11 @@ class PowerDNSV4 extends dns\SubmoduleAbstract implements
             }
         }
 
+        if( $this->config['increment_soa_serial'] === 'on' )
+        {
+            $records = $this->incrementSOASerial($records);
+        }
+
         $params = $this->recordsToParamArray($records);
 
         $errorType = null;
@@ -543,7 +548,8 @@ class PowerDNSV4 extends dns\SubmoduleAbstract implements
         {
             return false;
         }
-        $this->deleteRecord($record);
+        $clonedZone = $this->getClone($ip);
+        $clonedZone->deleteRecord($record);
         return true;
     }
 

+ 621 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero.php

@@ -0,0 +1,621 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;
+
+use Exception;
+use \MGModule\DNSManager2\mgLibs\custom\dns;
+use MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\RRSet;
+use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
+use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
+use \MGModule\DNSManager2\mgLibs\custom\dns\record\Record;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class RCodeZero extends dns\SubmoduleAbstract implements
+    interfaces\SubmoduleTTLInterface, interfaces\SubmoduleImportInterface
+{
+    const ERR_WHILE_REMOVING = 'errRemovingRecord';
+    const ERR_WHILE_CREATING  = 'errCreatingRecord';
+
+    public $configFields = array(
+        'token' => array(
+            'friendlyName' => 'Bearer Token',
+            'validators' => array(
+                'required' => 'required',
+            )
+        ),
+        'test' => array(
+            'friendlyName' => 'Development API',
+            'type' => 'yesno',
+            'validators' => array(
+                'required' => 'required',
+            )
+        ),
+        'newrecoverwrite' => array(
+            'friendlyName' => 'Overwrite NS & SOA upon creation',
+            'type' => 'yesno',
+            'validators' => array(
+                'required' => 'required',
+            )
+        ),
+        'soaemail' => array(
+            'friendlyName' => 'SOA E-mail Address',
+        ),
+        'defttl' => array(
+            'friendlyName' => 'Default TTL',
+        ),
+
+    );
+    private $dev_api = 'https://my-test.rcodezero.at/api/v1';
+    private $api = 'https://my.rcodezero.at/api/v1';
+
+    public $availableTypes = ['A', 'AAAA', 'NS', 'MX', 'CNAME', 'TXT', 'SRV', 'DNAME', 'SPF', 'PTR', 'ALIAS', 'NAPTR', 'CAA', 'SOA', 'DS', 'URI', 'TLSA',  'CERT', 'SSHFP', ];
+
+ private function get($function, $params = [], $method = 'GET')
+    {
+        $url = ($this->config['test'] == 'on' ? 
+        $this->dev_api.'/' : 
+        $this->api.'/').$function;
+        $headers = ['Content-Type: application/json', 'Authorization: Bearer '.$this->config['token']];
+        $ch = curl_init();
+       
+        $chOptions = array (
+            CURLOPT_URL => $url,
+            CURLINFO_HEADER_OUT => false,
+            CURLOPT_RETURNTRANSFER  =>  true,
+            CURLOPT_SSL_VERIFYPEER  =>  false,
+            CURLOPT_SSL_VERIFYHOST  =>  false,
+            CURLOPT_HTTPHEADER      => $headers,
+            CURLOPT_HEADER          =>  false,
+        );
+  
+        if($method != 'GET')
+        {
+            $chOptions[CURLOPT_CUSTOMREQUEST] = $method;
+            if(count($params) > 0)
+            {
+                $chOptions = $chOptions + [
+                 CURLOPT_POST            =>  true,
+                 CURLOPT_POSTFIELDS      =>  json_encode($params)];
+            }
+        }
+        curl_setopt_array($ch, $chOptions);
+        
+        $out = curl_exec($ch);
+        
+        curl_close($ch);
+        
+        if(curl_errno($ch) != 0 || $out === false) {
+            throw new exceptions\DNSSubmoduleException('cURL error: ' . (curl_error($ch)?: 'Unknown Error'), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
+        }
+        
+        if(preg_match('/^\*(.)+\*$/', strtolower(trim($out))))
+        {
+            throw new exceptions\DNSSubmoduleException(($out?:'Unknown Error'), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
+        }
+        
+        return $out;
+    }
+
+    public function testConnection()
+    {
+        $out = json_decode($this->get('zones'));
+        
+        if ( !$out )
+        {
+            throw new exceptions\DNSSubmoduleException('Incorrect API Key', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
+        }
+    }
+
+    public function getZoneID()
+    {
+
+        $zone     = json_decode($this->get("zones/" . $this->domain, []));
+
+        if ( isset($zone->id) )
+        {
+            return (string)$zone->id;
+        }
+        throw new exceptions\DNSSubmoduleException('Zone does not exists', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
+    }
+
+    public function zoneExists()
+    {
+        try
+        {
+            $this->getZoneID();
+            return true;
+        }
+        catch (exceptions\DNSSubmoduleException $e)
+        {
+            if ( $e->getCode() === dns\SubmoduleExceptionCodes::INVALID_PARAMETERS )
+            {
+                return false;
+            }
+            throw $e;
+        }
+    }
+
+    public function getRecords( $recordType = false )
+    {
+        $this->getZoneID();
+        $rrsets   = json_decode($this->get("zones/$this->domain/rrsets", []), false)->data;
+        
+        $out      = [];
+        $rrsets = array_filter($rrsets, [self::class, 'filterBrokenRRSets']);
+
+        /** @var dns\submodules\RCodeZero\RRSet $rrset */
+        foreach ( $rrsets as $rrset )
+        {
+            $type = $rrset->type;
+            $name = $rrset->name;
+            $ttl  = $rrset->ttl;
+
+            $recordClass        = 'MGModule\\DNSManager2\\mgLibs\\custom\\dns\\record\\type\\' . $type;
+            $recordAdapterClass = 'MGModule\\DNSManager2\\mgLibs\\custom\\dns\\submodules\\RCodeZero\\Adapters\\' . $type . 'Adapter';
+
+            if ( ($recordType && $recordType !== $type) || !class_exists($recordClass) || !in_array($type, $this->availableTypes, true) ) continue;
+
+            /** @var dns\submodules\RCodeZero\RecordFromServer $recordFromServer */
+            foreach ( $rrset->records as $recordId => $recordFromServer )
+            {
+                if ( class_exists($recordAdapterClass) && method_exists($recordAdapterClass, 'createRdata') )
+                {
+                    /** @var dns\submodules\RCodeZero\Adapters\AbstractRCodeZeroAdapter $record */
+                    $record        = new $recordAdapterClass();
+                    $record->name  = IdnaHelper::idnaEncode($name);
+                    $record->type  = $type;
+                    $record->line  = implode('|', [$name, $type, $recordId]);
+                    $record->ttl   = $ttl;
+
+                    $record->createRdata(IdnaHelper::idnaDecode($recordFromServer->content));
+                }
+                else
+                {
+                    $record        = new Record();
+                    $record->name  = IdnaHelper::idnaEncode($name);
+                    $record->type  = $type;
+                    $record->line  = implode('|', [$name, $type, $recordId]);
+                    $record->ttl   = $ttl;
+
+                    $additionalProps = explode(' ', IdnaHelper::idnaDecode($recordFromServer->content));
+                    $record->rdata   = new $recordClass();
+
+                    foreach ( array_keys(get_object_vars($record->rdata)) as $index => $additionalProperty )
+                    {
+                        $record->rdata->$additionalProperty = $additionalProps[$index];
+                    }
+                }
+                $out[] = $record;
+            }
+        }
+
+        usort($out, static function ( $a, $b )
+        {
+            /** @var Record $a */
+            /** @var Record $b */
+            $recordAllignment = ['SOA', 'A', 'AAAA', 'NS', 'MX', 'TXT'];
+            if ( !in_array($a->type, $recordAllignment, true) && !in_array($b->type, $recordAllignment, true) ) return 0;
+            if ( !in_array($a->type, $recordAllignment, true) ) return 1;
+            if ( !in_array($b->type, $recordAllignment, true) ) return -1;
+
+            if ( $a->type === $b->type )
+            {
+                return $a->name < $b->name ? -1 : 1;
+            }
+
+            return array_search($a->type, $recordAllignment, true) < array_search($b->type, $recordAllignment, true) ? -1 : 1;
+        });
+     
+        return $out;
+    }
+
+    /**
+     * Generates Valid RRSet format for one record ( usefull when we can use replace function )
+     *
+     * @param Record $record
+     * @param string $changetype
+     *
+     * @return array
+     */
+    private function recordToParamsArray( dns\record\Record $record, $changetype = 'REPLACE' )
+    {
+        return [
+            'rrsets' => [
+                'name'       => $record->name,
+                'type'       => $record->type,
+                'ttl'        => $record->ttl,
+                'changetype' => $changetype,
+                'comments'   => [],
+                'records'    => [$this->formatRecordToAPIFormat($record)]
+            ]
+        ];
+    }
+
+    /**
+     * @param Record $record
+     *
+     * @throws exceptions\DNSSubmoduleException
+     */
+    public function addRecord( dns\record\Record $record )
+    {
+        $records   = $this->getRecords();
+       
+        $record->name = IdnaHelper::idnaEncode($record->name);
+        $records[] = $record;
+        
+        $params = $this->recordsToParamArray($records);
+       
+        $params = $this->replaceTargetRRSetTTL($params, $record);
+       
+        $r= json_decode($this->get("zones/$this->domain/rrsets", $params, 'PATCH'));
+        if($r->status == 'failed')
+        {
+            throw new exceptions\DNSSubmoduleException($r->message);
+        }
+    }
+
+    public function editRecord( dns\record\Record $record )
+    {
+        $record->nameToAbsolute($this->domain);
+        $record->name = IdnaHelper::idnaEncode($record->name);
+
+        $records = $this->getRecords();
+
+        $recordsBeforeUpdate = $records;
+        /** @var Record $records */
+        foreach ( $records as $index => $recordBeforeUpdate )
+        {
+            if ( $recordBeforeUpdate->line === $record->line )
+            {
+                $records[$index] = $record;
+                break;
+            }
+        }
+
+        $params = $this->recordsToParamArray($records);
+        $params = $this->replaceTargetRRSetTTL($params, $record);
+
+        $errorType = null;
+        try
+        {
+            $this->removeRRsets($recordsBeforeUpdate);
+        }
+        catch( exceptions\DNSSubmoduleException $e )
+        {
+            $errorType = self::ERR_WHILE_REMOVING;
+        }
+        
+        try
+        {
+            $this->get("zones/$this->domain/rrsets", $params, 'PATCH');
+        }
+        catch( exceptions\DNSSubmoduleException $e )
+        {
+            $errorType = $errorType ? : self::ERR_WHILE_CREATING;
+            $this->restoreOldRecords($recordsBeforeUpdate,$errorType);
+        }
+
+    }
+
+    public function deleteRecord( dns\record\Record $record )
+    {
+        if( $record->type === 'SOA' ) throw new exceptions\DNSSubmoduleException('You are not allowed to delete SOA record');
+
+        $record->nameToAbsolute($this->domain);
+        $record->name = IdnaHelper::idnaEncode($record->name);
+        $records = $this->getRecords();
+
+        $recordsBeforeUpdate = $records;
+        
+        /** @var Record $records */
+        $typesBefore = [];
+       
+        foreach ( $records as $index => $recordBeforeUpdate )
+        {
+            $types[] = $recordBeforeUpdate->type;
+            if ( $recordBeforeUpdate->line === $record->line )
+            {
+                unset($records[$index]);
+                break;
+            }
+        }
+        
+        $params = $this->recordsToParamArray($records);
+
+        $errorType = null;
+        try
+        {
+            $this->removeRRsets($recordsBeforeUpdate);
+        }
+        catch( exceptions\DNSSubmoduleException $e )
+        {
+            $errorType = self::ERR_WHILE_REMOVING;
+        }
+        try
+        {
+            $r=$this->get("zones/$this->domain/rrsets", $params, 'PATCH');
+            
+        }
+        catch( exceptions\DNSSubmoduleException $e )
+        {
+            $errorType = $errorType ? : self::ERR_WHILE_CREATING;
+            $this->restoreOldRecords($recordsBeforeUpdate, $errorType);
+        }
+    }
+    
+    public function activateZone( $dnsZoneName = false )
+    {
+        $this->get('zones', [
+            'domain'      =>  $this->domain,
+            'type'      =>  'master'
+        ], 'POST');
+        
+        if($this->config['newrecoverwrite'])
+        {
+            $nameservers = $this->getNameServers(); 
+            $records = $this->getRecords('NS');
+
+            $nsRemove = [['name' => $this->domain.'.', 'type' => 'NS', 'changetype' => 'delete']];
+            $this->get("zones/$this->domain/rrsets", $nsRemove, 'PATCH');
+
+            $nsAdd = [
+                ['name' => $this->domain.'.',
+                 'type' => 'NS',
+                 'ttl' => $this->config['defttl'] ? $this->config['defttl'] : $records[0]->ttl,
+                'changetype' => 'add', 
+                'records'=>[
+                ['content' => $nameservers[0].'.'],
+                ['content' => $nameservers[1].'.']
+                ]
+            ]
+        ];
+        
+            $r=$this->get("zones/$this->domain/rrsets", $nsAdd, 'PATCH');
+            list($soarecord) = $this->getRecords('SOA');
+
+            $recordAdapterClass = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters\SOAAdapter';
+
+            $soarecord->rdata->mname = $nameservers[0].'.' ? $nameservers[0].'.' : $soarecord->rdata->mname;
+            $soarecord->rdata->rname = str_replace('@', '.', $this->config['soaemail']).'.';
+            $content = (new $recordAdapterClass)->parseContentToApiFormat($soarecord->rdata);
+            $soaUpdate = [[
+                'name' => $this->domain.'.', 
+                'type' => 'SOA', 
+                'ttl' =>  $this->config['defttl'] ? $this->config['defttl'] : $soarecord->ttl,
+                'changetype' => 'update', 'records' =>[
+                ['content' => 
+                    $content
+                ]
+            ]]];
+            $r=$this->get("zones/$this->domain/rrsets", $soaUpdate, 'PATCH');        
+        }
+    }
+
+    public function terminateZone()
+    {
+        $this->get("zones/$this->domain", [], 'DELETE');
+    }
+
+    public function getZones()
+    {
+        $ret      = json_decode($this->get("zones", []));
+
+        $out = [];
+        foreach ( $ret->data as $zone )
+        {
+            $masters = null;
+
+            $out[(string)$zone->domain] = $masters;
+        }
+       
+        return $out;
+    }
+
+    
+    /**
+     * Creates rrsets structure from Records array
+     * @param array  $records
+     * @param string $changeType
+     * @return mixed
+     */
+    private function recordsToParamArray( $records, $changeType = 'update')
+    {
+        $names = $this->getUniqueNames($records);
+        $types = $this->getUniqueTypes($records);
+
+        if( $changeType === 'DELETE' )
+        {
+            $types = array_diff($types, ['SOA']);
+        }
+
+        $out   = [];
+     
+        foreach ( $names as $name )
+        {
+            foreach ( $types as $type )
+            {
+                $rrset             = new RRSet();
+                $rrset->name       = $name;
+                $rrset->type       = $type;
+                $rrset->ttl        = $this->getRRSetTTL($name, $type, $records);
+                $rrset->changetype = $changeType;
+                $rrset->records    = $this->getRRsetForNameAndType($name, $type, $records);
+                if ( $rrset->records )
+                {
+                    $out[] = $rrset;
+                }
+            }
+        }
+        return $out;
+    }
+
+    /**
+     * @param array $records
+     * @return array
+     */
+    private function getUniqueNames( $records )
+    {
+        $names = [];
+        /** @var Record $record */
+        foreach ( $records as $record )
+        {
+            $names[] = $record->name;
+        }
+
+        return array_unique($names);
+    }
+
+    /**
+     * @param array $records
+     * @return array
+     */
+    private function getUniqueTypes( $records )
+    {
+        $types = [];
+        /** @var Record $record */
+        foreach ( $records as $record )
+        {
+            $types[] = $record->type;
+        }
+
+        return array_unique($types);
+    }
+
+    /**
+     * @param $name
+     * @param $type
+     * @param $records
+     * @return array
+     */
+    private function getRRsetForNameAndType( $name, $type, $records )
+    {
+        $out = [];
+        foreach ( $records as $record )
+        {
+            if ( $record->name === $name && $record->type === $type )
+            {
+                $out[] = $this->formatRecordToAPIFormat($record);
+            }
+        }
+        return $out;
+    }
+
+    /**
+     * @param Record $record
+     * @return array
+     */
+    private function formatRecordToAPIFormat( Record $record )
+    {
+        $recordAdapterClass = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters\\' . $record->type . 'Adapter';
+        if ( class_exists($recordAdapterClass) )
+        {
+            $content = (new $recordAdapterClass)->parseContentToApiFormat($record->rdata);
+        }
+        else
+        {
+            $content = str_replace("\t",' ',$record->rdata->toString());
+        }
+        return ['content' => $content, 'disabled' => false];
+    }
+
+    /**
+     * Function removes all RRsets from server
+     * It's easier when we remove all and then add all since we can't update single record cuz REPLACE duplicates record
+     * @param $records
+     * @return bool
+     * @throws exceptions\DNSSubmoduleException
+     */
+    private function removeRRsets( $records )
+    {
+        $removeParams = $this->recordsToParamArray($records, 'DELETE');
+       
+        $this->get("zones/$this->domain/rrsets", $removeParams, 'PATCH');
+
+        if( count($this->getRecords()) )
+        {
+            throw new exceptions\DNSSubmoduleException(self::ERR_WHILE_REMOVING);
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $name
+     * @param string $type
+     * @param array  $records
+     *
+     * @return int
+     */
+    private function getRRSetTTL( $name, $type, array $records )
+    {
+        foreach( $records as $record )
+        {
+            if( $record->type === $type && $record->name === $name )
+            {
+                return $record->ttl;
+            }
+        }
+        return 3600;
+    }
+
+    /**
+     * @param array  $params
+     * @param Record $record
+     *
+     * @return mixed
+     */
+    private function replaceTargetRRSetTTL( $params, Record $record )
+    {
+        /*** @var RRSet $rrset */
+        foreach( $params['rrsets'] as $index => $rrset )
+        {
+            if( $rrset->name === $record->name && $rrset->type === $record->type )
+            {
+                $params['rrsets'][$index]->ttl = $record->ttl ?: 3600;
+                return $params;
+            }
+        }
+        return $params;
+    }
+
+    /**
+     * @param array  $oldRecords
+     * @param string $errorType
+     *
+     * @throws exceptions\DNSSubmoduleException
+     */
+    private function restoreOldRecords( $oldRecords, $errorType = self::ERR_WHILE_CREATING )
+    {
+        $errors   = [];
+
+        foreach( $oldRecords as $oldRecord )
+        {
+            try
+            {
+                $this->get("zones/$this->domain", $this->recordToParamsArray($oldRecord), 'PATCH');
+            }
+            catch( exceptions\DNSSubmoduleException $e )
+            {
+                $errors[] = $e->getMessage();
+            }
+        }
+
+        if( count($errors) )
+        {
+            $msg = $errorType === self::ERR_WHILE_REMOVING ? 'Zone corrupted contact administrator. Affected records: ' : 'Couldn\'t create records: ';
+            throw new exceptions\DNSSubmoduleException($msg . implode('<br />', $errors));
+        }
+
+    }
+
+    /**
+     * @param RRSet $rrset
+     *
+     * @return bool
+     */
+    private function filterBrokenRRSets( $rrset )
+    {
+        $domainToCheck = rtrim(IdnaHelper::idnaEncode($this->domain), ' .') . '.';
+        return (bool)preg_match('/' . preg_quote($domainToCheck, '/') . '$/', $rrset->name);
+    }
+}

+ 33 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/AFSDBAdapter.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\AFSDB;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class AFSDBAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+        $this->rdata = new AFSDB();
+        $this->rdata->subtype = $contentArray[0];
+        $this->rdata->hostname = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[1]));
+    }
+
+    /**
+     * @param AFSDB $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->subtype,
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->hostname))
+        ];
+        return  implode(' ', $content);
+    }
+}

+ 27 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/ALIASAdapter.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\ALIAS;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class ALIASAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata = new ALIAS();
+        $this->rdata->target = $this->contentToRelative(IdnaHelper::idnaDecode($content));
+    }
+
+    /**
+     * @param ALIAS $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        return  $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->target));
+    }
+}

+ 19 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/AbstractRCodeZeroAdapter.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\Record;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\TXT;
+
+abstract class AbstractRCodeZeroAdapter extends Record
+{
+    public function contentToRelative( $content )
+    {
+        return rtrim($content,'.');
+    }
+
+    public function contentToAbsolute( $content )
+    {
+        return rtrim($content,'.').'.';
+    }
+}

+ 35 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/CAAAdapter.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\CAA;
+
+class CAAAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray        = explode(' ', $content);
+        $this->rdata         = new CAA();
+        $this->rdata->flag   = $contentArray[0];
+        $this->rdata->tag    = $contentArray[1];
+        $this->rdata->target = trim($contentArray[2], '"');
+    }
+
+    /**
+     * @param CAA $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->flag,
+            $rdata->tag,
+            ('"' . trim($rdata->target, '"') . '"')
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 28 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/CNAMEAdapter.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\CNAME;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class CNAMEAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata = new CNAME();
+        $this->rdata->cname = $this->contentToRelative(IdnaHelper::idnaDecode($content));
+    }
+
+    /**
+     * @param CNAME $rdata
+     * @return mixed
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        return $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->cname));
+    }
+}

+ 27 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DNAMEAdapter.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\DNAME;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class DNAMEAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata = new DNAME();
+        $this->rdata->target = $this->contentToRelative(IdnaHelper::idnaDecode($content));
+    }
+
+    /**
+     * @param DNAME $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        return  $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->target));
+    }
+}

+ 34 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DNSKEYAdapter.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\DNSKEY;
+
+class DNSKEYAdapter extends AbstractRCodeZeroAdapter
+{
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+        $this->rdata  = new DNSKEY();
+        $this->rdata->protocol = $contentArray[0];
+        $this->rdata->flags = $contentArray[1];
+        $this->rdata->algorithm = $contentArray[2];
+        $this->rdata->publickey = $contentArray[3];
+    }
+
+    /**
+     * @param DNSKEY $rdata
+     *
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->protocol,
+            $rdata->flags,
+            $rdata->algorithm,
+            trim($rdata->publickey)
+        ];
+        return implode(' ', $content);
+    }
+}

+ 36 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/DSAdapter.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\DS;
+
+class DSAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray            = explode(' ', $content);
+        $this->rdata             = new DS();
+        $this->rdata->keytag     = $contentArray[0];
+        $this->rdata->algorithm  = $contentArray[1];
+        $this->rdata->digesttype = $contentArray[2];
+        $this->rdata->digest     = strtoupper($contentArray[3]);
+    }
+
+    /**
+     * @param DS $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->keytag,
+            $rdata->algorithm,
+            $rdata->digesttype,
+            strtolower($rdata->digest)
+        ];
+        return implode(' ', $content);
+    }
+}

+ 33 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/HINFOAdapter.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\HINFO;
+
+class HINFOAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+        $this->rdata = new HINFO();
+        $this->rdata->cpu = trim($contentArray[0],'"');
+        $this->rdata->os = trim($contentArray[1],'"');
+    }
+
+    /**
+     * @param HINFO $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            ('"'.trim( $rdata->cpu,'"').'"'),
+            ('"'.trim( $rdata->os,'"').'"'),
+        ];
+
+        return  implode(' ', $content);
+    }
+}

+ 25 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/LOCAdapter.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\LOC;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+
+class LOCAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata             = new LOC();
+        $contentArray            = explode(' ', $content);
+        $this->rdata->latitude = $contentArray[0];
+        $this->rdata->longitude = $contentArray[1];
+        $this->rdata->altitude = $contentArray[2];
+        $this->rdata->size = $contentArray[3];
+        $this->rdata->horiz_pre = $contentArray[4];
+        $this->rdata->vert_pre = $contentArray[5];
+    }
+
+}

+ 35 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/MINFOAdapter.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\MINFO;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class MINFOAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+
+        $this->rdata = new MINFO();
+        $this->rdata->rmailbx = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[0]));
+        $this->rdata->emailbx = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[1]));
+    }
+
+    /**
+     * @param MINFO $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->rmailbx)),
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->emailbx))
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 35 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/MXAdapter.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\CNAME;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\MX;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class MXAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata             = new MX();
+        $contentArray            = explode(' ', $content);
+        $this->rdata->preference = $contentArray[0];
+        $this->rdata->exchange   = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[1]));
+    }
+
+    /**
+     * @param MX $rdata
+     * @return mixed
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->preference,
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->exchange))
+        ];
+        return implode(' ', $content);
+    }
+}

+ 44 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/NAPTRAdapter.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\NAPTR;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\NS;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class NAPTRAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+
+        $this->rdata = new NAPTR();
+        $this->rdata->order = $contentArray[0];
+        $this->rdata->preference = $contentArray[1];
+        $this->rdata->flags = trim( $contentArray[2],'"');
+        $this->rdata->services = trim( $contentArray[3],'"');
+        $this->rdata->regexp = trim( $contentArray[4],'"');
+        $this->rdata->replacement = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[5]));
+    }
+    /**
+     * @param NAPTR $rdata
+     * @return mixed
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->order,
+            $rdata->preference,
+            ('"'.trim( $rdata->flags,'"').'"'),
+            ('"'.trim( $rdata->services,'"').'"'),
+            ('"'.trim( $rdata->regexp,'"').'"'),
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->replacement))
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 28 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/NSAdapter.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\NS;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class NSAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata = new NS();
+        $this->rdata->nsdname = $this->contentToRelative($content);
+    }
+
+    /**
+     * @param NS $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        return $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->nsdname));
+    }
+
+}

+ 28 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/PTRAdapter.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\PTR;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+
+class PTRAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $this->rdata             = new PTR();
+        $contentArray            = explode(' ', $content);
+        $this->rdata->ptrdname = $this->contentToRelative($contentArray[0]);
+    }
+
+    /**
+     * @param PTR $rdata
+     * @return mixed
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        return $this->contentToAbsolute($rdata->ptrdname);
+    }
+}

+ 34 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/RPAdapter.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RP;
+
+class RPAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+
+        $this->rdata = new RP();
+        $this->rdata->mbox = $this->contentToRelative($contentArray[0]);
+        $this->rdata->txtdname = $this->contentToRelative($contentArray[1]);
+    }
+
+    /**
+     * @param RP $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $this->contentToAbsolute($rdata->mbox),
+            $this->contentToAbsolute($rdata->txtdname)
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 46 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/SOAAdapter.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\SOA;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class SOAAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+
+        $this->rdata          = new SOA();
+        $this->rdata->mname   = IdnaHelper::idnaDecode($contentArray[0]);
+        $this->rdata->rname   = IdnaHelper::idnaDecode($contentArray[1]);
+        $this->rdata->serial  = $contentArray[2];
+        $this->rdata->refresh = $contentArray[3];
+        $this->rdata->retry   = $contentArray[4];
+        $this->rdata->expire  = $contentArray[5];
+        $this->rdata->minimum = $contentArray[6];
+    }
+
+    /**
+     * @param SOA $rdata
+     *
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->mname)),
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->rname)),
+            $rdata->serial,
+            $rdata->refresh,
+            $rdata->retry,
+            $rdata->expire,
+            $rdata->minimum,
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 39 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/SRVAdapter.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\SRV;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+
+class SRVAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+
+        $this->rdata = new SRV();
+        $this->rdata->priority = $contentArray[0];
+        $this->rdata->weight = $contentArray[1];
+        $this->rdata->port = $contentArray[2];
+        $this->rdata->target = $this->contentToRelative(IdnaHelper::idnaDecode($contentArray[3]));
+    }
+
+    /**
+     * @param SRV $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->priority,
+            $rdata->weight,
+            $rdata->port,
+            $this->contentToAbsolute(IdnaHelper::idnaEncode($rdata->target))
+        ];
+
+        return implode(' ', $content);
+    }
+}

+ 24 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/TLSAAdapter.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\TLSA;
+
+class TLSAAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param TLSA $rdata
+     *
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->certusage,
+            $rdata->selector,
+            $rdata->matchingtype,
+            strtolower(trim($rdata->certdata))
+        ];
+        return implode(' ', $content);
+    }
+}

+ 25 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/TXTAdapter.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\Record;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\TXT;
+use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;
+class TXTAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $content = trim($content,'"');
+        $this->rdata = new TXT();
+        $this->rdata->txtdata = $content;
+    }
+    public function parseContentToApiFormat( $rdata )
+    {
+    	return  '"'.trim($rdata->txtdata,'"').'"';
+    }
+
+}

+ 35 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/Adapters/URIAdapter.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero\Adapters;
+
+use MGModule\DNSManager2\mgLibs\custom\dns\record\type\URI;
+
+class URIAdapter extends AbstractRCodeZeroAdapter
+{
+    /**
+     * @param string $content
+     */
+    public function createRdata( $content )
+    {
+        $contentArray = explode(' ', $content);
+        $this->rdata = new URI();
+        $this->rdata->priority = $contentArray[0];
+        $this->rdata->weight = $contentArray[1];
+        $this->rdata->target = $this->contentToRelative($contentArray[2]);
+    }
+
+    /**
+     * @param URI $rdata
+     * @return string
+     */
+    public function parseContentToApiFormat( $rdata )
+    {
+        $content = [
+            $rdata->priority,
+            $rdata->weight,
+            $rdata->target
+        ];
+
+        return  implode(' ', $content);
+    }
+}

+ 21 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/RRSet.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero;
+
+/**
+ * Class RRSet
+ * Groups Records With Same Domain and Record Type
+ * @package MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero
+ */
+class RRSet
+{
+    /** @var string */
+    public $name;
+    /** @var string */
+    public $type;
+    /** @var int */
+    public $ttl;
+    /** @var string */
+    public $changetype = 'update';
+     public $records    = [];
+}

+ 9 - 0
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/RCodeZero/RecordFromServer.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules\RCodeZero;
+
+class RecordFromServer
+{
+    /** @property string $content string of values connected by space*/
+    /** @property boolean disabled */
+}

+ 17 - 1
modules/addons/DNSManager2/mgLibs/custom/dns/submodules/SimpleDNSV8.php

@@ -6,7 +6,7 @@ use \MGModule\DNSManager2\mgLibs\custom\dns;
 use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
 use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
 use \MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;
-
+use MGModule\DNSManager2\mgLibs\custom\dns\utils\ReverseDNSHelper;
 
 class SimpleDNSV8 extends dns\SubmoduleAbstract implements interfaces\SubmoduleRDNSInterface, interfaces\SubmoduleTTLInterface, interfaces\SubmoduleImportInterface, interfaces\SubmoduleDNSSecInterface
 {
@@ -314,6 +314,22 @@ class SimpleDNSV8 extends dns\SubmoduleAbstract implements interfaces\SubmoduleR
         }
     }
 
+    public function getRDNSRecord( $ip )
+    {
+        $clone = $this->getClone($ip);
+        $reverseRecordName =  ReverseDNSHelper::reverseRecordName($ip);
+
+        foreach( $clone->getRecords('PTR') as $record )
+        {
+            $record->name = rtrim($record->name, '.') . '.';
+            if( $record->name === $reverseRecordName || $record->nameToRelative($clone->domain) === $reverseRecordName )
+            {
+                return $record;
+            }
+        }
+        return false;
+    }
+
     private function recordToParamsArray(dns\record\Record $record)
     {
         $relativeName = $record->nameToRelative($this->domain);

+ 28 - 10
modules/addons/DNSManager2/mgLibs/custom/helpers/ZoneUpdateHelper.php

@@ -39,6 +39,8 @@ class ZoneUpdateHelper
         $tmpBackupRecords = $this->backupRecords;
 
         $curCname = false;
+        $soaLine = '';
+        $soaSerial = '';
         foreach($currentRecords as $rKey => $cRecord)
         {
             foreach($tmpBackupRecords as $brKey => $bRecord)
@@ -51,32 +53,48 @@ class ZoneUpdateHelper
                 if($cRecord->type === 'SOA')
                 {
                     $curCname = true;
+                    $soaLine = $cRecord->line;
+                    $soaSerial = $cRecord->$soaSerial->serial;
                 }
                 
                 if($this->compareRecords($cRecord, $bRecord) === true)
                 {
-                    unset($currentRecords[$rKey]);                    unset($cRecord);
+                    unset($currentRecords[$rKey]);
+                    unset($cRecord);
                     unset($tmpBackupRecords[$brKey]); unset($bRecord);
                 }
             }
         }
-
+        $errorMessage = '';
         foreach($tmpBackupRecords as $bdRecord)
         {
-            if($bdRecord->type === 'SOA' && $curCname == false)
-            {
-                continue;
+            try {
+                if($bdRecord->type === 'SOA' && $curCname)
+                {
+                    $bdRecord->line = $soaLine;
+                    $bdRecord->rdata->serial = $soaSerial;
+                    $this->module->editRecord($bdRecord);
+                    continue;
+                }
+                $this->module->addRecord($bdRecord);
+                $this->zoneLoggerManager->logAddRecordToZone($this->zone, $bdRecord, 'Export: ');
+            } catch (\Exception $e) {
+                $errorMessage .= "<b>Record: " . $bdRecord->name . " - " . $bdRecord->type . " - " . $bdRecord->rdata->toString() . ": </b>" . $e->getMessage() . "\n";
             }
-            
-            $this->module->addRecord($bdRecord);
-            $this->zoneLoggerManager->logAddRecordToZone($this->zone, $bdRecord, 'Export: ');
         }
-        
+
+        if($errorMessage)
+            throw new \Exception($errorMessage);
+
         foreach($currentRecords as $cdRecord)
         {
             $cRecords = $this->module->getRecords();
             foreach($cRecords as $cValue)
             {
+                if($cValue->type === 'SOA' && $curCname)
+                {
+                    continue;
+                }
                 if($this->compareRecords($cdRecord, $cValue))
                 {
                     $this->module->deleteRecord($cValue);
@@ -129,7 +147,7 @@ class ZoneUpdateHelper
     
     protected function compareRecords(dns\record\Record $rec1, dns\record\Record $rec2)
     {
-        if($rec1->name === $rec2->name && $rec1->ttl === $rec2->ttl && $rec1->type === $rec2->type
+        if($rec1->name === $rec2->name && (int)$rec1->ttl === (int)$rec2->ttl && $rec1->type === $rec2->type
             && $rec1->rdata->toString() === $rec2->rdata->toString())
         {
             return true;

+ 1 - 1
modules/addons/DNSManager2/mgLibs/custom/manager/LogHelper.php

@@ -50,7 +50,7 @@ class LogHelper
         $log->action = $action;
         $log->value = $message;
         $log->name = $name?:'';
-        $log->clientid = $clientid?:'';
+        $log->clientid = $clientid?:0;
         $log->status = $type;
         $log->date = date('Y-m-d H:i:s');
     

+ 20 - 0
modules/addons/DNSManager2/mgLibs/custom/manager/RecordHelper.php

@@ -5,6 +5,8 @@ namespace MGModule\DNSManager2\mgLibs\custom\manager;
 use MGModule\DNSManager2\mgLibs\custom\dns;
 use MGModule\DNSManager2\models\custom\set;
 use MGModule\DNSManager2\models\custom\zone\Zone;
+use MGModule\DNSManager2\models\whmcs\servers\server;
+use MGModule\DNSManager2\models\whmcs\service\service;
 
 class RecordHelper {
     private $record = false;
@@ -55,6 +57,24 @@ class RecordHelper {
         $c = str_replace('{$domainextension}', $splitDomain['extension'], $b);
         $d = str_replace('{$serverhostname}', $this->serverConfiguration['hostname'], $c);
 
+        try {
+            $relid = $this->zone->relid;
+            $relatedService = new service($relid);
+            $relatedServer = new server($relatedService->serverID);
+            $assignedIps = $relatedServer->assignedips;
+        } catch (\Exception $e) {}
+
+        $assignedIps = preg_split('/\r\n|\r|\n/', $assignedIps);
+
+        if(count($assignedIps) > 0){
+            foreach (range(1, count($assignedIps)) as $i)
+            {
+                $search  = '{$serverassignedip' . $i . '}';
+                $replace = (is_array($assignedIps) && $assignedIps[$i - 1]) ? $assignedIps[$i - 1] : '';
+                $d       = str_replace($search, $replace, $d);
+            }
+        }
+
         foreach ( range(1, 5) as $i )
         {
             $search = '{$ns' . $i . '}';

+ 12 - 4
modules/addons/DNSManager2/mgLibs/custom/manager/ZoneCreator.php

@@ -59,13 +59,21 @@ class ZoneCreator
         $this->item     = $item;
         $this->package  = $item->getPackage();
 
-        foreach ($this->package->getServers() as $packageServers){
-            if($packageServers->isMaster())
-                $this->server = $packageServers->getServer();
+        foreach ($this->package->getServers() as $packageServer){
+            if($packageServer->isMaster())
+            {
+                if(!$packageServer->getServer()->isEnabled())
+                {
+                    $serverName = $packageServer->getServer() ? $packageServer->getServer()->name : '';
+                    throw new \Exception(main\mgLibs\lang::T('master_server_disabled') . $serverName);
+                }
+                $this->server = $packageServer->getServer();
+            }
         }
         if(!$this->server)
+        {
             $this->server = $this->package->getFirstActiveServer();
-
+        }
         if($this->server === false)
         {
             return ;

+ 0 - 5
modules/addons/DNSManager2/mgLibs/custom/task/ImportToFileWHMCS.php

@@ -118,11 +118,6 @@ class ImportToFileWHMCS extends TaskAbstract
             $zone->updated_at         = $this->getParams('updated_at');
             $zone->status             = 1; // TODO status
 
-            if (!$module_from->zoneExists())
-            {
-                $zone->status = 0;
-            }
-
             $file = $this->parent->getParams('toFile');
             $createdBy = $this->parent->getParams('createdBy');
             ClientFilesManage::saveIfNotExists($file, $zone->clientid, ClientFilesManage::BACKUP, $createdBy);

+ 170 - 64
modules/addons/DNSManager2/mgLibs/custom/task/RecordSynchronization.php

@@ -9,6 +9,11 @@ use MGModule\DNSManager2\models\custom\zone\Repository;
 
 class RecordSynchronization extends TaskAbstract
 {
+    public function __call($name, $arguments)
+    {
+        $name = explode('_', $name)[0];
+        $this->$name($arguments[0]);
+    }
 
     public function mainDescription()
     {
@@ -20,13 +25,23 @@ class RecordSynchronization extends TaskAbstract
      */
     public function syncAdd($params)
     {
-        $zone = $this->getZone($params);
+        try
+        {
+            $zone = $this->getZone($params);
 
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $this->loopThroughServers($zone, $params['submodule'], false, function ($module, $masterRecords) use ($params)
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
         {
-            $module->addRecord($params['values'][0]);
-        });
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
     }
 
@@ -35,16 +50,23 @@ class RecordSynchronization extends TaskAbstract
      */
     public function syncDelete($params)
     {
-        $zone = $this->getZone($params);
-
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-
-        $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+        try
         {
-            $this->syncRecords($module, $masterRecords);
-        });
+            $zone = $this->getZone($params);
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
     /**
@@ -52,67 +74,128 @@ class RecordSynchronization extends TaskAbstract
      */
     public function syncEdit($params)
     {
-        $zone = $this->getZone($params);
-
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+        try
         {
-            $this->syncRecords($module, $masterRecords);
-        });
+            $zone = $this->getZone($params);
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
+    /**
+     * @param $params
+     */
     public function syncActivateZone($params)
     {
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $zone = $this->getZone($params);
-        $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+        try
         {
-            $module->activateZone();
-            $module->wipeRecords();
-            $this->syncRecords($module, $masterRecords);
-        });
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $zone = $this->getZone($params);
+            $this->loopThroughServers($zone, $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $module->getSubmodule()->getModule()->activateZone();
+                $module->wipeRecords();
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
+    /**
+     * @param $params
+     */
     public function syncTerminateZone($params)
     {
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $this->loopThroughServers($params['zone'], $params['submodule'], false, function ($module, $masterRecords)
+        try
         {
-            $module->terminateZone();
-        });
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($params['zone'], $params['submodule'], false, function ($module, $masterRecords)
+            {
+                $module->getSubmodule()->getModule()->terminateZone();
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
+    /**
+     * @param $params
+     */
     public function syncUpdateRDNS($params)
     {
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $this->loopThroughServers($params['zone'], $params['submodule'], true, function ($module, $masterRecords)
+        try
         {
-            $this->syncRecords($module, $masterRecords);
-        });
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($params['zone'], $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
+    /**
+     * @param $params
+     */
     public function syncRemoveRDNS($params)
     {
-        $this->setStatus(TaskStatusEnum::IN_PROGRESS);
-        $this->loopThroughServers($params['zone'], $params['submodule'], true, function ($module, $masterRecords)
+        try
         {
-            $this->syncRecords($module, $masterRecords);
-        });
+            $this->setStatus(TaskStatusEnum::IN_PROGRESS);
+            $this->loopThroughServers($params['zone'], $params['submodule'], true, function ($module, $masterRecords)
+            {
+                $this->syncRecords($module, $masterRecords);
+            });
+        }
+        catch (\Exception $e)
+        {
+            $this->setStatus(TaskStatusEnum::ERROR);
+            $this->addResult(['error' => $e->getMessage()]);
+            LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
+            return;
+        }
         $this->setStatus(TaskStatusEnum::FINISHED);
-
     }
 
     protected function getZone($params)
     {
         $submodule      = $params['submodule'];
+        if(!$submodule)
+        {
+            $domain = explode('_', $this->description(),2)[1];
+            throw new \Exception('Zone does not exists:' . ($domain ? ': ' . $domain : ''));
+        }
         $zoneRepository = Repository::factory();
         return $zoneRepository->byServerID($submodule->getServer()->id)->byName($submodule->getDomain())->get()[0];
     }
@@ -127,6 +210,10 @@ class RecordSynchronization extends TaskAbstract
     {
         [$master, $slaves] = $this->getServers($zone);
         $masterRecords = [];
+        if ($master == null)
+        {
+            return;
+        }
 
         if ($withRecords)
         {
@@ -139,15 +226,20 @@ class RecordSynchronization extends TaskAbstract
         {
             try
             {
+                if(!$slaveServer->getServer()->isEnabled())
+                {
+                    continue;
+                }
                 $module = $slaveServer->getServer()->getModule();
                 $module->setDomain($submodule->getDomain());
 
                 $callable($module, $masterRecords);
             }
-            catch (\Exception $ex)
+            catch (\Exception $e)
             {
-//                var_dump($ex->getMessage());
-                LogHelper::addFailLogUsingZone('record_sync_on_' . $submodule->getDomain(), $ex->getMessage(), $zone);
+                $this->setStatus(TaskStatusEnum::ERROR);
+                $this->addResult(['error' => $e->getMessage()]);
+                LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
             }
         }
     }
@@ -158,7 +250,14 @@ class RecordSynchronization extends TaskAbstract
      */
     private function getServers($zone)
     {
+        if ($zone == null)
+        {
+            return;
+        }
         $package = $zone->getPackage();
+        if($package == false)
+            return [];
+
         $master  = null;
         $slaves  = [];
 
@@ -189,7 +288,6 @@ class RecordSynchronization extends TaskAbstract
     {
         $slaveRecords = $module->getRecords();
         $domain       = $module->getSubmodule()->getModule()->getDomain();
-
         /**
          * Add record which does not exist in slave
          */
@@ -206,24 +304,21 @@ class RecordSynchronization extends TaskAbstract
                 if ($masterRecord->compare($slaveRecord, $domain))
                 {
                     $found = true;
-
                     continue;
                 }
             }
-
             if (!$found)
             {
+                //this can cause CPanel records duplicate problem
                 $masterRecord->ttl = $masterRecord->ttl ?: 3600;
-//                echo "\n================ ADD RECORD ===================\n";
-//                print_r($masterRecord);
-
                 try
                 {
-                    $module->addRecord($masterRecord);
+                    $module->getSubmodule()->getModule()->addRecord($masterRecord);
                 }
                 catch (\Exception $e)
                 {
-                    echo $e->getMessage() . "\n";
+                    $this->addResult(['error' => $e->getMessage()]);
+                    LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
                 }
             }
         }
@@ -231,6 +326,7 @@ class RecordSynchronization extends TaskAbstract
         /**
          * Delete record from slave which does not exist in master
          */
+        $recordsToDelete = [];
         foreach ($slaveRecords as $slaveRecord)
         {
             if (in_array($slaveRecord->type, ['SOA']))
@@ -252,16 +348,26 @@ class RecordSynchronization extends TaskAbstract
 
             if (!$found)
             {
-//                echo "\n================ DELETE RECORD ===================\n";
-//                print_r($slaveRecord);
-                try
-                {
-                    $module->deleteRecord($slaveRecord);
-                }
-                catch (\Exception $e)
-                {
-                    echo $e->getMessage() . "\n";
-                }
+                $recordsToDelete[] = $slaveRecord;
+            }
+        }
+
+        //sorting because CPanel as slave dynamically changes line during removal
+        usort($recordsToDelete, function ($a, $b)
+        {
+            return $a == $b ? 0 : (int)$a->line < (int)$b->line;
+        });
+
+        foreach ($recordsToDelete as $recordToDelete)
+        {
+            try
+            {
+                $module->getSubmodule()->getModule()->deleteRecord($recordToDelete);
+            }
+            catch (\Exception $e)
+            {
+                $this->addResult(['error' => $e->getMessage()]);
+                LogHelper::addFailLog($this->mainDescription() . "_" . __FUNCTION__, $e->getMessage());
             }
         }
     }

+ 3 - 3
modules/addons/DNSManager2/mgLibs/lang.php

@@ -237,7 +237,7 @@ class lang
                }
                else
                {
-                   return htmlentities($lang[$find]);
+                   return html_entity_decode($lang[$find]);
                }
            }
            else
@@ -249,7 +249,7 @@ class lang
                }
                else
                {
-                   return htmlentities($find);
+                   return html_entity_decode($find);
                }
            }
        }
@@ -271,7 +271,7 @@ class lang
            return '$'."_LANG['".implode("']['", $history)."']";
        }
        
-       return htmlentities($find);
+       return html_entity_decode($find);
    }
    
     /**

+ 7 - 1
modules/addons/DNSManager2/models/whmcs/servers/server.php

@@ -60,7 +60,13 @@ class server extends main\mgLibs\models\orm {
      * @var string 
      */
     public $disabled;
-    
+
+
+    /**
+     * @Column(notRequired)
+     * @var string
+     */
+    public $assignedips;
     /** 
      * Load Server Data
      * 

+ 2 - 2
modules/addons/DNSManager2/moduleVersion.php

@@ -5,6 +5,6 @@
  * Below you can find current version & revision of this module
  */
 
-$moduleVersion = '2.15.0';
-$moduleRevision = 'b709e4c21a2856b49157bf6bba7f3b4e13c34e2b';
+$moduleVersion = '2.16.0';
+$moduleRevision = 'b7a65cf78a3307542bb35bdabecd8e369935f996';
 $moduleWikiUrl = 'http://www.docs.modulesgarden.com/DNS_Manager_For_WHMCS';

+ 1 - 1
modules/addons/DNSManager2/templates/admin/pages/settings/global.tpl

@@ -290,7 +290,7 @@
                                 <div class="col-sm-1 mg-tooltip-custom option-helper"><i class="fa fa-question-circle" title="{$MGLANG->T('showDnsManagerButton_desc')}"></i></div>
                             </div>
                             <div class="form-group">
-                                <label for="cx13" class="col-sm-3 control-label label-mg-custom">{$MGLANG->T('do_not_remove_empty_ptr_zones')}</label>
+                                <label for="cx13" class="col-sm-4 control-label label-mg-custom">{$MGLANG->T('do_not_remove_empty_ptr_zones')}</label>
                                 <div class="col-sm-1 mg-tooltip-custom">
                                     <div class=" checkbox">
                                         <input type="checkbox" id="cx13" name="settings[do_not_remove_empty_ptr_zones]" {if $settings.do_not_remove_empty_ptr_zones}checked=""{/if}/>

+ 1 - 1
modules/addons/DNSManager2/templates/clientarea/default/assets/css/layout.css

@@ -463,7 +463,7 @@
 
 #mg-wrapper .modal{
     width: 100%;
-    margin: 0;
+    margin: 0 !important;
     background: none;
 }
 #mg-wrapper .modal .modal-header{

+ 1 - 1
modules/addons/DNSManager2/templates/clientarea/default/main.tpl

@@ -88,7 +88,7 @@
                 <img src="{$assetsURL}/img/ajax-loader.gif" alt="Loading ..." />
             </div>
         </div>
-        <div id="MGConfirmationModal" class="modal" style="z-index: 1055; margin: 30vh auto; overflow-y: hidden;">
+        <div id="MGConfirmationModal" class="modal fade" style="z-index: 1055; margin: 30vh auto; overflow-y: hidden;">
             <div class="modal-dialog">
                 <div class="modal-content">
                     <div class="modal-header">

+ 47 - 47
modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-rdns.tpl

@@ -1,47 +1,47 @@
-<div class="modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <form class="form-horizontal" action="" method="post">
-                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-                    <h4 class="modal-title">{$MGLANG->T('add_new_rdns')}</h4>
-                </div>
-                <div class="modal-body">
-                    <div class="form-group">
-                        <label class="col-sm-3 control-label">{$MGLANG->T('related_item')}</label>
-                        <div class="col-sm-7">
-                            <select name="item" class="select2">
-                                <option value="">{$MGLANG->T('select_one')}</option>
-                                {foreach from=$items item="item"}
-                                    <option value="{$item.type}::{$item.relid}" data-act="getRDNSData" data-query="type={$item.type}&relid={$item.relid}" data-do-not-close-the-modal="" data-no-validate="1">{$item.name}</option>
-                                {/foreach}
-                            </select>
-                        </div>
-                    </div>
-                    <div id="rdns-data">
-                        
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
-                    <button type="button" class="btn btn-success" data-act="addRDNSSave">{$MGLANG->T('add')}</button>                                  
-                </div>
-            </form>
-        </div>
-    </div>
-</div>
-
-{literal}
-    <script data-cfasync="false" type="text/javascript">   
-        function getRDNSDataCallback(data, $obj) {
-            jQuery('#rdns-data').html(data.html);
-            $obj.parent().find('option[value=""]').remove();
-        }
-        
-        (function($) {
-            $(document).delegate('[name=ip]','change', function() {
-                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
-            });
-        })(jQuery);
-        
-    </script>
-{/literal}
+<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form class="form-horizontal" action="" method="post">
+                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    <h4 class="modal-title">{$MGLANG->T('add_new_rdns')}</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label">{$MGLANG->T('related_item')}</label>
+                        <div class="col-sm-7">
+                            <select name="item" class="select2">
+                                <option value="">{$MGLANG->T('select_one')}</option>
+                                {foreach from=$items item="item"}
+                                    <option value="{$item.type}::{$item.relid}" data-act="getRDNSData" data-query="type={$item.type}&relid={$item.relid}" data-do-not-close-the-modal="" data-no-validate="1">{$item.name}</option>
+                                {/foreach}
+                            </select>
+                        </div>
+                    </div>
+                    <div id="rdns-data">
+                        
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
+                    <button type="button" class="btn btn-success" data-act="addRDNSSave">{$MGLANG->T('add')}</button>                                  
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+
+{literal}
+    <script data-cfasync="false" type="text/javascript">   
+        function getRDNSDataCallback(data, $obj) {
+            jQuery('#rdns-data').html(data.html);
+            $obj.parent().find('option[value=""]').remove();
+        }
+        
+        (function($) {
+            $(document).delegate('[name=ip]','change', function() {
+                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
+            });
+        })(jQuery);
+        
+    </script>
+{/literal}

+ 121 - 121
modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-record.tpl

@@ -1,121 +1,121 @@
-<div class="modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <form class="form-horizontal" action="" method="post">
-                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-                    <h4 class="modal-title">{$MGLANG->T('add_new_record')}</h4>
-                </div>
-                <div class="modal-body">
-                    <input type="hidden" name="zone_id" value="{$zone_id}" />
-                    <div class="form-group" id="add-record-zone-name"> 
-                        <label class="control-label col-sm-3">{$MGLANG->T('name')}</label>
-                        <div class="col-sm-7">
-                            <input class="form-control" type="text" name="name" value="" title="{$MGLANG->absoluteT('addonAA','zones','record_info','name')}" placeholder="{$MGLANG->T('name')}" required="" />
-                        </div>
-                    </div>
-                        
-                    <div style="display: none" id="add-record-zone-ip">
-                        {if $custom_ip}
-                            <div class="form-group">
-                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                <div class="col-sm-7">
-                                    <input class="form-control" type="text" name="ip" value="" required pattern="{$patterns.ip}"/>
-                                </div>
-                            </div>
-                        {else}
-                            <div class="form-group">
-                                <label class="control-label col-sm-3">{if $ipmanager}{$MGLANG->T('ip_or_block')}{else}{$MGLANG->T('ip')}{/if}</label>
-                                <div class="col-sm-7">
-                                    <select class="select2" name="ip" title="{$MGLANG->absoluteT('addonAA','zones','record_info','name')}" required="">
-                                        {if $ipmanager}
-                                            {foreach from=$pools item="pool"}
-                                                <option value="block|{$pool.pool}|{$pool.mask}" data-pool>{$pool.pool}/{$pool.mask}</option>
-                                            {/foreach}
-                                        {/if}
-                                        {foreach from=$ips item="ip"}
-                                            <option value="{$ip}">{$ip}</option>
-                                        {foreachelse}
-                                            {if empty($pools)}
-                                            <option value="" disabled="">{$MGLANG->T('no_ip_available')}</option>
-                                            {/if}
-                                        {/foreach}
-                                    </select>
-                                </div>
-                            </div>
-                            <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}> 
-                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                <div class="col-sm-7">
-                                    <input class="form-control" type="text" name="ip_from_block" value="" required pattern="{$patterns.ip}"/>
-                                </div>
-                            </div>
-                            <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}> 
-                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                <div class="col-sm-7">
-                                    <input class="form-control" type="text" name="ip_from_block" value="" required pattern="{$patterns.ip}"/>
-                                </div>
-                            </div>     
-                        {/if}
-                    </div>
-
-                    <div class="form-group">
-                        <label class="control-label col-sm-3">{$MGLANG->T('type')}</label>
-                        <div class="col-sm-7">
-                            <select class="select2" name="type" required="">
-                                <option value="">{$MGLANG->T('select_one')}</option>
-                                {foreach from=$available_record_types item="record"}
-                                    <option value="{$record}" data-act="getRecordRdata" data-do-not-close-the-modal="" data-no-validate="1">{$record}</option>
-                                {/foreach}
-                            </select> 
-                        </div>
-                    </div>
-                    {if $ttl_enabled}
-                    <div class="form-group"> 
-                        <label class="control-label col-sm-3">{$MGLANG->T('ttl')}</label>
-                        <div class="col-sm-7">
-                            <input class="form-control" type="text" id="ttl" name="ttl" value="14400" title="{$MGLANG->absoluteT('addonAA','zones','record_info','ttl')}" placeholder="{$MGLANG->T('ttl')}" required="" min="1"/>
-                        </div>
-                    </div>
-                    {/if}
-                    <div class="form-group record-rdata" style="display: none;"> 
-                        <label class="control-label col-sm-3">{$MGLANG->T('rdata')}</label>
-                        <div class="col-sm-7">
-                            
-                        </div>
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
-                    <button type="button" class="btn btn-success" data-act="addRecordSave">{$MGLANG->T('add_record')}</button>                                  
-                </div>
-            </form>
-        </div>
-    </div>
-</div>
-                    
-{literal}
-    <script data-cfasync="false" type="text/javascript">
-        function getRecordRdataCallback(data, $obj) {
-            if(!data.html)
-                return ;
-            $obj.parents('.modal-body').first().find('.record-rdata').show().find('div').html(data.html);
-            $obj.parents('.modal-body').first().find('[name=type] option[value=""]').remove();
-
-            $obj.parents('.modal-body').first().find('#ttl').val(data.ttl);
-            
-            if($($obj[0]).val() == "PTR"){
-                $('#add-record-zone-ip').show();
-                $('#add-record-zone-name').hide();
-            }else{
-                $('#add-record-zone-ip').hide();
-                $('#add-record-zone-name').show();
-            }
-        }
-        
-        (function($) {
-            $('[name=ip]').change(function() {
-                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
-            });
-        })(jQuery);
-        
-    </script>
-{/literal}
+<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form class="form-horizontal" action="" method="post">
+                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    <h4 class="modal-title">{$MGLANG->T('add_new_record')}</h4>
+                </div>
+                <div class="modal-body">
+                    <input type="hidden" name="zone_id" value="{$zone_id}" />
+                    <div class="form-group" id="add-record-zone-name"> 
+                        <label class="control-label col-sm-3">{$MGLANG->T('name')}</label>
+                        <div class="col-sm-7">
+                            <input class="form-control" type="text" name="name" value="" title="{$MGLANG->absoluteT('addonAA','zones','record_info','name')}" placeholder="{$MGLANG->T('name')}" required="" />
+                        </div>
+                    </div>
+                        
+                    <div style="display: none" id="add-record-zone-ip">
+                        {if $custom_ip}
+                            <div class="form-group">
+                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                <div class="col-sm-7">
+                                    <input class="form-control" type="text" name="ip" value="" required pattern="{$patterns.ip}"/>
+                                </div>
+                            </div>
+                        {else}
+                            <div class="form-group">
+                                <label class="control-label col-sm-3">{if $ipmanager}{$MGLANG->T('ip_or_block')}{else}{$MGLANG->T('ip')}{/if}</label>
+                                <div class="col-sm-7">
+                                    <select class="select2" name="ip" title="{$MGLANG->absoluteT('addonAA','zones','record_info','name')}" required="">
+                                        {if $ipmanager}
+                                            {foreach from=$pools item="pool"}
+                                                <option value="block|{$pool.pool}|{$pool.mask}" data-pool>{$pool.pool}/{$pool.mask}</option>
+                                            {/foreach}
+                                        {/if}
+                                        {foreach from=$ips item="ip"}
+                                            <option value="{$ip}">{$ip}</option>
+                                        {foreachelse}
+                                            {if empty($pools)}
+                                            <option value="" disabled="">{$MGLANG->T('no_ip_available')}</option>
+                                            {/if}
+                                        {/foreach}
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}> 
+                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                <div class="col-sm-7">
+                                    <input class="form-control" type="text" name="ip_from_block" value="" required pattern="{$patterns.ip}"/>
+                                </div>
+                            </div>
+                            <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}> 
+                                <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                <div class="col-sm-7">
+                                    <input class="form-control" type="text" name="ip_from_block" value="" required pattern="{$patterns.ip}"/>
+                                </div>
+                            </div>     
+                        {/if}
+                    </div>
+
+                    <div class="form-group">
+                        <label class="control-label col-sm-3">{$MGLANG->T('type')}</label>
+                        <div class="col-sm-7">
+                            <select class="select2" name="type" required="">
+                                <option value="">{$MGLANG->T('select_one')}</option>
+                                {foreach from=$available_record_types item="record"}
+                                    <option value="{$record}" data-act="getRecordRdata" data-do-not-close-the-modal="" data-no-validate="1">{$record}</option>
+                                {/foreach}
+                            </select> 
+                        </div>
+                    </div>
+                    {if $ttl_enabled}
+                    <div class="form-group"> 
+                        <label class="control-label col-sm-3">{$MGLANG->T('ttl')}</label>
+                        <div class="col-sm-7">
+                            <input class="form-control" type="text" id="ttl" name="ttl" value="14400" title="{$MGLANG->absoluteT('addonAA','zones','record_info','ttl')}" placeholder="{$MGLANG->T('ttl')}" required="" min="1"/>
+                        </div>
+                    </div>
+                    {/if}
+                    <div class="form-group record-rdata" style="display: none;"> 
+                        <label class="control-label col-sm-3">{$MGLANG->T('rdata')}</label>
+                        <div class="col-sm-7">
+                            
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
+                    <button type="button" class="btn btn-success" data-act="addRecordSave">{$MGLANG->T('add_record')}</button>                                  
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+                    
+{literal}
+    <script data-cfasync="false" type="text/javascript">
+        function getRecordRdataCallback(data, $obj) {
+            if(!data.html)
+                return ;
+            $obj.parents('.modal-body').first().find('.record-rdata').show().find('div').html(data.html);
+            $obj.parents('.modal-body').first().find('[name=type] option[value=""]').remove();
+
+            $obj.parents('.modal-body').first().find('#ttl').val(data.ttl);
+            
+            if($($obj[0]).val() == "PTR"){
+                $('#add-record-zone-ip').show();
+                $('#add-record-zone-name').hide();
+            }else{
+                $('#add-record-zone-ip').hide();
+                $('#add-record-zone-name').show();
+            }
+        }
+        
+        (function($) {
+            $('[name=ip]').change(function() {
+                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
+            });
+        })(jQuery);
+        
+    </script>
+{/literal}

+ 117 - 117
modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/add-zone.tpl

@@ -1,118 +1,118 @@
-<div class="modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <form class="form-horizontal" action="" method="post" id="zone_form">
-                <input type="hidden" name="dnsaction" value="createzone" />
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-                    <h4 class="modal-title">{$MGLANG->T('add_dns_zone')}</h4>
-                </div>
-                <div class="modal-body">
-                    <div id="create_zone_area">
-                        <input type="hidden" name="type" value="{$type}" />
-                        <input type="hidden" name="relid" value="{$relid}" />
-                        <input type="hidden" name="isIpRequired" value="{$is_ip_required}"/>
-                        {if $zoneDomainItems == 'allowCustomDomain'}
-                            <div class="form-group">
-                                <label class="control-label col-sm-3">{$MGLANG->T('zone_name')}</label>
-                                <div class="col-sm-7">
-                                    <input class="form-control" type="text" name="zone_name" value="" required pattern="(.*\.)+.+" pattern="{literal}^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}{/literal}"/>
-                                </div>
-                            </div>
-                        {else}
-                            <div class="form-group">
-                                <label class="control-label col-sm-3">{$MGLANG->T('zone_name')}</label>
-                                <div class="col-sm-7">
-                                    <select class="select2" id="si1" name="zone_name">
-                                        {foreach from=$zoneDomainItems item="item"}
-                                            <option value="{$item}" >{$item}</option>
-                                        {/foreach}
-                                    </select>
-                                </div>
-                            </div>
-                        {/if}
-
-                        {if $is_ip_required}
-                            {if $custom_ip and $allowed_ips_flag}
-                                <div class="form-group">
-                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                    <div class="col-sm-7">
-                                        <select class="select2" name="zone_ip">
-                                            {foreach from=$allowed_ips item="ip"}
-                                                    {if !in_array($ip,$ip_blacklist)}
-                                                        <option value="{$ip}">{$ip}</option>
-                                                    {/if}
-                                            {/foreach}
-                                        </select>
-                                    </div>
-                                </div>
-                            {elseif $custom_ip}
-                                <div class="form-group">
-                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                    <div class="col-sm-7">
-                                        <input class="form-control" type="text" name="zone_ip" value="" {if $is_not_domain}required{/if} pattern="{$patterns.ip}"/>
-                                    </div>
-                                </div>
-                            {else}
-                                <div class="form-group {if !$ips and !$pools}hidden{/if}">
-                                    <label class="control-label col-sm-3">{if $ipmanager}{$MGLANG->T('ip_or_block')}{else}{$MGLANG->T('ip')}{/if}</label>
-                                    <div class="col-sm-7">
-                                        <select class="select2" name="zone_ip">
-                                            {if $ipmanager}
-                                                {foreach from=$pools item="pool"}
-                                                    <option value="block|{$pool.pool}|{$pool.mask}" data-pool>{$pool.pool}/{$pool.mask}</option>
-                                                {/foreach}
-                                            {/if}
-                                            {foreach from=$ips item="ip"}
-                                                <option value="{$ip}">{$ip}</option>
-                                            {/foreach}
-                                                <option value="">{$MGLANG->T('default_ip')}</option>
-                                        </select>
-                                    </div>
-                                </div>
-
-                                <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}>
-                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
-                                    <div class="col-sm-7">
-                                        <input class="form-control" type="text" name="zone_ip_from_block" value="" {if $is_not_domain}required{/if} pattern="{$patterns.ip}"/>
-                                    </div>
-                                </div>
-                            {/if}
-                        {/if}
-                        {if $allowManageRecordsSets === 'on'}
-                            <div class="form-group">
-                                <label class="control-label col-sm-3">{$MGLANG->T('selectRecordSet')}</label>
-                                <div class="col-sm-7">
-                                    <select class="select2" name="recordSet">
-                                        <option value="none"  >{$MGLANG->T('none')}</option>
-                                        {foreach from=$recordSets item="set"}
-                                            <option value="{$set->id}">{$set->name}</option>
-                                        {/foreach}
-                                        {foreach from=$adminSets item="aset"}
-                                            <option value="{$aset->id}">{$aset->name}</option>
-                                        {/foreach}
-                                            <option value="" selected >{$MGLANG->T('defaultSet')}</option>
-                                    </select>
-                                </div>
-                            </div>
-                        {/if}
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
-                    <button type="button" class="btn btn-success" data-act="addZoneSave">{$MGLANG->T('add_zone')}</button>
-                </div>
-            </form>
-        </div>
-    </div>
-</div>
-
-{literal}
-    <script data-cfasync="false" type="text/javascript">
-        (function($) {
-            $('[name=zone_ip]').change(function() {
-                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
-            });
-        })(jQuery);
-    </script>
+<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form class="form-horizontal" action="" method="post" id="zone_form">
+                <input type="hidden" name="dnsaction" value="createzone" />
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    <h4 class="modal-title">{$MGLANG->T('add_dns_zone')}</h4>
+                </div>
+                <div class="modal-body">
+                    <div id="create_zone_area">
+                        <input type="hidden" name="type" value="{$type}" />
+                        <input type="hidden" name="relid" value="{$relid}" />
+                        <input type="hidden" name="isIpRequired" value="{$is_ip_required}"/>
+                        {if $zoneDomainItems == 'allowCustomDomain'}
+                            <div class="form-group">
+                                <label class="control-label col-sm-3">{$MGLANG->T('zone_name')}</label>
+                                <div class="col-sm-7">
+                                    <input class="form-control" type="text" name="zone_name" value="" required pattern="(.*\.)+.+" pattern="{literal}^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}{/literal}"/>
+                                </div>
+                            </div>
+                        {else}
+                            <div class="form-group">
+                                <label class="control-label col-sm-3">{$MGLANG->T('zone_name')}</label>
+                                <div class="col-sm-7">
+                                    <select class="select2" id="si1" name="zone_name">
+                                        {foreach from=$zoneDomainItems item="item"}
+                                            <option value="{$item}" >{$item}</option>
+                                        {/foreach}
+                                    </select>
+                                </div>
+                            </div>
+                        {/if}
+
+                        {if $is_ip_required}
+                            {if $custom_ip and $allowed_ips_flag}
+                                <div class="form-group">
+                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                    <div class="col-sm-7">
+                                        <select class="select2" name="zone_ip">
+                                            {foreach from=$allowed_ips item="ip"}
+                                                    {if !in_array($ip,$ip_blacklist)}
+                                                        <option value="{$ip}">{$ip}</option>
+                                                    {/if}
+                                            {/foreach}
+                                        </select>
+                                    </div>
+                                </div>
+                            {elseif $custom_ip}
+                                <div class="form-group">
+                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                    <div class="col-sm-7">
+                                        <input class="form-control" type="text" name="zone_ip" value="" {if $is_not_domain}required{/if} pattern="{$patterns.ip}"/>
+                                    </div>
+                                </div>
+                            {else}
+                                <div class="form-group {if !$ips and !$pools}hidden{/if}">
+                                    <label class="control-label col-sm-3">{if $ipmanager}{$MGLANG->T('ip_or_block')}{else}{$MGLANG->T('ip')}{/if}</label>
+                                    <div class="col-sm-7">
+                                        <select class="select2" name="zone_ip">
+                                            {if $ipmanager}
+                                                {foreach from=$pools item="pool"}
+                                                    <option value="block|{$pool.pool}|{$pool.mask}" data-pool>{$pool.pool}/{$pool.mask}</option>
+                                                {/foreach}
+                                            {/if}
+                                            {foreach from=$ips item="ip"}
+                                                <option value="{$ip}">{$ip}</option>
+                                            {/foreach}
+                                                <option value="">{$MGLANG->T('default_ip')}</option>
+                                        </select>
+                                    </div>
+                                </div>
+
+                                <div class="form-group block-ip" {if count($pools) eq 0}style="display: none"{/if}>
+                                    <label class="control-label col-sm-3">{$MGLANG->T('ip')}</label>
+                                    <div class="col-sm-7">
+                                        <input class="form-control" type="text" name="zone_ip_from_block" value="" {if $is_not_domain}required{/if} pattern="{$patterns.ip}"/>
+                                    </div>
+                                </div>
+                            {/if}
+                        {/if}
+                        {if $allowManageRecordsSets === 'on'}
+                            <div class="form-group">
+                                <label class="control-label col-sm-3">{$MGLANG->T('selectRecordSet')}</label>
+                                <div class="col-sm-7">
+                                    <select class="select2" name="recordSet">
+                                        <option value="none"  >{$MGLANG->T('none')}</option>
+                                        {foreach from=$recordSets item="set"}
+                                            <option value="{$set->id}">{$set->name}</option>
+                                        {/foreach}
+                                        {foreach from=$adminSets item="aset"}
+                                            <option value="{$aset->id}">{$aset->name}</option>
+                                        {/foreach}
+                                            <option value="" selected >{$MGLANG->T('defaultSet')}</option>
+                                    </select>
+                                </div>
+                            </div>
+                        {/if}
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
+                    <button type="button" class="btn btn-success" data-act="addZoneSave">{$MGLANG->T('add_zone')}</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+
+{literal}
+    <script data-cfasync="false" type="text/javascript">
+        (function($) {
+            $('[name=zone_ip]').change(function() {
+                $(this).parents('form').first().find('.block-ip').removeClass('has-error').toggle(typeof $('option:selected', this).data('pool') != 'undefined');
+            });
+        })(jQuery);
+    </script>
 {/literal}

+ 22 - 22
modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/edit-rdns.tpl

@@ -1,23 +1,23 @@
-<div class="modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <form class="form-horizontal" action="" method="post">
-                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-                    <h4 class="modal-title">{$MGLANG->T('edit_rdns')}</h4>
-                </div>
-                <div class="modal-body">
-                    <div class="form-group record-rdata"> 
-                        <label class="control-label col-sm-3">{$MGLANG->T('rdata')}</label>
-                        <div class="col-sm-7">
-                            <input class="form-control" type="text" name="rdata" title="{$MGLANG->absoluteT('addonAA','zones','record_field_info','PTR', 'ptrdname')}" placeholder="{$MGLANG->absoluteT('addonCA','zones','record_field_placeholder','PTR', 'ptrdname')}" value="{$rdata}"/>    
-                        </div>
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
-                    <button type="button" class="btn btn-success" data-query="rid={$rid}" data-act="editRDNSSave">{$MGLANG->T('save_changes')}</button>                                  
-                </div>
-            </form>
-        </div>
-    </div>
+<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form class="form-horizontal" action="" method="post">
+                <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    <h4 class="modal-title">{$MGLANG->T('edit_rdns')}</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group record-rdata"> 
+                        <label class="control-label col-sm-3">{$MGLANG->T('rdata')}</label>
+                        <div class="col-sm-7">
+                            <input class="form-control" type="text" name="rdata" title="{$MGLANG->absoluteT('addonAA','zones','record_field_info','PTR', 'ptrdname')}" placeholder="{$MGLANG->absoluteT('addonCA','zones','record_field_placeholder','PTR', 'ptrdname')}" value="{$rdata}"/>    
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">{$MGLANG->T('cancel')}</button>
+                    <button type="button" class="btn btn-success" data-query="rid={$rid}" data-act="editRDNSSave">{$MGLANG->T('save_changes')}</button>                                  
+                </div>
+            </form>
+        </div>
+    </div>
 </div>

+ 58 - 58
modules/addons/DNSManager2/templates/clientarea/default/pages/dashboard/modal/set-records.tpl

@@ -1,59 +1,59 @@
-<div class="modal" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <form class="form-horizontal">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">{$MGLANG->T('close')}</span></button>
-                    <h4 class="modal-title">{$MGLANG->T('set_dns_record_set')}</h4>
-                </div>
-                <div class="modal-body">
-                    <div class="form-group">
-                        <label class="control-label col-sm-3">{$MGLANG->T('select_record_set')}</label>
-                        <div class="col-sm-7">
-                            <select class="select2" name="recordSet">
-                                {foreach from=$sets item="aset"}
-                                    <option value="{$aset->id}">{$aset->name}</option>
-                                {/foreach}
-                                <option value="" selected >{$MGLANG->T('defaultSet')}</option>
-                            </select> 
-                        </div>      
-                    </div>   
-                    <div class="form-group">
-                        <label for="wR" class="control-label col-sm-3">{$MGLANG->T('wipe_records')}</label>
-                        <div class="col-sm-7">
-                            <div class="checkbox" name="wipeCheckbox" style="margin-top:8px;">
-                                <input class="sel" id="wR" type="checkbox" name="wipeRecords" value=""/>
-                            </div> 
-                        </div>      
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default btn-inverse" data-dismiss="modal">{$MGLANG->T('cancel')}</button>   
-                    <button class="btn btn-primary btn-inverse" type="button" data-formid="zone-list" id="setRecordsDNS" data-act="setRecordsSave">{$MGLANG->T('set_dns_record_set')}</button>
-                </div>
-            </form>
-        </div>
-    </div>
-</div>
-                
-{literal}
-    <script data-cfasync="false" type="text/javascript">
-    (function($) {
-        jQuery('#setRecordsDNS').click(function(){
-            var set = jQuery('select[name="recordSet"]').val();
-            $('input[name="setRecord"]').val(set);
-        });   
-        
-          $('div[name="wipeCheckbox"]').on('ifChanged', function(event){
-            if(this.children[0].children[0].checked)
-            {
-                jQuery('input[name="wipe"]').val('on');
-            }
-            else
-            {
-                jQuery('input[name="wipe"]').val('');
-            }              
-          });
-    })(jQuery);
-    </script>
+<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <form class="form-horizontal">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">{$MGLANG->T('close')}</span></button>
+                    <h4 class="modal-title">{$MGLANG->T('set_dns_record_set')}</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label class="control-label col-sm-3">{$MGLANG->T('select_record_set')}</label>
+                        <div class="col-sm-7">
+                            <select class="select2" name="recordSet">
+                                {foreach from=$sets item="aset"}
+                                    <option value="{$aset->id}">{$aset->name}</option>
+                                {/foreach}
+                                <option value="" selected >{$MGLANG->T('defaultSet')}</option>
+                            </select> 
+                        </div>      
+                    </div>   
+                    <div class="form-group">
+                        <label for="wR" class="control-label col-sm-3">{$MGLANG->T('wipe_records')}</label>
+                        <div class="col-sm-7">
+                            <div class="checkbox" name="wipeCheckbox" style="margin-top:8px;">
+                                <input class="sel" id="wR" type="checkbox" name="wipeRecords" value=""/>
+                            </div> 
+                        </div>      
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default btn-inverse" data-dismiss="modal">{$MGLANG->T('cancel')}</button>   
+                    <button class="btn btn-primary btn-inverse" type="button" data-formid="zone-list" id="setRecordsDNS" data-act="setRecordsSave">{$MGLANG->T('set_dns_record_set')}</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+                
+{literal}
+    <script data-cfasync="false" type="text/javascript">
+    (function($) {
+        jQuery('#setRecordsDNS').click(function(){
+            var set = jQuery('select[name="recordSet"]').val();
+            $('input[name="setRecord"]').val(set);
+        });   
+        
+          $('div[name="wipeCheckbox"]').on('ifChanged', function(event){
+            if(this.children[0].children[0].checked)
+            {
+                jQuery('input[name="wipe"]').val('on');
+            }
+            else
+            {
+                jQuery('input[name="wipe"]').val('');
+            }              
+          });
+    })(jQuery);
+    </script>
 {/literal}