Pārlūkot izejas kodu

init from modulsgarden origin

root 4 gadi atpakaļ
revīzija
ba1aa5b536
500 mainītis faili ar 61144 papildinājumiem un 0 dzēšanām
  1. 7 0
      app/Config/configuration.yml
  2. 3 0
      app/Config/cron.yml
  3. 1 0
      app/Config/di/buildWithDefaultMethod.yml
  4. 1 0
      app/Config/di/interface.yml
  5. 2 0
      app/Config/di/register.yml
  6. 9 0
      app/Config/di/rewrite.yml
  7. 1 0
      app/Config/di/services.yml
  8. 29 0
      app/Config/events.php
  9. 18 0
      app/Config/hooks.yml
  10. 22 0
      app/Config/menu/admin.yml
  11. 1 0
      app/Config/menu/client.yml
  12. 1 0
      app/Config/ui/admin/home/index.yml
  13. 26 0
      app/Configuration/Addon/Activate/After.php
  14. 22 0
      app/Configuration/Addon/Activate/Before.php
  15. 22 0
      app/Configuration/Addon/Config/After.php
  16. 22 0
      app/Configuration/Addon/Config/Before.php
  17. 22 0
      app/Configuration/Addon/Deactivate/After.php
  18. 22 0
      app/Configuration/Addon/Deactivate/Before.php
  19. 44 0
      app/Configuration/Addon/Update/AddonUpgradeService.php
  20. 21 0
      app/Configuration/Addon/Update/After.php
  21. 22 0
      app/Configuration/Addon/Update/Before.php
  22. 366 0
      app/Configuration/Addon/Update/DefaultPatch.php
  23. 30 0
      app/Configuration/Addon/Update/Patch/M2M6P0.php
  24. 59 0
      app/Configuration/Addon/Update/Patch/M2M7P0.php
  25. 30 0
      app/Configuration/Addon/Update/Patch/M2M7P3.php
  26. 43 0
      app/Configuration/Addon/Update/Patch/M2M7P4.php
  27. 31 0
      app/Configuration/Addon/Update/Patch/M2M8P0.php
  28. 118 0
      app/Configuration/Addon/Update/Patch/M3M0P0.php
  29. 105 0
      app/Configuration/Addon/Update/Patch/M3M1P0.php
  30. 851 0
      app/Configuration/Addon/Update/ProductCloudConveter.php
  31. 873 0
      app/Configuration/Addon/Update/ProductConveter.php
  32. 32 0
      app/Configuration/Requirements/Files.php
  33. 244 0
      app/Cron/BackupRemove.php
  34. 163 0
      app/Cron/MigrateSync.php
  35. 61 0
      app/Cron/Queue.php
  36. 48 0
      app/Cron/QueueLoop.php
  37. 207 0
      app/Cron/RecoveryList.php
  38. 137 0
      app/Cron/RrdDataCommand.php
  39. 118 0
      app/Cron/Snapshots.php
  40. 113 0
      app/Cron/Task.php
  41. 363 0
      app/Cron/UsageUpdate.php
  42. 125 0
      app/Cron/Users.php
  43. 1 0
      app/Database/M2M6P0/data.sql
  44. 92 0
      app/Database/M2M6P0/schema.sql
  45. 1 0
      app/Database/M2M7P0/data.sql
  46. 107 0
      app/Database/M2M7P0/schema.sql
  47. 0 0
      app/Database/M2M7P3/data.sql
  48. 8 0
      app/Database/M2M7P3/schema.sql
  49. 0 0
      app/Database/M2M7P4/data.sql
  50. 0 0
      app/Database/M2M7P4/schema.sql
  51. 0 0
      app/Database/M2M8P0/data.sql
  52. 26 0
      app/Database/M2M8P0/schema.sql
  53. 0 0
      app/Database/M3M0P0/data.sql
  54. 74 0
      app/Database/M3M0P0/schema.sql
  55. 0 0
      app/Database/M3M1P0/data.sql
  56. 20 0
      app/Database/M3M1P0/schema.sql
  57. 0 0
      app/Database/data.sql
  58. 286 0
      app/Database/schema.sql
  59. 29 0
      app/Decorators/OsTemplateDecorator.php
  60. 27 0
      app/Enum/Cloud/ConfigurableOption.php
  61. 13 0
      app/Enum/Cloud/CustomField.php
  62. 32 0
      app/Enum/Vps/ConfigurableOption.php
  63. 19 0
      app/Enum/Vps/CustomField.php
  64. 12 0
      app/Events/Cloud/LxcUpdateEvent.php
  65. 32 0
      app/Events/Cloud/QemuUpdateEvent.php
  66. 13 0
      app/Events/Cloud/VmClonedEvent.php
  67. 13 0
      app/Events/Cloud/VmCreatedEvent.php
  68. 63 0
      app/Events/Cloud/VmEvent.php
  69. 13 0
      app/Events/Cloud/VmReinstalledEvent.php
  70. 36 0
      app/Events/Vps/LxcUpdateEvent.php
  71. 53 0
      app/Events/Vps/QemuUpdateEvent.php
  72. 36 0
      app/Events/Vps/VmClonedEvent.php
  73. 37 0
      app/Events/Vps/VmCreatedEvent.php
  74. 37 0
      app/Events/Vps/VmReinstalledEvent.php
  75. 37 0
      app/Factory/Ssh2Factory.php
  76. 127 0
      app/Helper/AbstractAvailableExtensions.php
  77. 376 0
      app/Helper/BuildTlds.php
  78. 42 0
      app/Helper/CartTotals.php
  79. 90 0
      app/Helper/ClientAreaPage.php
  80. 52 0
      app/Helper/DemonHelper.php
  81. 10 0
      app/Helper/ErrorCodesLib.php
  82. 13 0
      app/Hooks/AdminAreaHeadOutput.php
  83. 56 0
      app/Hooks/AdminProductConfigFields.php
  84. 33 0
      app/Http/Admin/CloudInitScript.php
  85. 97 0
      app/Http/Admin/Home.php
  86. 40 0
      app/Http/Admin/IpManagement.php
  87. 39 0
      app/Http/Admin/Jobs.php
  88. 42 0
      app/Http/Admin/Settings.php
  89. 404 0
      app/Jobs/BaseJob.php
  90. 51 0
      app/Jobs/Cloud/Agent/ChangePasswordJob.php
  91. 51 0
      app/Jobs/Cloud/Agent/ConfigureNetworkJob.php
  92. 183 0
      app/Jobs/Cloud/CloneQemuJob.php
  93. 249 0
      app/Jobs/Cloud/CreateLxcJob.php
  94. 395 0
      app/Jobs/Cloud/CreateQemuJob.php
  95. 33 0
      app/Jobs/Cloud/CreateSnippet.php
  96. 86 0
      app/Jobs/Cloud/LoadBalancer/MigrateVmJob.php
  97. 73 0
      app/Jobs/Cloud/LoadBalancer/ShutdownVmJob.php
  98. 59 0
      app/Jobs/Cloud/LoadBalancer/UpgradeVmJob.php
  99. 81 0
      app/Jobs/Cloud/MigrateVmJob.php
  100. 45 0
      app/Jobs/Cloud/NetworkRate/MaximumVmRateJob.php
  101. 39 0
      app/Jobs/Cloud/NetworkRate/MinimumVmRateJob.php
  102. 65 0
      app/Jobs/Cloud/RebootVmJob.php
  103. 43 0
      app/Jobs/Cloud/Reinstall/BackupVmJob.php
  104. 223 0
      app/Jobs/Cloud/Reinstall/CreateVmJob.php
  105. 103 0
      app/Jobs/Cloud/Reinstall/DeleteVmJob.php
  106. 46 0
      app/Jobs/Cloud/Reinstall/RestoreVm.php
  107. 48 0
      app/Jobs/Cloud/RestoreVm.php
  108. 93 0
      app/Jobs/Cloud/SnapshotVmJob.php
  109. 42 0
      app/Jobs/Vps/Agent/ChangePasswordJob.php
  110. 42 0
      app/Jobs/Vps/Agent/ConfigureNetworkJob.php
  111. 385 0
      app/Jobs/Vps/BaseJob.php
  112. 198 0
      app/Jobs/Vps/CloneQemuJob.php
  113. 239 0
      app/Jobs/Vps/CreateLxcJob.php
  114. 383 0
      app/Jobs/Vps/CreateQemuJob.php
  115. 40 0
      app/Jobs/Vps/CreateSnippet.php
  116. 84 0
      app/Jobs/Vps/LoadBalancer/MigrateVmJob.php
  117. 71 0
      app/Jobs/Vps/LoadBalancer/ShutdownVmJob.php
  118. 58 0
      app/Jobs/Vps/LoadBalancer/UpgradeVmJob.php
  119. 77 0
      app/Jobs/Vps/MigrateVmJob.php
  120. 42 0
      app/Jobs/Vps/NetworkRate/MaximumVmRateJob.php
  121. 36 0
      app/Jobs/Vps/NetworkRate/MinimumVmRateJob.php
  122. 58 0
      app/Jobs/Vps/RebootVmJob.php
  123. 41 0
      app/Jobs/Vps/Reinstall/BackupVmJob.php
  124. 233 0
      app/Jobs/Vps/Reinstall/CreateVmJob.php
  125. 90 0
      app/Jobs/Vps/Reinstall/DeleteVmJob.php
  126. 44 0
      app/Jobs/Vps/Reinstall/RestoreVm.php
  127. 44 0
      app/Jobs/Vps/RestoreVm.php
  128. 90 0
      app/Jobs/Vps/SnapshotVmJob.php
  129. 64 0
      app/Libs/Api/ENom.php
  130. 21 0
      app/Libs/Api/ENom/AvailableExtensions.php
  131. 25 0
      app/Libs/Api/ENom/Method/AbstractMethod.php
  132. 152 0
      app/Libs/Api/ENom/Method/CheckMoreDomains.php
  133. 70 0
      app/Libs/Api/ENom/Method/CheckOneDomain.php
  134. 22 0
      app/Libs/Api/ENom/Parser.php
  135. 97 0
      app/Libs/Api/OpenSRS.php
  136. 19 0
      app/Libs/Api/OpenSRS/APIException.php
  137. 534 0
      app/Libs/Api/OpenSRS/AvailableExtensions.php
  138. 22 0
      app/Libs/Api/OpenSRS/Exception.php
  139. 176 0
      app/Libs/Api/OpenSRS/FastLookup.php
  140. 69 0
      app/Libs/Api/OpenSRS/Fastlookup/FastDomainLookup.php
  141. 25 0
      app/Libs/Api/OpenSRS/Method/AbstractMethod.php
  142. 73 0
      app/Libs/Api/OpenSRS/Method/CheckMoreDomains.php
  143. 66 0
      app/Libs/Api/OpenSRS/Method/CheckOneDomain.php
  144. 111 0
      app/Libs/Api/OpenSRS/Request.php
  145. 45 0
      app/Libs/Api/OpenSRS/RequestFactory.php
  146. 4072 0
      app/Libs/BigInteger.php
  147. 74 0
      app/Libs/Format.php
  148. 220 0
      app/Libs/IPv6.php
  149. 11308 0
      app/Libs/TLDList/tld.list
  150. 185 0
      app/Listeners/Cloud/LxcUpdateListener.php
  151. 443 0
      app/Listeners/Cloud/QemuUpdateListener.php
  152. 36 0
      app/Listeners/Cloud/VmCreatedListener.php
  153. 64 0
      app/Listeners/Cloud/VmListener.php
  154. 35 0
      app/Listeners/Cloud/VmReinstalledListener.php
  155. 245 0
      app/Listeners/Vps/LxcUpdateListener.php
  156. 517 0
      app/Listeners/Vps/QemuUpdateListener.php
  157. 87 0
      app/Listeners/Vps/VmCreatedListener.php
  158. 68 0
      app/Listeners/Vps/VmReinstalledListener.php
  159. 28 0
      app/Models/CloudInitScript.php
  160. 88 0
      app/Models/IpAddress.php
  161. 65 0
      app/Models/Job.php
  162. 144 0
      app/Models/KeyPair.php
  163. 44 0
      app/Models/ModuleSetting.php
  164. 18 0
      app/Models/ModuleSettings.php
  165. 71 0
      app/Models/NodeSetting.php
  166. 29 0
      app/Models/PrivateInterface.php
  167. 24 0
      app/Models/PrivateNetwork.php
  168. 68 0
      app/Models/ProductConfiguration.php
  169. 64 0
      app/Models/RangeVm.php
  170. 85 0
      app/Models/RecoveryVm.php
  171. 50 0
      app/Models/RrdData.php
  172. 106 0
      app/Models/ServerConfiguration.php
  173. 45 0
      app/Models/SnapshotJob.php
  174. 83 0
      app/Models/Snippet.php
  175. 63 0
      app/Models/TaskHistory.php
  176. 71 0
      app/Models/User.php
  177. 86 0
      app/Models/VirtualInterface.php
  178. 58 0
      app/Models/VirtualNetwork.php
  179. 131 0
      app/Models/VmIpAddress.php
  180. 134 0
      app/Models/VmModel.php
  181. 37 0
      app/Models/Whmcs/ActivityLog.php
  182. 46 0
      app/Models/Whmcs/CustomField.php
  183. 47 0
      app/Models/Whmcs/CustomFieldValue.php
  184. 56 0
      app/Models/Whmcs/EmailTemplate.php
  185. 272 0
      app/Models/Whmcs/Hosting.php
  186. 150 0
      app/Models/Whmcs/Product.php
  187. 37 0
      app/Models/Whmcs/ToDoList.php
  188. 37 0
      app/Models/Whmcs/Upgrade.php
  189. 51 0
      app/Providers/SnippetProvider.php
  190. 251 0
      app/Repositories/AbstractProductConfigurationRepository.php
  191. 242 0
      app/Repositories/AbstractServerConfigurationRepository.php
  192. 1696 0
      app/Repositories/Cloud/ProductConfigurationRepository.php
  193. 83 0
      app/Repositories/RangeVmRepository.php
  194. 52 0
      app/Repositories/ServerConfigurationRepository.php
  195. 1566 0
      app/Repositories/Vps/ProductConfigurationRepository.php
  196. 101 0
      app/Services/ApiService.php
  197. 64 0
      app/Services/BaseService.php
  198. 138 0
      app/Services/Cloud/AclService.php
  199. 63 0
      app/Services/Cloud/AdditionalDiskService.php
  200. 51 0
      app/Services/Cloud/AdditionalMountPointService.php
  201. 131 0
      app/Services/Cloud/AgentService.php
  202. 170 0
      app/Services/Cloud/ClientAreaSidebarService.php
  203. 32 0
      app/Services/Cloud/ContainerService.php
  204. 29 0
      app/Services/Cloud/FirewallOptionService.php
  205. 58 0
      app/Services/Cloud/HighAvailabilityClusterService.php
  206. 156 0
      app/Services/Cloud/HostingService.php
  207. 93 0
      app/Services/Cloud/IpSetIpFilterService.php
  208. 814 0
      app/Services/Cloud/NetworkService.php
  209. 245 0
      app/Services/Cloud/ProductService.php
  210. 141 0
      app/Services/Cloud/Resource.php
  211. 185 0
      app/Services/Cloud/ResourceGuard.php
  212. 387 0
      app/Services/Cloud/ResourceManager.php
  213. 154 0
      app/Services/Cloud/UserService.php
  214. 202 0
      app/Services/Cloud/VirtualInterfaceConverter.php
  215. 71 0
      app/Services/CloudInitScriptConveter.php
  216. 177 0
      app/Services/CloudService.php
  217. 82 0
      app/Services/EmailService.php
  218. 143 0
      app/Services/Ip/Ipv4Range.php
  219. 461 0
      app/Services/LoadBalancerService.php
  220. 70 0
      app/Services/RecoveryVmDumpService.php
  221. 55 0
      app/Services/UrlService.php
  222. 225 0
      app/Services/Utility.php
  223. 68 0
      app/Services/Vm.php
  224. 130 0
      app/Services/Vps/AclService.php
  225. 102 0
      app/Services/Vps/AdditionalDiskService.php
  226. 81 0
      app/Services/Vps/AdditionalMountPointService.php
  227. 141 0
      app/Services/Vps/AgentService.php
  228. 135 0
      app/Services/Vps/ClientAreaSidebarService.php
  229. 106 0
      app/Services/Vps/ContainerService.php
  230. 28 0
      app/Services/Vps/FirewallOptionService.php
  231. 57 0
      app/Services/Vps/HighAvailabilityClusterService.php
  232. 148 0
      app/Services/Vps/HostingService.php
  233. 89 0
      app/Services/Vps/IpSetIpFilterService.php
  234. 794 0
      app/Services/Vps/NetworkService.php
  235. 235 0
      app/Services/Vps/ProductService.php
  236. 252 0
      app/Services/Vps/ResourceGuard.php
  237. 55 0
      app/Services/Vps/UrlService.php
  238. 219 0
      app/Services/Vps/UserService.php
  239. 158 0
      app/Services/Vps/VmIpAddressConveter.php
  240. 37 0
      app/Traits/Cloud/SnippetTrait.php
  241. 23 0
      app/Traits/Cloud/VmNetwork.php
  242. 42 0
      app/Traits/Vps/SnippetTrait.php
  243. 45 0
      app/UI/Admin/Templates/assets/js/hooks/server.js
  244. 4 0
      app/UI/Admin/Templates/cloudInitScript/update.js
  245. 30 0
      app/UI/Admin/Templates/home/nodeDetail.js
  246. 43 0
      app/UI/CloudInitScript/Buttons/DeleteButton.php
  247. 43 0
      app/UI/CloudInitScript/Buttons/DeleteMassButton.php
  248. 18 0
      app/UI/CloudInitScript/Buttons/UpdateButton.php
  249. 62 0
      app/UI/CloudInitScript/Forms/DeleteForm.php
  250. 52 0
      app/UI/CloudInitScript/Forms/DeleteMassForm.php
  251. 40 0
      app/UI/CloudInitScript/Modals/DeleteMassModal.php
  252. 40 0
      app/UI/CloudInitScript/Modals/DeleteModal.php
  253. 55 0
      app/UI/CloudInitScript/Pages/CloudInitScriptDataTable.php
  254. 41 0
      app/UI/CloudInitScript/Providers/CloudInitScriptDeleteProvider.php
  255. 40 0
      app/UI/CloudInitScriptCreate/Pages/CloudInitScriptContainter.php
  256. 49 0
      app/UI/CloudInitScriptCreate/Providers/CloudInitScriptProvider.php
  257. 28 0
      app/UI/CloudInitScriptCreate/Sections/GeneralSection.php
  258. 12 0
      app/UI/CloudInitScriptCreate/Sections/VariableSection.php
  259. 60 0
      app/UI/CloudInitScriptCreate/Templates/sections/generalSection.tpl
  260. 55 0
      app/UI/CloudInitScriptCreate/Templates/sections/variableSection.tpl
  261. 41 0
      app/UI/Cluster/Buttons/UpdateButton.php
  262. 76 0
      app/UI/Cluster/Forms/UpdateForm.php
  263. 40 0
      app/UI/Cluster/Modals/UpdateModal.php
  264. 136 0
      app/UI/Cluster/Pages/ClusterDataTable.php
  265. 129 0
      app/UI/Cluster/Providers/NodeSettingProvider.php
  266. 40 0
      app/UI/IpManagement/Buttons/CreateButton.php
  267. 43 0
      app/UI/IpManagement/Buttons/DeleteButton.php
  268. 43 0
      app/UI/IpManagement/Buttons/DeleteMassButton.php
  269. 40 0
      app/UI/IpManagement/Buttons/UpdateButton.php
  270. 85 0
      app/UI/IpManagement/Fields/NodeSelect.php
  271. 132 0
      app/UI/IpManagement/Forms/CreateForm.php
  272. 62 0
      app/UI/IpManagement/Forms/DeleteForm.php
  273. 52 0
      app/UI/IpManagement/Forms/DeleteMassForm.php
  274. 122 0
      app/UI/IpManagement/Forms/UpdateForm.php
  275. 41 0
      app/UI/IpManagement/Modals/CreateModal.php
  276. 40 0
      app/UI/IpManagement/Modals/DeleteMassModal.php
  277. 40 0
      app/UI/IpManagement/Modals/DeleteModal.php
  278. 40 0
      app/UI/IpManagement/Modals/UpdateModal.php
  279. 153 0
      app/UI/IpManagement/Pages/IpManagementDataTable.php
  280. 248 0
      app/UI/IpManagement/Providers/IpAddressProvider.php
  281. 41 0
      app/UI/Jobs/Buttons/DeleteButton.php
  282. 41 0
      app/UI/Jobs/Buttons/InfoButton.php
  283. 42 0
      app/UI/Jobs/Buttons/MassDeleteButton.php
  284. 43 0
      app/UI/Jobs/Buttons/RunButton.php
  285. 56 0
      app/UI/Jobs/Forms/DeleteForm.php
  286. 47 0
      app/UI/Jobs/Forms/MassDeleteForm.php
  287. 56 0
      app/UI/Jobs/Forms/RunForm.php
  288. 40 0
      app/UI/Jobs/Modals/DeleteModal.php
  289. 86 0
      app/UI/Jobs/Modals/InfoModal.php
  290. 40 0
      app/UI/Jobs/Modals/MassDeleteModal.php
  291. 41 0
      app/UI/Jobs/Modals/RunModal.php
  292. 74 0
      app/UI/Jobs/Others/StatusLabel.php
  293. 136 0
      app/UI/Jobs/Pages/JobsDataTable.php
  294. 82 0
      app/UI/Jobs/Providers/JobProvider.php
  295. 135 0
      app/UI/Jobs/Templates/modals/infoModal.tpl
  296. 111 0
      app/UI/NodeDetail/Pages/CpuGraph.php
  297. 30 0
      app/UI/NodeDetail/Pages/LoadAPIData.php
  298. 112 0
      app/UI/NodeDetail/Pages/MemoryGraph.php
  299. 110 0
      app/UI/NodeDetail/Pages/NetworkGraph.php
  300. 90 0
      app/UI/NodeDetail/Pages/ServerLoadGraph.php
  301. 57 0
      app/UI/NodeDetail/Pages/Subscription.php
  302. 91 0
      app/UI/NodeDetail/Pages/Summary.php
  303. 5 0
      app/UI/NodeDetail/Templates/pages/subscription.tpl
  304. 52 0
      app/UI/NodeDetail/Templates/pages/subscription_components.js
  305. 38 0
      app/UI/NodeDetail/Templates/pages/subscription_components.tpl
  306. 5 0
      app/UI/NodeDetail/Templates/pages/summary.tpl
  307. 52 0
      app/UI/NodeDetail/Templates/pages/summary_components.js
  308. 116 0
      app/UI/NodeDetail/Templates/pages/summary_components.tpl
  309. 41 0
      app/UI/RecoveryVms/Buttons/DetailButton.php
  310. 120 0
      app/UI/RecoveryVms/Forms/DetailForm.php
  311. 53 0
      app/UI/RecoveryVms/Modals/DetailModal.php
  312. 122 0
      app/UI/RecoveryVms/Pages/RecoveryVmsDataTable.php
  313. 39 0
      app/UI/RecoveryVms/Sections/TabContent.php
  314. 28 0
      app/UI/RecoveryVms/Templates/sections/tabContent.tpl
  315. 32 0
      app/UI/ServerDetail/Pages/ServerDetailContainer.php
  316. 92 0
      app/UI/ServerSettings/Forms/ServerSettingsForm.php
  317. 132 0
      app/UI/ServerSettings/Providers/ServerSettingProvider.php
  318. 38 0
      app/UI/ServerSettings/Sections/GeneralSection.php
  319. 40 0
      app/UI/Servers/Modals/UpdateModal.php
  320. 61 0
      app/UI/Servers/Pages/ResourcesRow.php
  321. 169 0
      app/UI/Servers/Pages/ServerResources.php
  322. 129 0
      app/UI/Servers/Pages/ServersDataTable.php
  323. 61 0
      app/UI/Servers/Pages/SuspendedRow.php
  324. 81 0
      app/UI/Servers/Providers/RangeVmProvider.php
  325. 92 0
      app/UI/Settings/Pages/SettingsContainer.php
  326. 65 0
      app/UI/Settings/Providers/SettingProvider.php
  327. 62 0
      app/UI/Settings/Sections/CronSection.php
  328. 33 0
      app/UI/Settings/Sections/GeneralSection.php
  329. 33 0
      app/UI/Settings/Sections/LoadBalancerSection.php
  330. 74 0
      app/UI/Settings/Templates/sections/cronSection.tpl
  331. 41 0
      app/UI/Settings/Templates/sections/generalSection.tpl
  332. 41 0
      app/UI/Settings/Templates/sections/loadBalancerSection.tpl
  333. 43 0
      app/UI/TaskHistory/Buttons/DeleteButton.php
  334. 43 0
      app/UI/TaskHistory/Buttons/DeleteMassButton.php
  335. 62 0
      app/UI/TaskHistory/Forms/DeleteForm.php
  336. 52 0
      app/UI/TaskHistory/Forms/DeleteMassForm.php
  337. 40 0
      app/UI/TaskHistory/Modals/DeleteMassModal.php
  338. 40 0
      app/UI/TaskHistory/Modals/DeleteModal.php
  339. 106 0
      app/UI/TaskHistory/Pages/TaskHistoryDataTable.php
  340. 63 0
      app/UI/TaskHistory/Providers/TaskHistoryProvider.php
  341. 40 0
      app/UI/Templates/Buttons/CreateButton.php
  342. 42 0
      app/UI/Templates/Buttons/DeleteButton.php
  343. 42 0
      app/UI/Templates/Buttons/DeleteMassButton.php
  344. 41 0
      app/UI/Templates/Buttons/UpdateButton.php
  345. 82 0
      app/UI/Templates/Fields/NodeSelect.php
  346. 63 0
      app/UI/Templates/Fields/VmidSelect.php
  347. 62 0
      app/UI/Templates/Forms/CreateForm.php
  348. 59 0
      app/UI/Templates/Forms/DeleteForm.php
  349. 52 0
      app/UI/Templates/Forms/DeleteMassForm.php
  350. 61 0
      app/UI/Templates/Forms/UpdateForm.php
  351. 40 0
      app/UI/Templates/Modals/CreateModal.php
  352. 40 0
      app/UI/Templates/Modals/DeleteMassModal.php
  353. 40 0
      app/UI/Templates/Modals/DeleteModal.php
  354. 40 0
      app/UI/Templates/Modals/UpdateModal.php
  355. 138 0
      app/UI/Templates/Pages/TemplatesDataTable.php
  356. 147 0
      app/UI/Templates/Providers/TemplateProvider.php
  357. 74 0
      app/UI/Validators/CidrValidator.php
  358. 60 0
      app/UI/Validators/IpAddressValidator.php
  359. 53 0
      app/UI/Validators/MacAddressValidator.php
  360. 87 0
      app/UI/Validators/NumberValidator.php
  361. 42 0
      app/UI/VmCleaner/Buttons/DeleteButton.php
  362. 42 0
      app/UI/VmCleaner/Buttons/DeleteMassButton.php
  363. 59 0
      app/UI/VmCleaner/Forms/DeleteForm.php
  364. 52 0
      app/UI/VmCleaner/Forms/DeleteMassForm.php
  365. 40 0
      app/UI/VmCleaner/Modals/DeleteMassModal.php
  366. 40 0
      app/UI/VmCleaner/Modals/DeleteModal.php
  367. 150 0
      app/UI/VmCleaner/Pages/VmCleanerDataTable.php
  368. 106 0
      app/UI/VmCleaner/Providers/VmProvider.php
  369. 41 0
      app/UI/Vms/Buttons/DetailButton.php
  370. 87 0
      app/UI/Vms/Modals/DetailModal.php
  371. 162 0
      app/UI/Vms/Pages/VmsDataTable.php
  372. 169 0
      app/UI/Vms/Pages/VmsDataTableRaw.php
  373. 85 0
      app/UI/Vms/Templates/modals/detailModal.tpl
  374. 20 0
      commands/commands.php
  375. 40 0
      composer.json
  376. 25 0
      core/Api/AbstractApi.php
  377. 158 0
      core/Api/AbstractApi/Curl.php
  378. 191 0
      core/Api/AbstractApi/Curl/Request.php
  379. 92 0
      core/Api/AbstractApi/Curl/Response.php
  380. 22 0
      core/Api/AbstractApi/Parser.php
  381. 89 0
      core/Api/Whmcs.php
  382. 178 0
      core/App/AppContext.php
  383. 60 0
      core/App/Application.php
  384. 20 0
      core/App/Controllers/AppController.php
  385. 33 0
      core/App/Controllers/AppControllers/Addon.php
  386. 14 0
      core/App/Controllers/AppControllers/Api.php
  387. 14 0
      core/App/Controllers/AppControllers/Cron.php
  388. 14 0
      core/App/Controllers/AppControllers/Hooks.php
  389. 32 0
      core/App/Controllers/AppControllers/Http.php
  390. 71 0
      core/App/Controllers/Instances/Addon/Activate.php
  391. 149 0
      core/App/Controllers/Instances/Addon/Config.php
  392. 44 0
      core/App/Controllers/Instances/Addon/Deactivate.php
  393. 48 0
      core/App/Controllers/Instances/Addon/Upgrade.php
  394. 16 0
      core/App/Controllers/Instances/AddonController.php
  395. 12 0
      core/App/Controllers/Instances/Api/ApiController.php
  396. 11 0
      core/App/Controllers/Instances/Http/AdminPageController.php
  397. 11 0
      core/App/Controllers/Instances/Http/ClientPageController.php
  398. 82 0
      core/App/Controllers/Instances/Http/ErrorPage.php
  399. 38 0
      core/App/Controllers/Instances/Http/Integration.php
  400. 28 0
      core/App/Controllers/Instances/Http/PageNotFound.php
  401. 166 0
      core/App/Controllers/Instances/HttpController.php
  402. 9 0
      core/App/Controllers/Interfaces/AddonController.php
  403. 8 0
      core/App/Controllers/Interfaces/AdminArea.php
  404. 9 0
      core/App/Controllers/Interfaces/AppController.php
  405. 8 0
      core/App/Controllers/Interfaces/ClientArea.php
  406. 9 0
      core/App/Controllers/Interfaces/DefaultController.php
  407. 137 0
      core/App/Controllers/ResponseResolver.php
  408. 102 0
      core/App/Controllers/Router.php
  409. 46 0
      core/App/ErrorHandler.php
  410. 88 0
      core/App/LowLevelLog.php
  411. 97 0
      core/App/Requirements/Checker.php
  412. 14 0
      core/App/Requirements/HandlerInterface.php
  413. 123 0
      core/App/Requirements/Handlers/Files.php
  414. 26 0
      core/App/Requirements/Instances/Files.php
  415. 13 0
      core/App/Requirements/RequirementInterface.php
  416. 50 0
      core/Bootstrap.php
  417. 207 0
      core/Cache/CacheManager.php
  418. 107 0
      core/CommandLine/AbstractCommand.php
  419. 47 0
      core/CommandLine/AbstractCronController.php
  420. 219 0
      core/CommandLine/AbstractCronControllerWithoutThread.php
  421. 59 0
      core/CommandLine/Application.php
  422. 54 0
      core/CommandLine/Command.php
  423. 86 0
      core/CommandLine/CommandLoop.php
  424. 12 0
      core/CommandLine/CommandManager.php
  425. 8 0
      core/CommandLine/CronManager.php
  426. 348 0
      core/CommandLine/CronManager.php_old
  427. 258 0
      core/CommandLine/Hypervisor.php
  428. 8 0
      core/CommandLine/Job.php
  429. 89 0
      core/CommandLine/JobsLoop.php
  430. 63 0
      core/CommandLine/ReaderCronTask.php
  431. 71 0
      core/CommandLine/Tick.php
  432. 9 0
      core/Config/configuration.yml
  433. 1 0
      core/Config/cron.yml
  434. 6 0
      core/Config/di/buildWithDefaultMethod.yml
  435. 1 0
      core/Config/di/interface.yml
  436. 28 0
      core/Config/di/register.yml
  437. 8 0
      core/Config/di/services.php
  438. 4 0
      core/Config/di/services.yml
  439. 19 0
      core/Configuration/Addon/AbstractAfter.php
  440. 17 0
      core/Configuration/Addon/AbstractBefore.php
  441. 23 0
      core/Configuration/Addon/Activate/After.php
  442. 35 0
      core/Configuration/Addon/Activate/Before.php
  443. 24 0
      core/Configuration/Addon/Config/After.php
  444. 23 0
      core/Configuration/Addon/Config/Before.php
  445. 24 0
      core/Configuration/Addon/Deactivate/After.php
  446. 24 0
      core/Configuration/Addon/Deactivate/Before.php
  447. 24 0
      core/Configuration/Addon/Update/After.php
  448. 22 0
      core/Configuration/Addon/Update/Before.php
  449. 73 0
      core/Configuration/Addon/Update/Patch/AbstractPatch.php
  450. 101 0
      core/Configuration/Addon/Update/PatchManager.php
  451. 122 0
      core/Configuration/Data.php
  452. 13 0
      core/Configuration/Data/Version.php
  453. 0 0
      core/Database/data.sql
  454. 75 0
      core/Database/schema.sql
  455. 13 0
      core/DependencyInjection.php
  456. 76 0
      core/DependencyInjection/Builder.php
  457. 204 0
      core/DependencyInjection/Container.php
  458. 58 0
      core/DependencyInjection/DependencyInjection.php
  459. 65 0
      core/DependencyInjection/Services.php
  460. 34 0
      core/Enum/Enum.php
  461. 33 0
      core/Enum/Level.php
  462. 37 0
      core/Enum/Status.php
  463. 47 0
      core/Enum/StatusColor.php
  464. 168 0
      core/Events/Dispatcher.php
  465. 8 0
      core/Events/Event.php
  466. 8 0
      core/Events/Listener.php
  467. 42 0
      core/FileReader/Directory.php
  468. 72 0
      core/FileReader/File.php
  469. 86 0
      core/FileReader/PathValidator.php
  470. 84 0
      core/FileReader/Reader.php
  471. 49 0
      core/FileReader/Reader/AbstractType.php
  472. 36 0
      core/FileReader/Reader/Css.php
  473. 36 0
      core/FileReader/Reader/Html.php
  474. 35 0
      core/FileReader/Reader/Ini.php
  475. 36 0
      core/FileReader/Reader/Js.php
  476. 33 0
      core/FileReader/Reader/Json.php
  477. 36 0
      core/FileReader/Reader/Php.php
  478. 67 0
      core/FileReader/Reader/Sql.php
  479. 23 0
      core/FileReader/Reader/Xml.php
  480. 46 0
      core/FileReader/Reader/Yml.php
  481. 109 0
      core/HandlerError/ErrorCodes/ErrorCode.php
  482. 73 0
      core/HandlerError/ErrorCodes/ErrorCodes.php
  483. 94 0
      core/HandlerError/ErrorCodes/ErrorCodesLib.php
  484. 261 0
      core/HandlerError/ErrorManager.php
  485. 318 0
      core/HandlerError/Exceptions/Exception.php
  486. 243 0
      core/HandlerError/Logger.php
  487. 114 0
      core/HandlerError/Model/AbstractModel.php
  488. 13 0
      core/HandlerError/Model/Error.php
  489. 13 0
      core/HandlerError/Model/Warning.php
  490. 24 0
      core/HandlerError/WhmcsErrorManagerWrapper.php
  491. 142 0
      core/HandlerError/WhmcsLogsHandler.php
  492. 37 0
      core/Helper/AdvancedUserHelper.php
  493. 101 0
      core/Helper/BuildUrl.php
  494. 40 0
      core/Helper/Converter/Json.php
  495. 125 0
      core/Helper/Country.php
  496. 210 0
      core/Helper/DatabaseCache.php
  497. 57 0
      core/Helper/DatabaseHelper.php
  498. 151 0
      core/Helper/DomainHelper.php
  499. 379 0
      core/Helper/Functions.php
  500. 107 0
      core/Helper/RandomStringGenerator.php

+ 7 - 0
app/Config/configuration.yml

@@ -0,0 +1,7 @@
+version: '3.1.0'
+systemName: 'proxmoxAddon'
+name: 'Proxmox Addon'
+description: 'This addon is an extension for Proxmox VPS and Proxmox Cloud VPS modules allowing you to monitor and manage your servers, IP addresses, and clusters.<br />For more info please visit our <a href="http://www.docs.modulesgarden.com/Proxmox_VPS_For_WHMCS" target="_blank" style="color: #4169E1">Proxmox VPS Wiki</a> or our <a href="http://www.docs.modulesgarden.com/Proxmox_Cloud_VPS_For_WHMCS" target="_blank" style="color: #4169E1">Proxmox Cloud VPS Wiki</a>'
+clientareaName: 'Proxmox Addon'
+author: '<a href="http://www.modulesgarden.com" targer="_blank">ModulesGarden</a>'
+moduleIcon: 'proxmox-cloud'

+ 3 - 0
app/Config/cron.yml

@@ -0,0 +1,3 @@
+list:
+  DemonTask: true
+  TaskManager: true

+ 1 - 0
app/Config/di/buildWithDefaultMethod.yml

@@ -0,0 +1 @@
+class: []

+ 1 - 0
app/Config/di/interface.yml

@@ -0,0 +1 @@
+interface: []

+ 2 - 0
app/Config/di/register.yml

@@ -0,0 +1,2 @@
+class:
+  - {namespace: '\\ModulesGarden\\ProxmoxAddon\\App\\Services\\Vm',            alias: "Vm", singleton: true , auto: false}

+ 9 - 0
app/Config/di/rewrite.yml

@@ -0,0 +1,9 @@
+class:
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Config\\After',     new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Config\\After'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Activate\\After',   new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Activate\\After'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Deactivate\\After', new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Deactivate\\After'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Update\\After',     new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Update\\After'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Config\\Before',    new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Config\\Before'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Activate\\Before',  new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Activate\\Before'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Deactivate\\Before',new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Deactivate\\Before'}
+  - {old: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Update\\Before',    new: '\\ModulesGarden\\ProxmoxAddon\\App\\Configuration\\Addon\\Update\\Before'}

+ 1 - 0
app/Config/di/services.yml

@@ -0,0 +1 @@
+- \ModulesGarden\ProxmoxAddon\App\Services\Vm

+ 29 - 0
app/Config/events.php

@@ -0,0 +1,29 @@
+<?php
+
+return [
+    \ModulesGarden\ProxmoxAddon\App\Events\Vps\VmCreatedEvent::class     => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Vps\VmCreatedListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Vps\VmReinstalledEvent::class => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Vps\VmReinstalledListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent::class    => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Vps\QemuUpdateListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Vps\LxcUpdateEvent::class     => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Vps\LxcUpdateListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmCreatedEvent::class     => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Cloud\VmCreatedListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmReinstalledEvent::class => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Cloud\VmReinstalledListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent::class    => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Cloud\QemuUpdateListener::class
+    ],
+    \ModulesGarden\ProxmoxAddon\App\Events\Cloud\LxcUpdateEvent::class     => [
+        \ModulesGarden\ProxmoxAddon\App\Listeners\Cloud\LxcUpdateListener::class
+    ]
+
+];

+ 18 - 0
app/Config/hooks.yml

@@ -0,0 +1,18 @@
+name:
+  AdminAreaFooterOutput: false
+  AdminAreaHeadOutput: true
+  AdminAreaHeaderOutput: false
+  AdminAreaPage: false
+  AdminInvoicesControlsOutput: false
+  ClientAreaDomainDetailsOutput: false
+  ClientAreaFooterOutput: false
+  ClientAreaHeadOutput: false
+  ClientAreaHeaderOutput: false
+  ClientAreaPage: false
+  ClientAreaProductDetailsOutput: false
+  ReportViewPostOutput: false
+  ReportViewPreOutput: false
+  ShoppingCartCheckoutOutput: false
+  ShoppingCartViewCartOutput: false
+  ShoppingCartViewCategoryAboveProductsOutput: false
+  ShoppingCartViewCategoryBelowProductsOutput: false

+ 22 - 0
app/Config/menu/admin.yml

@@ -0,0 +1,22 @@
+home:
+  icon: 'lu-nav__link-icon lu-zmdi lu-zmdi-home'
+  submenu:
+    servers: []
+    vms: []
+    recoveryVms: []
+    taskHistory: []
+ipManagement:
+  icon: 'lu-lu-nav__link-icon lu-zmdi lu-zmdi lu-zmdi-import-export'
+settings:
+  icon: 'lu-nav__link-icon lu-zmdi lu-zmdi-settings'
+Jobs:
+  icon: 'lu-nav__link-icon lu-zmdi lu-zmdi-assignment'
+CloudInitScript:
+  icon: 'lu-nav__link-icon lu-zmdi lu-zmdi-cloud-upload'
+documentation:
+  icon: 'lu-lu-nav__link-icon lu-zmdi lu-zmdi-file-text'
+  submenu:
+    vps:
+      externalUrl: "https://www.docs.modulesgarden.com/Proxmox_VPS_For_WHMCS"
+    cloud:
+      externalUrl: "https://www.docs.modulesgarden.com/Proxmox_Cloud_VPS_For_WHMCS"

+ 1 - 0
app/Config/menu/client.yml

@@ -0,0 +1 @@
+[]

+ 1 - 0
app/Config/ui/admin/home/index.yml

@@ -0,0 +1 @@
+

+ 26 - 0
app/Configuration/Addon/Activate/After.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Activate;
+
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\Core\Helper\DatabaseHelper;
+
+/**
+ * Description of After
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate\After
+{
+
+    /**
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $return      = parent::execute($params);
+        $defaultPath = new DefaultPatch(new DatabaseHelper);
+        $defaultPath->up();
+        return $return;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Activate/Before.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Activate;
+
+/**
+ * Description of Before
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate\Before
+{
+
+    /**
+     * @return array
+     */
+    public function execute()
+    {
+        $return = parent::execute();
+
+        return $return;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Config/After.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Config;
+
+/**
+ * Description of After
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config\After
+{
+
+    /**
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $return = parent::execute($params);
+
+        return $params;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Config/Before.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Config;
+
+/**
+ * Description of Before
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config\Before
+{
+
+    /**
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $return = parent::execute($params);
+
+        return $return;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Deactivate/After.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Deactivate;
+
+/**
+ * Description of After
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate\After
+{
+
+    /**
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $return = parent::execute($params);
+
+        return $return;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Deactivate/Before.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Deactivate;
+
+/**
+ * Description of Before
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate\Before
+{
+
+    /**
+     * @return array
+     */
+    public function execute()
+    {
+        $return = parent::execute();
+
+        return $return;
+    }
+}

+ 44 - 0
app/Configuration/Addon/Update/AddonUpgradeService.php

@@ -0,0 +1,44 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Illuminate\Database\Capsule\Manager as DB;
+
+class AddonUpgradeService
+{
+
+    public function getVersion(){
+        if (!file_exists(ModuleConstants::getModuleRootDir() . DIRECTORY_SEPARATOR . 'moduleVersion.php'))
+        {
+            return;
+        }
+        include ModuleConstants::getModuleRootDir() . DS . 'moduleVersion.php';
+        return $moduleVersion;
+    }
+
+    public function getDatabaseVersion(){
+        return DB::table("tbladdonmodules")->where("module","proxmoxAddon")->where("setting","version")->value("value");
+    }
+    public function run(){
+        $version = $this->getVersion();
+        if($version && $this->getDatabaseVersion() != $version ){
+
+            if(!function_exists('proxmoxAddon_upgrade')){
+
+                require_once ModuleConstants::getFullPath() . DIRECTORY_SEPARATOR . "proxmoxAddon.php";
+            }
+            if(function_exists('proxmoxAddon_upgrade')){
+                \proxmoxAddon_upgrade([
+                    "module"=> "proxmoxAddon",
+                    "modulelink" => "addonmodules.php?module=proxmoxAddon",
+                    "version"=> $version,
+                    "access" => 1
+                ]);
+            }
+        }
+    }
+
+}

+ 21 - 0
app/Configuration/Addon/Update/After.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+/**
+ * Description of After
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\After
+{
+
+    /**
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $return = parent::execute($params);
+        return $return;
+    }
+}

+ 22 - 0
app/Configuration/Addon/Update/Before.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+/**
+ * Description of Before
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\Before
+{
+
+    /**
+     * @return array
+     */
+    public function execute($version)
+    {
+        $return = parent::execute($version);
+
+        return $return;
+    }
+}

+ 366 - 0
app/Configuration/Addon/Update/DefaultPatch.php

@@ -0,0 +1,366 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Models\IpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\KeyPair;
+use ModulesGarden\ProxmoxAddon\App\Models\User;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualNetwork;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\EmailTemplate;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\Patch\AbstractPatch;
+
+class DefaultPatch extends AbstractPatch
+{
+
+    public function up()
+    {
+        //install email templates
+        $this->templates();
+
+    }
+
+    protected function templates()
+    {
+        //Service Creation Failed
+        if (EmailTemplate::ofAdmin()->ofName('Service Creation Failed')->count() == 0)
+        {
+            $entity = new EmailTemplate();
+            $entity->fill(
+                [
+                    "type"    => "admin",
+                    "name"    => "Service Creation Failed",
+                    "subject" => "Service Creation Failed",
+                    "custom"  => 1,
+                    "message" => "<p>This product/service automatic provisioning has failed and requires you to manually check & resolve.</p>\n"
+                                 . "<p>Client ID: {\$client_id}<br />Service ID: {\$service_id}<br />Product/Service: {\$service_product}<br />Domain: {\$service_domain}<br />Error: {\$error_msg}</p>\n"
+                                 . "<p>{\$whmcs_admin_link}</p>\n"
+                ]
+            )->save();
+        }
+        //Manual Upgrade Request
+        if (EmailTemplate::ofAdmin()->ofName('Manual Upgrade Request')->count() == 0)
+        {
+            $entity = new EmailTemplate();
+            $entity->fill(
+                [
+                    "type"    => "admin",
+                    "name"    => "Manual Upgrade Request",
+                    "subject" => "Manual Upgrade Request",
+                    "custom"  => 1,
+                    "message" => "<p>An upgrade order has received its payment, automatic upgrades  has been disabled and requires manually processing.</p>\n"
+                                 . "<p>Client ID: {\$client_id}<br />Service ID: {\$service_id}<br />Product/Service: {\$service_product}<br />Domain: {\$service_domain}</p>\n"
+                                 . "<p>{\$whmcs_admin_link}</p>\n"
+                ]
+            )->save();
+        }
+    }
+
+    public function vmIpAddresses()
+    {
+        try
+        {
+            $query = DB::table("proxmoxVPS_IP");
+            foreach ($query->get() as $entery)
+            {
+                if (VmIpAddress::ofHostingId($entery->hid)->ofIp($entery->ip)->count()) {
+                    continue;
+                }
+                $entity             = new VmIpAddress();
+                $entery->hosting_id = $entery->hid;
+                $entity->fill((array)$entery)
+                    ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    public function keyPairs()
+    {
+        try
+        {
+            $query = DB::table("proxmoxvps_key_pairs");
+            foreach ($query->get() as $entery)
+            {
+                if(KeyPair::ofHostingId($entery->hosting_id)->count()){
+                    continue;
+                }
+                $entity = new KeyPair();
+                $entity->fill((array)$entery)
+                    ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    public function users()
+    {
+        try
+        {
+            $query = DB::table("proxmoxVPS_Users");
+            foreach ($query->get() as $entery)
+            {
+                if(User::ofHostingId($entery->hosting_id)->where("username", $entery->username )->count()){
+                    continue;
+                }
+                $entity = new User();
+                $entity->fill((array)$entery)
+                         ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    /**
+     * Import VMs from Proxmox Cloud
+     * @throws \Exception
+     */
+    public function vms()
+    {
+        try
+        {
+            $query = DB::table("proxmoxcloud_vservers");
+            foreach ($query->get() as $entery)
+            {
+                if(VmModel::ofHostingId($entery->hosting_id)->ofVmid($entery->vmid)->count()){
+                    continue;
+                }
+                if(!Hosting::ofId($entery->hosting_id)->activeAndSuspended()->count()){
+                    continue;
+                }
+                $vm = new VmModel;
+                $vm->fill((array)$entery);
+                $vm->name = $entery->hostname;
+                $vm->disk = $entery->storage_usage;
+                $vm->disks = (int) $entery->hdds;
+                $vm->virtualization = $entery->type;
+                $vm->memory = $entery->memory_usage;
+                $vm->sockets = $entery->sockets_usage;
+                $vm->cores = $entery->cpu_cores_usage;
+                $vm->cpuunits = $entery->cpu_weigh_usage;
+                $vm->swap = $entery->swap_usage;
+                $vm->cpulimit = $entery->cpu_limit;
+                $vm->virtualization = str_replace('kvm','qemu',$entery->type);
+                $vm->save();
+                //ips
+                $this->vmIps($vm, $entery);
+                $this->vmPrivateInterfaces($vm, $entery);
+                //keys
+                $this->vmKeyPairs($vm, $entery);
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    /**
+     * @throws \Exception
+     * Import ip addresses from proxmox cloud
+     */
+    public function vmIps(VmModel $vmModel, $vserver)
+    {
+        try
+        {
+            $query = DB::table("proxmoxcloud_ips")->where('vid', $vserver->id);
+            foreach ($query->get() as $entery)
+            {
+                if (VmIpAddress::ofHostingId($vmModel->hosting_id)->ofIp($entery->ip)->count()) {
+                    continue;
+                }
+                //public ip only
+                if(!filter_var($entery->ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)){
+                    continue;
+                }
+                $entity             = new VmIpAddress();
+                $entery->hosting_id =  $vmModel->hosting_id;
+                $entery->server_id = $entery->sid;
+                $entery->vm_id = $vmModel->id;
+                $entity->fill((array)$entery)
+                       ->save();
+                //VI
+                $vi = new VirtualInterface();
+                $vi->vn_id = 0;
+                $vi->ip = $entity->ip;
+                if(preg_match('/\./', $entity->ip)){
+                    $vi->ip_long = ip2long($entity->ip);
+                }
+                $vi->vm_id = $vmModel->id;
+                $vi->hosting_id = $vmModel->hosting_id;
+                $vi->net = $entity->net;
+                $vi->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    public function vmKeyPairs(VmModel $vmModel, $vserver)
+    {
+        try
+        {
+            $query = DB::table("proxmoxcloud_key_pairs")->where('vserver_id', $vserver->id);
+            foreach ($query->get() as $entery)
+            {
+                if(KeyPair::ofHostingId($entery->hosting_id)->where('vm_id',$vmModel->id)->count()){
+                    continue;
+                }
+                $entity = new KeyPair();
+                $entery->vm_id = $vmModel->id;
+                $entity->fill((array)$entery)
+                    ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    public function productCloud()
+    {
+        try
+        {
+            $productConveter = new ProductCloudConveter();
+            $query = DB::table("proxmoxCloud_prodConfig");
+            foreach ($query->get() as $row)
+            {
+                if(!Product::where('id',$row->product_id )->count()){
+                    continue;
+                }
+                $setting = $productConveter->convert($row->setting, $row->value, $row->product_id);
+                if (is_null($setting))
+                {
+                    continue;
+                }
+
+                if ($productConveter->exist($setting))
+                {
+                    continue;
+                }
+                $setting->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+
+    }
+
+    protected function usersCloud(){
+        try {
+            foreach (Hosting::ofProxmoxCloudAndStatusActiveAndSuspended()->get() as $hosting) {
+                if (User::ofHostingId($hosting->id)->count()) {
+                    continue;
+                }
+                $realm = $query = DB::table("proxmoxCloud_prodConfig")
+                    ->where('product_id', $hosting->packageid)
+                    ->where('setting','realm')
+                    ->value('value');
+                $entity = new User();
+                $entity->user_id = $hosting->userid;
+                $entity->hosting_id = $hosting->id;
+                $entity->username = $hosting->username;
+                $entity->password = $hosting->password;
+                $entity->realm = $realm ? $realm : 'pve';
+                $entity->save();
+            }
+        }catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+    public function vmPrivateInterfaces(VmModel $vmModel, $vserver)
+    {
+        try
+        {
+            $query = DB::table("proxmoxcloud_private_interfaces")->where('vserver_id', $vserver->id);
+            foreach ($query->get() as $entery)
+            {
+                if (VirtualInterface::ofHostingId($vmModel->hosting_id)->ofIp($entery->ip)->ofVmId($vmModel->id)->count()) {
+                    continue;
+                }
+                $network = DB::table("proxmoxcloud_private_networks")->where('id', $entery->network_id)->first();
+                $ip = IpAddress::find($entery->ip_id);
+                if(VirtualNetwork::ofHostingId( $vmModel->hosting_id)->ofTag($network->vlan_tag)->count() ==0){
+                    $vn = new  VirtualNetwork;
+                    $vn->name =   $network->name;
+                    $vn->tag =   $network->vlan_tag;
+                    $vn->hosting_id =   $network->hosting_id;
+                    $vn->pool = $entery->ip;
+                    $vn->cidr = $ip->cidr;
+                    $vn->gateway = $ip->gateway;
+                    $vn->save();
+                }else{
+                    $vn =  VirtualNetwork::ofHostingId( $vmModel->hosting_id)->ofTag($network->vlan_tag)->first();
+                }
+                //VI
+                $vi = new VirtualInterface();
+                $vi->vn_id = $vn->id;
+                $vi->ip = $entery->ip;
+                if(preg_match('/\./',  $entery->ip)){
+                    $vi->ip_long = ip2long( $entery->ip);
+                }
+                $vi->vm_id = $vmModel->id;
+                $vi->hosting_id = $vmModel->hosting_id;
+                $vi->net =  $entery->device;
+                $vi->save();
+                $ip->hosting_id = 0 ;
+                $ip->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            print_r($ex); die();
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+}

+ 30 - 0
app/Configuration/Addon/Update/Patch/M2M6P0.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Description of M2M6P0
+ *
+ * @author <slawomir@modulesgarden.com>
+ */
+class M2M6P0 extends DefaultPatch
+{
+    public function execute()
+    {
+        $this->up();
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('"errorManager"')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+
+    }
+}

+ 59 - 0
app/Configuration/Addon/Update/Patch/M2M7P0.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\ProductConveter;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Description of M2M6P0
+ *
+ * @author <slawomir@modulesgarden.com>
+ */
+class M2M7P0 extends DefaultPatch
+{
+    public function execute()
+    {
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        $this->up();
+        //import ip addresses
+        $this->vmIpAddresses();
+        //import key pairs
+        $this->keyPairs();
+        //import users
+        $this->users();
+        try
+        {
+            $productConveter = new ProductConveter();
+            foreach (DB::table("proxmoxVPS_prodConfig")->get() as $row)
+            {
+                $setting = $productConveter->convert($row->setting, $row->value, $row->product_id);
+                if (is_null($setting))
+                {
+                    continue;
+                }
+                if ($productConveter->exist($setting))
+                {
+                    continue;
+                }
+                $setting->save();
+            }
+
+        }
+        catch (\Exception $ex)
+        {
+
+        }
+    }
+}

+ 30 - 0
app/Configuration/Addon/Update/Patch/M2M7P3.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Description of M2M6P0
+ *
+ * @author <slawomir@modulesgarden.com>
+ */
+class M2M7P3 extends DefaultPatch
+{
+    public function execute()
+    {
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        $this->up();
+    }
+}

+ 43 - 0
app/Configuration/Addon/Update/Patch/M2M7P4.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Description of M2M6P0
+ *
+ * @author <slawomir@modulesgarden.com>
+ */
+class M2M7P4 extends DefaultPatch
+{
+    public function execute()
+    {
+        //default options
+        foreach (Product::where('servertype','proxmoxVPS')->pluck('id')->all() as $productId) {
+            if(ProductConfiguration::ofProductId($productId)->ofSetting('permissionSshkeys')->count()){
+                continue;
+            }
+            $setting = new ProductConfiguration();
+            $setting->product_id = $productId;
+            $setting->setting = 'permissionSshkeys';
+            $setting->value = 'on';
+            $setting->save();
+        }
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        $this->up();
+    }
+}

+ 31 - 0
app/Configuration/Addon/Update/Patch/M2M8P0.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Description of M2M6P0
+ *
+ */
+class M2M8P0 extends DefaultPatch
+{
+    public function execute()
+    {
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        $this->up();
+    }
+}

+ 118 - 0
app/Configuration/Addon/Update/Patch/M3M0P0.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of M2M6P0
+ *
+ */
+class M3M0P0 extends DefaultPatch
+{
+    public function execute()
+    {
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        //data
+        $this->up();
+        //Proxmox Cloud import vms
+        $this->vms();
+        //ip addresses not assigned
+        $this->ipAddressesForCloud();
+        //proxmoxcloud_private_interfaces
+        //proxmoxcloud_private_networks
+        $this->productCloud();
+        $this->defaultProductOptions();
+        //users
+        $this->usersCloud();
+        //assign ip adresses
+        $this->changePackegeCloud();
+
+    }
+
+    protected function  defaultProductOptions(){
+        foreach (Product::ofProxmoxCloud()->get() as $product){
+            $setting =  ProductConfiguration::firstOrNew([
+                'product_id' => $product->id,
+                'setting' => 'permissionVirtualNetwork'
+            ]);
+            $setting->value = 'on';
+            $setting->save();
+        }
+    }
+
+    protected  function  changePackegeCloud(){
+
+        $file   = \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getFullPathWhmcs('modules', 'servers','ProxmoxCloudVps', 'core') . DIRECTORY_SEPARATOR . 'Bootstrap.php';
+        if(!file_exists($file))
+        {
+            return;
+        }
+        include_once  $file;
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ModuleConstants::getFullPathWhmcs('includes') . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        foreach (Hosting::ofProxmoxCloudAndStatusActiveAndSuspended()->get() as $hosting) {
+            $params = \ModuleBuildParams($hosting->id);
+            \ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl('whmcsParams')->setParams($params);
+            $controller = new \ModulesGarden\Servers\ProxmoxCloudVps\App\Http\Actions\ChangePackage();
+            $result = $controller->execute($params);
+            if($result != 'success'){
+                $msg = sprintf('Module Change Package Failed - Service ID: %s - Error: %s', $hosting->id, $result);
+                logActivity( $msg );
+            }
+        }
+    }
+
+    public function  ipAddressesForCloud(){
+        try
+        {
+            $query = DB::table("proxmoxcloud_ips")->where('vid', '0');
+            foreach ($query->get() as $entery)
+            {
+                if(!Hosting::ofId($entery->hid)->activeAndSuspended()->count()){
+                    continue;
+                }
+                if (VmIpAddress::ofHostingId($entery->hid)->ofIp($entery->ip)->count()) {
+                    continue;
+                }
+                //public ip only
+                if(!filter_var($entery->ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)){
+                    continue;
+                }
+                $entity             = new VmIpAddress();
+                $entery->hosting_id =  $entery->hid;
+                $entery->server_id = $entery->sid;
+                $entery->vm_id = null;
+                $entity->fill((array)$entery)
+                      ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+}

+ 105 - 0
app/Configuration/Addon/Update/Patch/M3M1P0.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\Patch;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\DefaultPatch;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of M2M6P0
+ *
+ */
+class M3M1P0 extends DefaultPatch
+{
+    public function execute()
+    {
+        if ($this->runSchema())
+        {
+            Helper\sl('logger')
+                ->addDebug("Correctly installed update {$this->getVersion()} .", []);
+        }
+        else
+        {
+            Helper\sl('errorManager')
+                ->addError(self::class, "Incorrectly installed update {$this->getVersion()} .", []);
+        }
+        //data
+        $this->up();
+    }
+
+    protected function  defaultProductOptions(){
+        foreach (Product::ofProxmoxCloud()->get() as $product){
+            $setting =  ProductConfiguration::firstOrNew([
+                'product_id' => $product->id,
+                'setting' => 'permissionVirtualNetwork'
+            ]);
+            $setting->value = 'on';
+            $setting->save();
+        }
+    }
+
+    protected  function  changePackegeCloud(){
+
+        $file   = \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getFullPathWhmcs('modules', 'servers','ProxmoxCloudVps', 'core') . DIRECTORY_SEPARATOR . 'Bootstrap.php';
+        if(!file_exists($file))
+        {
+            return;
+        }
+        include_once  $file;
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ModuleConstants::getFullPathWhmcs('includes') . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        foreach (Hosting::ofProxmoxCloudAndStatusActiveAndSuspended()->get() as $hosting) {
+            $params = \ModuleBuildParams($hosting->id);
+            \ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl('whmcsParams')->setParams($params);
+            $controller = new \ModulesGarden\Servers\ProxmoxCloudVps\App\Http\Actions\ChangePackage();
+            $result = $controller->execute($params);
+            if($result != 'success'){
+                $msg = sprintf('Module Change Package Failed - Service ID: %s - Error: %s', $hosting->id, $result);
+                logActivity( $msg );
+            }
+        }
+    }
+
+    public function  ipAddressesForCloud(){
+        try
+        {
+            $query = DB::table("proxmoxcloud_ips")->where('vid', '0');
+            foreach ($query->get() as $entery)
+            {
+                if(!Hosting::ofId($entery->hid)->activeAndSuspended()->count()){
+                    continue;
+                }
+                if (VmIpAddress::ofHostingId($entery->hid)->ofIp($entery->ip)->count()) {
+                    continue;
+                }
+                //public ip only
+                if(!filter_var($entery->ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)){
+                    continue;
+                }
+                $entity             = new VmIpAddress();
+                $entery->hosting_id =  $entery->hid;
+                $entery->server_id = $entery->sid;
+                $entery->vm_id = null;
+                $entity->fill((array)$entery)
+                      ->save();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match("/doesn\'t/", $ex->getMessage()))
+            {
+                throw $ex;
+            }
+        }
+    }
+
+}

+ 851 - 0
app/Configuration/Addon/Update/ProductCloudConveter.php

@@ -0,0 +1,851 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+
+class ProductCloudConveter
+{
+    const  data = [
+        "virtualization_type"              => [
+            "newSetting" => "virtualization",
+            'newValue'   => ["KVM" => "qemu", "LXC" => "lxc"]
+        ],
+        "default_node"                     => [
+            "newSetting" => "defaultNode",
+            'newValue'   => ["Server-Node" => "serverNode", "Auto-Node" => "autoNode"]
+        ],
+        "console_ip"                       => [
+            "newSetting" => "consoleHost",
+            'newValue'   => []
+        ],
+        "available_resources"              => [
+            "newSetting" => "checkResources",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "backupVmBeforeRebuild"            => [
+            "newSetting" => "backupVmBeforeReinstall",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "user_prefix"                      => [
+            "newSetting" => "userPrefix",
+            'newValue'   => []
+        ],
+        "user_comment"                     => [
+            "newSetting" => "userComment",
+            'newValue'   => []
+        ],
+        "domains"                          => [
+            "newSetting" => "realm",
+            'newValue'   => []
+        ],
+        "roles"                            => [
+            "newSetting" => "userRole",
+            'newValue'   => []
+        ],
+        "lxc_storage"                      => [
+            "newSetting" => "storage",
+            'newValue'   => []
+        ],
+        "lxc_arch"                         => [
+            "newSetting" => "arch",
+            'newValue'   => []
+        ],
+        "lxc_cmode"                        => [
+            "newSetting" => "cmode",
+            'newValue'   => []
+        ],
+        "lxc_console"                      => [
+            "newSetting" => "console",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_ostype"                       => [
+            "newSetting" => "ostype",
+            'newValue'   => []
+        ],
+        "lxc_onboot"                       => [
+            "newSetting" => "onboot",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_pool"                         => [
+            "newSetting" => "pool",
+            'newValue'   => []
+        ],
+        "lxc_protection"                   => [
+            "newSetting" => "protection",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_description"                  => [
+            "newSetting" => "description",
+            'newValue'   => []
+        ],
+        "lxc_startup"                      => [
+            "newSetting" => "startup",
+            'newValue'   => []
+        ],
+        "lxc_ostemplates"                  => [
+            "newSetting" => "permissionOsTemplates",
+            'newValue'   => []
+        ],
+        "lxc_tty"                          => [
+            "newSetting" => "tty",
+            'newValue'   => []
+        ],
+        "lxc_unprivileged"                 => [
+            "newSetting" => "unprivileged",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_sshKeyPairs"                  => [
+            "newSetting" => "sshKeyPairs",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_sshDeletePrivateKey"          => [
+            "newSetting" => "sshDeletePrivateKey",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_cpulimit"                     => [
+            "newSetting" => "cpulimit",
+            'newValue'   => []
+        ],
+        "default_cpu_cores_limit"                        => [
+            "newSetting" => "cores",
+            'newValue'   => []
+        ],
+        "lxc_swap"                         => [
+            "newSetting" => "swap",
+            'newValue'   => []
+        ],
+        "lxc_cpuunits"                     => [
+            "newSetting" => "cpuunits",
+            'newValue'   => []
+        ],
+        "default_memory_limit"                       => [
+            "newSetting" => "memory",
+            'newValue'   => []
+        ],
+        "default_storage_limit"                         => [
+            "newSetting" => "storageSize",
+            'newValue'   => []
+        ],
+        "default_ip_addresses_limit"                         => [
+            "newSetting" => "ipv4",
+            'newValue'   => []
+        ],
+        "default_ipv6_addresses_limit"                         => [
+            "newSetting" => "ipv6",
+            'newValue'   => []
+        ],
+        "lxc_snapshots_limit"              => [
+            "newSetting" => "snapshotMaxFiles",
+            'newValue'   => []
+        ],
+        "default_backup_limit_gb"              => [
+            "newSetting" => "backupMaxSize",
+            'newValue'   => []
+        ],
+        "default_backup_limit_maxfile"         => [
+            "newSetting" => "backupMaxFiles",
+            'newValue'   => []
+        ],
+        "default_bandwidth_limit"              => [
+            "newSetting" => "bandwidth",
+            'newValue'   => []
+        ],
+        "lxc_lan_rate"                     => [
+            "newSetting" => "rate",
+            'newValue'   => []
+        ],
+        "lxc_minimum_rate"                 => [
+            "newSetting" => "minimumRate",
+            'newValue'   => []
+        ],
+        "lxc_mpStorage"                    => [
+            "newSetting" => "mountPointStorage",
+            'newValue'   => []
+        ],
+        "lxc_mpAcl"                        => [
+            "newSetting" => "mountPointAcl",
+            'newValue'   => []
+        ],
+        "lxc_mpRo"                         => [
+            "newSetting" => "mountPointRo",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_mpQuota"                      => [
+            "newSetting" => "mountPointQuota",
+            'newValue'   => []
+        ],
+        "lxc_mpReplicate"                  => [
+            "newSetting" => "mountPointReplicate",
+            'newValue'   => []
+        ],
+        "lxc_ipv4_mode"                    => [
+            "newSetting" => "ipv4NetworkMode",
+            'newValue'   => []
+        ],
+        "lxc_ipv6_mode"                    => [
+            "newSetting" => "ipv6NetworkMode",
+            'newValue'   => []
+        ],
+        "lxc_bridge"                       => [
+            "newSetting" => "bridge",
+            'newValue'   => []
+        ],
+        "lxc_vlantag_from"                 => [
+            "newSetting" => "tagFrom",
+            'newValue'   => []
+        ],
+        "lxc_vlantag_to"                   => [
+            "newSetting" => "tagTo",
+            'newValue'   => []
+        ],
+        "lxc_private_bridge"               => [
+            "newSetting" => "privateBridge",
+            'newValue'   => []
+        ],
+        "lxc_firewall"                     => [
+            "newSetting" => "networkFirewall",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "kvm_ostype"                       => [
+            "newSetting" => "ostype",
+            'newValue'   => []
+        ],
+        "kvm_acpi"                         => [
+            "newSetting" => "acpi",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_agent"                        => [
+            "newSetting" => "agent",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_args"                         => [
+            "newSetting" => "args",
+            'newValue'   => []
+        ],
+        "kvm_autostart"                    => [
+            "newSetting" => "autostart",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_balloon"                      => [
+            "newSetting" => "balloon",
+            'newValue'   => []
+        ],
+        "kvm_shares"                       => [
+            "newSetting" => "shares",
+            'newValue'   => []
+        ],
+        "kvm_cdrom"                        => [
+            "newSetting" => "cdrom",
+            'newValue'   => []
+        ],
+        "kvm_cpu"                          => [
+            "newSetting" => "cpu",
+            'newValue'   => []
+        ],
+        "kvm_numa"                         => [
+            "newSetting" => "numa",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_pcid"                         => [
+            "newSetting" => "pcid",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_spec-ctrl"                    => [
+            "newSetting" => "spec-ctrl",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_description"                  => [
+            "newSetting" => "description",
+            'newValue'   => []
+        ],
+        "kvm_freeze"                       => [
+            "newSetting" => "freeze",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_hotplug"                      => [
+            "newSetting" => "hotplug",
+            'newValue'   => []
+        ],
+        "kvm_cdrom"                        => [
+            "newSetting" => "cdrom",
+            'newValue'   => []
+        ],
+        "kvm_keyboard"                     => [
+            "newSetting" => "keyboard",
+            'newValue'   => []
+        ],
+        "kvm_kvm"                          => [
+            "newSetting" => "kvm",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_localtime"                    => [
+            "newSetting" => "localtime",
+            'newValue'   => ['Yes' => "on", 'No' => "off"]
+        ],
+        "kvm_migrate_downtime"             => [
+            "newSetting" => "migrate_downtime",
+            'newValue'   => []
+        ],
+        "kvm_onboot"                       => [
+            "newSetting" => "onboot",
+            'newValue'   => ['Yes' => "on", 'No' => "off"]
+        ],
+        "kvm_pool"                         => [
+            "newSetting" => "pool",
+            'newValue'   => []
+        ],
+        "kvm_reboot"                       => [
+            "newSetting" => "reboot",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_startdate"                    => [
+            "newSetting" => "startdate",
+            'newValue'   => []
+        ],
+        "kvm_startup"                      => [
+            "newSetting" => "startup",
+            'newValue'   => []
+        ],
+        "kvm_storage"                      => [
+            "newSetting" => "diskStorage",
+            'newValue'   => []
+        ],
+        "kvm_tablet"                       => [
+            "newSetting" => "tablet",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_tdf"                          => [
+            "newSetting" => "tdf",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_vga"                          => [
+            "newSetting" => "vga",
+            'newValue'   => []
+        ],
+        "kvm_watchdog"                     => [
+            "newSetting" => "watchdog",
+            'newValue'   => []
+        ],
+        "kvm_os_template"                  => [
+            "newSetting" => "permissionOsTemplates",
+            'newValue'   => []
+        ],
+        "kvm_cloneMode"                    => [
+            "newSetting" => "cloneMode",
+            'newValue'   => []
+        ],
+        "kvm_sockets"                      => [
+            "newSetting" => "sockets",
+            'newValue'   => []
+        ],
+        "kvm_vcpus"                        => [
+            "newSetting" => "vcpus",
+            'newValue'   => []
+        ],
+        "default_cpuLimit"                     => [
+            "newSetting" => "cpulimit",
+            'newValue'   => []
+        ],
+        "default_cpuunits_limit"                     => [
+            "newSetting" => "cpuunits",
+            'newValue'   => []
+        ],
+        "kvm_lan_rate"                     => [
+            "newSetting" => "rate",
+            'newValue'   => []
+        ],
+        "kvm_minimum_rate"                 => [
+            "newSetting" => "minimumRate",
+            'newValue'   => []
+        ],
+        "kvm_cdrom_isoimage"               => [
+            "newSetting" => "isoImage",
+            'newValue'   => []
+        ],
+        "kvm_disk_type"                    => [
+            "newSetting" => "diskType",
+            'newValue'   => ['IDE' => "ide", 'SATA' => 'sata', 'VIRTIO' => 'virtio', 'SCSI' => 'scsi']
+        ],
+        "kvm_disk_format"                  => [
+            "newSetting" => "diskFormat",
+            'newValue'   => []
+        ],
+        "kvm_disk_cache"                   => [
+            "newSetting" => "diskCache",
+            'newValue'   => []
+        ],
+        "kvm_scsihw"                       => [
+            "newSetting" => "scsihw",
+            'newValue'   => []
+        ],
+        "kvm_discard"                      => [
+            "newSetting" => "discard",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_replicate"                    => [
+            "newSetting" => "replicate",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_ioThread"                     => [
+            "newSetting" => "ioThread",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDisksStorage"       => [
+            "newSetting" => "additionalDiskStorage",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDisksType"          => [
+            "newSetting" => "additionalDiskType",
+            'newValue'   => ['IDE' => "ide", 'SATA' => 'sata', 'VIRTIO' => 'virtio', 'SCSI' => 'scsi'],
+        ],
+        "kvm_AdditionalDisksFrmats"        => [
+            "newSetting" => "additionalDiskFormat",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskCache"          => [
+            "newSetting" => "additionalDiskCache",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskReplicate"      => [
+            "newSetting" => "additionalDiskReplicate",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskDiscard"        => [
+            "newSetting" => "additionalDiskDiscard",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskIoThread"       => [
+            "newSetting" => "additionalDiskIoThread",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskAllowBackups"   => [
+            "newSetting" => "permissionAdditionalDiskBackup",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_DiskReadLimit"                => [
+            "newSetting" => "mbps_rd",
+            'newValue'   => []
+        ],
+        "kvm_DiskWriteLimit"               => [
+            "newSetting" => "mbps_wr",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd"                      => [
+            "newSetting" => "iops_rd",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd"                      => [
+            "newSetting" => "iops_rd",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd_max"                  => [
+            "newSetting" => "iops_rd_max",
+            'newValue'   => []
+        ],
+        "kvm_iops_wr"                      => [
+            "newSetting" => "iops_wr",
+            'newValue'   => []
+        ],
+        "kvm_iops_wr_max"                  => [
+            "newSetting" => "iops_wr_max",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskReadLimit"      => [
+            "newSetting" => "additionalDiskMbps_rd",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskWriteLimit"     => [
+            "newSetting" => "additionalDiskMbps_wr",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_rd"        => [
+            "newSetting" => "additionalDiskIops_rd",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_rd_max"    => [
+            "newSetting" => "additionalDiskIops_rd_max",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_wr"        => [
+            "newSetting" => "additionalDiskIops_wr",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_wr_max"    => [
+            "newSetting" => "additionalDiskIops_wr_max",
+            'newValue'   => []
+        ],
+        "kvm_cdrom_type"                   => [
+            "newSetting" => "cdromType",
+            'newValue'   => [ 'IDE' => 'ide', 'SATA' => 'sata', 'SCSI' =>  'scsi']
+        ],
+        "kvm_cdrom_isoimage"                    => [
+            "newSetting" => "permissionIsoImages",
+            'newValue'   => []
+        ],
+        "kvm_lan_bridge"                   => [
+            "newSetting" => "bridge",
+            'newValue'   => []
+        ],
+        "kvm_lan_network_model"            => [
+            "newSetting" => "networkModel",
+            'newValue'   => []
+        ],
+        "kvm_disable_additional_nic"       => [
+            "newSetting" => "oneNetworkDevice",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "kvm_vlantag_from"                 => [
+            "newSetting" => "tagFrom",
+            'newValue'   => []
+        ],
+        "kvm_vlantag_to"                   => [
+            "newSetting" => "tagTo",
+            'newValue'   => []
+        ],
+        "kvm_private_bridge"               => [
+            "newSetting" => "privateBridge",
+            'newValue'   => []
+        ],
+        "kvm_private_lan_network_model"    => [
+            "newSetting" => "networkPrivateModel",
+            'newValue'   => []
+        ],
+        "kvm_network_firewall"             => [
+            "newSetting" => "networkFirewall",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_queues"                       => [
+            "newSetting" => "queues",
+            'newValue'   => []
+        ],
+        "kvm_boot_1"                       => [
+            "newSetting" => "bootDevice1",
+            'newValue'   => []
+        ],
+        "kvm_boot_2"                       => [
+            "newSetting" => "bootDevice2",
+            'newValue'   =>  []
+        ],
+        "kvm_boot_3"                       => [
+            "newSetting" => "bootDevice3",
+            'newValue'   =>  []
+        ],
+        "kvm_bootdisk"                     => [
+            "newSetting" => "bootdisk",
+            'newValue'   =>  []
+        ],
+        "backup_storage"                   => [
+            "newSetting" => "backupStorage",
+            'newValue'   => ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "backup_routing"                   => [
+            "newSetting" => "backupRouting",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_delete_older_than"         => [
+            "newSetting" => "backupStoreDays",
+            'newValue'   => []
+        ],
+        "firewall_interfaces"              => [
+            "newSetting" => "firewallInterfaces",
+            'newValue'   => []
+        ],
+        "firewall_rules_limit"             => [
+            "newSetting" => "firewallMaxRules",
+            'newValue'   => []
+        ],
+        "kvm_ipset_ip_filter"              => [
+            "newSetting" => "ipsetIpFilter",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "cluster_state"                    => [
+            "newSetting" => "clusterState",
+            'newValue'   => []
+        ],
+        "cluster_group"                    => [
+            "newSetting" => "clusterGroup",
+            'newValue'   => []
+        ],
+        "cluster_max_restart"              => [
+            "newSetting" => "clusterMaxRestart",
+            'newValue'   => []
+        ],
+        "cluster_max_relocate"             => [
+            "newSetting" => "clusterMaxRelocate",
+            'newValue'   => []
+        ],
+        "kvm_cloudInit"                    => [
+            "newSetting" => "cloudInit",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_ciuser"                       => [
+            "newSetting" => "ciuser",
+            'newValue'   => []
+        ],
+        "loadBalancer"                     => [
+            "newSetting" => "loadBalancer",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerOnUpgrade"            => [
+            "newSetting" => "loadBalancerOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerShutdownOnUpgrade"    => [
+            "newSetting" => "loadBalancerShutdownOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerStopOnUpgrade"        => [
+            "newSetting" => "loadBalancerStopOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_boot"                         => [
+            "newSetting" => "permissionStart",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_reboot"                       => [
+            "newSetting" => "permissionReboot",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_stop"                         => [
+            "newSetting" => "permissionStop",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_shutdown"                     => [
+            "newSetting" => "permissionShutdown",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_novnc_console"                => [
+            "newSetting" => "permissionNovnc",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_spice_console"                => [
+            "newSetting" => "permissionSpice",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "xtermjsConsole"                   => [
+            "newSetting" => "permissionXtermjs",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_reinstallation"               => [
+            "newSetting" => "permissionReinstall",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "kvm_per_kvm_templates"            => [
+            "newSetting" => "permissionOsTemplate",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_per_iso_images"               => [
+            "newSetting" => "permissionIsoImage",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_snapshots_limit"              => [
+            "newSetting" => "snapshotMaxFiles",
+            'newValue'   => []
+        ],
+        "per_mrtg_graphics"                => [
+            "newSetting" => "permissionGraph",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_list"                      => [
+            "newSetting" => "permissionBackup",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_jobs"                      => [
+            "newSetting" => "permissionBackupJob",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_task_history"                 => [
+            "newSetting" => "permissionTaskHistory",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_network"                      => [
+            "newSetting" => "permissionNetwork",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_snapshots"                    => [
+            "newSetting" => "permissionSnapshot",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_firewall"                     => [
+            "newSetting" => "permissionFirewall",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_firewallOptions"              => [
+            "newSetting" => "permissionFirewall",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_DisksManagement"              => [
+            "newSetting" => "permissionDisk",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "memory_unit"  => [
+            "newSetting" => "memoryUnit",
+            'newValue'   => []
+        ],
+        "swap_unit"  => [
+            "newSetting" => "swapUnit",
+            'newValue'   => []
+        ],
+        "disk_unit"  => [
+            "newSetting" => "diskUnit",
+            'newValue'   => []
+        ],
+        "adisk_unit"  => [
+            "newSetting" => "additionalDiskUnit",
+            'newValue'   => []
+        ],
+        'locations' => [
+            "newSetting" => "locations",
+            'newValue'   => []
+        ],
+        'default_ipv6' => [
+            "newSetting" => "permissionIpv6",
+            'newValue'   => ['1' => 'on', '0' => 'off']
+        ],
+        'default_ipv4' => [
+            "newSetting" => "permissionIpv4",
+            'newValue'   => ['1' => 'on', '0' => 'off']
+        ],
+        //Default Configuration
+        'kvm_private_network_limit' => [
+            "newSetting" => "virtualNetworks",
+            'newValue'   => []
+        ],
+        'lxc_networkRate'  => [
+            "newSetting" => "rate",
+            'newValue'   => []
+        ],
+        'lxc_private_network_limit' => [
+            "newSetting" => "virtualNetworks",
+            'newValue'   => []
+        ],
+        //Server Limits
+        'kvm_limit_sockets' => [
+            "newSetting" => "serverSockets",
+            'newValue'   => []
+        ],
+        'kvm_limit_vcpus' => [
+            "newSetting" => "serverVcpus",
+            'newValue'   => []
+        ],
+        'kvm_limit_cores' => [
+            "newSetting" => "serverCores",
+            'newValue'   => []
+        ],
+        'lxc_limit_cpu'=> [
+            "newSetting" => "serverCores",
+            'newValue'   => []
+        ],
+        'limit_cpulimit'=> [
+            "newSetting" => "serverCpulimit",
+            'newValue'   => []
+        ],
+        'limit_cpuunits'=> [
+            "newSetting" => "serverCpuunit",
+            'newValue'   => []
+        ],
+         'limit_memory'=> [
+             "newSetting" => "serverMemory",
+             'newValue'   => []
+         ],
+        'lxc_limit_swap' => [
+            "newSetting" => "serverSwap",
+            'newValue' => []
+        ],
+        'limit_storage' => [
+            "newSetting" => "serverDiskSize",
+            'newValue' => []
+        ],
+        'limit_ipv4' => [
+            "newSetting" => "serverIpv4",
+            'newValue' => []
+        ],
+        'limit_ipv6' => [
+            "newSetting" => "serverIpv6",
+            'newValue' => []
+        ],
+        //Cloud-Init
+        'kvm_cloudInitSetUsername'=> [
+            "newSetting" => "permissionUsername",
+            'newValue' => ['1' => 'on', '0'=> 'off']
+        ],
+        'kvm_cipassword'=> [
+            "newSetting" => "permissionPassword",
+            'newValue' => ['1' => 'on', '0'=> 'off']
+        ],
+         'kvm_sshkeys'=> [
+             "newSetting" => "permissionSshkeys",
+             'newValue' => ['1' => 'on', '0'=> 'off']
+         ],
+        'kvm_searchdomain' => [
+            "newSetting" => "permissionSearchdomain",
+            'newValue' => ['1' => 'on', '0' => 'off']
+        ],
+        'kvm_nameserver' => [
+            "newSetting" => "permissionNameservers",
+            'newValue' => ['1' => 'on', '0' => 'off']
+        ],
+    ];
+
+    /**
+     * @param $key
+     * @param $value
+     * @param $productId
+     * @return ProductConfiguration|null
+     */
+    public function convert($key, $value, $productId)
+    {
+        if (!self::data[$key] && !self::data[$key]['newSetting'])
+        {
+            return null;
+        }
+        $setting          = new ProductConfiguration();
+        $setting->setting = self::data[$key]['newSetting'];
+        if (preg_match("/\[\"/", $value))
+        {
+            $value = \json_decode($value, true);
+        }
+        $isset = !empty(self::data[$key]['newValue'][$value]);
+        $method = self::data[$key]['newSetting'];
+        if( method_exists($this,   $method )){
+            $this-> $method($value);
+            $setting->value = $value;
+        }
+        else  if ($isset && !is_array($value))
+        {
+            $setting->value = self::data[$key]['newValue'][$value];
+        }
+        else if(is_array($value) &&  $isset){
+            $newValue=[];
+            foreach($value as &$v){
+                $oldValue = $v;
+                if(!empty(self::data[$key]['newValue'][$oldValue])){
+                    $newValue[]=self::data[$key]['newValue'][$oldValue];
+                }
+            }
+            $setting->value = $newValue;
+
+        }
+        else
+        {
+            $setting->value = $value;
+        }
+        $setting->product_id = $productId;
+        return $setting;
+    }
+
+    public function exist(ProductConfiguration $setting)
+    {
+        return ProductConfiguration::ofProductId($setting->product_id)->ofSetting($setting->setting)->count();
+    }
+
+    public function additionalDiskType(&$value){
+        foreach ($value as &$v){
+            $v= strtolower($v);
+        }
+    }
+
+}

+ 873 - 0
app/Configuration/Addon/Update/ProductConveter.php

@@ -0,0 +1,873 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+
+class ProductConveter
+{
+    const  data = [
+        "virtualization_type"              => [
+            "newSetting" => "virtualization",
+            'newValue'   => ["KVM" => "qemu", "LXC" => "lxc"]
+        ],
+        "default_node"                     => [
+            "newSetting" => "defaultNode",
+            'newValue'   => ["Server-Node" => "serverNode", "Auto-Node" => "autoNode"]
+        ],
+        "console_ip"                       => [
+            "newSetting" => "consoleHost",
+            'newValue'   => []
+        ],
+        "lxc_randomHostname"               => [
+            "newSetting" => "randomHostname",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "available_resources"              => [
+            "newSetting" => "checkResources",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "backupVmBeforeRebuild"            => [
+            "newSetting" => "backupVmBeforeReinstall",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "default_node"                     => [
+            "newSetting" => "defaultNode",
+            'newValue'   => ["Server-Node" => "serverNode", "Auto-Node" => "autoNode"]
+        ],
+        "user_prefix"                      => [
+            "newSetting" => "userPrefix",
+            'newValue'   => []
+        ],
+        "user_comment"                     => [
+            "newSetting" => "userComment",
+            'newValue'   => []
+        ],
+        "oneUserPerVps"                    => [
+            "newSetting" => "oneUserPerVps",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "domains"                          => [
+            "newSetting" => "realm",
+            'newValue'   => []
+        ],
+        "roles"                            => [
+            "newSetting" => "userRole",
+            'newValue'   => []
+        ],
+        "welcomeEmailTemplateId"           => [
+            "newSetting" => "welcomeEmailTemplateId",
+            'newValue'   => []
+        ],
+        "rebootVmAfterChangePackage"       => [
+            "newSetting" => "rebootVmAfterChangePackage",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "deleteBackups"                    => [
+            "newSetting" => "deleteBackups",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "useServerNameservers"             => [
+            "newSetting" => "serverNameservers",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_storage"                      => [
+            "newSetting" => "storage",
+            'newValue'   => []
+        ],
+        "lxc_arch"                         => [
+            "newSetting" => "arch",
+            'newValue'   => []
+        ],
+        "lxc_cmode"                        => [
+            "newSetting" => "cmode",
+            'newValue'   => []
+        ],
+        "lxc_console"                      => [
+            "newSetting" => "console",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_ostype"                       => [
+            "newSetting" => "ostype",
+            'newValue'   => []
+        ],
+        "lxc_onboot"                       => [
+            "newSetting" => "onboot",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_pool"                         => [
+            "newSetting" => "pool",
+            'newValue'   => []
+        ],
+        "lxc_protection"                   => [
+            "newSetting" => "protection",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_description"                  => [
+            "newSetting" => "description",
+            'newValue'   => []
+        ],
+        "lxc_startup"                      => [
+            "newSetting" => "startup",
+            'newValue'   => []
+        ],
+        "lxc_ostemplates"                  => [
+            "newSetting" => "permissionOsTemplates",
+            'newValue'   => []
+        ],
+        "lxc_tty"                          => [
+            "newSetting" => "tty",
+            'newValue'   => []
+        ],
+        "lxc_unprivileged"                 => [
+            "newSetting" => "unprivileged",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_sshKeyPairs"                  => [
+            "newSetting" => "sshKeyPairs",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_sshDeletePrivateKey"          => [
+            "newSetting" => "sshDeletePrivateKey",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_ostemplate"                   => [
+            "newSetting" => "osTemplate",
+            'newValue'   => []
+        ],
+        "lxc_cpulimit"                     => [
+            "newSetting" => "cpulimit",
+            'newValue'   => []
+        ],
+        "lxc_cores"                        => [
+            "newSetting" => "cores",
+            'newValue'   => []
+        ],
+        "lxc_cores"                        => [
+            "newSetting" => "cores",
+            'newValue'   => []
+        ],
+        "lxc_swap"                         => [
+            "newSetting" => "swap",
+            'newValue'   => []
+        ],
+        "lxc_cpuunits"                     => [
+            "newSetting" => "cpuunits",
+            'newValue'   => []
+        ],
+        "lxc_memory"                       => [
+            "newSetting" => "memory",
+            'newValue'   => []
+        ],
+        "lxc_disk"                         => [
+            "newSetting" => "diskSize",
+            'newValue'   => []
+        ],
+        "lxc_AdditionalDisksSpace"         => [
+            "newSetting" => "additionalDiskSize",
+            'newValue'   => []
+        ],
+        "lxc_ipv4"                         => [
+            "newSetting" => "ipv4",
+            'newValue'   => []
+        ],
+        "lxc_ipv6"                         => [
+            "newSetting" => "ipv6",
+            'newValue'   => []
+        ],
+        "lxc_snapshots_limit"              => [
+            "newSetting" => "snapshotMaxFiles",
+            'newValue'   => []
+        ],
+        "lxc_backup_limit_gb"              => [
+            "newSetting" => "backupMaxFiles",
+            'newValue'   => []
+        ],
+        "lxc_backup_limit_maxfile"         => [
+            "newSetting" => "backupMaxFiles",
+            'newValue'   => []
+        ],
+        "lxc_bandwidth_limit"              => [
+            "newSetting" => "bandwidth",
+            'newValue'   => []
+        ],
+        "lxc_lan_rate"                     => [
+            "newSetting" => "rate",
+            'newValue'   => []
+        ],
+        "lxc_minimum_rate"                 => [
+            "newSetting" => "minimumRate",
+            'newValue'   => []
+        ],
+        "lxc_mpStorage"                    => [
+            "newSetting" => "mountPointStorage",
+            'newValue'   => []
+        ],
+        "lxc_mpAcl"                        => [
+            "newSetting" => "mountPointAcl",
+            'newValue'   => []
+        ],
+        "lxc_mpRo"                         => [
+            "newSetting" => "mountPointRo",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "lxc_mpQuota"                      => [
+            "newSetting" => "mountPointQuota",
+            'newValue'   => []
+        ],
+        "lxc_mpReplicate"                  => [
+            "newSetting" => "mountPointReplicate",
+            'newValue'   => []
+        ],
+        "lxc_ipv4_mode"                    => [
+            "newSetting" => "ipv4NetworkMode",
+            'newValue'   => []
+        ],
+        "lxc_ipv6_mode"                    => [
+            "newSetting" => "ipv6NetworkMode",
+            'newValue'   => []
+        ],
+        "lxc_bridge"                       => [
+            "newSetting" => "bridge",
+            'newValue'   => []
+        ],
+        "lxc_vlantag_from"                 => [
+            "newSetting" => "tagFrom",
+            'newValue'   => []
+        ],
+        "lxc_vlantag_to"                   => [
+            "newSetting" => "tagTo",
+            'newValue'   => []
+        ],
+        "lxc_private_bridge"               => [
+            "newSetting" => "privateBridge",
+            'newValue'   => []
+        ],
+        "lxc_firewall"                     => [
+            "newSetting" => "networkFirewall",
+            'newValue'   => ["0" => "off", "1" => "on"]
+        ],
+        "kvm_ostype"                       => [
+            "newSetting" => "ostype",
+            'newValue'   => []
+        ],
+        "kvm_acpi"                         => [
+            "newSetting" => "acpi",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_agent"                        => [
+            "newSetting" => "agent",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_args"                         => [
+            "newSetting" => "args",
+            'newValue'   => []
+        ],
+        "kvm_autostart"                    => [
+            "newSetting" => "autostart",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_balloon"                      => [
+            "newSetting" => "balloon",
+            'newValue'   => []
+        ],
+        "kvm_shares"                       => [
+            "newSetting" => "shares",
+            'newValue'   => []
+        ],
+        "kvm_cdrom"                        => [
+            "newSetting" => "cdrom",
+            'newValue'   => []
+        ],
+        "kvm_cpu"                          => [
+            "newSetting" => "cpu",
+            'newValue'   => []
+        ],
+        "kvm_numa"                         => [
+            "newSetting" => "numa",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_pcid"                         => [
+            "newSetting" => "pcid",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_spec-ctrl"                    => [
+            "newSetting" => "spec-ctrl",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_description"                  => [
+            "newSetting" => "description",
+            'newValue'   => []
+        ],
+        "kvm_freeze"                       => [
+            "newSetting" => "freeze",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_hotplug"                      => [
+            "newSetting" => "hotplug",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_cdrom"                        => [
+            "newSetting" => "cdrom",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_keyboard"                     => [
+            "newSetting" => "keyboard",
+            'newValue'   => []
+        ],
+        "kvm_kvm"                          => [
+            "newSetting" => "kvm",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_localtime"                    => [
+            "newSetting" => "localtime",
+            'newValue'   => ['Yes' => "on", 'No' => "off"]
+        ],
+        "kvm_migrate_downtime"             => [
+            "newSetting" => "migrate_downtime",
+            'newValue'   => []
+        ],
+        "kvm_onboot"                       => [
+            "newSetting" => "onboot",
+            'newValue'   => ['Yes' => "on", 'No' => "off"]
+        ],
+        "kvm_pool"                         => [
+            "newSetting" => "pool",
+            'newValue'   => []
+        ],
+        "kvm_reboot"                       => [
+            "newSetting" => "reboot",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_startdate"                    => [
+            "newSetting" => "startdate",
+            'newValue'   => []
+        ],
+        "kvm_startup"                      => [
+            "newSetting" => "startup",
+            'newValue'   => []
+        ],
+        "kvm_storage"                      => [
+            "newSetting" => "diskStorage",
+            'newValue'   => []
+        ],
+        "kvm_tablet"                       => [
+            "newSetting" => "tablet",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_tdf"                          => [
+            "newSetting" => "tdf",
+            'newValue'   => ['Enable' => "on", 'Disable' => "off"]
+        ],
+        "kvm_vga"                          => [
+            "newSetting" => "vga",
+            'newValue'   => []
+        ],
+        "kvm_watchdog"                     => [
+            "newSetting" => "watchdog",
+            'newValue'   => []
+        ],
+        "default_kvm_name"                 => [
+            "newSetting" => "containerPrefix",
+            'newValue'   => []
+        ],
+        "kvm_client_name_for_vps"          => [
+            "newSetting" => "clientNameForContainer",
+            'newValue'   => [0 => 0, 1 => "emptyHostnameOnly", 2 => "overwriteHostname", 3 => "overwriteHostnameWithPrefix"]
+        ],
+        "kvm_os_template"                  => [
+            "newSetting" => "permissionOsTemplates",
+            'newValue'   => []
+        ],
+        "kvm_templatesInAllNodes"          => [
+            "newSetting" => "osTemplatesInAllNodes",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_cloneMode"                    => [
+            "newSetting" => "cloneMode",
+            'newValue'   => []
+        ],
+        "kvm_vmTemplate"                   => [
+            "newSetting" => "osTemplate",
+            'newValue'   => []
+        ],
+        "kvm_sockets"                      => [
+            "newSetting" => "sockets",
+            'newValue'   => []
+        ],
+        "kvm_cores"                        => [
+            "newSetting" => "cores",
+            'newValue'   => []
+        ],
+        "kvm_vcpus"                        => [
+            "newSetting" => "vcpus",
+            'newValue'   => []
+        ],
+        "kvm_cpulimit"                     => [
+            "newSetting" => "cpulimit",
+            'newValue'   => []
+        ],
+        "kvm_cpuunits"                     => [
+            "newSetting" => "cpuunits",
+            'newValue'   => []
+        ],
+        "kvm_memory"                       => [
+            "newSetting" => "memory",
+            'newValue'   => []
+        ],
+        "kvm_disk_space"                   => [
+            "newSetting" => "diskSize",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDisksSpace"         => [
+            "newSetting" => "additionalDiskSize",
+            'newValue'   => []
+        ],
+        "kvm_lan_rate"                     => [
+            "newSetting" => "rate",
+            'newValue'   => []
+        ],
+        "kvm_minimum_rate"                 => [
+            "newSetting" => "minimumRate",
+            'newValue'   => []
+        ],
+        "kvm_ip_addresses"                 => [
+            "newSetting" => "ipv4",
+            'newValue'   => []
+        ],
+        "kvm_ipv6_addresses"                => [
+            "newSetting" => "ipv6",
+            'newValue'   => []
+        ],
+        "kvm_backup_limit_gb"              => [
+            "newSetting" => "backupMaxSize",
+            'newValue'   => []
+        ],
+        "kvm_backup_limit_maxfile"         => [
+            "newSetting" => "backupMaxFiles",
+            'newValue'   => []
+        ],
+        "kvm_bandwidth_limit"              => [
+            "newSetting" => "bandwidth",
+            'newValue'   => []
+        ],
+        "kvm_cdrom_isoimage"               => [
+            "newSetting" => "isoImage",
+            'newValue'   => []
+        ],
+        "kvm_disk_storage"                 => [
+            "newSetting" => "diskStorage",
+            'newValue'   => []
+        ],
+        "kvm_disk_type"                    => [
+            "newSetting" => "diskType",
+            'newValue'   => ['IDE' => "ide", 'SATA' => 'sata', 'VIRTIO' => 'virtio', 'SCSI' => 'scsi']
+        ],
+        "kvm_disk_format"                  => [
+            "newSetting" => "diskFormat",
+            'newValue'   => []
+        ],
+        "kvm_disk_cache"                   => [
+            "newSetting" => "diskCache",
+            'newValue'   => []
+        ],
+        "kvm_scsihw"                       => [
+            "newSetting" => "scsihw",
+            'newValue'   => []
+        ],
+        "kvm_discard"                      => [
+            "newSetting" => "discard",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_replicate"                    => [
+            "newSetting" => "replicate",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_ioThread"                     => [
+            "newSetting" => "ioThread",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDisksStorage"       => [
+            "newSetting" => "additionalDiskStorage",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDisksType"          => [
+            "newSetting" => "additionalDiskType",
+            'newValue'   => ['IDE' => "ide", 'SATA' => 'sata', 'VIRTIO' => 'virtio', 'SCSI' => 'scsi']
+        ],
+        "kvm_AdditionalDisksFrmats"        => [
+            "newSetting" => "additionalDiskFormat",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskCache"          => [
+            "newSetting" => "additionalDiskCache",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskReplicate"      => [
+            "newSetting" => "additionalDiskReplicate",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskDiscard"        => [
+            "newSetting" => "additionalDiskDiscard",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskIoThread"       => [
+            "newSetting" => "additionalDiskIoThread",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_AdditionalDiskAllowBackups"   => [
+            "newSetting" => "permissionAdditionalDiskBackup",
+            'newValue'   => ['1' => "on", '0' => "off"]
+        ],
+        "kvm_DiskReadLimit"                => [
+            "newSetting" => "mbps_rd",
+            'newValue'   => []
+        ],
+        "kvm_DiskWriteLimit"               => [
+            "newSetting" => "mbps_wr",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd"                      => [
+            "newSetting" => "iops_rd",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd"                      => [
+            "newSetting" => "iops_rd",
+            'newValue'   => []
+        ],
+        "kvm_iops_rd_max"                  => [
+            "newSetting" => "iops_rd_max",
+            'newValue'   => []
+        ],
+        "kvm_iops_wr"                      => [
+            "newSetting" => "iops_wr",
+            'newValue'   => []
+        ],
+        "kvm_iops_wr_max"                  => [
+            "newSetting" => "iops_wr_max",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskReadLimit"      => [
+            "newSetting" => "additionalDiskMbps_rd",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskWriteLimit"     => [
+            "newSetting" => "additionalDiskMbps_wr",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_rd"        => [
+            "newSetting" => "additionalDiskIops_rd",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_rd_max"    => [
+            "newSetting" => "additionalDiskIops_rd_max",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_wr"        => [
+            "newSetting" => "additionalDiskIops_wr",
+            'newValue'   => []
+        ],
+        "kvm_AdditionalDiskIops_wr_max"    => [
+            "newSetting" => "additionalDiskIops_wr_max",
+            'newValue'   => []
+        ],
+        "kvm_cdrom_type"                   => [
+            "newSetting" => "cdromType",
+            'newValue'   => []
+        ],
+        "kvm_isoimages"                    => [
+            "newSetting" => "permissionIsoImages",
+            'newValue'   => []
+        ],
+        "kvm_lan_bridge"                   => [
+            "newSetting" => "bridge",
+            'newValue'   => []
+        ],
+        "kvm_lan_network_model"            => [
+            "newSetting" => "networkModel",
+            'newValue'   => []
+        ],
+        "kvm_disable_additional_nic"       => [
+            "newSetting" => "oneNetworkDevice",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "kvm_vlantag_from"                 => [
+            "newSetting" => "tagFrom",
+            'newValue'   => []
+        ],
+        "kvm_vlantag_to"                   => [
+            "newSetting" => "tagTo",
+            'newValue'   => []
+        ],
+        "kvm_private_bridge"               => [
+            "newSetting" => "privateBridge",
+            'newValue'   => []
+        ],
+        "kvm_private_lan_network_model"    => [
+            "newSetting" => "networkPrivateModel",
+            'newValue'   => []
+        ],
+        "kvm_network_firewall"             => [
+            "newSetting" => "networkFirewall",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_queues"                       => [
+            "newSetting" => "queues",
+            'newValue'   => []
+        ],
+        "kvm_boot_1"                       => [
+            "newSetting" => "bootDevice1",
+            'newValue'   => ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "kvm_boot_2"                       => [
+            "newSetting" => "bootDevice2",
+            'newValue'   =>  ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "kvm_boot_3"                       => [
+            "newSetting" => "bootDevice3",
+            'newValue'   =>  ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "kvm_bootdisk"                     => [
+            "newSetting" => "bootdisk",
+            'newValue'   =>  ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "backup_storage"                   => [
+            "newSetting" => "backupStorage",
+            'newValue'   => ["Hard Disk" => "c", "Network" => "n", "CD-ROM" => "d"]
+        ],
+        "backup_routing"                   => [
+            "newSetting" => "backupRouting",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_delete_older_than"         => [
+            "newSetting" => "backupStoreDays",
+            'newValue'   => []
+        ],
+        "firewall_interfaces"              => [
+            "newSetting" => "firewallInterfaces",
+            'newValue'   => []
+        ],
+        "firewall_rules_limit"             => [
+            "newSetting" => "firewallMaxRules",
+            'newValue'   => []
+        ],
+        "kvm_ipset_ip_filter"              => [
+            "newSetting" => "ipsetIpFilter",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "cluster_state"                    => [
+            "newSetting" => "clusterState",
+            'newValue'   => []
+        ],
+        "cluster_group"                    => [
+            "newSetting" => "clusterGroup",
+            'newValue'   => []
+        ],
+        "cluster_max_restart"              => [
+            "newSetting" => "clusterMaxRestart",
+            'newValue'   => []
+        ],
+        "cluster_max_relocate"             => [
+            "newSetting" => "clusterMaxRelocate",
+            'newValue'   => []
+        ],
+        "serviceCreationFailed"            => [
+            "newSetting" => "serviceCreationFailedTemplateId",
+            'newValue'   => []
+        ],
+        "toDoList"                         => [
+            "newSetting" => "toDoList",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "upgradeNotification"              => [
+            "newSetting" => "upgradeNotificationTemplateId",
+            'newValue'   => []
+        ],
+        "kvm_cloudInit"                    => [
+            "newSetting" => "cloudInit",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_cloudInitServiceUserName"     => [
+            "newSetting" => "cloudInitServiceUsername",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_cloudInitServicePassword"     => [
+            "newSetting" => "cloudInitServicePassword",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "'kvm_cloudInitServiceNameservers" => [
+            "newSetting" => "cloudInitServiceNameservers",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_searchdomain"                 => [
+            "newSetting" => "searchdomain",
+            'newValue'   => []
+        ],
+        "kvm_ciuser"                       => [
+            "newSetting" => "ciuser",
+            'newValue'   => []
+        ],
+        "loadBalancer"                     => [
+            "newSetting" => "loadBalancer",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerOnUpgrade"            => [
+            "newSetting" => "loadBalancerOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerShutdownOnUpgrade"    => [
+            "newSetting" => "loadBalancerShutdownOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "loadBalancerStopOnUpgrade"        => [
+            "newSetting" => "loadBalancerStopOnUpgrade",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_boot"                         => [
+            "newSetting" => "permissionStart",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_reboot"                       => [
+            "newSetting" => "permissionReboot",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_stop"                         => [
+            "newSetting" => "permissionStop",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_shutdown"                     => [
+            "newSetting" => "permissionShutdown",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_novnc_console"                => [
+            "newSetting" => "permissionNovnc",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_spice_console"                => [
+            "newSetting" => "permissionSpice",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "xtermjsConsole"                   => [
+            "newSetting" => "permissionXtermjs",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_reinstallation"               => [
+            "newSetting" => "permissionReinstall",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "kvm_per_kvm_templates"            => [
+            "newSetting" => "permissionOsTemplate",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_per_iso_images"               => [
+            "newSetting" => "permissionIsoImage",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "kvm_snapshots_limit"              => [
+            "newSetting" => "snapshotMaxFiles",
+            'newValue'   => []
+        ],
+        "per_mrtg_graphics"                => [
+            "newSetting" => "permissionGraph",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_list"                      => [
+            "newSetting" => "permissionBackup",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "backup_jobs"                      => [
+            "newSetting" => "permissionBackupJob",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_task_history"                 => [
+            "newSetting" => "permissionTaskHistory",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_network"                      => [
+            "newSetting" => "permissionNetwork",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_snapshots"                    => [
+            "newSetting" => "permissionSnapshot",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_firewall"                     => [
+            "newSetting" => "permissionFirewall",
+            'newValue'   => ['No' => "off", 'Yes' => "on"]
+        ],
+        "per_firewallOptions"              => [
+            "newSetting" => "permissionFirewall",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "per_DisksManagement"              => [
+            "newSetting" => "permissionDisk",
+            'newValue'   => ['0' => "off", '1' => "on"]
+        ],
+        "memory_unit"  => [
+            "newSetting" => "memoryUnit",
+            'newValue'   => []
+        ],
+        "swap_unit"  => [
+            "newSetting" => "swapUnit",
+            'newValue'   => []
+        ],
+        "disk_unit"  => [
+            "newSetting" => "diskUnit",
+            'newValue'   => []
+        ],
+        "adisk_unit"  => [
+            "newSetting" => "additionalDiskUnit",
+            'newValue'   => []
+        ],
+
+    ];
+
+    /**
+     * @param $key
+     * @param $value
+     * @param $productId
+     * @return ProductConfiguration|null
+     */
+    public function convert($key, $value, $productId)
+    {
+        if (!self::data[$key] && !self::data[$key]['newSetting'])
+        {
+            return null;
+        }
+        $setting          = new ProductConfiguration();
+        $setting->setting = self::data[$key]['newSetting'];
+        if (preg_match("/\[\"/", $value))
+        {
+            $value = \json_decode($value, true);
+        }
+        $isset = !empty(self::data[$key]['newValue'][$value]);
+        if ($isset && !is_array($value))
+        {
+            $setting->value = self::data[$key]['newValue'][$value];
+        }else if(is_array($value) &&  $isset){
+            $newValue=[];
+            foreach($value as &$v){
+                $oldValue = $v;
+                if(!empty(self::data[$key]['newValue'][$oldValue])){
+                    $newValue[]=self::data[$key]['newValue'][$oldValue];
+                }
+            }
+            $setting->value = $newValue;
+
+        }
+        else
+        {
+            $setting->value = $value;
+        }
+        $setting->product_id = $productId;
+        return $setting;
+    }
+
+    public function exist(ProductConfiguration $setting)
+    {
+        return ProductConfiguration::ofProductId($setting->product_id)->ofSetting($setting->setting)->count();
+    }
+
+}

+ 32 - 0
app/Configuration/Requirements/Files.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Configuration\Requirements;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Requirements\ActionTypes;
+
+/**
+ * Description of RemoveFiles
+ *
+ * @author INBSX-37H
+ */
+class Files extends \ModulesGarden\ProxmoxAddon\Core\App\Requirements\Instances\Files
+{
+    protected $fileList = [
+        [
+            self::PATH => self::MODULE_PATH . '/storage',
+            self::TYPE => self::IS_WRITABLE
+        ],
+        [
+            self::PATH => self::MODULE_PATH . '/storage/app',
+            self::TYPE => self::IS_WRITABLE
+        ],
+        [
+            self::PATH => self::MODULE_PATH . '/storage/crons',
+            self::TYPE => self::IS_WRITABLE
+        ],
+        [
+            self::PATH => self::MODULE_PATH . '/storage/logs',
+            self::TYPE => self::IS_WRITABLE
+        ]
+    ];
+}

+ 244 - 0
app/Cron/BackupRemove.php

@@ -0,0 +1,244 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\di;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+
+class BackupRemove extends Command
+{
+    use WhmcsParams;
+    use ProductService;
+    use ApiService;
+
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'remove-backups';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Updating backups';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected $input;
+    protected $output;
+    protected $io;
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $this->input = $input;
+        $this->output = $output;
+        $this->io = $io;
+        $io->title('Update Backups: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        $pc = (new ProductConfiguration())->getTable();
+
+        $productIds = ProductConfiguration::pluck("product_id")
+            ->all();
+        if (empty($productIds))
+        {
+            $io->error("Store The Backup For X Days is not configured");
+            return;
+        }
+        $h        = (new Hosting())->getTable();
+        $s        = (new Server())->getTable();
+        $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$h}.packageid", $productIds)
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps" ])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+
+
+        /**
+         * @var Hosting $hosting
+         */
+        foreach ($hostings->get() as $hosting)
+        {
+            $i++;
+            $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
+            try
+            {
+                $params = \ModuleBuildParams($hosting->id);
+                $method = lcfirst($params['moduletype'])."Process";
+                if(!method_exists($this,  $method )){
+                    continue;
+                }
+                $this->{$method}($params);
+                $output->writeln(sprintf("Hosting: %s has been synchronized", $hosting->id));
+            }
+            catch (\Exception $ex)
+            {
+                $msg = $ex->getMessage();
+                if ($hosting)
+                {
+                    $msg = sprintf("Hosting Id #%s, %s", $hosting->id, $ex->getMessage());
+                }
+                $io->error($msg);
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize hostings: %s Entries Processed.", $i),
+            "Update Backups: Done"
+        ]);
+    }
+
+    private function proxmoxVPSProcess($params){
+        if (!$params['customfields'][Vps\CustomField::VMID])
+        {
+            throw new \Exception("Custom Field \"vmid\" is empty");
+        }
+        if (!$params['customfields'][Vps\CustomField::NODE])
+        {
+            throw new \Exception("Custom Field \"node\" is empty");
+        }
+        $whmcsParams = di('whmcsParams');
+        $whmcsParams->setParams($params);
+        unset($this->vm, $this->api, $this->configuration);
+        $storage        = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->setApi($this->api());
+        $fileRepository->findBackup($this->vm())
+            ->findByStorages([$storage]);
+        if($this->configuration()->getBackupStoreDays()){
+            foreach ($fileRepository->fetch() as $id => $backup)
+            {
+                $matches = [];
+                preg_match('/[0-9]{4}_[0-9]{2}_[0-9]{2}/', $backup->getVolid(), $matches);
+                $dateCreated = str_replace("_", "-", $matches[0]);
+                if (!$dateCreated)
+                {
+                    continue;
+                }
+                $dateCreated = new \DateTime($dateCreated);
+                $now         = new \DateTime();
+                $dDiff       = $dateCreated->diff($now);
+                if ($dDiff->days > $this->configuration()->getBackupStoreDays())
+                {
+                    $backup->delete();
+                    $fileRepository->remove($id);
+                    $this->output->writeln(sprintf("Backup with date %s has been deleted", $params['serviceid'], $backup->getDate()));
+                }
+            }
+        }
+        $sizeMax = $this->getWhmcsConfigOption(Vps\ConfigurableOption::BACKUPS_SIZE,  $this->configuration()->getBackupMaxSize());
+        if($sizeMax && $sizeMax!="-1" && $fileRepository->getSizeInGb() > $sizeMax){
+            $this->output->writeln(sprintf("The maximum size set for a backup has been exceeded of %s GB", $fileRepository->getSizeInGb()- $sizeMax ));
+            foreach ($fileRepository->sortByTime()->fetch() as $id => &$backup)
+            {
+                if($fileRepository->getSizeInGb() <= $sizeMax){
+                    break;
+                }
+                $this->output->writeln(sprintf("Backup with date %s %s has been deleted", $backup->getDate(), $backup->getHour()));
+                $fileRepository->remove($id);
+//                $backup->delete();
+            }
+        }
+        $limitMax = $this->getWhmcsConfigOption(Vps\ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles());
+        if($limitMax && $limitMax!="-1" && $fileRepository->count() > $limitMax){
+            $this->output->writeln(sprintf("The maximum number set for a backup has been exceeded of %s", $fileRepository->count() - $limitMax ));
+            foreach ($fileRepository->sortByTime()->fetch() as $id => &$backup)
+            {
+                if($fileRepository->count() <= $limitMax){
+                    break;
+                }
+                $this->output->writeln(sprintf("Backup with date %s %s has been deleted", $params['serviceid'], $backup->getDate(), $backup->getHour()));
+                $fileRepository->remove($id);
+                $backup->delete();
+            }
+        }
+    }
+
+    private function proxmoxCloudVpsProcess($params){
+        $whmcsParams = di('whmcsParams');
+        $whmcsParams->setParams($params);
+        unset($this->vm, $this->api, $this->configuration);
+        $storage        = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->setApi($this->api());
+        $fileRepository->findBackupByVmModel(VmModel::ofHostingId($params['serviceid'])->get())
+                      ->findByStorages([$storage]);
+        if($this->configuration()->getBackupStoreDays()){
+            foreach ($fileRepository->fetch() as $id => $backup)
+            {
+                $matches = [];
+                preg_match('/[0-9]{4}_[0-9]{2}_[0-9]{2}/', $backup->getVolid(), $matches);
+                $dateCreated = str_replace("_", "-", $matches[0]);
+                if (!$dateCreated)
+                {
+                    continue;
+                }
+                $dateCreated = new \DateTime($dateCreated);
+                $now         = new \DateTime();
+                $dDiff       = $dateCreated->diff($now);
+                if ($dDiff->days > $this->configuration()->getBackupStoreDays())
+                {
+                    $backup->delete();
+                    $fileRepository->remove($id);
+                    $this->output->writeln(sprintf("Backup with date %s has been deleted", $params['serviceid'], $backup->getDate()));
+                }
+            }
+        }
+        $sizeMax = $this->getWhmcsConfigOption(Cloud\ConfigurableOption::BACKUPS_SIZE,  $this->configuration()->getBackupMaxSize());
+        if($sizeMax && $sizeMax!="-1" && $fileRepository->getSizeInGb() > $sizeMax){
+            $this->output->writeln(sprintf("The maximum size set for a backup has been exceeded of %s GB", $fileRepository->getSizeInGb()- $sizeMax ));
+            foreach ($fileRepository->sortByTime()->fetch() as $id => &$backup)
+            {
+                if($fileRepository->getSizeInGb() <= $sizeMax){
+                    break;
+                }
+                $this->output->writeln(sprintf("Backup with date %s %s has been deleted", $params['serviceid'], $backup->getDate(), $backup->getHour()));
+                $fileRepository->remove($id);
+                $backup->delete();
+            }
+        }
+        $limitMax = $this->getWhmcsConfigOption(Cloud\ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles());
+        if($limitMax && $limitMax!="-1" && $fileRepository->count() > $limitMax){
+            $this->output->writeln(sprintf("The maximum number set for a backup has been exceeded of %s", $fileRepository->count() - $limitMax ));
+            foreach ($fileRepository->sortByTime()->fetch() as $id => &$backup)
+            {
+                if($fileRepository->count() <= $limitMax){
+                    break;
+                }
+                $this->output->writeln(sprintf("Backup with date %s %s has been deleted", $backup->getDate(), $backup->getHour()));
+                $fileRepository->remove($id);
+                $backup->delete();
+            }
+        }
+
+
+    }
+
+}

+ 163 - 0
app/Cron/MigrateSync.php

@@ -0,0 +1,163 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 19, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Description of MigrateSync
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class MigrateSync extends Command
+{
+
+    use main\App\Services\BaseService;
+    /** Command name
+     * @var string
+     */
+    protected $name = 'migrateSync';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = '';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    /**
+     * Run your custom code
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int|null|void
+     */
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Synchronize migrate: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        //Get Hostings
+        $h        = (new main\Core\Models\Whmcs\Hosting)->getTable();
+        $s        = (new main\Core\Models\Whmcs\Server)->getTable();
+        $hostings = main\Core\Models\Whmcs\Hosting::select("{$h}.*")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+        foreach ($hostings->get() as $hosting)
+        {
+            /* @var $hosting main\Core\Models\Whmcs\Hosting */
+            $i++;
+            $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
+            try
+            {
+                $params = \ModuleBuildParams($hosting->id);
+                $this->setServerId($hosting->server);
+                if ($this->getServer()->id != $this->getServerId())
+                {
+                    unset($this->server, $this->api);
+                }
+                $this->getApi()->setInstance();
+                $resurceRepository = new proxmox\repository\ClusterResourcesRepository;
+                if ($params['moduletype'] == "proxmoxVPS")
+                {
+                    if (!$params['customfields']['vmid'])
+                    {
+                        throw new \Exception("Custom Field \"vmid\" is empty");
+                    }
+                    if (!$params['customfields']['node'])
+                    {
+                        throw new \Exception("Custom Field \"node\" is empty");
+                    }
+
+                    foreach ($resurceRepository->fetch() as $resource)
+                    {
+                        if ($resource->getVmid() == $params['customfields']['vmid'] && $params['customfields']['node'] != $resource->getNode())
+                        {
+                            $output->writeln(sprintf("Hosting: %s, migration  has been found on VMID %s", $hosting->id, $params['customfields']['vmid']));
+                            $f  = (new main\Core\Models\Whmcs\CustomField)->getTable();
+                            $fv = (new main\Core\Models\Whmcs\CustomFieldValue())->getTable();
+                            $cf = main\Core\Models\Whmcs\CustomFieldValue::rightJoin($f, "{$fv}.fieldid", '=', "{$f}.id")
+                                ->where("{$f}.type", "product")
+                                ->where("{$fv}.relid", $hosting->id)
+                                ->where("{$f}.fieldname", "LIKE", "node%")
+                                ->update(["value" => $resource->getNode()]);
+                            break;
+                        }
+                    }
+                }
+                else
+                {
+                    if ($params['moduletype'] == "ProxmoxCloudVps")
+                    {
+                        $vservers = main\App\Models\VmModel::ofHostingId($hosting->id);
+                        foreach ($vservers->get() as $vserver)
+                        {
+                            foreach ($resurceRepository->fetch() as $resource)
+                            {
+                                if ($resource->getVmid() == $vserver->vmid && $vserver->node != $resource->getNode())
+                                {
+                                    $output->writeln(sprintf("Hosting: %s, migration  has been found on VMID %s", $hosting->id, $vserver->vmid));
+                                    $vserver->node = $resource->getNode();
+                                    $vserver->save();
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                $output->writeln(sprintf("Hosting: %s has been synchronized", $hosting->id));
+            }
+            catch (\Exception $ex)
+            {
+                if ($hosting)
+                {
+                    $io->error("Hosting Id #{$hosting->id}, " . $ex->getMessage());
+                }
+                else
+                {
+                    $io->error($ex->getMessage());
+                }
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize migrate: %s Entries Processed.", $i),
+            "Synchronize migrate: Done"
+        ]);
+    }
+}

+ 61 - 0
app/Cron/Queue.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\AbstractCommand;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Class Queue
+ * @package ModulesGarden\ProxmoxAddon\App\Cron
+ */
+class Queue extends AbstractCommand
+{
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'queue';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Run module queue';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = 'Just run that command to start all queued tasks!';
+
+    /**
+     * Configure command
+     */
+    protected function setup()
+    {
+
+    }
+
+    /**
+     * Run your custom code
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int|null|void
+     */
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $queue = new \ModulesGarden\ProxmoxAddon\Core\Queue\Queue();
+        $queue->setCallBefore(function (Job $job) use ($input,$io)
+        {
+            $io->writeln("Running {$job->job}");
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        });
+        $queue->process();
+    }
+}

+ 48 - 0
app/Cron/QueueLoop.php

@@ -0,0 +1,48 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\CommandLoop;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class QueueLoop extends  CommandLoop
+{
+
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'queue-loop';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Run module queue loop';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = 'Just run that command to start all queued tasks!';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $queue = new \ModulesGarden\ProxmoxAddon\Core\Queue\Queue();
+        $queue->setCallBefore(function (Job $job) use ($input,$io)
+        {
+            $io->writeln("Running {$job->job}");
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        });
+        $queue->process();
+    }
+
+
+}

+ 207 - 0
app/Cron/RecoveryList.php

@@ -0,0 +1,207 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 19, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Description of RecoveryList
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RecoveryList extends Command
+{
+
+    use main\App\Services\BaseService;
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'recoveryList';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = '';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Synchronize recovery list: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        $activeHostings = [];
+        $activeVservers = [];
+        //Get Hostings
+        $h        = (new main\Core\Models\Whmcs\Hosting)->getTable();
+        $s        = (new main\Core\Models\Whmcs\Server)->getTable();
+        $hostings = main\Core\Models\Whmcs\Hosting::select("{$h}.*")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+        foreach ($hostings->get() as $hosting)
+        {
+            /* @var $hosting main\Core\Models\Whmcs\Hosting */
+            $i++;
+            try
+            {
+                $activeHostings[] = (int)$hosting->id;
+                $params           = \ModuleBuildParams($hosting->id);
+                $this->setServerId($hosting->server);
+                if ($this->getServer()->id != $this->getServerId())
+                {
+                    unset($this->server, $this->api);
+                }
+                $this->getApi()->setInstance();
+                if ($params['moduletype'] == "proxmoxVPS")
+                {
+                    if (!$params['customfields']['vmid'])
+                    {
+                        throw new \Exception("Custom Field \"vmid\" is empty");
+                    }
+                    if (!$params['customfields']['node'])
+                    {
+                        throw new \Exception("Custom Field \"node\" is empty");
+                    }
+                    $virtualization = \json_decode(DB::table('ProxmoxAddon_ProductConfiguration')
+                        ->where('product_id', $hosting->packageid)
+                        ->where('setting', 'virtualization')
+                        ->value('value'));
+                    $vm             = proxmox\Factory::vmVps($virtualization, $params);
+                    if (main\App\Models\RecoveryVm::where('service_id', $hosting->id)->count())
+                    {
+                        $recovery = main\App\Models\RecoveryVm::where('service_id', $hosting->id)->first();
+                    }
+                    else
+                    {
+                        $recovery = new main\App\Models\RecoveryVm();
+                    }
+                    $recovery->client_id      = $hosting->userid;
+                    $recovery->service_id     = $hosting->id;
+                    $recovery->server_id      = $this->getServer()->id;
+                    $recovery->vserver_id     = 0;
+                    $recovery->last_update    = date('Y-m-d H:i:s', strtotime('now'));
+                    $recovery->status         = json_encode($vm->status());
+                    $recovery->config         = json_encode($vm->config());
+                    $recovery->virtualization = $vm->getVirtualization();
+                    $recovery->vmid           = $vm->getVmid();
+                    $recovery->node           = $vm->getNode();
+                    try
+                    {
+                        $recovery->dns = json_encode($vm->node()->getDns());
+                    }
+                    catch (\Exception $ex)
+                    {
+                        $recovery->dns = "Error: " . $ex->getMessage();
+                    }
+                    $recovery->save();
+                }
+                else
+                {
+                    if ($params['moduletype'] == "ProxmoxCloudVps")
+                    {
+                        $vservers = main\App\Models\VmModel::ofHostingId($hosting->id);
+                        foreach ($vservers->get() as $vserver)
+                        {
+                            $activeVservers[] = $vserver->id;
+
+                            $vm               =  (new proxmox\VmFactory())->fromVmModel($vserver);
+                            if (main\App\Models\RecoveryVm::where('service_id', $hosting->id)->where('vserver_id', $vserver->id)->count())
+                            {
+                                $recovery = main\App\Models\RecoveryVm::where('service_id', $hosting->id)->where('vserver_id', $vserver->id)->first();
+                            }
+                            else
+                            {
+                                $recovery = new main\App\Models\RecoveryVm();
+                            }
+                            $recovery->client_id      = $hosting->userid;
+                            $recovery->service_id     = $hosting->id;
+                            $recovery->server_id      = $this->getServer()->id;
+                            $recovery->vserver_id     = $vserver->id;
+                            $recovery->last_update    = date('Y-m-d H:i:s', strtotime('now'));
+                            $recovery->status         = json_encode($vm->status());
+                            $recovery->config         = json_encode($vm->config());
+                            $recovery->virtualization = $vm->getVirtualization();
+                            $recovery->vmid           = $vm->getVmid();
+                            $recovery->node           = $vm->getNode();
+                            try
+                            {
+                                $recovery->dns = json_encode($vm->node()->getDns());
+                            }
+                            catch (\Exception $ex)
+                            {
+                                $recovery->dns = "Error: " . $ex->getMessage();
+                            }
+                            $recovery->save();
+                        }
+                        $output->writeln(sprintf("Hosting: %s has been synchronized", $hosting->id));
+                    }
+                }
+            }
+            catch (\Exception $ex)
+            {
+                if ($hosting)
+                {
+                    $io->error("Hosting Id #{$hosting->id}, " . $ex->getMessage());
+                }
+                else
+                {
+                    $io->error($ex->getMessage());
+                }
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        //Remove entries which does not exist  on $activeHostings
+        if (!empty($activeHostings))
+        {
+            main\App\Models\RecoveryVm::whereNotIn('service_id', $activeHostings)->delete();
+        }
+        //Remove entries which does not exist  on $activeVservers
+        if (!empty($activeVservers))
+        {
+            $activeVservers[] = '0';
+            main\App\Models\RecoveryVm::whereNotIn('vserver_id', $activeVservers)->delete();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize recovery list: %s Entries Processed.", $i),
+            "Synchronize recovery list: Done"
+        ]);
+    }
+}

+ 137 - 0
app/Cron/RrdDataCommand.php

@@ -0,0 +1,137 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (Apr 19, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Models\RrdData;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of Users
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RrdDataCommand extends Command
+{
+    use WhmcsParams;
+    use \ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+    use ApiService;
+
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'rrddata';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Updating RRD Data';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Update RRD Data: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        //Update hosting bandwidth
+        $h        = (new Hosting())->getTable();
+        $s        = (new Server())->getTable();
+        $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$s}.type", [ "ProxmoxCloudVps"])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+        /**
+         * @var Hosting $hosting
+         */
+        foreach ($hostings->get() as $hosting)
+        {
+            $i++;
+            $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
+            try
+            {
+                $params = \ModuleBuildParams($hosting->id);
+                sl("whmcsParams")->setParams($params);
+                unset($this->vm, $this->api, $this->configuration, $this->productId, $this->hostingId);
+                $this->api();
+                RrdData::ofHostingId($hosting->id)->olderThanDayAgo()->delete();
+                if ($params['moduletype'] == "ProxmoxCloudVps"){
+                    $vservers = VmModel::ofHostingId($hosting->id);
+                    foreach ($vservers->get() as $vserver)
+                    {
+                        $vm = (new VmFactory())->fromVmModel($vserver);
+                        $rrData    = $vm->rrdData(["timeframe" => "hour", "cf" => "AVERAGE"]);
+                        foreach ($rrData as $k => $rrd)
+                        {
+                            $rrdModel = new RrdData();
+                            $rrdModel->fill($rrd);
+                            $rrdModel->time =  date("Y-m-d H:i:s", $rrd['time']);
+                            $rrdModel->hosting_id = $hosting->id;
+                            $rrdModel->vm_id = $vserver->id;
+                            $rrdModel->save();
+                        }
+                    }
+                    $output->writeln(sprintf("Hosting: %s has been synchronized", $hosting->id));
+                }
+
+            }
+            catch (\Exception $ex)
+            {
+                $msg = $ex->getMessage();
+                if ($hosting)
+                {
+                    $msg = sprintf("Hosting Id #%s, %s", $hosting->id, $ex->getMessage());
+                }
+                $io->error($msg);
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize hostings: %s Entries Processed.", $i),
+            "Update RRD Data: Done"
+        ]);
+    }
+
+
+
+}

+ 118 - 0
app/Cron/Snapshots.php

@@ -0,0 +1,118 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\SnapshotVmJob;
+use ModulesGarden\ProxmoxAddon\App\Models\Job;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\SnapshotJob;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
+
+class Snapshots extends Command
+{
+    use WhmcsParams;
+    use ProductService;
+    use ApiService;
+
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'snapshots';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Snapshots schedule';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @param SymfonyStyle $io
+     * @throws \Exception
+     * @query daily SELECT *, DATE(`updated_at`) as t FROM `ProxmoxAddon_SnapshotJob` where `period` = 'daily' and TIMESTAMP(`start_time`) < TIMESTAMP(NOW()) and TIMESTAMP(updated_at) < TIMESTAMP(`start_time`) or ( `period` = 'daily' AND `start_time` is NULL AND DATE(`updated_at`) != DATE(NOW()) )
+     * @query hourly SELECT *, DATE(`updated_at`) as t FROM `ProxmoxAddon_SnapshotJob` where `period` = 'hourly' and TIMESTAMP(NOW()) >= TIMESTAMP( DATE_ADD(updated_at, INTERVAL `run_every` HOUR ))
+     */
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Snapshots schedule: Starting');
+        $productIds = ProductConfiguration::ofSetting('permissionSnapshotJob')
+            ->where("value", "not like", '\"\"')
+            ->pluck("product_id")
+            ->all();
+        if (empty($productIds))
+        {
+            $io->error("Scheduled Snapshot Jobs is not configured");
+            return;
+        }
+        $h        = (new Hosting())->getTable();
+        $sj =  (new SnapshotJob())->getTable();
+        $entities = SnapshotJob::select("{$sj}.*")
+            ->leftJoin($h, "{$h}.id", '=', "{$sj}.hosting_id")
+            ->whereRaw("{$sj}.period = 'daily' 
+                        AND TIMESTAMP({$sj}.start_time) < TIMESTAMP(NOW()) 
+                        AND TIMESTAMP({$sj}.updated_at) < TIMESTAMP({$sj}.start_time)
+                        OR ( {$sj}.period = 'daily' 
+                             AND {$sj}.start_time IS NULL 
+                             AND DATE({$sj}.updated_at) != DATE(NOW()) 
+                             )
+                        OR (
+                            {$sj}.period = 'hourly' 
+                            AND  TIMESTAMP(NOW()) >= TIMESTAMP( DATE_ADD({$sj}.updated_at, INTERVAL {$sj}.run_every HOUR ))
+                            )
+                        ")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$h}.packageid", $productIds);
+        $i        = 0;
+        /**
+         * DAYName(NOW())        Friday
+         * @var  SnapshotJob $entity
+         */
+        foreach ($entities->get() as $entity)
+        {
+
+            try
+            {
+                $now = new \DateTime();
+                $today = $now->format("l");
+                if($entity->period == "daily" && ( $entity->days && !in_array($today, $entity->days) ) ){
+                    continue;
+                }
+                $i++;
+                $output->writeln(sprintf("Synchronize snapshot job #%s (Hosting ID %s)",$entity->id, $entity->hosting_id));
+                if(!Job::waiting()->ofJob(SnapshotVmJob::class)->ofHostingId($entity->hosting_id)->count()){
+                    $job=["snapshotJobId"=> $entity->id];
+                    queue(SnapshotVmJob::class, $job, null, "hosting", $entity->hosting_id);
+                    $entity->update(['updated_at' => date("Y-m-d H:i:s")]);
+                    $output->writeln(sprintf("Snapshot job #%s has been synchronized", $entity->id));
+                }
+            }catch (\Exception $ex)
+            {
+                $io->error( $ex->getMessage());
+            }
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize snapshots schedule: %s Entries Processed.", $i),
+            "Snapshots schedule: Done"
+        ]);
+    }
+
+}

+ 113 - 0
app/Cron/Task.php

@@ -0,0 +1,113 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 19, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Description of Task
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class Task extends Command
+{
+
+    use main\App\Services\BaseService;
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'task';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = '';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Synchronize tasks: Starting');
+        $i = 0;
+        foreach (main\App\Models\TaskHistory::where('status', "0")->get() as $taskHistory)
+        {
+            try
+            {
+                /* @var $taskHistory main\App\Models\TaskHistory */
+                $i++;
+                if (!$taskHistory->hosting)
+                {
+                    $taskHistory->delete();
+                    $io->warning(sprintf("Task: %s, Hosting %s does not exist.", $taskHistory->id, $taskHistory->hosting_id));
+                    continue;
+                }
+                $this->setServerId($taskHistory->hosting->server);
+                if ($this->getServer()->id != $this->getServerId())
+                {
+                    unset($this->server, $this->api);
+                }
+                $this->getApi()->setInstance();
+                $node        = new proxmox\models\Node($taskHistory->node);
+                $proxmoxTask = $node->task($taskHistory->upid);
+                if (!$proxmoxTask->getExitstatus())
+                {
+                    $output->writeln(sprintf("Task: %s current in progress.", $taskHistory->id));
+                    continue;
+                }
+                $taskHistory->description = $proxmoxTask->getExitstatus();
+                $taskHistory->start_time  = $proxmoxTask->getStartDate();
+                $taskHistory->status      = $proxmoxTask->getExitstatus() == "OK" ? "1" : "2";
+                $taskHistory->save();
+                $output->writeln(sprintf("Task: %s has been synchronized.", $taskHistory->id));
+            }
+            catch (\Exception $ex)
+            {
+                if ($taskHistory)
+                {
+                    $io->error("Task Id #{$taskHistory->id}, " . $ex->getMessage());
+                }
+                else
+                {
+                    $io->error($ex->getMessage());
+                }
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize tasks: %s Entries Processed.", $i),
+            "Synchronize tasks: Done"
+        ]);
+    }
+}

+ 363 - 0
app/Cron/UsageUpdate.php

@@ -0,0 +1,363 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (Apr 19, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate\MaximumVmRateJob;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate\MinimumVmRateJob;
+use ModulesGarden\ProxmoxAddon\App\Models\ModuleSetting;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\Core\Api\Whmcs;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Admins;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+
+/**
+ * Description of Users
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class UsageUpdate extends Command
+{
+    use WhmcsParams;
+    use ProductService;
+    use ApiService;
+
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'update-server-usage';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = 'Updating Disk & Bandwidth Usage Stat';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Update Server Usage: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        //Update hosting date 0000-00-00 00:00:00 to now
+        $this->updateEmptyLastUpdate();
+        //init bandwidth reset date to run daily cron job
+        $this->updateEmptyBandwidthResetDate();
+        //Run daily
+        if($this->isDailyJob()){
+            //Reset BW
+            $this->resetBandwidth();
+            $this->unsuspendOverageUsage();
+            $this->endDailyJob();
+        }
+        //Update hosting bandwidth
+        $h        = (new Hosting())->getTable();
+        $s        = (new Server())->getTable();
+        $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+        /**
+         * @var Hosting $hosting
+         */
+        foreach ($hostings->get() as $hosting)
+        {
+            $i++;
+            $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
+            try
+            {
+                $params = \ModuleBuildParams($hosting->id);
+                sl("whmcsParams")->setParams($params);
+                unset($this->vm, $this->api, $this->configuration, $this->productId, $this->hostingId);
+                $this->api();
+                if ($params['moduletype'] == "proxmoxVPS"){
+                    if (!$params['customfields'][Vps\CustomField::VMID])
+                    {
+                        throw new \Exception("Custom Field \"vmid\" is empty");
+                    }
+                    if (!$params['customfields'][Vps\CustomField::NODE])
+                    {
+                        throw new \Exception("Custom Field \"node\" is empty");
+                    }
+                    $status    = $this->vm()->status();
+                    $rrData    = $this->vm()->rrdData(["timeframe" => "hour", "cf" => "AVERAGE"]);
+                    $bandwidth = 0;
+                    foreach ($rrData as $k => $rrd)
+                    {
+                        if ($rrd['time'] < $hosting->getLastUpdate()->getTimestamp())
+                        {
+                            continue;
+                        }
+                        $total = ((float)$rrd['netin'] + (float)$rrd['netout']);
+                        if ($total > 1000000000)
+                        {
+                            $total = 0;
+                        }
+                        $bandwidth    += ($total * 57.3);
+                        $bandwithTime = date("Y-m-d H:i:s", $rrd['time']);
+                    }
+                    if (!$bandwithTime)
+                    {
+                        continue;
+                    }
+                    $diskUsage = (int)$status['disk'];
+                    Utility::unitFormat($bandwidth, "bytes", "mb");
+                    Utility::unitFormat($diskUsage, "bytes", "mb");
+                    $hosting->bwusage += $bandwidth;
+                    $hosting->update(
+                        [
+                            "diskusage"  => $diskUsage,
+                            "bwusage"    => $hosting->bwusage,
+                            "lastupdate" => $bandwithTime
+                        ]
+                    );
+                    $rate = null;
+                    if ($this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate())  &&  $this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) !="-1")
+                    {
+                        $rate = $this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+                    }
+                    //Minimum VM Network Rate
+                    if ($hosting->isBandwidthOverageUsage() && $this->configuration()->getMinimumRate() &&
+                        $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0] &&
+                        $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $this->configuration()->getMinimumRate())
+                    {
+                        queue(MinimumVmRateJob::class, [], null, "hosting", $hosting->id);
+                        //Maximum VM Network Rate
+                    } else if (!$hosting->isBandwidthOverageUsage() &&
+                        $this->configuration()->getMinimumRate() &&
+                        $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0] &&
+                        $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $rate) {
+                        queue(MaximumVmRateJob::class, [], null, "hosting", $hosting->id);
+                    }
+                    //Suspend On Bandwidth Overage
+                    if ($this->configuration()->isSuspendOnBandwidthOverage() && $hosting->domainstatus != "Suspended" && $hosting->isBandwidthOverageUsage())
+                    {
+                        $localApi = new Whmcs(new Admins());
+                        $localApi->call("modulesuspend", ["accountid" => $hosting->id, "suspendreason" => "Overage Usage"]);
+                        $output->writeln(sprintf("Hosting: %s has been suspended, reanson: Overage Usage", $hosting->id));
+                    }
+                    $output->writeln(sprintf("Hosting: %s has been synchronized, bandwith usage: %s MB", $hosting->id, $bandwidth));
+                }
+                if ($params['moduletype'] == "ProxmoxCloudVps"){
+                    $bandwidth = 0;
+                    $diskUsage = 0;
+                    $rate = null;
+                    if ($this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate())  &&  $this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) !="-1")
+                    {
+                        $rate = $this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+                    }
+                    $vservers = VmModel::ofHostingId($hosting->id);
+                    foreach ($vservers->get() as $vserver)
+                    {
+                        $vm = (new VmFactory())->fromVmModel($vserver);
+                        $status    = $vm->status();
+                        $diskUsage += (int)$status['disk'];
+                        $rrData    = $vm->rrdData(["timeframe" => "hour", "cf" => "AVERAGE"]);
+                        foreach ($rrData as $k => $rrd)
+                        {
+                            if ($rrd['time'] < $hosting->getLastUpdate()->getTimestamp())
+                            {
+                                continue;
+                            }
+                            $total = ((float)$rrd['netin'] + (float)$rrd['netout']);
+                            if ($total > 1000000000)
+                            {
+                                $total = 0;
+                            }
+                            $bandwidth    += ($total * 57.3);
+                            $bandwithTime = date("Y-m-d H:i:s", $rrd['time']);
+                        }
+                        if (!$bandwithTime)
+                        {
+                            continue;
+                        }
+
+                        //Minimum VM Network Rate
+                        if ($hosting->isBandwidthOverageUsage() && $this->configuration()->getMinimumRate() &&
+                            $vm->getNetworkDevices($this->configuration()->getBridge())[0] &&
+                            $vm->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $this->configuration()->getMinimumRate())
+                        {
+                            queue(\ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate\MinimumVmRateJob::class, [], null, "hosting", $hosting->id, $vserver->id);
+                            //Maximum VM Network Rate
+                        } else if (!$hosting->isBandwidthOverageUsage() &&
+                            $this->configuration()->getMinimumRate() &&
+                            $vm->getNetworkDevices($this->configuration()->getBridge())[0] &&
+                            $vm->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $rate) {
+                            queue(\ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate\MaximumVmRateJob::class, [], null, "hosting", $hosting->id, $vserver->id);
+                        }
+                    }
+                    Utility::unitFormat($bandwidth, "bytes", "mb");
+                    Utility::unitFormat($diskUsage, "bytes", "mb");
+                    $hosting->bwusage += $bandwidth;
+                    $hosting->update(
+                        [
+                            "diskusage"  => $diskUsage,
+                            "bwusage"    => $hosting->bwusage,
+                            "lastupdate" => $bandwithTime
+                        ]
+                    );
+                    //Suspend On Bandwidth Overage
+                    if ($this->configuration()->isSuspendOnBandwidthOverage() && $hosting->domainstatus != "Suspended" && $hosting->isBandwidthOverageUsage())
+                    {
+                        $localApi = new Whmcs(new Admins());
+                        $localApi->call("modulesuspend", ["accountid" => $hosting->id, "suspendreason" => "Overage Usage"]);
+                        $output->writeln(sprintf("Hosting: %s has been suspended, reanson: Overage Usage", $hosting->id));
+                    }
+                    $output->writeln(sprintf("Hosting: %s has been synchronized, bandwith usage: %s MB", $hosting->id, $bandwidth));
+                }
+
+            }
+            catch (\Exception $ex)
+            {
+                $msg = $ex->getMessage();
+                if ($hosting)
+                {
+                    $msg = sprintf("Hosting Id #%s, %s", $hosting->id, $ex->getMessage());
+                }
+                $io->error($msg);
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize hostings: %s Entries Processed.", $i),
+            "Update Server Usage: Done"
+        ]);
+    }
+
+    private function updateEmptyLastUpdate()
+    {
+        $h = 'tblhosting';
+        Hosting::ofProxmoxVpsAndProxmoxCloud()
+            ->where("{$h}.domainstatus", 'Active')
+            ->where("{$h}.lastupdate", "0000-00-00 00:00:00")
+            ->update(["{$h}.lastupdate" => Hosting::raw('NOW()')]);
+        return $this;
+    }
+
+    private function updateEmptyBandwidthResetDate()
+    {
+        if (ModuleSetting::bandwidthResetDate()->count())
+        {
+            return $this;
+        }
+        $setting          = new ModuleSetting();
+        $setting->setting = "bandwidthResetDate";
+        $setting->value   = date("Y-m-d H:i:s");
+        $setting->save();
+        return $this;
+    }
+
+    private function isDailyJob(){
+        return ModuleSetting::bandwidthResetDate()->whereRaw("DATE(`value`) <= DATE_SUB(NOW(),INTERVAL 24 HOUR) ")->count() > 0;
+    }
+
+    private function endDailyJob(){
+        $setting = ModuleSetting::bandwidthResetDate()->firstOrFail();
+        // save last rest bandwidth date
+        $setting->value = date("Y-m-d H:i:s");
+        $setting->save();
+    }
+
+    private function resetBandwidth()
+    {
+        $productIds = ProductConfiguration::ofSetting('resetUsageFirstDayOfMonth')
+            ->where("value", "like", '\"on\"')
+            ->pluck("product_id")
+            ->all();
+        $h       = 'tblhosting';
+        Hosting::ofProxmoxVpsAndProxmoxCloud()
+            ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
+            ->whereNotIn("{$h}.packageid", $productIds)
+            ->whereRaw(" DAY({$h}.regdate) = DAY(NOW()) ")
+            ->update(["{$h}.bwusage" => 0]);
+        //Reset Usage First Day Of Month
+        if(date("d")=="01"){
+            Hosting::ofProxmoxVpsAndProxmoxCloud()
+                ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
+                ->whereIn("{$h}.packageid", $productIds)
+                ->update(["{$h}.bwusage" => 0]);
+        }
+
+    }
+
+    private  function unsuspendOverageUsage(){
+        //get products where option suspendOnBandwidthOverage is on
+        $productIds = ProductConfiguration::ofSetting('suspendOnBandwidthOverage')
+            ->where("value", "like", '\"on\"')
+            ->pluck("product_id")
+            ->all();
+        if(empty(   $productIds)){
+            return;
+        }
+        //get hosting where domainstatus is Suspended and registration day is now and suspendreason is Overage Usage
+        $h        = (new Hosting())->getTable();
+        $s        = (new Server())->getTable();
+        $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Suspended")
+            ->where("{$h}.suspendreason", "Overage Usage")
+            ->whereIn("{$h}.packageid", $productIds)
+            ->where("{$s}.type", "proxmoxVPS")
+            ->whereRaw(" DAY({$h}.regdate) = DAY(NOW()) ");
+        $i        = 0;
+        /**
+         * @var Hosting $hosting
+         */
+        foreach ($hostings->get() as $hosting)
+        {
+            try{
+                $localApi = new Whmcs(new Admins());
+                $localApi->call("ModuleUnsuspend", ["accountid" => $hosting->id]);
+            } catch (\Exception $ex)
+            {
+                echo sprintf("Error: %s, Hosting ID: #%s",$ex->getMessage(), $hosting->id);
+            }
+        }
+
+    }
+}

+ 125 - 0
app/Cron/Users.php

@@ -0,0 +1,125 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 19, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Cron;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+/**
+ * Description of Users
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class Users extends Command
+{
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = 'users';
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = '';
+
+    /**
+     * Command help text
+     * @var string
+     */
+    protected $help = '';
+
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        $io->title('Synchronize users: Starting');
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        //Get Hostings
+        $h        = (new main\Core\Models\Whmcs\Hosting)->getTable();
+        $s        = (new main\Core\Models\Whmcs\Server)->getTable();
+        $hostings = main\Core\Models\Whmcs\Hosting::select("{$h}.*")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$h}.domainstatus", "Active")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"])
+            ->orderBy("{$h}.server");
+        $i        = 0;
+        foreach ($hostings->get() as $hosting)
+        {
+            /* @var $hosting main\Core\Models\Whmcs\Hosting */
+            $i++;
+            $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
+            try
+            {
+                $params = \ModuleBuildParams($hosting->id);
+                if ($params['moduletype'] == "proxmoxVPS")
+                {
+                    if (!$params['customfields'][Vps\CustomField::VMID])
+                    {
+                        throw new \Exception("Custom Field \"vmid\" is empty");
+                    }
+                    if (!$params['customfields'][Vps\CustomField::NODE])
+                    {
+                        throw new \Exception("Custom Field \"node\" is empty");
+                    }
+                    \ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl("whmcsParams")->setParams($params);
+                    $controler = new \ModulesGarden\Servers\ProxmoxVps\App\Http\Actions\ChangeUserRole();
+                    $controler->execute($params);
+                }
+                else
+                {
+                    if ($params['moduletype'] == "ProxmoxCloudVps")
+                    {
+                        \ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl("whmcsParams")->setParams($params);
+                        $controler = new \ModulesGarden\Servers\ProxmoxCloudVps\App\Http\Actions\ChangeUserRole();
+                        $controler->execute($params);
+                    }
+                }
+                $output->writeln(sprintf("Hosting: %s has been synchronized", $hosting->id));
+            }
+            catch (\Exception $ex)
+            {
+                if ($hosting)
+                {
+                    $io->error("Hosting Id #{$hosting->id}, " . $ex->getMessage());
+                }
+                else
+                {
+                    $io->error($ex->getMessage());
+                }
+            }
+            (new Hypervisor($this->getName(), $input->getOptions()))
+                ->ping();
+        }
+        $output->writeln("");
+        $io->success([
+            sprintf("Synchronize hostings: %s Entries Processed.", $i),
+            "Synchronize hostings: Done"
+        ]);
+    }
+}

+ 1 - 0
app/Database/M2M6P0/data.sql

@@ -0,0 +1 @@
+UPDATE `mg_proxmox_addon_ip` SET mac_address=NULL WHERE mac_address='auto';

+ 92 - 0
app/Database/M2M6P0/schema.sql

@@ -0,0 +1,92 @@
+--
+-- `#prefix#ModuleSettings`
+-- 
+CREATE TABLE IF NOT EXISTS `#prefix#ModuleSettings`
+  (
+     `setting` VARCHAR (64) NOT NULL UNIQUE,
+     `value`   TEXT NOT NULL,
+     PRIMARY KEY ( `setting` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+-- 
+-- `#prefix#NodeSetting`
+-- 
+CREATE TABLE IF NOT EXISTS `#prefix#NodeSetting`
+  (
+     `id`        INT (11) NOT NULL auto_increment,
+     `server_id` INT (10) UNSIGNED NOT NULL,
+     `node`      VARCHAR (64) NOT NULL,
+     `setting`   VARCHAR (64) NOT NULL,
+     `value`     TEXT NOT NULL,
+     PRIMARY KEY ( `id` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+--
+-- Update MAC Addresses
+--
+UPDATE `mg_proxmox_addon_ip` SET  `mac_address`= NULL WHERE `mac_address`='auto';
+--
+-- `#prefix#Logger`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Logger` (
+    `id`            int(10) unsigned NOT NULL AUTO_INCREMENT,
+    `id_ref`        int(10) unsigned NOT NULL,
+    `id_type`       VARCHAR(255) NOT NULL,
+    `type`          VARCHAR(255) NOT NULL,
+    `level`         VARCHAR(255) NOT NULL,
+    `date`          DATETIME DEFAULT null,
+    `request`       TEXT NOT NULL,
+    `response`      TEXT NOT NULL,
+    `before_vars`   TEXT NOT NULL,
+    `vars`          TEXT NOT NULL,
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#Commands`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Commands` (
+    `name`             VARCHAR(64) NOT NULL UNIQUE,
+    `uuid`             VARCHAR(64) NOT NULL UNIQUE,
+    `parent_uuid`      VARCHAR(64) DEFAULT NULL,
+    `status`           enum('stopped', 'running', 'error', 'sleeping') DEFAULT 'stopped',
+    `action`           enum('none', 'stop', 'reboot') DEFAULT 'none',
+    `params`           TEXT NOT NULL,
+    `created_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+    `updated_at`       timestamp,
+    PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#Job`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Job` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `retry_after` datetime NOT NULL,
+    `retry_count` int(10) unsigned NOT NULL,
+    `job` varchar(255) NOT NULL,
+    `data` text,
+    `parent_id` int(10) unsigned DEFAULT NULL,
+    `rel_id` int(10) unsigned DEFAULT NULL,
+    `rel_type` varchar(32) DEFAULT NULL,
+    `custom_id` int(10) unsigned DEFAULT NULL,
+    `status` varchar(32) NOT NULL,
+    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+    PRIMARY KEY (`id`),
+    KEY(`parent_id`),
+    KEY(`rel_type`, `rel_id`, `custom_id`),
+    KEY(`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+
+--
+-- `#prefix#JobLog`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#JobLog` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `job_id` int(10) unsigned NOT NULL,
+    `type` varchar(32) NOT NULL,
+    `message` varchar(512) NOT NULL,
+    `additional` text,
+    `created_at` datetime NOT NULL,
+    `updated_at` datetime NOT NULL,
+    PRIMARY KEY (`id`),
+    KEY `job_id` (`job_id`),
+    KEY `type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;

+ 1 - 0
app/Database/M2M7P0/data.sql

@@ -0,0 +1 @@
+UPDATE `mg_proxmox_addon_ip` SET mac_address=NULL WHERE mac_address='auto';

+ 107 - 0
app/Database/M2M7P0/schema.sql

@@ -0,0 +1,107 @@
+--
+-- `#prefix#ModuleSettings`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#ModuleSettings`
+  (
+     `setting` VARCHAR (64) NOT NULL UNIQUE,
+     `value`   TEXT NOT NULL,
+     PRIMARY KEY ( `setting` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#ProductConfiguration`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#ProductConfiguration` (
+    `product_id` int(11),
+    `setting`  varchar (255),
+    `value`  text,
+    PRIMARY KEY (`setting`,`product_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#KeyPair`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#KeyPair` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` INT(11) NOT NULL,
+  `vm_id` INT (11) DEFAULT  null,
+  `public` text NOT NULL,
+  `private` text NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`hosting_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VmIpAddress`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VmIpAddress` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11),
+  `server_id` INT (11),
+  `vm_id` INT (11) DEFAULT  null,
+  `ip` varchar(200) NOT NULL,
+  `mac_address` varchar(200) DEFAULT 'auto',
+  `subnet_mask` varchar(200),
+  `gateway` varchar(200),
+  `cidr` INT (6),
+  `trunks` INT (11) DEFAULT NULL,
+  `tag` INT (11) DEFAULT NULL,
+  `net` varchar(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`ip`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#User`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#User` (
+  `id`  int(12)    NOT NULL AUTO_INCREMENT,
+  `user_id`  int(12),
+  `hosting_id`  int(12) NOT NULL,
+  `username` varchar(360) NOT NULL,
+  `password` varchar(360) NOT NULL,
+  `realm` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- ALTER TABLE `#prefix#Job`
+--
+ALTER TABLE `#prefix#Job` ADD `parent_id` INT(10) unsigned  NULL DEFAULT NULL AFTER `data`, ADD `rel_id` INT(10) unsigned NULL DEFAULT NULL AFTER `parent_id`, ADD `rel_type` INT(10) unsigned NULL DEFAULT NULL AFTER `rel_id`, ADD `custom_id` INT(10) unsigned NULL DEFAULT NULL AFTER `rel_type`;
+ALTER TABLE `#prefix#Job` ADD KEY(`parent_id`, `rel_type`, `rel_id`, `custom_id`) ;
+--
+-- `#prefix#Job`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Job` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `retry_after` datetime NOT NULL,
+    `retry_count` int(10) unsigned NOT NULL,
+    `job` varchar(255) NOT NULL,
+    `data` text,
+    `parent_id` int(10) unsigned DEFAULT NULL,
+    `rel_id` int(10) unsigned DEFAULT NULL,
+    `rel_type` varchar(32) DEFAULT NULL,
+    `custom_id` int(10) unsigned DEFAULT NULL,
+    `status` varchar(32) NOT NULL,
+    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+    PRIMARY KEY (`id`),
+    KEY(`parent_id`),
+    KEY(`rel_type`, `rel_id`, `custom_id`),
+    KEY(`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#JobLog`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#JobLog` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `job_id` int(10) unsigned NOT NULL,
+    `type` varchar(32) NOT NULL,
+    `message` varchar(512) NOT NULL,
+    `additional` text,
+    `created_at` datetime NOT NULL,
+    `updated_at` datetime NOT NULL,
+    PRIMARY KEY (`id`),
+    KEY `job_id` (`job_id`),
+    KEY `type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#mg_proxmox_addon_ip`
+--
+UPDATE `mg_proxmox_addon_ip` SET  `mac_address`= NULL WHERE `mac_address`='auto';
+ALTER TABLE `mg_proxmox_addon_ip` CHANGE `mac_address` `mac_address` VARCHAR(200) DEFAULT NULL;

+ 0 - 0
app/Database/M2M7P3/data.sql


+ 8 - 0
app/Database/M2M7P3/schema.sql

@@ -0,0 +1,8 @@
+UPDATE `ProxmoxAddon_Job` SET `status` = 'waiting' WHERE `status` = 'running' AND TIMESTAMP(`created_at`) <= TIMESTAMP(NOW() - INTERVAL 1 HOUR);
+ALTER TABLE `mg_proxmox_addon_ip` DEFAULT COLLATE #collation#;
+ALTER TABLE `mg_proxmox_addon_ip` CHANGE `ip` `ip` VARCHAR(200) CHARACTER SET utf8 COLLATE #collation# NOT NULL;
+ALTER TABLE `mg_proxmox_addon_ip`  CHANGE `type` `type` ENUM('IPv4','IPv6') CHARACTER SET utf8 COLLATE #collation# NULL DEFAULT 'IPv4';
+ALTER TABLE `mg_proxmox_addon_ip`  CHANGE `mac_address` `mac_address` VARCHAR(200) CHARACTER SET utf8 COLLATE #collation# NULL DEFAULT NULL;
+ALTER TABLE `mg_proxmox_addon_ip`  CHANGE `subnet_mask` `subnet_mask` VARCHAR(200) CHARACTER SET utf8 COLLATE #collation# NULL DEFAULT NULL;
+ALTER TABLE `mg_proxmox_addon_ip` CHANGE `visualization` `visualization` ENUM('Auto','KVM','LXC') CHARACTER SET utf8 COLLATE #collation# NULL DEFAULT 'Auto';
+ALTER TABLE `mg_proxmox_addon_ip` CHANGE `node` `node` VARCHAR(200) CHARACTER SET utf8 COLLATE #collation# NULL DEFAULT '0';

+ 0 - 0
app/Database/M2M7P4/data.sql


+ 0 - 0
app/Database/M2M7P4/schema.sql


+ 0 - 0
app/Database/M2M8P0/data.sql


+ 26 - 0
app/Database/M2M8P0/schema.sql

@@ -0,0 +1,26 @@
+--
+-- `#prefix#SnapshootJob`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#SnapshotJob` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11)  NOT NULL,
+  `vm_id` INT (11) DEFAULT NULL,
+  `name` varchar(200) NOT NULL,
+  `description` text  DEFAULT NULL,
+  `vmstate` TINYINT(1) DEFAULT NULL,
+  `period` varchar(100) NOT NULL,
+  `run_every` INT (2) DEFAULT NULL,
+  `days` text DEFAULT NULL,
+  `start_time`  TIME DEFAULT NULL,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- ALTER TABLE
+--
+ALTER TABLE `ProxmoxAddon_Commands` CHANGE `status` `status` ENUM('stopped','running','error','sleeping') CHARACTER SET #charset# COLLATE #collation# NULL DEFAULT 'stopped';
+--
+-- Fix missing ip addresses
+--
+delete i.* from ProxmoxAddon_VmIpAddress i left join tblhosting h ON (h.id = i.hosting_id) where h.domainstatus NOT IN ("Active", "Suspended");

+ 0 - 0
app/Database/M3M0P0/data.sql


+ 74 - 0
app/Database/M3M0P0/schema.sql

@@ -0,0 +1,74 @@
+--
+-- `#prefix#VmModel`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Vm` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `node` varchar(250) NOT NULL,
+  `vmid` int(11) NOT NULL,
+  `virtualization` varchar(128) NOT NULL,
+  `name` varchar(250) NOT NULL ,
+  `password` varchar(250) NOT NULL,
+  `cores` INT (2) DEFAULT '0',
+  `sockets` INT (2) DEFAULT '0',
+  `vcpus` INT (2) DEFAULT '0',
+  `cpulimit` FLOAT(5) DEFAULT '0',
+  `cpuunits` INT (8) NULL,
+  `memory`  INT (10) NOT NULL,
+  `swap` INT (10) DEFAULT '0',
+  `disk`  INT (10) NOT NULL,
+  `disks` INT (12) DEFAULT '0',
+  `netin` INT (12) DEFAULT '0',
+  `netout` INT (12) DEFAULT '0',
+  `template` INT (1) DEFAULT '0',
+    `data`  text,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  PRIMARY KEY (`id`,`hosting_id`,`vmid`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VirtualNetwork`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VirtualNetwork` (
+ `id`  int(11) NOT NULL AUTO_INCREMENT,
+ `hosting_id` int(11) NOT NULL,
+ `name` varchar(200) NOT NULL,
+ `tag` int(11) NOT NULL,
+ `pool` varchar(200) NOT NULL,
+ `cidr` int(11) NOT NULL,
+ `gateway`  varchar(200) NOT NULL,
+ `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+ `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VirtualInterface`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VirtualInterface` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `vn_id` int(11) NOT NULL,
+  `vm_id` int(11) NOT NULL,
+  `ip` varchar(200) NOT NULL,
+  `ip_long` int(11) NOT NULL,
+  `net` varchar(10) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#RrdDataCommand`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#RrdData` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `vm_id` int(11) NOT NULL,
+  `diskread` float NOT NULL DEFAULT 0,
+  `diskwrite` float NOT NULL DEFAULT 0,
+  `cpu` float NOT NULL DEFAULT 0,
+  `maxcpu` float NOT NULL DEFAULT 0,
+  `mem` float NOT NULL DEFAULT 0,
+  `maxmem` float NOT NULL DEFAULT 0,
+  `netin` float NOT NULL DEFAULT 0,
+  `netout` float NOT NULL DEFAULT 0,
+  `time` timestamp NOT NULL,
+ PRIMARY KEY (`id`, `hosting_id`, `vm_id`, `time`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;

+ 0 - 0
app/Database/M3M1P0/data.sql


+ 20 - 0
app/Database/M3M1P0/schema.sql

@@ -0,0 +1,20 @@
+--
+-- `#prefix#CloudInitScript`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#CloudInitScript` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `script` text NOT NULL,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#ServerConfiguration`
+--
+  CREATE TABLE IF NOT EXISTS `#prefix#ServerConfiguration` (
+    `server_id` int(11),
+    `setting`  varchar (255),
+    `value`  text,
+    PRIMARY KEY (`setting`,`server_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;

+ 0 - 0
app/Database/data.sql


+ 286 - 0
app/Database/schema.sql

@@ -0,0 +1,286 @@
+--
+-- `#prefix#ModuleSettings`
+-- 
+CREATE TABLE IF NOT EXISTS `#prefix#ModuleSettings`
+  (
+     `setting` VARCHAR (64) NOT NULL UNIQUE,
+     `value`   TEXT NOT NULL,
+     PRIMARY KEY ( `setting` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+-- 
+-- `mg_proxmox_addon_ip`
+-- 
+CREATE TABLE IF NOT EXISTS `mg_proxmox_addon_ip`
+  (
+     `id`            INT (11) NOT NULL auto_increment,
+     `ip`            VARCHAR (200) NOT NULL,
+     `type`          ENUM ('IPv4', 'IPv6') DEFAULT 'IPv4',
+     `mac_address`   VARCHAR (200) DEFAULT NULL,
+     `subnet_mask`   VARCHAR (200),
+     `gateway`       VARCHAR (200),
+     `cidr`          INT (6),
+     `sid`           INT (11),
+     `visualization` ENUM ('Auto', 'KVM', 'LXC') DEFAULT 'Auto',
+     `last_check`    DATETIME DEFAULT NULL,
+     `private`       TINYINT (1) DEFAULT 0,
+     `hosting_id`    INT (11) DEFAULT 0,
+     `trunks`        INT (11) DEFAULT NULL,
+     `tag`           INT (11) DEFAULT NULL,
+     `node`          VARCHAR (200) DEFAULT '0',
+     PRIMARY KEY ( `id` ),
+     UNIQUE ( `ip` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+-- 
+-- `mg_proxmox_vmranges`
+-- 
+CREATE TABLE IF NOT EXISTS `mg_proxmox_vmranges`
+  (
+     `server_id` INT (11) NOT NULL,
+     `vmid_from` INT (11),
+     `vmid_to`   INT (11),
+     PRIMARY KEY ( `server_id` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+-- 
+-- Table structure for table `mg_proxmox_addon_tasks`
+-- 
+CREATE TABLE IF NOT EXISTS `mg_proxmox_addon_tasks`
+  (
+     `id` INT (11) NOT NULL auto_increment,
+     `hosting_id`  INT (11) NOT NULL,
+     `upid`        VARCHAR (128) NOT NULL,
+     `name`        VARCHAR (128) NOT NULL,
+     `description` TEXT,
+     `node`        VARCHAR (128) NOT NULL,
+     `vmid`        INT (11) NOT NULL,
+     `status`      INT (11) NOT NULL,
+     `start_time`  DATETIME DEFAULT NULL,
+     PRIMARY KEY ( `id` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+--
+-- Table structure for table `mg_proxmox_addon_recovery_vm_list`
+--
+CREATE TABLE IF NOT EXISTS `mg_proxmox_addon_recovery_vm_list` (
+  `id` INT (11) NOT NULL auto_increment,
+  `client_id` int(11) NOT NULL,
+  `service_id` int(11) NOT NULL,
+  `server_id` int(11) NOT NULL,
+  `vserver_id` int(11) NOT NULL,
+  `node` varchar(128) NOT NULL,
+  `vmid` int(11) NOT NULL,
+  `virtualization` varchar(128) NOT NULL,
+  `config` text,
+  `status` text,
+  `dns` text,
+  `last_update` datetime DEFAULT NULL,
+  PRIMARY KEY ( `id` )
+) ENGINE=InnoDB DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+-- 
+-- `#prefix#NodeSetting`
+-- 
+CREATE TABLE IF NOT EXISTS `#prefix#NodeSetting`
+  (
+     `id`        INT (11) NOT NULL auto_increment,
+     `server_id` INT (10) UNSIGNED NOT NULL,
+     `node`      VARCHAR (64) NOT NULL,
+     `setting`   VARCHAR (64) NOT NULL,
+     `value`     TEXT NOT NULL,
+     PRIMARY KEY ( `id` )
+  ) engine=innodb DEFAULT charset=#charset# DEFAULT COLLATE #collation#;
+
+  CREATE TABLE IF NOT EXISTS `#prefix#ProductConfiguration` (
+    `product_id` int(11),
+    `setting`  varchar (255),
+    `value`  text,
+    PRIMARY KEY (`setting`,`product_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VmIpAddress`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VmIpAddress` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11),
+  `server_id` INT (11),
+  `vm_id` INT (11) DEFAULT  null,
+  `ip` varchar(200) NOT NULL,
+  `mac_address` varchar(200) DEFAULT NULL,
+  `subnet_mask` varchar(200),
+  `gateway` varchar(200),
+  `cidr` INT (6),
+  `trunks` INT (11) DEFAULT NULL,
+  `tag` INT (11) DEFAULT NULL,
+  `net` varchar(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`ip`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#User`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#User` (
+  `id`  int(12)    NOT NULL AUTO_INCREMENT,
+  `user_id`  int(12),
+  `hosting_id`  int(12) NOT NULL,
+  `username` varchar(360) NOT NULL,
+  `password` varchar(360) NOT NULL,
+  `realm` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#KeyPair`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#KeyPair` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` INT(11) NOT NULL,
+  `vm_id` INT (11) DEFAULT  null,
+  `public` text NOT NULL,
+  `private` text NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`hosting_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#Job`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Job` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `retry_after` datetime NOT NULL,
+    `retry_count` int(10) unsigned NOT NULL,
+    `job` varchar(255) NOT NULL,
+    `data` text,
+    `parent_id` int(10) unsigned DEFAULT NULL,
+    `rel_id` int(10) unsigned DEFAULT NULL,
+    `rel_type` varchar(32) DEFAULT NULL,
+    `custom_id` int(10) unsigned DEFAULT NULL,
+    `status` varchar(32) NOT NULL,
+    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+    PRIMARY KEY (`id`),
+    KEY(`parent_id`),
+    KEY(`rel_type`, `rel_id`, `custom_id`),
+    KEY(`status`, `created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#JobLog`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#JobLog` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `job_id` int(10) unsigned NOT NULL,
+    `type` varchar(32) NOT NULL,
+    `message` varchar(512) NOT NULL,
+    `additional` text,
+    `created_at` datetime NOT NULL,
+    `updated_at` datetime NOT NULL,
+    PRIMARY KEY (`id`),
+    KEY `job_id` (`job_id`),
+    KEY `type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#SnapshootJob`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#SnapshotJob` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `vm_id` INT (11) DEFAULT NULL,
+  `name` varchar(200) NOT NULL,
+  `description` text  DEFAULT NULL,
+  `vmstate` TINYINT(1) DEFAULT NULL,
+  `period` varchar(100) NOT NULL,
+  `run_every` INT (2) DEFAULT NULL,
+  `days` text DEFAULT NULL,
+  `start_time`  TIME DEFAULT NULL,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VmModel`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Vm` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `node` varchar(250) NOT NULL,
+  `vmid` int(11) NOT NULL,
+  `virtualization` varchar(128) NOT NULL,
+  `name` varchar(250) NOT NULL ,
+  `password` varchar(250) NOT NULL,
+  `cores` INT (2) DEFAULT '0',
+  `sockets` INT (2) DEFAULT '0',
+  `vcpus` INT (2) DEFAULT '0',
+  `cpulimit` FLOAT(5) DEFAULT '0',
+  `cpuunits` INT (8) NULL,
+  `memory`  INT (10) NOT NULL,
+  `swap` INT (10) DEFAULT '0',
+  `disk`  INT (10) NOT NULL,
+  `disks` INT (12) DEFAULT '0',
+  `netin` INT (12) DEFAULT '0',
+  `netout` INT (12) DEFAULT '0',
+  `template` INT (1) DEFAULT '0',
+  `data`  text,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  PRIMARY KEY (`id`,`hosting_id`,`vmid`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VirtualNetwork`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VirtualNetwork` (
+ `id`  int(11) NOT NULL AUTO_INCREMENT,
+ `hosting_id` int(11) NOT NULL,
+ `name` varchar(200) NOT NULL,
+ `tag` int(11) NOT NULL,
+ `pool` varchar(200) NOT NULL,
+ `cidr` int(11) NOT NULL,
+ `gateway`  varchar(200) NOT NULL,
+ `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+ `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#VirtualInterface`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#VirtualInterface` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `vn_id` int(11) NOT NULL,
+  `vm_id` int(11) NOT NULL,
+  `ip` varchar(200) NOT NULL,
+  `ip_long` int(11) NOT NULL,
+  `net` varchar(10) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#RrdDataCommand`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#RrdData` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `hosting_id` int(11) NOT NULL,
+  `vm_id` int(11) NOT NULL,
+  `diskread` float NOT NULL DEFAULT 0,
+  `diskwrite` float NOT NULL DEFAULT 0,
+  `cpu` float NOT NULL DEFAULT 0,
+  `maxcpu` float NOT NULL DEFAULT 0,
+  `mem` float NOT NULL DEFAULT 0,
+  `maxmem` float NOT NULL DEFAULT 0,
+  `netin` float NOT NULL DEFAULT 0,
+  `netout` float NOT NULL DEFAULT 0,
+  `time` timestamp NOT NULL,
+ PRIMARY KEY (`id`, `hosting_id`, `vm_id`, `time`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#CloudInitScript`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#CloudInitScript` (
+  `id`  int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) NOT NULL,
+  `script` text NOT NULL,
+  `updated_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+  `created_at`       timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#ServerConfiguration`
+--
+  CREATE TABLE IF NOT EXISTS `#prefix#ServerConfiguration` (
+    `server_id` int(11),
+    `setting`  varchar (255),
+    `value`  text,
+    PRIMARY KEY (`setting`,`server_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;

+ 29 - 0
app/Decorators/OsTemplateDecorator.php

@@ -0,0 +1,29 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\Decorators;
+
+class OsTemplateDecorator
+{
+
+    protected $name;
+
+    /**
+     * OsTemplateDecorator constructor.
+     * @param $name
+     */
+    public function __construct($name)
+    {
+        $this->name = $name;
+    }
+
+    public function toFriendlyName(){
+        $ex           = explode("/", $this->name);
+        $friendlyName = end($ex);
+        $s            = array("-", "_", ".tar.gz", ".iso", '.tar.xz');
+        $r            = array(" ", " ", "", "", "");
+        $friendlyName = str_replace($s, $r, $friendlyName);
+        $friendlyName = ucwords($friendlyName);
+        return $friendlyName;
+    }
+
+
+}

+ 27 - 0
app/Enum/Cloud/ConfigurableOption.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+
+use ModulesGarden\ProxmoxAddon\Core\Enum\Enum;
+
+final  class ConfigurableOption extends Enum
+{
+    const SOCKETS          = "CPU Sockets Limit";
+    const CORES_PER_SOCKET     = "The number of cores per socket";
+    const CORES            = "CPU Cores Limit";
+    const CPU_LIMIT        = "cpuLimit";
+    const CPU_UNITS        = "CPU Weight Limit";
+    const MEMORY           = "Memory Limit";
+    const STORAGE          = "Storage Limit";
+    const IPV4             = "IP Addresses Limit";
+    const IPV6             = "IPv6 Addresses Limit";
+    const SWAP             = "SWAP Limit";
+    const BACKUPS_SIZE     = "Backups Size Limit";
+    const BACKUPS_FILES    = "Backups Files Limit";
+    const SNAPSHOTS        = "snapshots";
+    const NETWORK_RATE     = "networkRate";
+    const VCPUS            = "vcpus";
+    const BANDWIDTH        = 'Bandwidth';
+    const VIRTUAL_NETWORKS = 'virtualNetworks';
+    const DISK_SIZE        = "Disk Space";
+}

+ 13 - 0
app/Enum/Cloud/CustomField.php

@@ -0,0 +1,13 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+
+use ModulesGarden\ProxmoxAddon\Core\Enum\Enum;
+
+class CustomField extends  Enum
+{
+    const AUTHENTICATION = 'Authentication';
+    const TAG = 'VLAN Tag';
+    const ADVANCED_USER  = 'proxmoxCloudVpsAdvancedUser';
+}

+ 32 - 0
app/Enum/Vps/ConfigurableOption.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+
+use ModulesGarden\ProxmoxAddon\Core\Enum\Enum;
+
+final  class ConfigurableOption extends Enum
+{
+    const SOCKETS              = "The number of CPU sockets";
+    const CORES_PER_SOCKET     = "The number of cores per socket";
+    const CORES                = "cores";
+    const CPU_LIMIT            = "cpulimit";
+    const CPU_UNITS            = "CPU weight for a VM";
+    const MEMORY               = "Amount of RAM";
+    const DISK_SIZE            = "Disk Space";
+    const ADDITIONAL_DISKS_SIZE = "additionalDisksSpace";
+    const IPV4                 = "IP Addresses";
+    const IPV6                 = "IPv6 Addresses";
+    const SWAP                 = "Amount of SWAP";
+    const BACKUPS_SIZE         = "Backups Limit";
+    const BACKUPS_FILES        = "Backups Files Limit";
+    const SNAPSHOTS            = "snapshots";
+    const OS_TEMPLATE          = "VM Template";
+    const NETWORK_RATE         = "Network Rate";
+    const VCPUS                = "vcpus";
+    const ISO_IMAGE            = "ISO Image";
+    const OS_TYPE              = "osType";
+    const BANDWIDTH = 'Bandwidth';
+    const SNAPSHOT_JOBS = 'snapshotJobs';
+    const CICUSTOM = 'cicustom';
+    const CLOUD_INIT_SCRIPT       = "ciscript";
+}

+ 19 - 0
app/Enum/Vps/CustomField.php

@@ -0,0 +1,19 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Enum\Enum;
+
+class CustomField extends  Enum
+{
+    const VMID = 'vmid';
+    const NODE =  'node';
+    const TAG = 'VLAN Tag';
+    const USERNAME ='userName';
+    const SSH_PUBLIC_KEY = 'sshkeys';
+    const HOSTNAME = 'proxmoxHostname';
+    const CI_USER = 'ciuser';
+
+}

+ 12 - 0
app/Events/Cloud/LxcUpdateEvent.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class LxcUpdateEvent extends VmEvent
+{
+
+
+}

+ 32 - 0
app/Events/Cloud/QemuUpdateEvent.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class QemuUpdateEvent extends VmEvent
+{
+
+    protected $changeVmPassword = true;
+
+    /**
+     * @return bool
+     */
+    public function isChangeVmPassword()
+    {
+        return $this->changeVmPassword;
+    }
+
+    /**
+     * @param bool $changeVmPassword
+     */
+    public function setChangeVmPassword($changeVmPassword)
+    {
+        $this->changeVmPassword = $changeVmPassword;
+        return $this;
+    }
+
+
+}

+ 13 - 0
app/Events/Cloud/VmClonedEvent.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmClonedEvent extends VmEvent
+{
+
+
+
+}

+ 13 - 0
app/Events/Cloud/VmCreatedEvent.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmCreatedEvent extends VmEvent
+{
+
+}

+ 63 - 0
app/Events/Cloud/VmEvent.php

@@ -0,0 +1,63 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+abstract class VmEvent extends Event
+{
+
+    /**
+     * @var VmModel
+     */
+    protected $vmModel;
+    /**
+     * @var array
+     */
+    protected $config;
+
+    public function __construct(VmModel $vm, $config=[])
+    {
+        $this->vmModel = $vm;
+        $this->config = $config;
+
+    }
+
+    /**
+     * @return VmModel
+     */
+    public function getVmModel()
+    {
+        return $this->vmModel;
+    }
+
+    /**
+     * @param VmModel $vmModel
+     */
+    public function setVmModel($vmModel)
+    {
+        $this->vmModel = $vmModel;
+    }
+
+    /**
+     * @return array
+     */
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
+    /**
+     * @param array $config
+     * @return $this
+     */
+    public function setConfig($config)
+    {
+        $this->config = $config;
+        return $this;
+    }
+
+}

+ 13 - 0
app/Events/Cloud/VmReinstalledEvent.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Cloud;
+
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmReinstalledEvent extends VmEvent
+{
+
+
+}

+ 36 - 0
app/Events/Vps/LxcUpdateEvent.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Vps;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class LxcUpdateEvent extends Event
+{
+
+    protected $vm;
+
+    public function __construct(VmInterface $vm)
+    {
+        $this->vm = $vm;
+
+    }
+
+    /**
+     * @return VmInterface
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param VmInterface $vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+    }
+
+
+}

+ 53 - 0
app/Events/Vps/QemuUpdateEvent.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Vps;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class QemuUpdateEvent extends Event
+{
+
+    protected $vm;
+    protected $changeVmPassword = true;
+
+    public function __construct(VmInterface $vm)
+    {
+        $this->vm = $vm;
+
+    }
+
+    /**
+     * @return VmInterface
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param VmInterface $vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isChangeVmPassword()
+    {
+        return $this->changeVmPassword;
+    }
+
+    /**
+     * @param bool $changeVmPassword
+     */
+    public function setChangeVmPassword($changeVmPassword)
+    {
+        $this->changeVmPassword = $changeVmPassword;
+    }
+
+
+}

+ 36 - 0
app/Events/Vps/VmClonedEvent.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Vps;
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmClonedEvent extends Event
+{
+
+    protected $vm;
+
+    public function __construct(VmInterface $vm)
+    {
+        $this->vm = $vm;
+
+    }
+
+    /**
+     * @return VmInterface
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param VmInterface $vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+    }
+
+
+}

+ 37 - 0
app/Events/Vps/VmCreatedEvent.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Vps;
+
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmCreatedEvent extends Event
+{
+
+    protected $vm;
+
+    public function __construct(VmInterface $vm)
+    {
+        $this->vm = $vm;
+
+    }
+
+    /**
+     * @return VmInterface
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param VmInterface $vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+    }
+
+
+}

+ 37 - 0
app/Events/Vps/VmReinstalledEvent.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Events\Vps;
+
+
+use MGProvision\Proxmox\v2\interfaces\VmInterface;
+use ModulesGarden\ProxmoxAddon\Core\Events\Event;
+
+class VmReinstalledEvent extends Event
+{
+
+    protected $vm;
+
+    public function __construct(VmInterface $vm)
+    {
+        $this->vm = $vm;
+
+    }
+
+    /**
+     * @return VmInterface
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param VmInterface $vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+    }
+
+
+}

+ 37 - 0
app/Factory/Ssh2Factory.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Factory;
+
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Net\SSH2;
+
+class Ssh2Factory
+{
+
+    /**
+     * @param ServerConfigurationRepository $serConf
+     * @return SSH2
+     * @throws \Exception
+     */
+    public function fromServerConfiguration(ServerConfigurationRepository $serConf){
+
+        if(!$serConf->sshHost){
+            throw  new \InvalidArgumentException("SSH Host is empty");
+        }
+        $ssh2 = new SSH2($serConf->sshHost, $serConf->sshPort );
+        $keyOrPassword =  $serConf->getSshPassword();
+        //private key
+        if($serConf->hasSshKey()){
+             $keyOrPassword = PublicKeyLoader::load($serConf->getSshKey());
+        }
+        //login
+        if(!$ssh2->login($serConf->sshUser, $keyOrPassword)){
+            throw  new \Exception(sprintf("Login to SSH Host: %s failed", $serConf->sshHost));
+        }
+        return $ssh2;
+    }
+
+
+}

+ 127 - 0
app/Helper/AbstractAvailableExtensions.php

@@ -0,0 +1,127 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Doe\SubmoduleSettings;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\DomainPricing;
+
+/**
+ * Description of AbstractAvailableExtensions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AbstractAvailableExtensions
+{
+    protected $submoduleConfigKey = null;
+    protected $tlds = [];
+
+    /**
+     * @var DomainPricing
+     */
+    protected $domainPricing;
+
+    public function __construct(DomainPricing $domainPricing)
+    {
+        $this->domainPricing = $domainPricing;
+    }
+
+    public function isExist($name)
+    {
+        return in_array($name, $this->tlds, true);
+    }
+
+    public function removeTldsById($ids)
+    {
+        if (is_array($ids))
+        {
+            $names = $this->getDomainPricingById($ids);
+
+            foreach ($names as $name)
+            {
+                $this->unsetTld($name['tld']);
+            }
+        }
+        else
+        {
+            $name = $this->getDomainPricingById($ids);
+            $this->unsetTld($name['tld']);
+        }
+
+        return $this;
+    }
+
+    protected function getDomainPricingById($ids)
+    {
+        if (is_array($ids))
+        {
+            return $this->domainPricing
+                ->select("{$this->domainPricing->getTable()}.extension as tld")
+                ->whereIn("{$this->domainPricing->getTable()}.id", $ids)
+                ->get()
+                ->toArray();
+        }
+        else
+        {
+            return $this->domainPricing
+                ->select("{$this->domainPricing->getTable()}.extension as tld")
+                ->where("{$this->domainPricing->getTable()}.id", "LIKE", (int)$ids)
+                ->first()
+                ->toArray();
+        }
+
+        return null;
+    }
+
+    protected function unsetTld($name)
+    {
+        if ($key = array_search($name, $this->tlds, true))
+        {
+            unset($this->tlds[$key]);
+        }
+
+        return $this;
+    }
+
+    public function removeTldsByName($names)
+    {
+        if (is_array($names))
+        {
+            foreach ($names as $name)
+            {
+                $this->unsetTld($name);
+            }
+        }
+        else
+        {
+            $this->unsetTld($names);
+        }
+
+        return $this;
+    }
+
+    public function getAllowedTldList()
+    {
+        return array_diff($this->getTlds(), $this->loadDisabledTldList());
+    }
+
+    public function getTlds()
+    {
+        return $this->tlds;
+    }
+
+    protected function loadDisabledTldList()
+    {
+        $model   = Helper\di(SubmoduleSettings::class);
+        $setting = $model->query()->getQuery()->select('value')
+            ->where('submodule', '=', $this->submoduleConfigKey)
+            ->where('setting', '=', 'disabledTlds')->value('value');
+
+        if ($setting)
+        {
+            return json_decode($setting) ?: [];
+        }
+
+        return [];
+    }
+}

+ 376 - 0
app/Helper/BuildTlds.php

@@ -0,0 +1,376 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Models\BuildTlds\Collection;
+use ModulesGarden\ProxmoxAddon\App\Models\Doe\DomainCategory;
+use ModulesGarden\ProxmoxAddon\App\Models\Doe\DomainCategoryRelations;
+use ModulesGarden\ProxmoxAddon\App\Models\Doe\DomainLabel;
+use ModulesGarden\ProxmoxAddon\App\UI\DoeSectionsSettings\Helpers\DoeSearchTypes;
+use ModulesGarden\ProxmoxAddon\App\UI\DoeSectionsSettings\Providers\DoeSettingsDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\Helper\DomainHelper;
+use ModulesGarden\ProxmoxAddon\Core\Http\Request;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Client;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Currency;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\DomainPricing;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Pricing;
+
+/**
+ * Description of BuildTlds
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class BuildTlds
+{
+    /**
+     *
+     * @var DomainCategory
+     */
+    protected $domainCategory;
+
+    /**
+     * @var DomainPricing
+     */
+    protected $domainPricing;
+
+    /**
+     * @var Pricing
+     */
+    protected $pricing;
+
+    /**
+     * @var  DomainCategoryRelations
+     */
+    protected $domainCategoryRelations;
+
+    /**
+     * @var Client
+     */
+    protected $client;
+
+    /**
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * @var Currency
+     */
+    protected $currency;
+    protected $specialTld;
+
+    /**
+     * @var Collection
+     */
+    protected $collection;
+
+    /**
+     * @var DomainLabel
+     */
+    protected $domainLabel;
+
+    /**
+     * @var DoeSettingsDataProvider
+     */
+    protected $doeSettingsDataProvider;
+
+    /**
+     * @var string
+     */
+    protected $domainSearchType;
+
+    /**
+     * @var array
+     */
+    protected $tlds = [];
+
+    /**
+     * @var array
+     */
+    protected $categoresTld = [];
+    protected $subCategores = [];
+
+    public function __construct(DoeSettingsDataProvider $doeSettingsDataProvider, DomainLabel $domainLabel, Collection $collection, Currency $currency, Request $request, Client $client, Pricing $pricing, DomainPricing $domainPricing, DomainCategory $domainCategory, DomainCategoryRelations $domainCategoryRelations)
+    {
+        $this->domainCategoryRelations = $domainCategoryRelations;
+        $this->doeSettingsDataProvider = $doeSettingsDataProvider;
+
+        $this->domainLabel    = $domainLabel;
+        $this->collection     = $collection;
+        $this->currency       = $currency;
+        $this->request        = $request;
+        $this->client         = $client;
+        $this->pricing        = $pricing;
+        $this->domainCategory = $domainCategory;
+        $this->domainPricing  = $domainPricing;
+        $this->loadData();
+    }
+
+    protected function loadData()
+    {
+        $sessionDomain = $this->getDomainWithSession();
+        $this->collection->addSessionDomain($sessionDomain)
+            ->setSessionSearchDomain($this->request->get('formData')['search'])
+            ->setSessionCartDomain($this->request->getSession('cart', ['domains' => []])['domains']);
+        if ($this->getDomainSearchType() === DoeSearchTypes::CATEGORY_SEARCH || $this->getDomainSearchType() === DoeSearchTypes::MULTIPLE_DOMAIN_SEARCH)
+        {
+            $this->buildCategoryTree($this->domainCategory->orderBy('suggested', 'DESC')->orderBy('order', 'ASC')->get()->toArray());
+            $this->collection->generateCollection($this->categoresTld);
+        }
+
+        $princing = $this->pricing->getTable();
+
+        $curency = $this->getCurrency();
+        $this->collection->addCurrentName($this->getCurrencyName($curency));
+
+        $query = $this->domainCategory
+            ->WithDomainCategoresRelations()
+            ->WithDomainPrincing()
+            ->WithPrincing()
+            ->WithDomainLabelRelations()
+            ->WithDomainLabel()
+            ->select(
+                $this->domainCategory->getTable() . '.id', $this->domainCategory->getTable() . '.parent_id', $this->domainCategory->getTable() . '.header', $this->domainCategory->getTable() . '.title', $this->domainCategory->getTable() . '.type', $this->domainCategory->getTable() . '.order', $this->domainCategory->getTable() . '.suggested', "{$this->domainPricing->getTable()}.id as tld_id", "{$this->domainCategoryRelations->getTable()}.order as tld_order", "{$this->domainCategoryRelations->getTable()}.suggested as tld_suggested", "{$this->domainPricing->getTable()}.extension as tld_extension", "{$princing}.id as prince_id", "{$princing}.currency as tld_currency", "{$princing}.type as prince_type", "{$princing}.msetupfee as prince_one", "{$princing}.qsetupfee as prince_two", "{$princing}.ssetupfee as prince_three", "{$princing}.asetupfee as prince_four", "{$princing}.bsetupfee as prince_five", "{$princing}.monthly as prince_six", "{$princing}.quarterly as prince_seven", "{$princing}.semiannually as prince_eight", "{$princing}.annually as prince_nine", "{$princing}.biennially as prince_ten", "{$this->domainLabel->getTable()}.id as label_id", "{$this->domainLabel->getTable()}.title as label_title", "{$this->domainLabel->getTable()}.message as label_message", "{$this->domainLabel->getTable()}.order as label_order", "{$this->domainLabel->getTable()}.color as label_color", "{$this->domainLabel->getTable()}.background_color as label_background_color"
+            )
+            ->where("{$this->pricing->getTable()}.currency", "LIKE", $curency)
+            ->where(DB::raw("(IF(`{$princing}`.`msetupfee` >= 0, `{$princing}`.`msetupfee`, 0) "
+                            . "+ IF(`{$princing}`.`qsetupfee` >= 0, `{$princing}`.`qsetupfee`, 0) "
+                            . "+ IF(`{$princing}`.`ssetupfee` >= 0, `{$princing}`.`ssetupfee`, 0) "
+                            . "+ IF(`{$princing}`.`asetupfee` >= 0, `{$princing}`.`asetupfee`, 0) "
+                            . "+ IF(`{$princing}`.`bsetupfee` >= 0, `{$princing}`.`bsetupfee`, 0) "
+                            . "+ IF(`{$princing}`.`monthly` >= 0, `{$princing}`.`monthly`, 0) "
+                            . "+ IF(`{$princing}`.`quarterly` >= 0, `{$princing}`.`quarterly`, 0) "
+                            . "+ IF(`{$princing}`.`annually` >= 0, `{$princing}`.`annually`, 0) "
+                            . "+ IF(`{$princing}`.`biennially` >= 0, `{$princing}`.`biennially`, 0) "
+                            . "+ IF(`{$princing}`.`semiannually` >= 0, `{$princing}`.`semiannually`, 0))"), '>', 0.00);
+
+        if ($this->getDomainSearchType() === DoeSearchTypes::CATEGORY_SEARCH || $this->getDomainSearchType() === DoeSearchTypes::MULTIPLE_DOMAIN_SEARCH)
+        {
+            $query = $query->orderBy('suggested', 'DESC')
+                ->orderBy('order', 'ASC')
+                ->orderBy("{$this->domainCategoryRelations->getTable()}.suggested", 'DESC')
+                ->orderBy("{$this->domainLabel->getTable()}.order", 'DESC')
+                ->orderBy("{$this->domainCategoryRelations->getTable()}.order", 'ASC');
+
+            $return = $query->get()->toArray();
+            $this->collection->addTlds($return)->setDomainSearchType($this->getDomainSearchType());
+        }
+        elseif ($this->getDomainSearchType() === DoeSearchTypes::SINGLE_DOMAIN_SEARCH)
+        {
+            $query = $query->orderBy("{$this->domainCategoryRelations->getTable()}.suggested", 'DESC')
+                ->orderBy("{$this->domainLabel->getTable()}.order", 'DESC')
+                ->orderBy("{$this->domainCategoryRelations->getTable()}.order", 'ASC');
+
+            $return = $query->get()->toArray();
+            $this->collection->addTldsWithoutCategory($return)->setDomainSearchType($this->getDomainSearchType());
+        }
+
+        $this->buildTld($return);
+    }
+
+    protected function getDomainWithSession()
+    {
+        $domains = [];
+        try
+        {
+            $domainsSession = $this->request->getSession('cart')['domains'];
+            foreach ($domainsSession as $domain)
+            {
+                $domain                          = new DomainHelper($domain['domain']);
+                $domains[$domain->getDomain()][] = $domain->getTLDWithDot();
+            }
+        }
+        catch (\Exception $exc)
+        {
+
+        }
+
+        return $domains;
+    }
+
+    public function getDomainSearchType()
+    {
+        if (isset($this->domainSearchType) === false)
+        {
+            $this->domainSearchType = $this->doeSettingsDataProvider->getValueById('domainSearchType')['value'];
+        }
+
+        return $this->domainSearchType;
+    }
+
+    protected function buildCategoryTree($data)
+    {
+        foreach ($data as $record)
+        {
+            if ($record['parent_id'] !== 0)
+            {
+                $this->subCategores[] = $record;
+            }
+            else
+            {
+                $this->createCategory($record);
+            }
+        }
+
+        foreach ($this->subCategores as $record)
+        {
+            $this->createSubCategory($record);
+        }
+
+        return $this;
+    }
+
+    private function createCategory($data)
+    {
+
+        $this->categoresTld[$data['id']] = [
+            'id'        => $data['id'],
+            'parent_id' => $data['parent_id'],
+            'title'     => $data['title'],
+            'header'    => $data['header'],
+            'type'      => $data['type'],
+            'suggested' => $data['suggested'],
+            'order'     => $data['order']
+        ];
+
+        if (isset($this->categoresTld[$data['id']]['subCategory']) === false)
+        {
+            $this->categoresTld[$data['id']]['subCategory'] = [];
+        }
+
+        return $this;
+    }
+
+    private function createSubCategory($data)
+    {
+        $this->categoresTld[$data['parent_id']]['subCategory'][$data['id']] = [
+            'id'        => $data['id'],
+            'parent_id' => $data['parent_id'],
+            'title'     => $data['title'],
+            'header'    => $data['header'],
+            'type'      => $data['type'],
+            'suggested' => $data['suggested'],
+            'order'     => $data['order']
+        ];
+
+        return $this;
+    }
+
+    protected function getCurrency()
+    {
+        if ($userId = $this->request->getSession("uid", false))
+        {
+            $currency = $this->client->find($userId)->toArray()['currency'];
+        }
+        elseif ($currencyId = $this->request->getSession("currency", false))
+        {
+            $currency = $currencyId;
+        }
+        else
+        {
+            $currency = $this->currency->where("default", "LIKE", 1)->first()->toArray()['id'];
+        }
+        return $currency;
+    }
+
+    protected function getCurrencyName($id)
+    {
+        return $this->currency->where("id", "LIKE", $id)->first()->toArray()['code'];
+    }
+
+    protected function buildTld($data)
+    {
+        foreach ($data as $record)
+        {
+            if (in_array($record['tld_extension'], $this->tlds, true) === false)
+            {
+                $this->tlds[$record['tld_extension']] = $record['tld_extension'];
+            }
+        }
+        return $this;
+    }
+
+    public function setSpecialChoice($specialChoice)
+    {
+        $this->specialTld = $specialChoice;
+
+        return $this;
+    }
+
+    public function getSpecialTld()
+    {
+        return $this->collection->getSpecialTld($this->specialTld);
+    }
+
+    public function getTlds()
+    {
+        $tlds = $this->tlds;
+        foreach ($this->collection->getRemoveTlds() as $tld)
+        {
+            if (array_key_exists($tld, $tlds))
+            {
+                unset($tlds[$tld]);
+            }
+        }
+        return $tlds;
+    }
+
+    public function getTldsCount()
+    {
+        return (count($this->tlds) - $this->collection->getSessionFindTld());
+    }
+
+    public function getCategoresTld()
+    {
+        return $this->collection->getDataCollection();
+    }
+
+    protected function buildCategoresTld($data)
+    {
+        foreach ($data as $record)
+        {
+            if ($record['parent_id'] !== 0)
+            {
+                $this->addTldToSubCategory($record);
+            }
+            else
+            {
+                $this->addTldToCategory($record);
+            }
+        }
+        return $this;
+    }
+
+    private function addTldToSubCategory($data)
+    {
+        if (in_array(
+                [
+                    'id'   => $data['id_tld'],
+                    'name' => $data['extension']
+                ], $this->categoresTld[$data['parent_id']]['subCategory'][$data['id']]['tlds'], true
+            ) === false
+        )
+        {
+            $this->categoresTld[$data['parent_id']]['subCategory'][$data['id']]['tlds'][] = ['id' => $data['id_tld'], 'name' => $data['extension']];
+        }
+
+        return $this;
+    }
+
+    private function addTldToCategory($data)
+    {
+        if (in_array(
+                [
+                    'id'   => $data['id_tld'],
+                    'name' => $data['extension']
+                ], $this->categoresTld[$data['id']]['tlds'], true
+            ) === false
+        )
+        {
+            $this->categoresTld[$data['id']]['tlds'][] = ['id' => $data['id_tld'], 'name' => $data['extension']];
+        }
+
+        return $this;
+    }
+}

+ 42 - 0
app/Helper/CartTotals.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of CartTotals
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CartTotals
+{
+    private $requreFiles = [
+        'cartfunctions',
+        'clientfunctions',
+        'domainfunctions',
+        'configoptionsfunctions',
+        'invoicefunctions',
+        'orderfunctions'
+    ];
+
+    public function __construct()
+    {
+        $fullPath = ModuleConstants::getFullPathWhmcs('includes');
+
+        foreach ($this->requreFiles as $fileName)
+        {
+            ModuleConstants::requireFile($fullPath . DS . $fileName . ".php");
+        }
+    }
+
+    public function getCartInfo()
+    {
+        $calcCartTotals = calcCartTotals();
+        foreach ($calcCartTotals['addons'] as $key => &$addon)
+        {
+            $calcCartTotals['addons'][$key]['pricingtext'] = (string)$addon['pricingtext'];
+        }
+        return $calcCartTotals;
+    }
+}

+ 90 - 0
app/Helper/ClientAreaPage.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use ModulesGarden\ProxmoxAddon\App\UI\DoeSectionsSettings\Providers\DoeSettingsDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Http\Request;
+
+/**
+ * Description of ClientAreaPage
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class ClientAreaPage
+{
+    /**
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * @var DoeSettingsDataProvider
+     */
+    protected $doeSettingsDataProvider;
+    protected $vars = [];
+
+    /**
+     * @param Request $request
+     * @param DoeSettingsDataProvider $doeSettingsDataProvider
+     */
+    public function __construct(Request $request, DoeSettingsDataProvider $doeSettingsDataProvider)
+    {
+        $this->request                 = $request;
+        $this->doeSettingsDataProvider = $doeSettingsDataProvider;
+    }
+
+    public function setVars(array $vars = [])
+    {
+        $this->vars = $vars;
+
+        return $this;
+    }
+
+    public function isOrderdomain()
+    {
+        return (($this->getVar("currentPageName") === "orderdomain") ? true : false);
+    }
+
+    protected function getVar($key = "")
+    {
+        if (isset($this->vars[$key]) === false)
+        {
+            return null;
+        }
+
+        return $this->vars[$key];
+    }
+
+    public function isChangePage()
+    {
+        $fileName                 = $this->getVar("filename");
+        $action                   = $this->getRequest()->get('a', false);
+        $domeain                  = $this->getRequest()->get('domain', false);
+        $replaceStandardRegistrar = $this->doeSettingsDataProvider->getValueById("replaceStandardRegistrar");
+
+        if ($fileName == 'domainchecker' || ($fileName == 'cart' && $action == 'add' && $domeain == 'transfer') || ($fileName == 'cart' && $action == 'add' && $domeain == 'register')
+        )
+        {
+            if ($replaceStandardRegistrar === "on")
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @return Request
+     */
+    public function getRequest()
+    {
+        return $this->request;
+    }
+
+    public function redirect($action = "", array $params = [])
+    {
+        return Helper\redirect("orderDomain", $action, $params);
+    }
+}

+ 52 - 0
app/Helper/DemonHelper.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Doe\DemonTask;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\DomainPricing;
+
+/**
+ * Description of DemonHelper
+ *
+ * @author Rafał Ossowski <rafal.so@modulesgarden.com>
+ */
+class DemonHelper
+{
+    /**
+     * @var DemonTask
+     */
+    protected $demonTask;
+
+    /**
+     * @var DomainPricing
+     */
+    protected $domainPricing;
+
+    public function __construct(DemonTask $demonTask, DomainPricing $domainPricing)
+    {
+        $this->demonTask     = $demonTask;
+        $this->domainPricing = $domainPricing;
+    }
+
+    public function getReadyTask($sessionId)
+    {
+        $return = $this->demonTask
+            ->select(
+                $this->demonTask->getTable() . ".domain_status as status", $this->domainPricing->getTable() . ".id as id"
+            )->WithDomainPrincing()
+            ->WithSessionId($sessionId)
+            ->StatusReady()
+            ->whereNull($this->demonTask->getTable() . '.deleted_at')
+            ->orderBy("{$this->demonTask->getTable()}.created_at", "ASC")
+            ->get()
+            ->toArray();
+
+        $this->demonTask->WithSessionId($sessionId)
+            ->StatusReady()
+            ->whereNull($this->demonTask->getTable() . '.deleted_at')
+            ->orderBy("{$this->demonTask->getTable()}.created_at", "ASC")
+            ->update(['deleted_at' => time()]);
+
+        return $return;
+    }
+}

+ 10 - 0
app/Helper/ErrorCodesLib.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Helper;
+
+use ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodes;
+
+class ErrorCodesLib extends ErrorCodes
+{
+    const YOUR_APP_000001 = 'Your error message.';
+}

+ 13 - 0
app/Hooks/AdminAreaHeadOutput.php

@@ -0,0 +1,13 @@
+<?php
+$hookManager->register(
+    function ($vars) {
+        if (isset($vars['filename']) && $vars['filename'] != 'configservers' && (isset($_GET['action']) && $_GET['action'] != "manage" || !isset($_GET['id'])))
+            return;
+
+        $jsFile = ROOTDIR . DIRECTORY_SEPARATOR . "modules" . DIRECTORY_SEPARATOR . "addons" . DIRECTORY_SEPARATOR . "proxmoxAddon" . DIRECTORY_SEPARATOR . "app" . DIRECTORY_SEPARATOR . "UI" . DIRECTORY_SEPARATOR . "Admin" . DIRECTORY_SEPARATOR . "Templates" . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . "js" . DIRECTORY_SEPARATOR . "hooks" . DIRECTORY_SEPARATOR . "server.js";
+        if (!file_exists($jsFile)) {
+            return;
+        }
+
+        return '<script type="text/javascript"> ' . file_get_contents($jsFile) . '</script>';
+    }, 2);

+ 56 - 0
app/Hooks/AdminProductConfigFields.php

@@ -0,0 +1,56 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (26.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+
+
+$hookManager->register(
+    function ($args)
+    {
+        if(! \ModulesGarden\ProxmoxAddon\App\Models\ModuleSetting::where("setting","duplicateProduct")->count()){
+            return;
+        }
+        if(!file_exists(\ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getFullPathWhmcs('modules', 'servers','proxmoxVPS', 'core') . DIRECTORY_SEPARATOR . 'Bootstrap.php')){
+            return;
+        }
+
+        $file   = \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getFullPathWhmcs('modules', 'servers','proxmoxVPS', 'core') . DIRECTORY_SEPARATOR . 'Bootstrap.php';
+        if(!file_exists($file))
+        {
+            return;
+        }
+
+        require_once $file;
+
+        $provider = new \ModulesGarden\Servers\ProxmoxVps\App\UI\Admin\Product\Providers\ProductProvider($args['pid']);
+        /**
+         * @var $setting ModuleSetting
+         */
+        $setting = \ModulesGarden\ProxmoxAddon\App\Models\ModuleSetting::where("setting","duplicateProduct")->firstOrFail();
+        $setting->value = json_decode(  $setting->value , true);
+        if (!$provider->isSupportedModule())
+        {
+            $setting->delete();
+            return;
+        }elseif(!Product::where("id", $args['pid'])->where("name", $setting->value['newproductname'])->count() ){
+            return;
+        }
+        $setting->delete();
+        $provider->replicate($setting->value['existingproduct']);
+    }, 1001);

+ 33 - 0
app/Http/Admin/CloudInitScript.php

@@ -0,0 +1,33 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Http\Admin;
+
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Pages\CloudInitScriptContainter;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Pages\CloudInitScriptDataTable;
+use ModulesGarden\ProxmoxAddon\Core\Http\AbstractController;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\view;
+
+class CloudInitScript extends AbstractController
+{
+
+    /**
+     * Example of static page
+     * @return type
+     */
+    public function index()
+    {
+        return view()->addElement(CloudInitScriptDataTable::class);
+    }
+
+    public function create()
+    {
+        return $this->update();
+    }
+
+    public function update()
+    {
+        return view()->setCustomJsCode()->addElement(CloudInitScriptContainter::class);
+    }
+}

+ 97 - 0
app/Http/Admin/Home.php

@@ -0,0 +1,97 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Http\Admin;
+
+use ModulesGarden\ProxmoxAddon\App\Services\RecoveryVmDumpService;
+use ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\Subscription;
+use ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\Summary;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Http\AbstractController;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+/**
+ * Example admin home page controler
+ */
+class Home extends AbstractController
+{
+
+    /**
+     * Example of static page
+     * @return type
+     */
+    public function index()
+    {
+        return Helper\redirect('home', 'servers');
+    }
+
+    public function servers()
+    {
+
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\Servers\Pages\ServersDataTable::class);
+    }
+
+    public function vms()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\Vms\Pages\VmsDataTable::class);
+    }
+
+    public function recoveryVms()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Pages\RecoveryVmsDataTable::class);
+    }
+
+    public function recoveryToFile()
+    {
+        $response = new StreamedResponse();
+        $response->setStatusCode(200);
+        $response->headers->set('Content-Type', 'application/x-pem-file; charset=utf-8');
+        $response->setCallback(function ()
+        {
+            echo (new RecoveryVmDumpService())->generate();
+        });
+        $response->headers->set(
+            'Content-Disposition', 'attachment; filename="recover_vm_list_' . date("Y-m-d H:i:s") . '.txt"'
+        );
+        $response->send();
+    }
+
+    public function taskHistory()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Pages\TaskHistoryDataTable::class);
+    }
+
+    public function serverDetail()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\ServerDetail\Pages\ServerDetailContainer::class);
+    }
+
+    public function nodeDetail()
+    {
+        return Helper\view()->setCustomJsCode()
+            ->addElement(Summary::class)
+            ->addElement(Subscription::class)
+            ->addElement(\ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\CpuGraph::class)
+            ->addElement(\ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\ServerLoadGraph::class)
+            ->addElement(\ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\MemoryGraph::class)
+            ->addElement(\ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages\NetworkGraph::class);
+    }
+
+}

+ 40 - 0
app/Http/Admin/IpManagement.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Http\Admin;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Pages\IpManagementDataTable;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Http\AbstractController;
+
+/**
+ * Description of IpManagament
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class IpManagement extends AbstractController
+{
+
+    public function index()
+    {
+        return Helper\view()
+            ->addElement(IpManagementDataTable::class);
+    }
+}

+ 39 - 0
app/Http/Admin/Jobs.php

@@ -0,0 +1,39 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Http\Admin;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Pages\JobsDataTable;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Http\AbstractController;
+
+/**
+ *  Sttiongs page controller
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class Jobs extends AbstractController
+{
+
+    public function index()
+    {
+        return Helper\view()->addElement(JobsDataTable::class);
+    }
+
+}

+ 42 - 0
app/Http/Admin/Settings.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Http\Admin;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\Http\AbstractController;
+
+/**
+ *  Sttiongs page controller
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class Settings extends AbstractController
+{
+
+    public function index()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\Settings\Pages\SettingsContainer::class);
+    }
+
+    public function general()
+    {
+        return Helper\view()->addElement(\ModulesGarden\ProxmoxAddon\App\UI\Settings\Pages\SettingsContainer::class);
+    }
+}

+ 404 - 0
app/Jobs/BaseJob.php

@@ -0,0 +1,404 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
+use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ActivityLog;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ToDoList;
+use ModulesGarden\ProxmoxAddon\App\Repositories\RangeVmRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalMountPointService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Configuration;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Job;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+
+abstract class BaseJob extends Job
+{
+    use ApiService;
+    use WhmcsParams;
+    use HostingService;
+    use Smarty;
+
+    /**
+     * @var VmModel
+     */
+    protected $vmModel;
+
+
+    protected $params = [];
+
+    /**
+     * @var EmailService
+     */
+    protected $emailService;
+    /**
+     * @var ContainerService
+     */
+    protected $containerService;
+
+    /**
+     * @var AgentService
+     */
+    protected $agentService;
+    /**
+     * @var AdditionalDiskService
+     */
+    protected $additionalDiskService;
+
+    /**
+     * @var AdditionalMountPointService
+     */
+    protected $additionalMountPointService;
+
+    protected function initServices()
+    {
+
+
+    }
+
+    /**
+     * @return VmModel
+     */
+    public function getVmModel(){
+        return $this->vmModel = VmModel::ofHostingId($this->model->rel_id)->where('id', $this->model->custom_id)->firstOrFail();
+    }
+
+    public function initVm(){
+        $vm = (new VmFactory())->fromVmModel($this->getVmModel());
+        sl('Vm')->setVm($vm);
+        sl('Vm')->setVmModel($this->getVmModel());
+        return $this;
+    }
+
+    public function initParams()
+    {
+        if (!$this->model->rel_id)
+        {
+            new \InvalidArgumentException(sprintf("Job model: #%s rel_id cannot be empty", $this->model->id));
+        }
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ModuleConstants::getFullPathWhmcs('includes') . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        $this->params = \ModuleBuildParams($this->model->rel_id);
+        sl("whmcsParams")->setParams($this->params);
+        if(function_exists('\ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl')){
+            \ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl("whmcsParams")->setParams($this->params);
+        }
+        $this->setHostingId($this->params['serviceid']);
+        return $this;
+    }
+
+    protected function sleep($seconds = 60)
+    {
+        $this->model->setWaiting();
+        $this->model->setRetryAfter(date("Y-m-d H:i:s", strtotime("+{$seconds} seconds")));
+        $this->model->increaseRetryCount();
+    }
+
+    protected function vmidExistInWhmcs($vmid)
+    {
+        //vps
+        $cfv   = 'tblcustomfieldsvalues';
+        $cfn   = 'tblcustomfields';
+        $h     = 'tblhosting';
+        $query = DB::table($cfv)
+            ->rightJoin($cfn, "{$cfn}.id", "=", "{$cfv}.fieldid")
+            ->leftJoin($h, "{$h}.id", "=", "{$cfv}.relid")
+            ->where("{$cfn}.fieldname", "like", "vmid%")
+            ->where("{$cfv}.value", $vmid)
+            ->whereIn("{$h}.domainstatus", ['Active', "Suspended"]);
+        if ($query->count())
+        {
+            return true;
+        }
+        //cloud
+        try
+        {
+            $query = VmModel::where("vmid", $vmid);
+            return $query->count() > 0;
+        }
+        catch (\Exception $ex)
+        { // table does not exist
+        }
+        return false;
+    }
+
+    protected function findFreeVmid($vmid)
+    {
+        for ($i = $vmid; $i <= 1000000; $i++)
+        {
+            if ($this->vmidExistInWhmcs($i))
+            {
+                continue;
+            }
+            try
+            {
+                $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
+            }
+            catch (\Exception $ex)
+            {
+                continue;
+            }
+            if ($res == $i)
+            {
+                return $i;
+            }
+        }
+        throw new \Exception("Unable to obtain vmid");
+    }
+
+    protected function isVmRange()
+    {
+        if (RangeVm::ofServerId($this->getWhmcsParamByKey("serverid"))->count())
+        {
+            return true;
+        }
+        return Configuration::where("setting", "proxmoxVPSMinimumVMID")->count() > 0;
+    }
+
+    protected function nextVmid()
+    {
+        $data = $this->api()->get("/cluster/nextid", []);
+        $vmid = (int)$data ? (int)$data : 100;
+        $vmid = $this->findFreeVmid($vmid);
+        if ($this->isVmRange())
+        {
+            $rageVm = new RangeVmRepository($this->getWhmcsParamByKey('serverid'));
+            if (!$rageVm->has() && !$rageVm->getMin())
+            {
+                return $vmid;
+            }
+            else
+            {
+                if (!$rageVm->getMax() && $rageVm->getMin())
+                {
+                    $from = (int)$rageVm->getMin();
+                    $to   = (int)$rageVm->getMin() * 100;
+                }
+                else
+                {
+                    $from = (int)$rageVm->getMin();
+                    $to   = (int)$rageVm->getMax();
+                }
+            }
+            for ($i = $from; $i <= $to; $i++)
+            {
+                try
+                {
+                    if ($this->vmidExistInWhmcs($i))
+                    {
+                        continue;
+                    }
+                    $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
+                    if ((int)$res == $i)
+                    {
+                        $vmID = $i;
+                        break;
+                    }
+                }
+                catch (\Exception $ex)
+                {
+                    continue;
+                }
+            }
+            if (!$vmID)
+            {
+                throw new \Exception("VM Ranges have been exited for this server. Please setup VM Ranges", 321);
+            }
+        }
+        return $vmID ? $vmID : $vmid;
+    }
+
+    protected function createTaskHistory($taskId, $action)
+    {
+        $type = str_replace(["qemu", "lxc"], ["VM", "CT"], $this->getVmModel()->virtualization);
+        $task = new TaskHistory();
+        $task->fill([
+            'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
+            'upid'       => $taskId,
+            'name'       => sprintf("%s %s - %s", $type, $this->getVmModel()->vmid, $action),
+            'vmid'       => $this->getVmModel()->vmid,
+            'node'       => $this->getVmModel()->node,
+            'status'     => 0
+        ])->save();
+    }
+
+    protected function getModelData()
+    {
+        return unserialize($this->model->data);
+    }
+
+    protected function putModelDataAndSave(array $newData)
+    {
+
+        $data = $this->getModelData();
+        $data += $newData;
+        $this->setModelDataAndSave($data);
+        return $this;
+    }
+
+    protected function setModelDataAndSave(array $data)
+    {
+        $this->model->data = serialize($data);
+        $this->model->save();
+        return $this;
+    }
+
+    /**
+     * @return \MGProvision\Proxmox\v2\models\Task
+     * @throws \MGProvision\Proxmox\v2\ProxmoxApiException
+     */
+    protected function getTask()
+    {
+        $taskId = $this->getModelData()['taskId'];
+        //Init API service
+        if($this->getModelData()['templateNode']){
+            $node = new Node($this->getModelData()['templateNode']);
+        }else{
+            $node = new Node($this->getModelData()['node']);
+        }
+        $node->setApi($this->api());
+        return $node->task($taskId);
+    }
+
+    protected function isTask()
+    {
+        return !is_null($this->getModelData()['taskId']);
+    }
+
+    protected function isDone()
+    {
+        $taskId = $this->getModelData()['taskId'];
+        if (!$taskId || !$this->getModelData()['node'])
+        {
+            return false;
+        }
+        $task = $this->getTask();
+        if ($task->getStatus() == "running")
+        {
+            $this->log->success(sprintf("Waiting to finish. Task Id %s Node: %s ", $task->getUpid(), $task->getNode()));
+            return false;
+        }
+        //Failed
+        if ($task->getExitstatus() && $task->getExitstatus() != "OK")
+        {
+            $this->log->error($task->getExitstatus());
+            $this->failed($task->getExitstatus());
+            $data = $this->getModelData();
+            unset($data['taskId'], $data['node']);
+            $this->setModelDataAndSave($data);
+            return false;
+        }
+        return true;
+    }
+
+    protected function isTaskRunning()
+    {
+        return $this->isTask() && $this->getTask()->isRunning();
+    }
+
+    protected function isFailed()
+    {
+        $task = $this->getTask();
+        return $task->getExitstatus() && $task->getExitstatus() != "OK";
+    }
+
+    protected function failed($error)
+    {
+
+        if ((int)$this->model->retry_count != 21)
+        {
+            return;
+        }
+        if (!preg_match("/Create/", $this->model->job) || preg_match("/Clone/", $this->model->job))
+        {
+            return;
+        }
+        //create new entery on to do list
+        if ($this->configuration()->isToDoList())
+        {
+            $title = sprintf('Creation Failed - Service ID: %s', $this->getWhmcsParamByKey('serviceid'));
+            if (ToDoList::ofTitle($title)->pending()->count())
+            {
+                return;
+            }
+            $entity = new ToDoList();
+            $entity->fill(
+                [
+                    'date'        => date("Y-m-d H:i:s"),
+                    "duedate"     => date("Y-m-d H:i:s"),
+                    'title'       => $title,
+                    "status"      => "Pending",
+                    "description" => $error,
+                    "admin"       => 0,
+                ]
+            );
+            $entity->save();
+        }
+        //send admin message
+        if ($this->configuration()->getServiceCreationFailedTemplateId())
+        {
+            //init email template
+            $this->emailService->template($this->configuration()->getServiceCreationFailedTemplateId());
+            //check if already send
+            $description = printf('Email Sent to Admin (%s) - Service ID: %s', $this->emailService->getVars()['messagename'], $this->getWhmcsParamByKey('serviceid'));
+            if (ActivityLog::ofDescription($description)->today()->count() > 0)
+            {
+                return;
+            }
+            //email vars
+            global $customadminpath;
+            $adminDir      = $customadminpath ? $customadminpath : "admin";
+            $adminAreaLink = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}";
+            $hosting       = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}/clientsservices.php?id=" . $this->getWhmcsParamByKey('serviceid');
+            $emailVars     = [
+                "client_id"        => $this->getWhmcsParamByKey('clientsdetails')['id'],
+                "service_id"       => $this->getWhmcsParamByKey('serviceid'),
+                "service_product"  => "<a href='{$hosting}/'>{$hosting}</a>",
+                "service_domain"   => $this->getWhmcsParamByKey('domain'),
+                "error_msg"        => $error,
+                "whmcs_admin_link" => "<a href='{$adminAreaLink}/'>{$adminAreaLink}</a>",
+            ];
+            $this->emailService->vars($emailVars)->sendToAdmin();
+            logActivity($description);
+        }
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\App\Models\Job:
+     */
+    protected function getParentModel()
+    {
+        if (is_null($this->model->parent_id))
+        {
+            throw new \InvalidArgumentException("The Parent Id is not valid");
+        }
+        if ($this->parent)
+        {
+            return $this->parent;
+        }
+        return $this->parent = \ModulesGarden\ProxmoxAddon\App\Models\Job::ofId($this->model->parent_id)->firstOrFail();
+    }
+
+    protected function getParentModelData()
+    {
+        return unserialize($this->getParentModel()->data);
+    }
+
+}

+ 51 - 0
app/Jobs/Cloud/Agent/ChangePasswordJob.php

@@ -0,0 +1,51 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Agent;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class ChangePasswordJob extends BaseJob
+{
+    use ProductService;
+
+    protected function initServices()
+    {
+        $this->agentService = new AgentService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        $this->api();
+        try{
+            if(!sl('Vm')->getVm()->isRunning()){
+                $this->log->info(sprintf("VM %s - Start", sl('Vm')->getVm()->getVmid()));
+                sl('Vm')->getVm()->start();
+                $this->sleep(40);
+                return false;
+            }
+            sl('Vm')->getVm()->agent()->ping();
+            $this->agentService->passwordUpdate();
+
+        }catch (ProxmoxApiException $ex){
+            if(preg_match("/not running/", $ex->getMessage())){
+                $this->log->info($ex->getMessage());
+            }else{
+                $this->log->error($ex->getMessage());
+            }
+            //sleep
+            $this->sleep(30);
+            return false;
+        }
+    }
+
+}

+ 51 - 0
app/Jobs/Cloud/Agent/ConfigureNetworkJob.php

@@ -0,0 +1,51 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Agent;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class ConfigureNetworkJob extends BaseJob
+{
+    use ProductService;
+
+    protected function initServices()
+    {
+        $this->agentService = new AgentService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        $this->api();
+        try{
+            if(!sl('Vm')->getVm()->isRunning()){
+                $this->log->info(sprintf("VM %s - Start", sl('Vm')->getVm()->getVmid()));
+                sl('Vm')->getVm()->start();
+                $this->sleep(40);
+                return false;
+            }
+            sl('Vm')->getVm()->agent()->ping();
+            $this->agentService->configureNetwork();
+
+        }catch (ProxmoxApiException $ex){
+            if(preg_match("/not running/", $ex->getMessage())){
+                $this->log->info($ex->getMessage());
+            }else{
+                $this->log->error($ex->getMessage());
+            }
+            //sleep
+            $this->sleep(30);
+            return false;
+        }
+    }
+
+}

+ 183 - 0
app/Jobs/Cloud/CloneQemuJob.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\UserService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CloneQemuJob extends BaseJob
+{
+
+    use ProductService;
+    use UserService;
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService
+     */
+    protected $containerService;
+
+    /**
+     * @var Kvm|Lxc
+     */
+    protected $vm;
+    /**
+     * @var AdditionalDiskService
+     */
+    protected $additionalDiskService;
+    protected function initServices()
+    {
+        $this->emailService     = new EmailService();
+        $this->containerService = new ContainerService();
+        $this->agentService = new AgentService();
+        $this->additionalDiskService = new AdditionalDiskService();
+
+    }
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        //create task validation
+        if ($this->isDone())
+        {
+            $this->initVm();
+            if(!sl('Vm')->getVm()->isRunning()){
+                $this->deleteNetwork();
+            }
+            if($this->getModelData()['additionalDiskSize1'] && !$this->additionalDiskService->hasDisk()){
+                $this->additionalDiskService->create($this->getModelData());
+            }
+            fire(new QemuUpdateEvent($this->getVmModel(), $this->getModelData()));
+            try{
+
+                if($this->agentService->isEnabled()){
+                    if(!sl('Vm')->getVm()->isRunning()){
+                        $this->log->info(sprintf("VM %s - Start", sl('Vm')->getVm()->getVmid()));
+                        sl('Vm')->getVm()->start();
+                        $this->sleep(5);
+                        return false;
+                    }
+                    sl('Vm')->getVm()->agent()->ping();
+                    $this->agentService ->passwordUpdate();
+                    $this->agentService ->configureNetwork();
+                }
+            }catch (ProxmoxApiException $ex){
+                if(preg_match("/not running/", $ex->getMessage())){
+                    $this->log->info($ex->getMessage());
+                }else{
+                    $this->log->error($ex->getMessage());
+                }
+                //sleep
+                $this->sleep(5);
+                return false;
+            }
+            fire(new VmCreatedEvent($this->getVmModel()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            $osTemplate = $this->getModelData()['osTemplate'];
+            //Support for configurable options i.e vmname|OS Name
+            if (preg_match('/\//', $osTemplate))
+            {
+                list($templateNode, $templateVmid) = explode("/", $osTemplate);
+            }
+            //vmid
+            $vmid = $this->nextVmid();
+            $this->getVmModel();
+            $this->vmModel->vmid = $vmid;
+            $this->vmModel->save();
+            //init container
+            $container = [
+                "newid"  => $vmid,
+                "full"   => $this->configuration()->getCloneMode(),
+                "target" => $this->vmModel->node
+            ];
+            //description
+            $container['description'] = $this->getModelData()['description'];
+            //hostname
+            $container['name'] = $this->getModelData()['name'];
+            //Storage
+            if (!$this->configuration()->isCloneOnTheSameStorage() && $this->configuration()->getDiskStorage() && $this->configuration()->getCloneMode() == "1")
+            {
+                $container['storage'] = $this->configuration()->getDiskStorage();
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //bwlimit
+            if($this->configuration()->getBwLimit()){
+                $container['bwlimit'] = $this->configuration()->getBwLimit();
+            }
+            //Create
+            $template = new Kvm($templateNode, $templateVmid);
+            $template->setApi($this->api());
+            $taskId = $template->cloneVm($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            $this->failed($ex->getMessage());
+            throw $ex;
+        }
+        //task history
+        $task = new TaskHistory();
+        $task->fill([
+            'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
+            'upid'       => $taskId,
+            'name'       => sprintf("VM %s - %s", $vmid, "Clone"),
+            'vmid'       => $template->getVmid(),
+            'node'       => $template->getNode(),
+            'status'     => 0
+        ])->save();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $template->getNode(), "templateNode" => $templateNode]);
+        //sleep
+        $this->sleep();
+        return false;
+    }
+
+    private function  deleteNetwork(){
+
+        $deleteNetwork = [];
+        foreach (sl('Vm')->getVm()->getNetworkDevices() as $networkDevice){
+            if(VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                           ->ofNet($networkDevice->getId())
+                          ->ofVmId(sl('Vm')->getVmModel()->id)
+                          ->count()){
+                continue;
+            }
+            $deleteNetwork[]=$networkDevice->getId();
+        }
+        if(!empty($deleteNetwork)){
+            sl('Vm')->getVm()->deleteConfig(implode(",",$deleteNetwork));
+        }
+    }
+
+}

+ 249 - 0
app/Jobs/Cloud/CreateLxcJob.php

@@ -0,0 +1,249 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Features;
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AdditionalMountPointService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\UserService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\VmNetwork;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CreateLxcJob extends BaseJob
+{
+
+    use ProductService;
+    use UserService;
+    use VmNetwork;
+
+    /**
+     * @var ContainerService
+     */
+    protected $containerService;
+
+    protected function initServices()
+    {
+        $this->emailService     = new EmailService();
+        $this->containerService = new ContainerService();
+        $this->agentService = new AgentService();
+        $this->additionalMountPointService = new AdditionalMountPointService();
+
+    }
+
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //create task validation
+        if ($this->isDone())
+        {
+            $this->initVm();
+            if($this->getModelData()['additionalDiskSize1'] && !$this->additionalMountPointService->hasMountPoint()){
+                $this->additionalMountPointService->create($this->getModelData());
+            }
+            fire(new VmCreatedEvent($this->getVmModel(), $this->getModelData()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //vmid
+            $vmid = $this->nextVmid();
+            $this->getVmModel();
+            $this->vmModel->vmid = $vmid;
+            $this->vmModel->save();
+            sl('Vm')->setVm(null);
+            sl('Vm')->setVmModel(   $this->vmModel);
+            $container = [
+                'vmid'       => $vmid,
+                'ostemplate' => $this->getModelData()['osTemplate'],
+                'hostname'   => $this->getVmModel()->name,
+                'password'   => $this->getVmModel()->getPassword(),
+            ];
+            //Configurable Options
+            if ($this->getVmModel()->cpulimit )
+            {
+                $container['cpulimit'] = $this->getVmModel()->cpulimit;
+            }
+            //Memory
+            $container['memory'] = $this->getVmModel()->memory;
+            //SWAP
+            $container['swap'] = $this->getVmModel()->swap;
+            //cpuunits
+            $container['cpuunits'] = $this->getVmModel()->cpuunits;
+            //Name servers
+            $ns = [];
+            for ($i = 0; $i <= 1; $i++)
+            {
+                $n = trim($this->getModelData()['nameserver'][$i]);
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            //cores
+            if ($this->getVmModel()->cores)
+            {
+                $container['cores'] = $this->getVmModel()->cores;
+            }
+            //arch
+            if ($this->configuration()->getArch())
+            {
+                $container['arch'] = $this->configuration()->getArch();
+            }
+            //cmode
+            if ($this->configuration()->getCmode())
+            {
+                $container['cmode'] = $this->configuration()->getCmode();
+            }
+            //console
+            if ($this->configuration()->isConsole())
+            {
+                $container['console'] = 1;
+            }
+            //description
+            if ($this->getModelData()['description'])
+            {
+                $container['description'] = $this->getModelData()['description'];
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //ostype
+            if ($this->configuration()->getOsType())
+            {
+                $container['ostype'] = $this->configuration()->getOsType();
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //protection
+            if ($this->configuration()->isProtection())
+            {
+                $container['protection'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //storage
+            $dafaultStorage  = NodeSetting::ofServer($this->getWhmcsParamByKey('serverid'))
+                                           ->ofNode($this->getNode()->getNode())
+                                           ->ofSetting('defaultStorage')
+                                           ->value("value");
+            $container['storage'] = $dafaultStorage ? $dafaultStorage: $this->configuration()->getStorage();
+            //tty
+            if ($this->configuration()->getTty())
+            {
+                $container['tty'] = $this->configuration()->getTty();
+            }
+            //unprivileged
+            $container['unprivileged'] = $this->configuration()->isUnprivileged() ? 1 : 0;
+            //Disk
+            $diskSize = $this->getVmModel()->disk;
+            //Rootfs
+            $container['rootfs'] = "{$container['storage']}:{$diskSize}";
+            //SSH Public key.
+            if ($this->configuration()->isSshKeyPairs())
+            {
+                $keyPairs =  $this->containerService->makeKeyPairs();
+                $keyPairs->vm_id = $this->getVmModel()->id;
+                $keyPairs->save();
+                $container['ssh-public-keys'] = $keyPairs->getPublic();
+            }
+            //Network
+            $networkService = new NetworkService();
+            $container      += $this->createNetwork();
+            //start
+            if ($this->configuration()->isStart()  && version_compare($this->api()->getVersion(), "4.4", '>'))
+            {
+                $container['start'] = 1;
+            }
+            //features
+            $features = new Features();
+            //Keyctl
+            if($this->configuration()->isFeatureKeyctl() && $this->configuration()->isUnprivileged()){
+                $features->setKeyctl(1);
+            }
+            //Nesting
+            if($this->configuration()->isFeatureNesting() ){
+                $features->setNesting(1);
+            }
+            //NFS
+            if($this->configuration()->isFeatureNfs() ){
+                $features->addNfs();
+            }
+            //CIFS
+            if($this->configuration()->isFeatureCifs() ){
+                $features->addCifs();
+            }
+            //Fuse
+            if($this->configuration()->isFeatureFuse() ){
+                $features->setFuse(1);
+            }
+            //Mknod
+            if($this->configuration()->isFeatureMknod() ){
+                $features->setMknod(1);
+            }
+            if(!$features->isEmpty()){
+                $container[Features::ID] = $features->asConfig();
+            }
+
+            //Create
+            $vm          =  (new VmFactory())->fromVmModel($this->getVmModel());
+            $taskId = $vm->create($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            $this->failed($ex->getMessage());
+            throw $ex;
+        }
+        //task history
+        $this->createTaskHistory($taskId, "Create");
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->getVmModel()->node]);
+        //sleep
+        $this->sleep();
+        return false;
+
+    }
+
+
+}

+ 395 - 0
app/Jobs/Cloud/CreateQemuJob.php

@@ -0,0 +1,395 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\HardDisk;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\UserService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\VmNetwork;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CreateQemuJob extends BaseJob
+{
+    use ProductService;
+    use UserService;
+    use VmNetwork;
+
+
+    /**
+     * @var NetworkService
+     */
+    protected $networkService;
+
+    protected function initServices()
+    {
+        $this->emailService     = new EmailService();
+        $this->containerService = new ContainerService();
+        $this->networkService   = new NetworkService();
+        $this->additionalDiskService = new AdditionalDiskService();
+    }
+
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //create task validation
+        if ($this->isDone())
+        {
+            $this->initVm();
+            if($this->getModelData()['additionalDiskSize1'] && !$this->additionalDiskService->hasDisk()){
+                $this->additionalDiskService->create($this->getModelData());
+            }
+            fire(new VmCreatedEvent($this->getVmModel()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //vmid
+            $vmid = $this->nextVmid();
+            $vmModel =    $this->getVmModel();
+            $vmModel->vmid = $vmid;
+            $vmModel->save();
+            sl('Vm')->setVm(null);
+            sl('Vm')->setVmModel( $vmModel);
+            $container = [
+                'vmid'   => $vmid,
+                'name'   =>  $this->getVmModel()->name,
+                "ostype" =>  $this->getModelData()['ostype'] ? $this->getModelData()['ostype'] : $this->configuration()->getOsType(),
+            ];
+            //vcpus
+            if ($this->getVmModel()->vcpus)
+            {
+                $container['vcpus'] = $this->getVmModel()->vcpus ;
+            }
+            //cpulimit
+            if ($this->getVmModel()->cpulimit)
+            {
+                $container['cpulimit'] = $this->getVmModel()->cpulimit;
+            }
+            // Boot order device
+            if ($this->configuration()->getBootOrder())
+            {
+                $container['boot'] = $this->configuration()->getBootOrder();
+            }
+            //numa
+            if ($this->configuration()->isNuma())
+            {
+                $container['numa'] = 1;
+            }
+            //Memory
+            if ($this->getVmModel()->memory)
+            {
+                $container['memory'] = $this->getVmModel()->memory;
+            }
+            //cpuunits
+            if ($this->getVmModel()->cpuunits)
+            {
+                $container['cpuunits'] = $this->getVmModel()->cpuunits;
+            }
+            //Name servers
+            $ns = [];
+            for ($i = 0; $i <= 1; $i++)
+            {
+                $n = trim($this->getModelData()['nameserver'][$i]);
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            //sockets
+            if ($this->getVmModel()->sockets)
+            {
+                $container['sockets'] = $this->getVmModel()->sockets;
+            }
+            //cores
+            if ($this->getVmModel()->cores)
+            {
+                $container['cores'] = $this->getVmModel()->cores;
+            }
+            //description
+            if ($this->getModelData()['description'])
+            {
+                $container['description'] = $this->getModelData()['description'];
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //busDevces
+            $busDevices = [
+                "ide"    => 0,
+                "sata"   => 0,
+                "virtio" => 0,
+                "scsi"   => 0
+            ];
+            if (!$this->configuration()->getCdromType())
+            {
+                throw new \InvalidArgumentException("CD-ROM Type is empty, product configuration is not valid");
+            }
+            //CD-ROM primary ISO
+            $type = $this->configuration()->getCdromType();
+            $bus  = $busDevices[$type];
+            $busDevices[$type]++;
+            $isoImage =  $this->getModelData()['isoImage'] ? $this->getModelData()['isoImage'] : "none";
+            $container[$type . $bus] = "{$isoImage},media=cdrom";
+            //secondaryIsoImage
+            if($this->getModelData()['secondaryIsoImage']){
+                $bus  = $busDevices[$type];
+                $busDevices[$type]++;
+                $container[$type . $bus] = "{$this->getModelData()['secondaryIsoImage']},media=cdrom";
+            }
+            //Disk
+            $diskSize = $this->getVmModel()->disk;
+            $type = $this->configuration()->getDiskType();
+            $bus  = $busDevices[$type];
+            $busDevices[$type]++;
+            $harDisk = new HardDisk($type . $bus);
+            $dafaultStorage  = NodeSetting::ofServer($this->getWhmcsParamByKey('serverid'))
+                                           ->ofNode($this->getNode()->getNode())
+                                           ->ofSetting('defaultStorage')
+                                           ->value("value");
+            $harDisk->setSize($diskSize)
+                ->setStorage($dafaultStorage ? $dafaultStorage : $this->configuration()->getDiskStorage())
+                ->setCache($this->configuration()->getDiskCache())
+                ->setFormat($this->configuration()->getDiskFormat())
+                ->setMbps_rd($this->configuration()->getMbpsRd())
+                ->setMbps_wr($this->configuration()->getMbpsWr())
+                ->setDiscard($this->configuration()->isDiscard() ? "on" : null)
+                ->setIops_rd($this->configuration()->getIopsRd())
+                ->setIops_rd_max($this->configuration()->getIopsRdMax())
+                ->setIops_wr($this->configuration()->getIopsWr())
+                ->setIops_wr_max($this->configuration()->getIopsWrMax())
+                ->setReplicate($this->configuration()->isReplicate() ? 0 : null)
+                ->setSsd($this->configuration()->isSsd() ? 1 : null);
+            if ($this->configuration()->isIoThread() && in_array($type, ['virtio', 'scsi']))
+            {
+                $harDisk->setIothread($this->configuration()->isIoThread());
+            }
+            $container[$harDisk->getId()] = $harDisk->asConfig();
+            //Network
+            $container += $this->createNetwork();
+            //acpi
+            if ($this->configuration()->isAcpi())
+            {
+                $container['acpi'] = 1;
+            }
+            //agent
+            if ($this->configuration()->isAgent())
+            {
+                $container['agent'] = 1;
+            }
+            //autostart
+            if ($this->configuration()->isAutostart())
+            {
+                $container['autostart'] = 1;
+            }
+            //balloon
+            if ($this->configuration()->getBalloon())
+            {
+                $container['balloon'] = $this->configuration()->getBalloon();
+            }
+            //shares
+            if ($this->configuration()->getShares())
+            {
+                $container['shares'] = $this->configuration()->getShares();
+            }
+            //cdrom
+            if ($this->configuration()->getCdrom())
+            {
+                $container['cdrom'] = $this->configuration()->getCdrom();
+            }
+            //cpu
+            if ($this->configuration()->getCpu())
+            {
+                $container['cpu'] = $this->configuration()->getCpu();
+            }
+            //freeze
+            if ($this->configuration()->isFreeze())
+            {
+                $container['freeze'] = 1;
+            }
+            //hotplug
+            if ($this->configuration()->getHotplug())
+            {
+                $container['hotplug'] = $this->configuration()->getHotplug();
+            }
+            //keyboard
+            if ($this->configuration()->getKeyboard())
+            {
+                $container['keyboard'] = $this->configuration()->getKeyboard();
+            }
+            //kvm
+            if ($this->configuration()->isKvm())
+            {
+                $container['kvm'] = 1;
+            }
+            //localtime
+            if ($this->configuration()->isLocaltime())
+            {
+                $container['localtime'] = 1;
+            }
+            //migrate_downtime
+            if ($this->configuration()->getMigrateDowntime())
+            {
+                $container['migrate_downtime'] = $this->configuration()->getMigrateDowntime();
+            }
+            //migrate_speed
+            if ($this->configuration()->getMigrateSpeed())
+            {
+                $container['migrate_speed'] = $this->configuration()->getMigrateSpeed();
+            }
+            //reboot
+            if ($this->configuration()->isReboot())
+            {
+                $container['reboot'] = 1;
+            }
+            //startdate
+            if ($this->configuration()->getStartdate())
+            {
+                $container['startdate'] = $this->configuration()->getStartdate();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tablet
+            if ($this->configuration()->isTablet())
+            {
+                $container['tablet'] = 1;
+            }
+            //tdf
+            if ($this->configuration()->isTdf())
+            {
+                $container['tdf'] = 1;
+            }
+            //watchdog
+            if ($this->configuration()->getWatchdog())
+            {
+                $container['watchdog'] = $this->configuration()->getWatchdog();
+            }
+            //bootdisk
+            if ($this->configuration()->getBootdisk())
+            {
+                $container['bootdisk'] = $this->configuration()->getBootdisk();
+            }
+            //scsihw
+            if ($this->configuration()->getScsihw())
+            {
+                $container['scsihw'] = $this->configuration()->getScsihw();
+            }
+            //args
+            if ($this->configuration()->getArgs())
+            {
+                $container['args'] = $this->configuration()->getArgs();
+            }
+            //vga
+            $vga=[];
+            if ($this->configuration()->getVga())
+            {
+                $vga[] = $this->configuration()->getVga();
+            }
+            if($container['vga']!="none" && $this->configuration()->getVgaMemory()){
+                $vga[]="memory=".$this->configuration()->getVgaMemory();
+            }
+            if(!empty($vga)){
+                $container['vga'] = implode(",",$vga);
+            }
+            //xterm.js Console
+            if ($this->configuration()->isPermissionXtermjs())
+            {
+                $container['serial0'] = 'socket';
+            }
+            //cpu flags
+
+            if ($this->configuration()->hasCpuFlags()  && version_compare($this->api()->getVersion(), "5.2", '>'))
+            {
+                $container['cpu'] .= ',flags=' .$this->configuration()->getCpuFlagsAsSource();
+            }
+            //start
+            if ($this->configuration()->isStart())
+            {
+                $container['start'] = 1;
+            }
+            //bios
+            if ($this->configuration()->getBios())
+            {
+                $container['bios'] = $this->configuration()->getBios();
+            }
+            //machine
+            if ($this->configuration()->getMachine())
+            {
+                $container['machine'] = $this->configuration()->getMachine();
+            }
+            //Create
+            $nodeService = new Node($this->getVmModel()->node);
+            $vm          = $nodeService->kvm();
+            $this->setVm($vm);
+            $vm = (new VmFactory())->fromVmModel( $vmModel);
+            //init vm
+            sl('Vm')->setVm($vm);
+            $taskId = $vm->create($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            echo $ex->getMessage();
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+        //task history
+        $this->createTaskHistory($taskId, "Create");
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $nodeService->getNode()]);
+        //sleep
+        $this->sleep();
+        return false;
+    }
+
+
+
+
+}

+ 33 - 0
app/Jobs/Cloud/CreateSnippet.php

@@ -0,0 +1,33 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Factory\Ssh2Factory;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Providers\SnippetProvider;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\CloudInitScriptConveter;
+
+class CreateSnippet extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text)
+    {
+        $this->initParams();
+        $this->initServices();
+        //Get cloud init script
+        $id = $this->getModelData()['cloudInitScript'];
+        $serverConfiguration = new ServerConfigurationRepository($this->getWhmcsParamByKey('serverid'));
+        $ssh = (new Ssh2Factory())->fromServerConfiguration( $serverConfiguration);
+        $cloudInitScript = CloudInitScript::findOrFail($id);
+        $vmModel =  $this->getVmModel();
+        $vmModel->ciuser = $this->getModelData()['ciuser'];
+        $vmModel->sshkeys = $this->getModelData()['sshkeys'];
+        $conveter = new CloudInitScriptConveter($cloudInitScript, $serverConfiguration, $vmModel);
+        $snippetProvider = new SnippetProvider($ssh);
+        $snippet = $conveter->convert();
+        $snippetProvider->create($snippet);
+    }
+}

+ 86 - 0
app/Jobs/Cloud/LoadBalancer/MigrateVmJob.php

@@ -0,0 +1,86 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\LoadBalancer;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class MigrateVmJob extends BaseJob
+{
+    use ProductService;
+    use HostingService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //Migrate
+        if (!$this->getModelData()['taskId'] || ($this->isTask()  && !$this->isTaskRunning() && $this->isFailed()))
+        {
+            $this->highAvailabilityClusterService->delete();
+            $delete = (array)$this->getModelData()['config']['delete'];
+            foreach (sl('Vm')->getVm()->config() as $k => $v)
+            {
+                if (preg_match('/cloudinit/', $v))
+                {
+                    sl('Vm')->getVm()->deleteConfig($k);
+                    $delete[$k] = $v;
+                }
+            }
+            $taskId = sl('Vm')->getVm()->migrate([
+                "target" => $this->getModelData()['targetNode'],
+                "online" => sl('Vm')->getVm()->isRunning() ? 1 : 0,
+            ]);
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "migrate" => true, "config" => ["delete" => $delete]]);
+            $this->log->info(sprintf("VM %s - Migrate", sl('Vm')->getVm()->getVmid()));
+            //sleep
+            $this->sleep(20);
+            return false;
+        }
+        else
+        {
+            if ($this->isDone())
+            {
+                $this->customFieldUpdate("node", $this->getModelData()['targetNode']);
+                sl('Vm')->getVm()->setNode($this->getModelData()['targetNode']);
+                $delete = (array)$this->getModelData()['config']['delete'];
+                foreach ($delete as $k => $v)
+                {
+                    if (preg_match('/cloudinit/', $v))
+                    {
+                        $ex      = explode(":", $v);
+                        $storage = $ex[0];
+                        sl('Vm')->getVm()->updateConfig([$k => "{$storage}:cloudinit,format=raw"]);
+                        unset($delete[$k]);
+                    }
+                }
+                return true;
+            }
+            else
+            {
+                if ($this->isTaskRunning())
+                {
+                    //sleep
+                    $this->sleep(20);
+                    return false;
+                }
+            }
+        }
+        return true;
+
+    }
+
+}

+ 73 - 0
app/Jobs/Cloud/LoadBalancer/ShutdownVmJob.php

@@ -0,0 +1,73 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\LoadBalancer;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class ShutdownVmJob extends BaseJob
+{
+    use ProductService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //shutdown
+        if (!$this->getModelData()['shutdown'] && sl('Vm')->getVm()->isRunning())
+        {
+            $this->highAvailabilityClusterService->delete();
+            $taskId = sl('Vm')->getVm()->shutdown();
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "shutdown" => true]);
+            $this->log->info(sprintf("VM %s - Shutdown", sl('Vm')->getVm()->getVmid()));
+            //sleep
+            $this->sleep(20);
+            return false;
+        }
+        else
+        {
+            if ($this->getModelData()['shutdown'])
+            {
+                //start
+                if ($this->isDone() && !sl('Vm')->getVm()->isRunning())
+                {
+                    return true;
+                }
+                else
+                {
+                    if ($this->getTask()->getStatus() == "running")
+                    {
+                        //sleep
+                        $this->sleep(20);
+                        return false;
+                    }
+                    else
+                    {
+                        if ($this->getTask()->isFalied() && $this->configuration()->isLoadBalancerStopOnUpgrade())
+                        {
+                            $taskId = sl('Vm')->getVm()->stop();
+                            $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "shutdown" => true]);
+                            $this->log->info(sprintf("VM %s - Stop", sl('Vm')->getVm()->getVmid()));
+                            $this->sleep(20);
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+}

+ 59 - 0
app/Jobs/Cloud/LoadBalancer/UpgradeVmJob.php

@@ -0,0 +1,59 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\LoadBalancer;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class UpgradeVmJob extends BaseJob
+{
+    use ProductService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+    protected $ipSetIpFilterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->ipSetIpFilterService           = new IpSetIpFilterService();
+        //Upgrade
+        if ($this->configuration()->isQemu())
+        {
+            $evnet = new QemuUpdateEvent($this->getVmModel());
+            $evnet->isChangeVmPassword(false);
+            fire($evnet);
+        }
+        elseif ($this->configuration()->isLxc())
+        {
+            fire(new LxcUpdateEvent($this->getVmModel()));
+        }
+        //createHaResource
+        if ($this->highAvailabilityClusterService->isConfigured()  )
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        //ip set filter
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetIpFilterService->create();
+        }
+        //start vm
+        if (!sl('Vm')->getVm()->isRunning())
+        {
+            sl('Vm')->getVm()->start();
+        }
+
+    }
+
+}

+ 81 - 0
app/Jobs/Cloud/MigrateVmJob.php

@@ -0,0 +1,81 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class MigrateVmJob extends BaseJob
+{
+    use ProductService;
+    use HostingService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //Migrate
+        if (!$this->getModelData()['taskId'] || ($this->isTask()  && !$this->isTaskRunning() && $this->isFailed())) {
+            if($this->isTask() && $this->isFailed()){
+                $this->log->error(ucfirst($this->getTask()->getExitstatus()));
+            }
+            $this->highAvailabilityClusterService->delete();
+            //delete config
+            $delete = (array)$this->getModelData()['config']['delete'];
+            foreach (sl('Vm')->getVm()->config() as $k => $v) {
+                if (preg_match('/cloudinit/', $v)) {
+                    sl('Vm')->getVm()->deleteConfig($k);
+                    $delete[$k] = $v;
+                }
+            }
+            $taskId = sl('Vm')->getVm()->migrate([
+                "target" => $this->getModelData()['targetNode'],
+                "online" => $this->getModelData()['online'],
+            ]);
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "migrate" => true, "config" => ["delete" => $delete]]);
+            $this->log->info(sprintf("VM %s - Migrate", sl('Vm')->getVm()->getVmid()));
+            //sleep
+            $this->sleep(5);
+            return false;
+        } else if ($this->isDone()) {
+            $this->getVmModel()->node =  $this->getModelData()['targetNode'];
+            $this->getVmModel()->save();
+            sl('Vm')->getVm()->setNode($this->getModelData()['targetNode']);
+            //restore config
+            if(sl('Vm')->getVm() instanceof  Kvm && !sl('Vm')->getVm()->hasCloudInit()){
+                $delete = (array)$this->getModelData()['config']['delete'];
+                foreach ($delete as $k => $v) {
+                    if (preg_match('/cloudinit/', $v)) {
+                        $ex = explode(":", $v);
+                        $storage = $ex[0];
+                        sl('Vm')->getVm()->updateConfig([$k => "{$storage}:cloudinit,format=raw"]);
+                        unset($delete[$k]);
+                    }
+                }
+            }
+            if ($this->highAvailabilityClusterService->isConfigured()) {
+                $this->highAvailabilityClusterService->create();
+            }
+            return true;
+        } else if ($this->isTaskRunning()) {
+            //sleep
+            $this->sleep(5);
+            return false;
+
+        }
+    }
+}

+ 45 - 0
app/Jobs/Cloud/NetworkRate/MaximumVmRateJob.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class MaximumVmRateJob extends BaseJob
+{
+
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->api();
+        $this->initVm();
+        $container = [];
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        foreach (sl('Vm')->getVm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        if (!empty($container))
+        {
+            sl('Vm')->getVm()->updateConfig($container);
+        }
+    }
+}

+ 39 - 0
app/Jobs/Cloud/NetworkRate/MinimumVmRateJob.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class MinimumVmRateJob extends BaseJob
+{
+
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->api();
+        $this->initVm();
+        $container = [];
+        $rate      = $this->configuration()->getMinimumRate();
+        foreach (sl('Vm')->getVm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        if (!empty($container))
+        {
+            sl('Vm')->getVm()->updateConfig($container);
+        }
+    }
+}

+ 65 - 0
app/Jobs/Cloud/RebootVmJob.php

@@ -0,0 +1,65 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class RebootVmJob extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->api();
+        $this->initVm();
+        //shutdown
+        if (!$this->getModelData()['shutdown'] && sl('Vm')->getVm()->isRunning())
+        {
+            $taskId = sl('Vm')->getVm()->shutdown();
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "shutdown" => true]);
+            $this->log->info(sprintf("VM %s - Shutdown", sl('Vm')->getVm()->getVmid()));
+            //sleep
+            $this->sleep();
+            return false;
+        }
+        else
+        {
+            if ($this->getModelData()['shutdown'])
+            {
+                //start
+                if ($this->isDone() && !sl('Vm')->getVm()->isRunning())
+                {
+                    sl('Vm')->getVm()->start();
+                    $this->log->info(sprintf("VM %s - Start", sl('Vm')->getVm()->getVmid()));
+                    return true;
+                    //in progress
+                }
+                else
+                {
+                    if ($this->getTask()->isRunning())
+                    {
+                        $this->log->info(sprintf("VM %s - Waiting for Shutdown ", sl('Vm')->getVm()->getVmid()));
+                        $this->sleep(5);
+                        return false;
+                    }
+                    else
+                    {
+                        $this->putModelDataAndSave(["shutdown" => false]);
+                        //sleep
+                        $this->sleep();
+                        return false;
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 43 - 0
app/Jobs/Cloud/Reinstall/BackupVmJob.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Reinstall;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class BackupVmJob extends BaseJob
+{
+    use ProductService;
+
+    protected function initServices()
+    {
+
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        if ($this->isDone())
+        {
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            $this->sleep(20);
+            return false;
+        }
+        $storage = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $routing = $this->configuration()->isBackupRouting() == "Yes" ? "1" : "0";
+        //backup
+        $taskId = sl('Vm')->getVm()->backup($storage, $routing);
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+        $this->sleep(20);
+        return false;
+    }
+
+
+}

+ 223 - 0
app/Jobs/Cloud/Reinstall/CreateVmJob.php

@@ -0,0 +1,223 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Reinstall;
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\ClusterResourcesRepository;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmReinstalledEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\Job;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CreateVmJob extends BaseJob
+{
+    use ProductService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    protected function initServices()
+    {
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->containerService = new ContainerService();
+        $this->agentService = new AgentService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        //is done
+        if ($this->isTask() && $this->getTask()->isDone()) {
+
+            //update password
+            if ($this->getModelData()['password']) {
+                $vmModel =   $this->getVmModel();
+                $vmModel->password = $this->getModelData()['password'];
+                $vmModel->save();
+            }
+            //reinstalled event
+            if ($this->configuration()->isLxc()) {
+                fire(new LxcUpdateEvent($this->getVmModel()));
+            } elseif ($this->configuration()->isQemu()) {
+                $this->deleteNetwork();
+                if(!sl('Vm')->getVm()->isRunning()){
+                    $this->deleteNetwork();
+                }
+                fire((new QemuUpdateEvent($this->getVmModel()))->setChangeVmPassword(true));
+                //Agent
+                try{
+                    if($this->agentService->isEnabled()){
+                        if(!sl('Vm')->getVm()->isRunning()){
+                            $this->log->info(sprintf("VM %s - Start", sl('Vm')->getVm()->getVmid()));
+                            sl('Vm')->getVm()->start();
+                            $this->sleep(40);
+                            return false;
+                        }
+                        sl('Vm')->getVm()->agent()->ping();
+                        $this->agentService ->passwordUpdate();
+                        $this->agentService ->configureNetwork();
+                    }
+                }catch (ProxmoxApiException $ex){
+                    if(preg_match("/not running/", $ex->getMessage())){
+                        $this->log->info($ex->getMessage());
+                    }else{
+                        $this->log->error($ex->getMessage());
+                    }
+                    //sleep
+                    $this->sleep(30);
+                    return false;
+                }
+            }
+            fire(new VmReinstalledEvent($this->getVmModel()));
+            return true;
+        } else if ($this->isTask() && $this->getTask()->isRunning()) {
+            $this->sleep(20);
+            return false;
+        } else if ($this->isTask() && $this->getTask()->isFalied()) {
+            if ($this->configuration()->isBackupVmBeforeReinstall()) {
+                $this->log->error($this->getTask()->getExitstatus());
+                $this->restoreJob();
+                $this->model->setStatus(Job::STATUS_FAILED);
+                return;
+            }
+            throw new \Exception($this->getTask()->getExitstatus());
+        } //create vm
+        else if (!$this->isTask() && $this->configuration()->isLxc()) {
+            try {
+                $container = $this->getParentModelData()['container'];
+                unset($container['mp1'],$container['mp2']);
+                unset($container['lxc'], $container['parent']);
+                //pool
+                if ($this->configuration()->getPool()) {
+                    $container['pool'] = $this->configuration()->getPool();
+                }
+                $node = new Node(sl('Vm')->getVm()->getNode());
+                $node->setApi($this->api());
+                $taskId = $node->lxc()->create($container);
+                //save task id
+                $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+                //task history
+                $this->createTaskHistory($taskId, "Create");
+            } catch (\Exception $ex) {
+                $this->log->error($ex->getMessage());
+                $this->restoreJob();
+                return false;
+            }
+            //clone vm
+        } else if (!$this->isTask() && $this->configuration()->isQemu()) {
+            try {
+                $osTemplate = $this->getModelData()['osTemplate'];
+                //Support for configurable options i.e vmname|OS Name
+                if (is_string($osTemplate) && !preg_match('/\//', $osTemplate)) {
+                    $templateNode = sl('Vm')->getVm()->getNode();
+                    $clusterRepository = new ClusterResourcesRepository();
+                    $clusterRepository->setApi($this->api());
+                    $clusterRepository->findByNodes([$templateNode])
+                        ->findKvmTemplate();
+                    foreach ($clusterRepository->fetch() as $resurce) {
+                        if ($resurce->getName() == $osTemplate && $templateNode == $resurce->getNode()) {
+                            $templateVmid = $resurce->getVmid();
+                            break;
+                        }
+                    }
+                    if (!$templateVmid) {
+                        throw new \Exception(sprintf("Unable to find KVM  template: %s on node: %s", $osTemplate, $templateNode));
+                    }
+                    //Support for configurable options like nodename/vmid|OS Name
+                } elseif (preg_match('/\//', $osTemplate)) {
+                        list($templateNode, $templateVmid) = explode("/", $osTemplate);
+                    }
+                //init container
+                $container = [
+                    "newid" => sl('Vm')->getVm()->getVmid(),
+                    "full" => $this->configuration()->getCloneMode(),
+                    "target" => sl('Vm')->getVm()->getNode()
+                ];
+                //description
+                if ($this->getModelData()['description'])
+                {
+                    $container['description'] = $this->getModelData()['description'];
+                }
+                //hostname
+                $hostname = $this->getVmModel()->name;
+                if ($hostname) {
+                    $container['name'] = $hostname;
+                }
+                //Storage
+                if (!$this->configuration()->isCloneOnTheSameStorage() && $this->configuration()->getDiskStorage() && $this->configuration()->getCloneMode() == "1") {
+                    $container['storage'] = $this->configuration()->getDiskStorage();
+                }
+                //pool
+                if ($this->configuration()->getPool()) {
+                    $container['pool'] = $this->configuration()->getPool();
+                }
+                //Create
+                $template = new Kvm($templateNode, $templateVmid);
+                $template->setApi($this->api());
+                $taskId = $template->cloneVm($container);
+
+                //save task id
+                $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode(), "templateNode" => $templateNode]);
+                //task history
+                $this->createTaskHistory($taskId, "Create");
+            } catch (\Exception $ex) {
+                if ($this->configuration()->isBackupVmBeforeReinstall()) {
+                    $this->log->error($ex->getMessage());
+                    $this->restoreJob();
+                    $this->model->setStatus(Job::STATUS_FAILED);
+                    return;
+                }
+                throw $ex;
+            }
+        }
+        $this->sleep(20);
+        return false;
+    }
+
+    private function restoreJob()
+    {
+        //create restore job
+        $storage = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->findBackup(sl('Vm')->getVm())
+            ->findByStorages([$storage]);
+        if (!$fileRepository->count()) {
+            return;
+        }
+        queue(RestoreVm::class, [], null, "hosting", $this->getWhmcsParamByKey("serviceid"));
+        $this->model->setStatus(Job::STATUS_CANCELLED)->save();
+        return false;
+    }
+
+    private function  deleteNetwork(){
+
+        $deleteNetwork = [];
+        foreach (sl('Vm')->getVm()->getNetworkDevices() as $networkDevice){
+            if(VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                ->ofVmId(sl('Vm')->getVmModel()->id)
+                ->ofNet($networkDevice->getId())->count()){
+                continue;
+            }
+            $deleteNetwork[]=$networkDevice->getId();
+        }
+        if(!empty($deleteNetwork)){
+            sl('Vm')->getVm()->deleteConfig(implode(",",$deleteNetwork));
+        }
+    }
+}

+ 103 - 0
app/Jobs/Cloud/Reinstall/DeleteVmJob.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Reinstall;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class DeleteVmJob extends BaseJob
+{
+    use ProductService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    protected function initServices()
+    {
+
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->containerService               = new ContainerService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        if ($this->isDone())
+        {
+            //Reset IP addresses usage
+            if($this->configuration()->isQemu()){
+                VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                    ->ofVmId(sl('Vm')->getVmModel()->id)
+                    ->update(['net' => "" ]);
+                VirtualInterface::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                    ->ofVmId(sl('Vm')->getVmModel()->id)
+                    ->update(['net' => "" ]);
+            }
+            return true;
+        }
+        else
+        {
+            if ($this->isTaskRunning())
+            {
+                $this->sleep(20);
+                return false;
+            }
+        }
+        //Delete HA
+        if ($this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->delete();
+        }
+        if (sl('Vm')->getVm()->isRunning())
+        {
+            sl('Vm')->getVm()->stop();
+            sleep(15);
+        }
+        //vm protection
+        if (sl('Vm')->getVm()->config()['protection'] == "1")
+        {
+            sl('Vm')->getVm()->protectionOff();
+            sleep(1);
+        }
+        //vm container
+        $this->saveVmContainer();
+        //delete vm
+        $taskId = sl('Vm')->getVm()->delete();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+        //task history
+        $this->createTaskHistory($taskId, "Destroy");
+        $this->sleep(10);
+        return false;
+    }
+
+    private function saveVmContainer()
+    {
+        if ($this->getModelData()['container'])
+        {
+            return;
+        }
+        if ($this->configuration()->isLxc())
+        {
+            $container               = sl('Vm')->getVm()->getReinstallConfig();
+            $container['vmid']       = sl('Vm')->getVm()->getVmid();
+            $container['ostemplate'] = $this->getModelData()['osTemplate'];
+            $container['password']   = decrypt($this->getModelData()['password']);
+            //SSH Public key.
+            if ($this->configuration()->isSshKeyPairs())
+            {
+                $container['ssh-public-keys'] = $this->containerService->makeKeyPairs()->getPublic();
+            }
+            $this->putModelDataAndSave(['container' => $container]);
+        }
+    }
+}

+ 46 - 0
app/Jobs/Cloud/Reinstall/RestoreVm.php

@@ -0,0 +1,46 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\Reinstall;
+
+
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class RestoreVm extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->initVm();
+        if ($this->isTaskRunning())
+        {
+            $this->sleep(20);
+            return false;
+        }
+        //get backup
+        $storage        = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->findBackup(sl('Vm')->getVm())
+            ->findByStorages([$storage]);
+        $backup = $fileRepository->fetchLast();
+        //done process is finished
+        if ($this->isDone())
+        {
+            $backup->delete();
+            return true;
+        }
+        //restore
+        $taskId = sl('Vm')->getVm()->restore($backup->getVolid());
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+        $this->sleep(20);
+        return false;
+    }
+
+}

+ 48 - 0
app/Jobs/Cloud/RestoreVm.php

@@ -0,0 +1,48 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class RestoreVm extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->api();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        if ($this->isDone())
+        {
+            if ($this->configuration()->isQemu())
+            {
+                fire(new QemuUpdateEvent($this->getVmModel()));
+            }
+            if (sl('Vm')->getVm()->isRunning())
+            {
+                sl('Vm')->getVm()->start();
+            }
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        $taskId = sl('Vm')->getVm()->restore($this->getModelData()['volid']);
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+        //sleep
+        $this->sleep(5);
+        return false;
+    }
+}

+ 93 - 0
app/Jobs/Cloud/SnapshotVmJob.php

@@ -0,0 +1,93 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Cloud;
+
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Snapshot;
+use MGProvision\Proxmox\v2\repository\SnapshotRepository;
+use ModulesGarden\ProxmoxAddon\App\Jobs\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\SnapshotJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class SnapshotVmJob extends BaseJob
+{
+
+    use ProductService;
+
+    /**
+     * @var SnapshotJob
+     */
+    private  $snapshotJob;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->initVm();
+        if ($this->isDone())
+        {
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        $this->clean();
+        $snapshot = new Snapshot();
+        $snapshot->setApi($this->api());
+        $snapshot->setPath(sl('Vm')->getVm()->getPath() . "/snapshot");
+        $snapshot->setAttributes([
+            "name"        => $this->getSnapshotJob()->name."_".$this->model->id,
+            "description" => $this->snapshotJob->description,
+        ]);
+        if (!is_null($this->snapshotJob->vmstate ) && sl('Vm')->getVm() instanceof  Kvm)
+        {
+            $snapshot->setVmstate($this->snapshotJob->vmstate );
+        }
+        $taskId =  $snapshot->create();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => sl('Vm')->getVm()->getNode()]);
+        //sleep
+        $this->sleep(5);
+        return false;
+    }
+
+    /**
+     * @return SnapshotJob
+     */
+    private  function getSnapshotJob(){
+        return $this->snapshotJob = SnapshotJob::findOrFail($this->getModelData()['snapshotJobId']);
+    }
+
+    private function clean(){
+        $limit = $this->getWhmcsConfigOption(ConfigurableOption::SNAPSHOTS, $this->configuration()->getSnapshotMaxFiles());
+        if($limit=="-1"){
+            return;
+        }
+        if(!$limit || !is_numeric($limit)){
+            throw new \InvalidArgumentException("Snapshot limit cannot be empty");
+        }
+        $snapshotRepository = new SnapshotRepository();
+        $snapshotRepository->setApi($this->api());
+        $snapshotRepository->findByVm(sl('Vm')->getVm());
+        $snapshotRepository->ignoreCurrent(true);
+        if($snapshotRepository->count() < $limit){
+            return;
+        }
+        $toDelete = (int) $snapshotRepository->count() - $limit;
+        foreach ($snapshotRepository->sortBySnaptime()->fetch() as $entity)
+        {
+            if($toDelete <=0){
+                break;
+            }
+            $entity->delete();
+            $toDelete --;
+        }
+    }
+}

+ 42 - 0
app/Jobs/Vps/Agent/ChangePasswordJob.php

@@ -0,0 +1,42 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Agent;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class ChangePasswordJob extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        try{
+            if(!$this->vm()->isRunning()){
+                $this->log->info(sprintf("VM %s - Start", $this->vm()->getVmid()));
+                $this->vm()->start();
+                $this->sleep(40);
+                return false;
+            }
+            $this->vm()->agent()->ping();
+            $this->agentService->passwordUpdate();
+
+        }catch (ProxmoxApiException $ex){
+            if(preg_match("/not running/", $ex->getMessage())){
+                $this->log->info($ex->getMessage());
+            }else{
+                $this->log->error($ex->getMessage());
+            }
+            //sleep
+            $this->sleep(30);
+            return false;
+        }
+    }
+
+}

+ 42 - 0
app/Jobs/Vps/Agent/ConfigureNetworkJob.php

@@ -0,0 +1,42 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Agent;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class ConfigureNetworkJob extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        try{
+            if(!$this->vm()->isRunning()){
+                $this->log->info(sprintf("VM %s - Start", $this->vm()->getVmid()));
+                $this->vm()->start();
+                $this->sleep(40);
+                return false;
+            }
+            $this->vm()->agent()->ping();
+            $this->agentService->configureNetwork();
+
+        }catch (ProxmoxApiException $ex){
+            if(preg_match("/not running/", $ex->getMessage())){
+                $this->log->info($ex->getMessage());
+            }else{
+                $this->log->error($ex->getMessage());
+            }
+            //sleep
+            $this->sleep(30);
+            return false;
+        }
+    }
+
+}

+ 385 - 0
app/Jobs/Vps/BaseJob.php

@@ -0,0 +1,385 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\models\Node;
+use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
+use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ActivityLog;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ToDoList;
+use ModulesGarden\ProxmoxAddon\App\Repositories\RangeVmRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalMountPointService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Configuration;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Job;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+
+class BaseJob extends Job
+{
+    use ApiService;
+    use WhmcsParams;
+    use HostingService;
+    use Smarty;
+
+    protected $params = [];
+
+    /**
+     * @var EmailService
+     */
+    protected $emailService;
+    /**
+     * @var ContainerService
+     */
+    protected $containerService;
+
+    /**
+     * @var AgentService
+     */
+    protected $agentService;
+    /**
+     * @var AdditionalDiskService
+     */
+    protected $additionalDiskService;
+
+    /**
+     * @var AdditionalMountPointService
+     */
+    protected $additionalMountPointService;
+
+    protected function initServices()
+    {
+        $this->emailService     = new EmailService();
+        $this->containerService = new ContainerService();
+        $this->agentService = new AgentService();
+        $this->additionalDiskService = new AdditionalDiskService();
+        $this->additionalMountPointService  = new AdditionalMountPointService();
+
+    }
+
+    public function initParams()
+    {
+        if (!$this->model->rel_id)
+        {
+            new \InvalidArgumentException(sprintf("Job model: #%s rel_id cannot be empty", $this->model->id));
+        }
+        if (!function_exists('ModuleBuildParams'))
+        {
+            require_once ModuleConstants::getFullPathWhmcs('includes') . DIRECTORY_SEPARATOR . "modulefunctions.php";
+        }
+        $this->params = \ModuleBuildParams($this->model->rel_id);
+        sl("whmcsParams")->setParams($this->params);
+        \ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl("whmcsParams")->setParams($this->params);
+        $this->setHostingId($this->params['serviceid']);
+        return $this;
+    }
+
+    protected function sleep($seconds = 60)
+    {
+        $this->model->setWaiting();
+        $this->model->setRetryAfter(date("Y-m-d H:i:s", strtotime("+{$seconds} seconds")));
+        $this->model->increaseRetryCount();
+    }
+
+    protected function vmidExistInWhmcs($vmid)
+    {
+        //vps
+        $cfv   = 'tblcustomfieldsvalues';
+        $cfn   = 'tblcustomfields';
+        $h     = 'tblhosting';
+        $query = DB::table($cfv)
+            ->rightJoin($cfn, "{$cfn}.id", "=", "{$cfv}.fieldid")
+            ->leftJoin($h, "{$h}.id", "=", "{$cfv}.relid")
+            ->where("{$cfn}.fieldname", "like", "vmid%")
+            ->where("{$cfv}.value", $vmid)
+            ->whereIn("{$h}.domainstatus", ['Active', "Suspended"]);
+        if ($query->count())
+        {
+            return true;
+        }
+        //cloud
+        try
+        {
+            $query = VmModel::where("vmid", $vmid);
+            return $query->count() > 0;
+        }
+        catch (\Exception $ex)
+        { // table does not exist
+        }
+        return false;
+    }
+
+    protected function findFreeVmid($vmid)
+    {
+        for ($i = $vmid; $i <= 1000000; $i++)
+        {
+            if ($this->vmidExistInWhmcs($i))
+            {
+                continue;
+            }
+            try
+            {
+                $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
+            }
+            catch (\Exception $ex)
+            {
+                continue;
+            }
+            if ($res == $i)
+            {
+                return $i;
+            }
+        }
+        throw new \Exception("Unable to obtain vmid");
+    }
+
+    protected function isVmRange()
+    {
+        if (RangeVm::ofServerId($this->getWhmcsParamByKey("serverid"))->count())
+        {
+            return true;
+        }
+        return Configuration::where("setting", "proxmoxVPSMinimumVMID")->count() > 0;
+    }
+
+    protected function nextVmid()
+    {
+        $data = $this->api()->get("/cluster/nextid", []);
+        $vmid = (int)$data ? (int)$data : 100;
+        $vmid = $this->findFreeVmid($vmid);
+        if ($this->isVmRange())
+        {
+            $rageVm = new RangeVmRepository($this->getWhmcsParamByKey('serverid'));
+            if (!$rageVm->has() && !$rageVm->getMin())
+            {
+                return $vmid;
+            }
+            else
+            {
+                if (!$rageVm->getMax() && $rageVm->getMin())
+                {
+                    $from = (int)$rageVm->getMin();
+                    $to   = (int)$rageVm->getMin() * 100;
+                }
+                else
+                {
+                    $from = (int)$rageVm->getMin();
+                    $to   = (int)$rageVm->getMax();
+                }
+            }
+            for ($i = $from; $i <= $to; $i++)
+            {
+                try
+                {
+                    if ($this->vmidExistInWhmcs($i))
+                    {
+                        continue;
+                    }
+                    $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
+                    if ((int)$res == $i)
+                    {
+                        $vmID = $i;
+                        break;
+                    }
+                }
+                catch (\Exception $ex)
+                {
+                    continue;
+                }
+            }
+            if (!$vmID)
+            {
+                throw new \Exception("VM Ranges have been exited for this server. Please setup VM Ranges", 321);
+            }
+        }
+        return $vmID ? $vmID : $vmid;
+    }
+
+    protected function createTaskHistory($taskId, $action)
+    {
+        $type = str_replace(["qemu", "lxc"], ["VM", "CT"], $this->vm()->getVirtualization());
+        $task = new TaskHistory();
+        $task->fill([
+            'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
+            'upid'       => $taskId,
+            'name'       => sprintf("%s %s - %s", $type, $this->vm()->getVmid(), $action),
+            'vmid'       => $this->vm()->getVmid(),
+            'node'       => $this->vm()->getNode(),
+            'status'     => 0
+        ])->save();
+    }
+
+    protected function getModelData()
+    {
+        return unserialize($this->model->data);
+    }
+
+    protected function putModelDataAndSave(array $newData)
+    {
+
+        $data = $this->getModelData();
+        $data += $newData;
+        $this->setModelDataAndSave($data);
+        return $this;
+    }
+
+    protected function setModelDataAndSave(array $data)
+    {
+        $this->model->data = serialize($data);
+        $this->model->save();
+        return $this;
+    }
+
+    /**
+     * @return \MGProvision\Proxmox\v2\models\Task
+     * @throws \MGProvision\Proxmox\v2\ProxmoxApiException
+     */
+    protected function getTask()
+    {
+        $taskId = $this->getModelData()['taskId'];
+        //Init API service
+        if($this->getModelData()['templateNode']){
+            $node = new Node($this->getModelData()['templateNode']);
+        }else{
+            $node = new Node($this->getModelData()['node']);
+        }
+        $node->setApi($this->api());
+        return $node->task($taskId);
+    }
+
+    protected function isTask()
+    {
+        return !is_null($this->getModelData()['taskId']);
+    }
+
+    protected function isDone()
+    {
+        $taskId = $this->getModelData()['taskId'];
+        if (!$taskId || !$this->getModelData()['node'])
+        {
+            return false;
+        }
+        $task = $this->getTask();
+        if ($task->getStatus() == "running")
+        {
+            $this->log->success(sprintf("Waiting to finish. Task Id %s Node: %s ", $task->getUpid(), $task->getNode()));
+            return false;
+        }
+        //Failed
+        if ($task->getExitstatus() && $task->getExitstatus() != "OK")
+        {
+            $this->log->error($task->getExitstatus());
+            $this->failed($task->getExitstatus());
+            $data = $this->getModelData();
+            unset($data['taskId'], $data['node']);
+            $this->setModelDataAndSave($data);
+            return false;
+        }
+        return true;
+    }
+
+    protected function isTaskRunning()
+    {
+        return $this->isTask() && $this->getTask()->isRunning();
+    }
+
+    protected function isFailed()
+    {
+        $task = $this->getTask();
+        return $task->getExitstatus() && $task->getExitstatus() != "OK";
+    }
+
+    protected function failed($error)
+    {
+
+        if ((int)$this->model->retry_count != 21)
+        {
+            return;
+        }
+        if (!preg_match("/Create/", $this->model->job) || preg_match("/Clone/", $this->model->job))
+        {
+            return;
+        }
+        //create new entery on to do list
+        if ($this->configuration()->isToDoList())
+        {
+            $title = sprintf('Creation Failed - Service ID: %s', $this->getWhmcsParamByKey('serviceid'));
+            if (ToDoList::ofTitle($title)->pending()->count())
+            {
+                return;
+            }
+            $entity = new ToDoList();
+            $entity->fill(
+                [
+                    'date'        => date("Y-m-d H:i:s"),
+                    "duedate"     => date("Y-m-d H:i:s"),
+                    'title'       => $title,
+                    "status"      => "Pending",
+                    "description" => $error,
+                    "admin"       => 0,
+                ]
+            );
+            $entity->save();
+        }
+        //send admin message
+        if ($this->configuration()->getServiceCreationFailedTemplateId())
+        {
+            //init email template
+            $this->emailService->template($this->configuration()->getServiceCreationFailedTemplateId());
+            //check if already send
+            $description = printf('Email Sent to Admin (%s) - Service ID: %s', $this->emailService->getVars()['messagename'], $this->getWhmcsParamByKey('serviceid'));
+            if (ActivityLog::ofDescription($description)->today()->count() > 0)
+            {
+                return;
+            }
+            //email vars
+            global $customadminpath;
+            $adminDir      = $customadminpath ? $customadminpath : "admin";
+            $adminAreaLink = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}";
+            $hosting       = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}/clientsservices.php?id=" . $this->getWhmcsParamByKey('serviceid');
+            $emailVars     = [
+                "client_id"        => $this->getWhmcsParamByKey('clientsdetails')['id'],
+                "service_id"       => $this->getWhmcsParamByKey('serviceid'),
+                "service_product"  => "<a href='{$hosting}/'>{$hosting}</a>",
+                "service_domain"   => $this->getWhmcsParamByKey('domain'),
+                "error_msg"        => $error,
+                "whmcs_admin_link" => "<a href='{$adminAreaLink}/'>{$adminAreaLink}</a>",
+            ];
+            $this->emailService->vars($emailVars)->sendToAdmin();
+            logActivity($description);
+        }
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\App\Models\Job:
+     */
+    protected function getParentModel()
+    {
+        if (is_null($this->model->parent_id))
+        {
+            throw new \InvalidArgumentException("The Parent Id is not valid");
+        }
+        if ($this->parent)
+        {
+            return $this->parent;
+        }
+        return $this->parent = \ModulesGarden\ProxmoxAddon\App\Models\Job::ofId($this->model->parent_id)->firstOrFail();
+    }
+
+    protected function getParentModelData()
+    {
+        return unserialize($this->getParentModel()->data);
+    }
+
+}

+ 198 - 0
app/Jobs/Vps/CloneQemuJob.php

@@ -0,0 +1,198 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\ClusterResourcesRepository;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Factory\Ssh2Factory;
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Providers\SnippetProvider;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\CloudInitScriptConveter;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class CloneQemuJob extends BaseJob
+{
+
+    use ProductService;
+    use UserService;
+
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //create task validation
+        if ($this->isDone())
+        {
+            if(!$this->vm()->isRunning()){
+                $this->deleteNetwork();
+            }
+            if($this->configuration()->isAdditionalDisk() &&   !$this->additionalDiskService->hasDisk() ){
+                $this->additionalDiskService->create();
+            }
+            fire(new QemuUpdateEvent($this->vm()));
+            try{
+
+                if($this->agentService->isEnabled()){
+                    if(!$this->vm()->isRunning()){
+                        $this->log->info(sprintf("VM %s - Start", $this->vm()->getVmid()));
+                        $this->vm()->start();
+                        $this->sleep(5);
+                        return false;
+                    }
+                    $this->vm()->agent()->ping();
+                    $this->agentService ->getUserAndUpdate();
+                    $this->agentService ->passwordUpdate();
+                    $this->agentService ->configureNetwork();
+                }
+            }catch (ProxmoxApiException $ex){
+                if(preg_match("/not running/", $ex->getMessage())){
+                    $this->log->info($ex->getMessage());
+                }else{
+                    $this->log->error($ex->getMessage());
+                }
+                //sleep
+                $this->sleep(5);
+                return false;
+            }
+            fire(new VmCreatedEvent($this->vm()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            $osTemplate = $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate() );
+            //Support for configurable options i.e vmname|OS Name
+            if (is_string($osTemplate) && !preg_match('/\//', $osTemplate))
+            {
+                $templateNode      = $this->getNode()->getNode();
+                $clusterRepository = new ClusterResourcesRepository();
+                $clusterRepository->setApi($this->api());
+                if(!$this->configuration()->isOsTemplatesInAllNodes()){
+                    $clusterRepository->findByNodes([$templateNode]);
+                }
+                $clusterRepository->findKvmTemplate();
+                foreach ($clusterRepository->fetch() as $resurce)
+                {
+                    if ($resurce->getName() == $osTemplate)
+                    {
+                        $templateVmid = $resurce->getVmid();
+                        $templateNode = $resurce->getNode();
+                        break;
+                    }
+                }
+                if (!$templateVmid)
+                {
+                    throw new \Exception(sprintf("Unable to find KVM template: %s on node: %s", $osTemplate,  $templateNode ));
+                }
+                //Support for configurable options like nodename/vmid|OS Name
+            }
+            else
+            {
+                if (preg_match('/\//', $osTemplate))
+                {
+                    list($templateNode, $templateVmid) = explode("/", $osTemplate);
+                }
+            }
+            //node
+            $node = $this->getWhmcsCustomField(CustomField::NODE) ?  $this->getWhmcsCustomField(CustomField::NODE) :$this->getNode()->getNode();
+            //vmid
+            $vmid = $this->nextVmid();
+            $this->customFieldUpdate("vmid", $vmid);
+            $this->customFieldUpdate("node", $node);
+            //init container
+            $container = [
+                "newid"  => $vmid,
+                "full"   => $this->configuration()->getCloneMode(),
+                "target" => $node
+            ];
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //hostname
+            $hostname = $this->containerService->hostname();
+            if ($hostname)
+            {
+                $container['name'] = $hostname;
+            }
+            //Storage
+            if (!$this->configuration()->isCloneOnTheSameStorage() && $this->configuration()->getDiskStorage() && $this->configuration()->getCloneMode() == "1")
+            {
+                $container['storage'] = $this->configuration()->getDiskStorage();
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //bwlimit
+            if($this->configuration()->getBwLimit()){
+                $container['bwlimit'] = $this->configuration()->getBwLimit();
+            }
+            //Create
+            $template = new Kvm($templateNode, $templateVmid);
+            $template->setApi($this->api());
+            $taskId = $template->cloneVm($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            $this->failed($ex->getMessage());
+            throw $ex;
+        }
+        //task history
+        $task = new TaskHistory();
+        $task->fill([
+            'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
+            'upid'       => $taskId,
+            'name'       => sprintf("VM %s - %s", $vmid, "Clone"),
+            'vmid'       => $template->getVmid(),
+            'node'       => $template->getNode(),
+            'status'     => 0
+        ])->save();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $template->getNode(), "templateNode" => $templateNode]);
+        //sleep
+        $this->sleep();
+        return false;
+    }
+
+    private function  deleteNetwork(){
+
+        $deleteNetwork = [];
+        foreach ($this->vm()->getNetworkDevices() as $networkDevice){
+            if(VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))->ofNet($networkDevice->getId())->count()){
+                continue;
+            }
+            $deleteNetwork[]=$networkDevice->getId();
+        }
+        if(!empty($deleteNetwork)){
+            $this->vm()->deleteConfig(implode(",",$deleteNetwork));
+        }
+    }
+
+
+}

+ 239 - 0
app/Jobs/Vps/CreateLxcJob.php

@@ -0,0 +1,239 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Features;
+use MGProvision\Proxmox\v2\models\Node;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class CreateLxcJob extends BaseJob
+{
+
+    use ProductService;
+    use UserService;
+
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //create task validation
+        if ($this->isDone())
+        {
+            if($this->configuration()->isMountPoint()){
+                $this->additionalMountPointService->create();
+            }
+            fire(new VmCreatedEvent($this->vm()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //vmid
+            $vmid = $this->nextVmid();
+            $this->customFieldUpdate("vmid", $vmid);
+            $container = [
+                'vmid'       => $vmid,
+                'ostemplate' => $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate()),
+                'hostname'   => $this->containerService->hostname(),
+                'password'   => $this->getWhmcsParamByKey('password'),
+            ];
+            //Configurable Options
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit()) )
+            {
+                $container['cpulimit'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit());
+            }
+            //Memory
+            $container['memory'] = $this->configuration()->getMemory();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $container['memory'] = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($container['memory'], $this->configuration()->getMemoryUnit(), 'mb');
+            }
+            //SWAP
+            $container['swap'] = $this->configuration()->getSwap();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::SWAP))
+            {
+                $container['swap'] = $this->getWhmcsConfigOption(ConfigurableOption::SWAP);
+                Utility::unitFormat($container['swap'], $this->configuration()->getSwapUnit(), 'mb');
+            }
+            //cpuunits
+            $container['cpuunits'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits() );
+            //Name servers
+            $ns = [];
+            for ($i = 1; $i <= 2; $i++)
+            {
+                $n = trim($this->hosting()->{"ns{$i}"});
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            //cores
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CORES, $this->configuration()->getCores()))
+            {
+                $container['cores'] =$this->getWhmcsConfigOption(ConfigurableOption::CORES, $this->configuration()->getCores());
+            }
+            //arch
+            if ($this->configuration()->getArch())
+            {
+                $container['arch'] = $this->configuration()->getArch();
+            }
+            //cmode
+            if ($this->configuration()->getCmode())
+            {
+                $container['cmode'] = $this->configuration()->getCmode();
+            }
+            //console
+            if ($this->configuration()->isConsole())
+            {
+                $container['console'] = 1;
+            }
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //ostype
+            if ($this->configuration()->getOsType())
+            {
+                $container['ostype'] = $this->configuration()->getOsType();
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //protection
+            if ($this->configuration()->isProtection())
+            {
+                $container['protection'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //storage
+            $dafaultStorage  = NodeSetting::ofServer($this->getWhmcsParamByKey('serverid'))
+                                           ->ofNode($this->getNode()->getNode())
+                                           ->ofSetting('defaultStorage')
+                                           ->value("value");
+            $container['storage'] = $dafaultStorage ? $dafaultStorage: $this->configuration()->getStorage();
+            //tty
+            if ($this->configuration()->getTty())
+            {
+                $container['tty'] = $this->configuration()->getTty();
+            }
+            //unprivileged
+            $container['unprivileged'] = $this->configuration()->isUnprivileged() ? 1 : 0;
+            //Disk
+            $diskSize = $this->configuration()->getDiskSize();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+            {
+                $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
+            }
+            //Rootfs
+            $container['rootfs'] = "{$container['storage']}:{$diskSize}";
+            //SSH Public key.
+            if ($this->configuration()->isSshKeyPairs())
+            {
+                $container['ssh-public-keys'] = $this->containerService->makeKeyPairs()->getPublic();
+            }
+            //Network
+            $networkService = new NetworkService();
+            $container      += $networkService->buildLxc();
+            //start
+            if ($this->configuration()->isStart()  && version_compare($this->api()->getVersion(), "4.4", '>'))
+            {
+                $container['start'] = 1;
+            }
+            //features
+            $features = new Features();
+            //Keyctl
+            if($this->configuration()->isFeatureKeyctl() && $this->configuration()->isUnprivileged()){
+                $features->setKeyctl(1);
+            }
+            //Nesting
+            if($this->configuration()->isFeatureNesting() ){
+                $features->setNesting(1);
+            }
+            //NFS
+            if($this->configuration()->isFeatureNfs() ){
+                $features->addNfs();
+            }
+            //CIFS
+            if($this->configuration()->isFeatureCifs() ){
+                $features->addCifs();
+            }
+            //Fuse
+            if($this->configuration()->isFeatureFuse() ){
+                $features->setFuse(1);
+            }
+            //Mknod
+            if($this->configuration()->isFeatureMknod() ){
+                $features->setMknod(1);
+            }
+            if(!$features->isEmpty()){
+                $container[Features::ID] = $features->asConfig();
+            }
+            //Create
+            $nodeService = $this->getWhmcsCustomField(CustomField::NODE)? new Node($this->getWhmcsCustomField(CustomField::NODE)) : $this->getNode();
+            $vm          = $nodeService->lxc();
+            $this->setVm($vm);
+            $taskId = $vm->create($container);
+            $this->customFieldUpdate("node", $nodeService->getNode());
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            $this->failed($ex->getMessage());
+            throw $ex;
+        }
+        //task history
+        $this->createTaskHistory($taskId, "Create");
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $nodeService->getNode()]);
+        //sleep
+        $this->sleep();
+        return false;
+
+    }
+
+
+}

+ 383 - 0
app/Jobs/Vps/CreateQemuJob.php

@@ -0,0 +1,383 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\HardDisk;
+use MGProvision\Proxmox\v2\models\Node;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class CreateQemuJob extends BaseJob
+{
+    use ProductService;
+    use UserService;
+
+    private $networkService;
+
+    protected function initServices()
+    {
+        $this->emailService     = new EmailService();
+        $this->containerService = new ContainerService();
+        $this->networkService   = new NetworkService();
+        $this->additionalDiskService = new AdditionalDiskService();
+    }
+
+    public function handle()
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //create task validation
+        if ($this->isDone())
+        {
+            if($this->configuration()->isAdditionalDisk()){
+                $this->additionalDiskService->create();
+            }
+            fire(new VmCreatedEvent($this->vm()));
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //vmid
+            $vmid = $this->nextVmid();
+            $this->customFieldUpdate("vmid", $vmid);
+            $container = [
+                'vmid'   => $vmid,
+                'name'   => $this->containerService->hostname(),
+                "ostype" => $this->getWhmcsConfigOption(ConfigurableOption::OS_TYPE, $this->configuration()->getOsType())
+            ];
+            //vcpus
+            if ($this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus() ))
+            {
+                $container['vcpus'] = $this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus() );
+            }
+            //cpulimit
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() ))
+            {
+                $container['cpulimit'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() );
+            }
+            // Boot order device
+            if ($this->configuration()->getBootOrder())
+            {
+                $container['boot'] = $this->configuration()->getBootOrder();
+            }
+            //numa
+            if ($this->configuration()->isNuma())
+            {
+                $container['numa'] = 1;
+            }
+            //Memory
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $container['memory'] = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($container['memory'], $this->configuration()->getMemoryUnit(), 'mb');
+            }
+            else if($this->configuration()->getMemory())
+            {
+                $container['memory'] = $this->configuration()->getMemory();
+            }
+            //cpuunits
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits() ))
+            {
+                $container['cpuunits'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits() );
+            }
+            //Name servers
+            $ns = [];
+            for ($i = 1; $i <= 2; $i++)
+            {
+                $n = trim($this->hosting()->{"ns{$i}"});
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            //sockets
+            if ($this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets() ))
+            {
+                $container['sockets'] = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets() );
+            }
+            //cores
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores() ))
+            {
+                $container['cores'] = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores() );
+            }
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //pool
+            if ($this->configuration()->getPool())
+            {
+                $container['pool'] = $this->configuration()->getPool();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //busDevces
+            $busDevices = [
+                "ide"    => 0,
+                "sata"   => 0,
+                "virtio" => 0,
+                "scsi"   => 0
+            ];
+            //CD-ROM
+            if ($this->configuration()->getCdromType())
+            {
+                $iso  = $this->getWhmcsConfigOption(ConfigurableOption::ISO_IMAGE,  $this->configuration()->getIsoImage() );
+                $type = $this->configuration()->getCdromType();
+                $bus  = $busDevices[$type];
+                $busDevices[$type]++;
+                $container[$type . $bus] = "{$iso},media=cdrom";
+            }
+            //Disk
+            $diskSize = $this->configuration()->getDiskSize();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+            {
+                $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
+            }
+            $type = $this->configuration()->getDiskType();
+            $bus  = $busDevices[$type];
+            $busDevices[$type]++;
+            $harDisk = new HardDisk($type . $bus);
+            $dafaultStorage  = NodeSetting::ofServer($this->getWhmcsParamByKey('serverid'))
+                                           ->ofNode($this->getNode()->getNode())
+                                           ->ofSetting('defaultStorage')
+                                           ->value("value");
+            $harDisk->setSize($diskSize)
+                ->setStorage($dafaultStorage ? $dafaultStorage : $this->configuration()->getDiskStorage())
+                ->setCache($this->configuration()->getDiskCache())
+                ->setFormat($this->configuration()->getDiskFormat())
+                ->setMbps_rd($this->configuration()->getMbpsRd())
+                ->setMbps_wr($this->configuration()->getMbpsWr())
+                ->setDiscard($this->configuration()->isDiscard() ? "on" : null)
+                ->setIops_rd($this->configuration()->getIopsRd())
+                ->setIops_rd_max($this->configuration()->getIopsRdMax())
+                ->setIops_wr($this->configuration()->getIopsWr())
+                ->setIops_wr_max($this->configuration()->getIopsWrMax())
+                ->setReplicate($this->configuration()->isReplicate() ? 0 : null)
+                ->setSsd($this->configuration()->isSsd() ? 1 : null);
+            if ($this->configuration()->isIoThread() && in_array($type, ['virtio', 'scsi']))
+            {
+                $harDisk->setIothread($this->configuration()->isIoThread());
+            }
+            $container[$harDisk->getId()] = $harDisk->asConfig();
+            //Network
+            $container += $this->networkService->buildQemu();
+            //acpi
+            if ($this->configuration()->isAcpi())
+            {
+                $container['acpi'] = 1;
+            }
+            //agent
+            if ($this->configuration()->isAgent())
+            {
+                $container['agent'] = 1;
+            }
+            //autostart
+            if ($this->configuration()->isAutostart())
+            {
+                $container['autostart'] = 1;
+            }
+            //balloon
+            if ($this->configuration()->getBalloon())
+            {
+                $container['balloon'] = $this->configuration()->getBalloon();
+            }
+            //shares
+            if ($this->configuration()->getShares())
+            {
+                $container['shares'] = $this->configuration()->getShares();
+            }
+            //cdrom
+            if ($this->configuration()->getCdrom())
+            {
+                $container['cdrom'] = $this->configuration()->getCdrom();
+            }
+            //cpu
+            if ($this->configuration()->getCpu())
+            {
+                $container['cpu'] = $this->configuration()->getCpu();
+            }
+            //freeze
+            if ($this->configuration()->isFreeze())
+            {
+                $container['freeze'] = 1;
+            }
+            //hotplug
+            if ($this->configuration()->getHotplug())
+            {
+                $container['hotplug'] = $this->configuration()->getHotplug();
+            }
+            //keyboard
+            if ($this->configuration()->getKeyboard())
+            {
+                $container['keyboard'] = $this->configuration()->getKeyboard();
+            }
+            //kvm
+            if ($this->configuration()->isKvm())
+            {
+                $container['kvm'] = 1;
+            }
+            //localtime
+            if ($this->configuration()->isLocaltime())
+            {
+                $container['localtime'] = 1;
+            }
+            //migrate_downtime
+            if ($this->configuration()->getMigrateDowntime())
+            {
+                $container['migrate_downtime'] = $this->configuration()->getMigrateDowntime();
+            }
+            //migrate_speed
+            if ($this->configuration()->getMigrateSpeed())
+            {
+                $container['migrate_speed'] = $this->configuration()->getMigrateSpeed();
+            }
+            //reboot
+            if ($this->configuration()->isReboot())
+            {
+                $container['reboot'] = 1;
+            }
+            //startdate
+            if ($this->configuration()->getStartdate())
+            {
+                $container['startdate'] = $this->configuration()->getStartdate();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tablet
+            if ($this->configuration()->isTablet())
+            {
+                $container['tablet'] = 1;
+            }
+            //tdf
+            if ($this->configuration()->isTdf())
+            {
+                $container['tdf'] = 1;
+            }
+            //watchdog
+            if ($this->configuration()->getWatchdog())
+            {
+                $container['watchdog'] = $this->configuration()->getWatchdog();
+            }
+            //bootdisk
+            if ($this->configuration()->getBootdisk())
+            {
+                $container['bootdisk'] = $this->configuration()->getBootdisk();
+            }
+            //scsihw
+            if ($this->configuration()->getScsihw())
+            {
+                $container['scsihw'] = $this->configuration()->getScsihw();
+            }
+            //args
+            if ($this->configuration()->getArgs())
+            {
+                $container['args'] = $this->configuration()->getArgs();
+            }
+            //vga
+            $vga=[];
+            if ($this->configuration()->getVga())
+            {
+                $vga[] = $this->configuration()->getVga();
+            }
+            if($container['vga']!="none" && $this->configuration()->getVgaMemory()){
+                $vga[]="memory=".$this->configuration()->getVgaMemory();
+            }
+            if(!empty($vga)){
+                $container['vga'] = implode(",",$vga);
+            }
+            //xterm.js Console
+            if ($this->configuration()->isPermissionXtermjs())
+            {
+                $container['serial0'] = 'socket';
+            }
+            //cpu flags
+
+            if ($this->configuration()->hasCpuFlags()  && version_compare($this->api()->getVersion(), "5.2", '>'))
+            {
+                $container['cpu'] .= ',flags=' .$this->configuration()->getCpuFlagsAsSource();
+            }
+            //start
+            if ($this->configuration()->isStart())
+            {
+                $container['start'] = 1;
+            }
+            //bios
+            if ($this->configuration()->getBios())
+            {
+                $container['bios'] = $this->configuration()->getBios();
+            }
+            //machine
+            if ($this->configuration()->getMachine())
+            {
+                $container['machine'] = $this->configuration()->getMachine();
+            }
+            //Create
+            $nodeService = $this->getWhmcsCustomField(CustomField::NODE)? new Node($this->getWhmcsCustomField(CustomField::NODE)) : $this->getNode();
+            $vm          = $nodeService->kvm();
+            $this->setVm($vm);
+            $taskId = $vm->create($container);
+            $this->customFieldUpdate("node", $nodeService->getNode());
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            echo $ex->getMessage();
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+        //task history
+        $this->createTaskHistory($taskId, "Create");
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $nodeService->getNode()]);
+        //sleep
+        $this->sleep();
+        return false;
+    }
+
+
+}

+ 40 - 0
app/Jobs/Vps/CreateSnippet.php

@@ -0,0 +1,40 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Factory\Ssh2Factory;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Providers\SnippetProvider;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\CloudInitScriptConveter;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class CreateSnippet extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //Get cloud init script
+        if($this->isWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT) !=0 ){
+            $id =  $this->getWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT);
+        }else{
+            $id = $this->configuration()->getCloudInitScript();
+        }
+        $serverConfiguration = new ServerConfigurationRepository($this->getWhmcsParamByKey('serverid'));
+        $ssh = (new Ssh2Factory())->fromServerConfiguration( $serverConfiguration);
+        $cloudInitScript = CloudInitScript::findOrFail($id);
+        $conveter = new CloudInitScriptConveter($cloudInitScript, $serverConfiguration);
+        $snippetProvider = new SnippetProvider($ssh);
+        $snippet = $conveter->convert();
+        $snippetProvider->create($snippet);
+    }
+}

+ 84 - 0
app/Jobs/Vps/LoadBalancer/MigrateVmJob.php

@@ -0,0 +1,84 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\LoadBalancer;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class MigrateVmJob extends BaseJob
+{
+    use ProductService;
+    use HostingService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //Migrate
+        if (!$this->getModelData()['taskId'] || ($this->isTask()  && !$this->isTaskRunning() && $this->isFailed()))
+        {
+            $this->highAvailabilityClusterService->delete();
+            $delete = (array)$this->getModelData()['config']['delete'];
+            foreach ($this->vm()->config() as $k => $v)
+            {
+                if (preg_match('/cloudinit/', $v))
+                {
+                    $this->vm()->deleteConfig($k);
+                    $delete[$k] = $v;
+                }
+            }
+            $taskId = $this->vm()->migrate([
+                "target" => $this->getModelData()['targetNode'],
+                "online" => $this->vm()->isRunning() ? 1 : 0,
+            ]);
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "migrate" => true, "config" => ["delete" => $delete]]);
+            $this->log->info(sprintf("VM %s - Migrate", $this->vm()->getVmid()));
+            //sleep
+            $this->sleep(20);
+            return false;
+        }
+        else
+        {
+            if ($this->isDone())
+            {
+                $this->customFieldUpdate("node", $this->getModelData()['targetNode']);
+                $this->vm()->setNode($this->getModelData()['targetNode']);
+                $delete = (array)$this->getModelData()['config']['delete'];
+                foreach ($delete as $k => $v)
+                {
+                    if (preg_match('/cloudinit/', $v))
+                    {
+                        $ex      = explode(":", $v);
+                        $storage = $ex[0];
+                        $this->vm()->updateConfig([$k => "{$storage}:cloudinit,format=raw"]);
+                        unset($delete[$k]);
+                    }
+                }
+                return true;
+            }
+            else
+            {
+                if ($this->isTaskRunning())
+                {
+                    //sleep
+                    $this->sleep(20);
+                    return false;
+                }
+            }
+        }
+        return true;
+
+    }
+
+}

+ 71 - 0
app/Jobs/Vps/LoadBalancer/ShutdownVmJob.php

@@ -0,0 +1,71 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\LoadBalancer;
+
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class ShutdownVmJob extends BaseJob
+{
+    use ProductService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //shutdown
+        if (!$this->getModelData()['shutdown'] && $this->vm()->isRunning())
+        {
+            $this->highAvailabilityClusterService->delete();
+            $taskId = $this->vm()->shutdown();
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "shutdown" => true]);
+            $this->log->info(sprintf("VM %s - Shutdown", $this->vm()->getVmid()));
+            //sleep
+            $this->sleep(20);
+            return false;
+        }
+        else
+        {
+            if ($this->getModelData()['shutdown'])
+            {
+                //start
+                if ($this->isDone() && !$this->vm()->isRunning())
+                {
+                    return true;
+                }
+                else
+                {
+                    if ($this->getTask()->getStatus() == "running")
+                    {
+                        //sleep
+                        $this->sleep(20);
+                        return false;
+                    }
+                    else
+                    {
+                        if ($this->getTask()->isFalied() && $this->configuration()->isLoadBalancerStopOnUpgrade())
+                        {
+                            $taskId = $this->vm()->stop();
+                            $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "shutdown" => true]);
+                            $this->log->info(sprintf("VM %s - Stop", $this->vm()->getVmid()));
+                            $this->sleep(20);
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+}

+ 58 - 0
app/Jobs/Vps/LoadBalancer/UpgradeVmJob.php

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\LoadBalancer;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class UpgradeVmJob extends BaseJob
+{
+    use ProductService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+    protected $ipSetIpFilterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->ipSetIpFilterService           = new IpSetIpFilterService();
+        //Upgrade
+        if ($this->configuration()->isQemu())
+        {
+            $evnet = new QemuUpdateEvent($this->vm());
+            $evnet->isChangeVmPassword(false);
+            fire($evnet);
+        }
+        elseif ($this->configuration()->isLxc())
+        {
+            fire(new LxcUpdateEvent($this->vm()));
+        }
+        //createHaResource
+        if ($this->highAvailabilityClusterService->isConfigured()  )
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        //ip set filter
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetIpFilterService->create();
+        }
+        //start vm
+        if (!$this->vm()->isRunning())
+        {
+            $this->vm()->start();
+        }
+
+    }
+
+}

+ 77 - 0
app/Jobs/Vps/MigrateVmJob.php

@@ -0,0 +1,77 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class MigrateVmJob extends BaseJob
+{
+    use ProductService;
+    use HostingService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        //Migrate
+        if (!$this->getModelData()['taskId'] || ($this->isTask()  && !$this->isTaskRunning() && $this->isFailed())) {
+            if($this->isTask() && $this->isFailed()){
+                $this->log->error(ucfirst($this->getTask()->getExitstatus()));
+            }
+            $this->highAvailabilityClusterService->delete();
+            //delete config
+            $delete = (array)$this->getModelData()['config']['delete'];
+            foreach ($this->vm()->config() as $k => $v) {
+                if (preg_match('/cloudinit/', $v)) {
+                    $this->vm()->deleteConfig($k);
+                    $delete[$k] = $v;
+                }
+            }
+            $taskId = $this->vm()->migrate([
+                "target" => $this->getModelData()['targetNode'],
+                "online" => $this->getModelData()['online'],
+            ]);
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "migrate" => true, "config" => ["delete" => $delete]]);
+            $this->log->info(sprintf("VM %s - Migrate", $this->vm()->getVmid()));
+            //sleep
+            $this->sleep(5);
+            return false;
+        } else if ($this->isDone()) {
+            $this->customFieldUpdate("node", $this->getModelData()['targetNode']);
+            $this->vm()->setNode($this->getModelData()['targetNode']);
+            //restore config
+            if($this->vm() instanceof  Kvm && !$this->vm()->hasCloudInit()){
+                $delete = (array)$this->getModelData()['config']['delete'];
+                foreach ($delete as $k => $v) {
+                    if (preg_match('/cloudinit/', $v)) {
+                        $ex = explode(":", $v);
+                        $storage = $ex[0];
+                        $this->vm()->updateConfig([$k => "{$storage}:cloudinit,format=raw"]);
+                        unset($delete[$k]);
+                    }
+                }
+            }
+            if ($this->highAvailabilityClusterService->isConfigured()) {
+                $this->highAvailabilityClusterService->create();
+            }
+            return true;
+        } else if ($this->isTaskRunning()) {
+            //sleep
+            $this->sleep(5);
+            return false;
+
+        }
+    }
+}

+ 42 - 0
app/Jobs/Vps/NetworkRate/MaximumVmRateJob.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+
+class MaximumVmRateJob extends BaseJob
+{
+
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $container = [];
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        foreach ($this->vm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        if (!empty($container))
+        {
+            $this->vm()->updateConfig($container);
+        }
+    }
+}

+ 36 - 0
app/Jobs/Vps/NetworkRate/MinimumVmRateJob.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class MinimumVmRateJob extends BaseJob
+{
+
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $container = [];
+        $rate      = $this->configuration()->getMinimumRate();
+        foreach ($this->vm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        if (!empty($container))
+        {
+            $this->vm()->updateConfig($container);
+        }
+    }
+}

+ 58 - 0
app/Jobs/Vps/RebootVmJob.php

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class RebootVmJob extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        //shutdown
+        if (!$this->getModelData()['shutdown'] && $this->vm()->isRunning())
+        {
+            $taskId = $this->vm()->shutdown();
+            //save task id
+            $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "shutdown" => true]);
+            $this->log->info(sprintf("VM %s - Shutdown", $this->vm()->getVmid()));
+            //sleep
+            $this->sleep();
+            return false;
+        }
+        else if ($this->getModelData()['shutdown'])
+        {
+                //start
+                if ($this->isDone() )
+                {
+                    if(!$this->vm()->isRunning()){
+                        $this->vm()->start();
+                        $this->log->info(sprintf("VM %s - Start", $this->vm()->getVmid()));
+                    }
+                    return true;
+                    //in progress
+                }
+                else if ($this->getTask()->isRunning())
+                {
+                    $this->log->info(sprintf("VM %s - Waiting for Shutdown ", $this->vm()->getVmid()));
+                    $this->sleep(5);
+                    return false;
+                }
+                else
+                {
+                    $this->putModelDataAndSave(["shutdown" => false]);
+                    //sleep
+                    $this->sleep();
+                    return false;
+                }
+
+        }
+    }
+
+}

+ 41 - 0
app/Jobs/Vps/Reinstall/BackupVmJob.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class BackupVmJob extends BaseJob
+{
+    use ProductService;
+
+    protected function initServices()
+    {
+
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        if ($this->isDone())
+        {
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            $this->sleep(20);
+            return false;
+        }
+        $storage = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $routing = $this->configuration()->isBackupRouting() == "Yes" ? "1" : "0";
+        //backup
+        $taskId = $this->vm()->backup($storage, $routing);
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+        $this->sleep(20);
+        return false;
+    }
+
+
+}

+ 233 - 0
app/Jobs/Vps/Reinstall/CreateVmJob.php

@@ -0,0 +1,233 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall;
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\ClusterResourcesRepository;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmReinstalledEvent;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Models\Job;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AgentService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CreateVmJob extends BaseJob
+{
+    use ProductService;
+    use HostingService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    protected function initServices()
+    {
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->containerService = new ContainerService();
+        $this->agentService = new AgentService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        //is done
+        if ($this->isTask() && $this->getTask()->isDone()) {
+
+            //update password
+            if ($this->getModelData()['password']   ) {
+                if($this->params['customfields']['cipassword']){
+                    $this->customFieldUpdate('cipassword', $this->getModelData()['password']);
+                    $this->params['customfields']['cipassword'] = decrypt($this->getModelData()['password']);
+                }else{
+                    $this->hosting()->update(["password" => $this->getModelData()['password']]);
+                    $this->params['password'] = decrypt($this->getModelData()['password']);
+                }
+                sl("whmcsParams")->setParams($this->params);
+                \ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl("whmcsParams")->setParams($this->params);
+            }
+            //reinstalled event
+            if ($this->configuration()->isLxc()) {
+                fire(new LxcUpdateEvent($this->vm()));
+            } elseif ($this->configuration()->isQemu()) {
+                //Reset IP addresses usage
+                VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                    ->update([
+                        'net'   => null
+                    ]);
+                $this->deleteNetwork();
+                if(!$this->vm()->isRunning()){
+                    $this->deleteNetwork();
+                }
+                fire(new QemuUpdateEvent($this->vm()));
+                //Agent
+                try{
+                    if($this->agentService->isEnabled()){
+                        if(!$this->vm()->isRunning()){
+                            $this->log->info(sprintf("VM %s - Start", $this->vm()->getVmid()));
+                            $this->vm()->start();
+                            $this->sleep(40);
+                            return false;
+                        }
+                        $this->vm()->agent()->ping();
+                        $this->agentService ->getUserAndUpdate();
+                        $this->agentService ->passwordUpdate();
+                        $this->agentService ->configureNetwork();
+                    }
+                }catch (ProxmoxApiException $ex){
+                    if(preg_match("/not running/", $ex->getMessage())){
+                        $this->log->info($ex->getMessage());
+                    }else{
+                        $this->log->error($ex->getMessage());
+                    }
+                    //sleep
+                    $this->sleep(30);
+                    return false;
+                }
+            }
+            fire(new VmReinstalledEvent($this->vm()));
+            return true;
+        } else if ($this->isTask() && $this->getTask()->isRunning()) {
+            $this->sleep(20);
+            return false;
+        } else if ($this->isTask() && $this->getTask()->isFalied()) {
+            if ($this->configuration()->isBackupVmBeforeReinstall()) {
+                $this->log->error($this->getTask()->getExitstatus());
+                $this->restoreJob();
+                $this->model->setStatus(Job::STATUS_FAILED);
+                return;
+            }
+            throw new \Exception($this->getTask()->getExitstatus());
+        } //create vm
+        else if (!$this->isTask() && $this->configuration()->isLxc()) {
+            try {
+                $container = $this->getParentModelData()['container'];
+                unset($container['lxc'], $container['parent']);
+                //pool
+                if ($this->configuration()->getPool())
+                {
+                    $container['pool'] = $this->configuration()->getPool();
+                }
+                $node = new Node($this->vm()->getNode());
+                $node->setApi($this->api());
+                $taskId = $node->lxc()->create($container);
+                //save task id
+                $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+                //task history
+                $this->createTaskHistory($taskId, "Create");
+            } catch (\Exception $ex) {
+                $this->log->error($ex->getMessage());
+                $this->restoreJob();
+                return false;
+            }
+            //clone vm
+        } else if (!$this->isTask() && $this->configuration()->isQemu()) {
+            try {
+                $osTemplate = $this->getModelData()['osTemplate'];
+                //Support for configurable options i.e vmname|OS Name
+                if (is_string($osTemplate) && !preg_match('/\//', $osTemplate)) {
+                    $templateNode = $this->vm()->getNode();
+                    $clusterRepository = new ClusterResourcesRepository();
+                    $clusterRepository->setApi($this->api());
+                    $clusterRepository->findByNodes([$templateNode])
+                        ->findKvmTemplate();
+                    foreach ($clusterRepository->fetch() as $resurce) {
+                        if ($resurce->getName() == $osTemplate && $templateNode == $resurce->getNode()) {
+                            $templateVmid = $resurce->getVmid();
+                            break;
+                        }
+                    }
+                    if (!$templateVmid) {
+                        throw new \Exception(sprintf("Unable to find KVM  template: %s on node: %s", $osTemplate, $templateNode));
+                    }
+                    //Support for configurable options like nodename/vmid|OS Name
+                } elseif (preg_match('/\//', $osTemplate)) {
+                        list($templateNode, $templateVmid) = explode("/", $osTemplate);
+                    }
+                //init container
+                $container = [
+                    "newid" => $this->vm()->getVmid(),
+                    "full" => $this->configuration()->getCloneMode(),
+                    "target" => $this->vm()->getNode()
+                ];
+                //description
+                if ($this->configuration()->getDescription()) {
+                    $container['description'] = $this->containerService->description();
+                }
+                //hostname
+                $hostname = $this->containerService->hostname();
+                if ($hostname) {
+                    $container['name'] = $hostname;
+                }
+                //Storage
+                if (!$this->configuration()->isCloneOnTheSameStorage() && $this->configuration()->getDiskStorage() && $this->configuration()->getCloneMode() == "1") {
+                    $container['storage'] = $this->configuration()->getDiskStorage();
+                }
+                //pool
+                if ($this->configuration()->getPool()) {
+                    $container['pool'] = $this->configuration()->getPool();
+                }
+                //Create
+                $template = new Kvm($templateNode, $templateVmid);
+                $template->setApi($this->api());
+                $taskId = $template->cloneVm($container);
+
+                //save task id
+                $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode(), "templateNode" => $templateNode]);
+                //task history
+                $this->createTaskHistory($taskId, "Create");
+            } catch (\Exception $ex) {
+                if ($this->configuration()->isBackupVmBeforeReinstall()) {
+                    $this->log->error($ex->getMessage());
+                    $this->restoreJob();
+                    $this->model->setStatus(Job::STATUS_FAILED);
+                    return;
+                }
+                throw $ex;
+            }
+        }
+        $this->sleep(20);
+        return false;
+    }
+
+    private function restoreJob()
+    {
+        //create restore job
+        $storage = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->findBackup($this->vm())
+            ->findByStorages([$storage]);
+        if (!$fileRepository->count()) {
+            return;
+        }
+        queue(RestoreVm::class, [], null, "hosting", $this->getWhmcsParamByKey("serviceid"));
+        $this->model->setStatus(Job::STATUS_CANCELLED)->save();
+        return false;
+    }
+
+    private function  deleteNetwork(){
+
+        $deleteNetwork = [];
+        foreach ($this->vm()->getNetworkDevices() as $networkDevice){
+            if(VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))->ofNet($networkDevice->getId())->count()){
+                continue;
+            }
+            $deleteNetwork[]=$networkDevice->getId();
+        }
+        if(!empty($deleteNetwork)){
+            $this->vm()->deleteConfig(implode(",",$deleteNetwork));
+        }
+    }
+}

+ 90 - 0
app/Jobs/Vps/Reinstall/DeleteVmJob.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall;
+
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class DeleteVmJob extends BaseJob
+{
+    use ProductService;
+
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    protected function initServices()
+    {
+
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->containerService               = new ContainerService();
+    }
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        if ($this->isDone())
+        {
+            return true;
+        }
+        else
+        {
+            if ($this->isTaskRunning())
+            {
+                $this->sleep(20);
+                return false;
+            }
+        }
+        //Delete HA
+        if ($this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->delete();
+        }
+        if ($this->vm()->isRunning())
+        {
+            $this->vm()->stop();
+            sleep(15);
+        }
+        //vm protection
+        if ($this->vm()->config()['protection'] == "1")
+        {
+            $this->vm()->protectionOff();
+            sleep(1);
+        }
+        //vm container
+        $this->saveVmContainer();
+        //delete vm
+        $taskId = $this->vm()->delete();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+        //task history
+        $this->createTaskHistory($taskId, "Destroy");
+        $this->sleep(10);
+        return false;
+    }
+
+    private function saveVmContainer()
+    {
+        if ($this->getModelData()['container'])
+        {
+            return;
+        }
+        if ($this->configuration()->isLxc())
+        {
+            $container               = $this->vm()->getReinstallConfig();
+            $container['vmid']       = $this->vm()->getVmid();
+            $container['ostemplate'] = $this->getModelData()['osTemplate'];
+            $container['password']   = decrypt($this->getModelData()['password']);
+            //SSH Public key.
+            if ($this->configuration()->isSshKeyPairs())
+            {
+                $container['ssh-public-keys'] = $this->containerService->makeKeyPairs()->getPublic();
+            }
+            $this->putModelDataAndSave(['container' => $container]);
+        }
+    }
+}

+ 44 - 0
app/Jobs/Vps/Reinstall/RestoreVm.php

@@ -0,0 +1,44 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall;
+
+
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\BaseJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+
+class RestoreVm extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        if ($this->isTaskRunning())
+        {
+            $this->sleep(20);
+            return false;
+        }
+        //get backup
+        $storage        = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->findBackup($this->vm())
+            ->findByStorages([$storage]);
+        $backup = $fileRepository->fetchLast();
+        //done process is finished
+        if ($this->isDone())
+        {
+            $backup->delete();
+            return true;
+        }
+        //restore
+        $taskId = $this->vm()->restore($backup->getVolid());
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+        $this->sleep(20);
+        return false;
+    }
+
+}

+ 44 - 0
app/Jobs/Vps/RestoreVm.php

@@ -0,0 +1,44 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
+
+class RestoreVm extends BaseJob
+{
+    use ProductService;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->initServices();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        if ($this->isDone())
+        {
+            if ($this->configuration()->isQemu())
+            {
+                fire(new QemuUpdateEvent($this->vm()));
+            }
+            if (!$this->vm()->isRunning())
+            {
+                $this->vm()->start();
+            }
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        $taskId = $this->vm()->restore($this->getModelData()['volid']);
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+        //sleep
+        $this->sleep(5);
+        return false;
+    }
+}

+ 90 - 0
app/Jobs/Vps/SnapshotVmJob.php

@@ -0,0 +1,90 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;
+
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Snapshot;
+use MGProvision\Proxmox\v2\repository\SnapshotRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\SnapshotJob;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+
+class SnapshotVmJob extends BaseJob
+{
+
+    use ProductService;
+
+    /**
+     * @var SnapshotJob
+     */
+    private  $snapshotJob;
+
+    public function handle($text = null)
+    {
+        $this->initParams();
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        if ($this->isDone())
+        {
+            return true;
+        }
+        elseif ($this->isTaskRunning())
+        {
+            //sleep
+            $this->sleep(5);
+            return false;
+        }
+        $this->clean();
+        $snapshot = new Snapshot();
+        $snapshot->setApi($this->api());
+        $snapshot->setPath($this->vm()->getPath() . "/snapshot");
+        $snapshot->setAttributes([
+            "name"        => $this->getSnapshotJob()->name."_".$this->model->id,
+            "description" => $this->snapshotJob->description,
+        ]);
+        if (!is_null($this->snapshotJob->vmstate ) && $this->vm() instanceof  Kvm)
+        {
+            $snapshot->setVmstate($this->snapshotJob->vmstate );
+        }
+        $taskId =  $snapshot->create();
+        //save task id
+        $this->putModelDataAndSave(["taskId" => $taskId, "node" => $this->vm()->getNode()]);
+        //sleep
+        $this->sleep(5);
+        return false;
+    }
+
+    /**
+     * @return SnapshotJob
+     */
+    private  function getSnapshotJob(){
+        return $this->snapshotJob = SnapshotJob::findOrFail($this->getModelData()['snapshotJobId']);
+    }
+
+    private function clean(){
+        $limit = $this->getWhmcsConfigOption(ConfigurableOption::SNAPSHOTS, $this->configuration()->getSnapshotMaxFiles());
+        if($limit=="-1"){
+            return;
+        }
+        if(!$limit || !is_numeric($limit)){
+            throw new \InvalidArgumentException("Snapshot limit cannot be empty");
+        }
+        $snapshotRepository = new SnapshotRepository();
+        $snapshotRepository->setApi($this->api());
+        $snapshotRepository->findByVm($this->vm());
+        $snapshotRepository->ignoreCurrent(true);
+        if($snapshotRepository->count() < $limit){
+            return;
+        }
+        $toDelete = (int) $snapshotRepository->count() - $limit;
+        foreach ($snapshotRepository->sortBySnaptime()->fetch() as $entity)
+        {
+            if($toDelete <=0){
+                break;
+            }
+            $entity->delete();
+            $toDelete --;
+        }
+    }
+}

+ 64 - 0
app/Libs/Api/ENom.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api;
+
+use ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom\Parser;
+use ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Mso\IdnaConvert\IdnaConvert;
+
+/**
+ * Description of ENom
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class ENom extends AbstractApi
+{
+    const METHOD_API_CHECK_ONE_DOMAIN   = 'CheckOneDomain';
+    const METHOD_API_CHECK_MORE_DOMAINS = 'CheckMoreDomains';
+    protected $settings;
+    protected $request;
+
+    /**
+     * @var IdnaConvert
+     */
+    protected $idnaConvert;
+
+    public function __construct(IdnaConvert $idnaConvert)
+    {
+        $this->idnaConvert = $idnaConvert;
+    }
+
+    public function setSettings($settings)
+    {
+        $this->settings = $settings;
+
+        return $this;
+    }
+
+    public function checkMoreDomain($records)
+    {
+        return $this->loadRequest()->callToMethod(self::METHOD_API_CHECK_MORE_DOMAINS, $records);
+    }
+
+    protected function callToMethod($name, $params = null)
+    {
+        $namespace = "\\" . ModuleConstants::getRootNamespace() . "\App\Libs\Api\ENom\Method\\" . $name;
+        return (new $namespace($this->request, $this->settings, $this->idnaConvert))->execute($params);
+    }
+
+    protected function loadRequest()
+    {
+        $this->request = $this->getNewRequest();
+        $this->request->setCurlParser(new Parser())
+            ->setUrl('https://' . $this->settings['hostname'] . '/interface.asp')
+            ->resetHeaders();
+
+        return $this;
+    }
+
+    public function checkOneDomain($record)
+    {
+        return $this->loadRequest()->callToMethod(self::METHOD_API_CHECK_ONE_DOMAIN, $record);
+    }
+}

+ 21 - 0
app/Libs/Api/ENom/AvailableExtensions.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom;
+
+use ModulesGarden\ProxmoxAddon\App\Helper\AbstractAvailableExtensions;
+
+/**
+ * Description of AvailableExtensions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AvailableExtensions extends AbstractAvailableExtensions
+{
+    protected $submoduleConfigKey = 'ENom';
+    protected $tlds = [
+        '.com',
+        '.net',
+        '.tv',
+        '.cc'
+    ];
+}

+ 25 - 0
app/Libs/Api/ENom/Method/AbstractMethod.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom\Method;
+
+/**
+ * Description of AbstractMethod
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AbstractMethod
+{
+    const RESPONSE_RESULT_ERROR = 'error';
+    const RESPONSE_RESULT_TAKEN = 'taken';
+    const RESPONSE_RESULT_FREE  = 'free';
+    protected $request;
+    protected $settings;
+    protected $idnaConvert;
+
+    public function __construct($request, $settings, $idnaConvert)
+    {
+        $this->request     = $request;
+        $this->settings    = $settings;
+        $this->idnaConvert = $idnaConvert;
+    }
+}

+ 152 - 0
app/Libs/Api/ENom/Method/CheckMoreDomains.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom\Method;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper\DomainHelper;
+
+/**
+ * Description of CheckMoreDomains
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CheckMoreDomains extends AbstractMethod
+{
+
+    public function execute($records)
+    {
+        $returnData = [];
+        $domainList = $this->getDomainList($records);
+
+        if (count($domainList) > 30)
+        {
+            while (!empty($domainList))
+            {
+                $returnData = array_merge(
+                    $returnData, $this->checkDomainList(array_slice($domainList, 0, 30))
+                );
+                array_splice($domainList, 0, 30);
+            }
+        }
+        else
+        {
+            $returnData = $this->checkDomainList($domainList);
+        }
+
+        return $returnData;
+    }
+
+    protected function getDomainList($records = [])
+    {
+        $domainList = [];
+        foreach ($records as $record)
+        {
+            $domainList[] = $this->idnaConvert->encode(strtolower($record['sld'])) . "."
+                            . $this->idnaConvert->encode(strtolower($record['tld']));
+        }
+
+        return $domainList;
+    }
+
+    protected function checkDomainList($domainList)
+    {
+        $response = $this->request->post([
+            'Command'      => 'check',
+            'DomainList'   => str_replace(' ', '', implode(',', $domainList)),
+            'ResponseType' => 'xml',
+            'UID'          => $this->settings['username'],
+            'PW'           => $this->settings['password']
+        ]);
+
+        if ($response && $response->isSuccess())
+        {
+            $response = $response->getBody();
+            if ($response->Done == 'true')
+            {
+                return $this->formatResponse($response);
+            }
+        }
+
+        return false;
+    }
+
+    protected function formatResponse($response)
+    {
+        $result = [];
+        foreach ($response as $key => $value)
+        {
+            switch ($key)
+            {
+                case 'DomainCount':
+                case 'IsPremiumName':
+                case 'PremiumPrice':
+                case 'PremiumAboveThresholdPrice':
+                case 'Command':
+                case 'ErrCount':
+                case 'ErrX':
+                case 'Done':
+                    continue;
+                default :
+                    $this->parserResponse($result, $this->convertDomain($key, $value));
+            }
+        }
+
+        return $result;
+    }
+
+    protected function parserResponse(&$result, $data)
+    {
+        switch ($data['key'])
+        {
+            case 'Domain':
+                $result[$data['name']]['sld'] = $data['value']->getDomain();
+                $result[$data['name']]['tld'] = $data['value']->getTLDWithDot();
+                break;
+            case 'RRPCode':
+                $result[$data['name']]['status'] = $data['value'];
+                break;
+        }
+    }
+
+    protected function convertDomain($key, $value)
+    {
+        $name = $this->getName($key);
+        if (strpos($key, 'Domain') !== false)
+        {
+            $key   = 'Domain';
+            $value = new DomainHelper($this->idnaConvert->decode($value));
+        }
+        elseif (strpos($key, 'RRPCode') !== false)
+        {
+            $key   = 'RRPCode';
+            $value = $this->choseResult($value);
+        }
+        else
+        {
+            $key = null;
+        }
+
+        return [
+            'name'  => $name,
+            'key'   => $key,
+            'value' => $value
+        ];
+    }
+
+    protected function getName($key)
+    {
+        return substr($key, (strpos($key, 'Domain') !== false) ? 6 : 7);
+    }
+
+    protected function choseResult($value)
+    {
+        switch ($value)
+        {
+            case 210:
+                return self::RESPONSE_RESULT_FREE;
+            case 211:
+                return self::RESPONSE_RESULT_TAKEN;
+            default :
+                return self::RESPONSE_RESULT_ERROR;
+        }
+    }
+}

+ 70 - 0
app/Libs/Api/ENom/Method/CheckOneDomain.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom\Method;
+
+/**
+ * Description of CheckOneDomain
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CheckOneDomain extends AbstractMethod
+{
+    protected $domain;
+    protected $tld;
+
+    public function execute($params)
+    {
+        $this->domain = str_replace(' ', '', $params['sld']);
+        $this->tld    = $params['tld'];
+        return $this->checkOneDomain();
+    }
+
+    public function checkOneDomain()
+    {
+
+        $response = $this->request->post([
+            'Command'      => 'check',
+            'SLD'          => $this->idnaConvert->encode(strtolower($this->domain)),
+            'TLD'          => $this->idnaConvert->encode(strtolower(substr($this->tld, 1))),
+            'ResponseType' => 'xml',
+            'UID'          => $this->settings['username'],
+            'PW'           => $this->settings['password'],
+        ]);
+
+        return $this->formatResponse($response);
+    }
+
+    protected function formatResponse($response)
+    {
+        $resultData = [];
+        if ($response && $response->isSuccess())
+        {
+            $response = $response->getBody();
+            $status   = ($response->Done == 'true') ? $this->getStatus((int)$response->RRPCode) : self::RESPONSE_RESULT_ERROR;
+        }
+        else
+        {
+            $status = self::RESPONSE_RESULT_ERROR;
+        }
+        $resultData[] = [
+            'sld'    => $this->domain,
+            'tld'    => $this->tld,
+            'status' => $status
+        ];
+
+        return $resultData;
+    }
+
+    protected function getStatus($rRPCode)
+    {
+        $status = self::RESPONSE_RESULT_ERROR;
+        switch ($rRPCode)
+        {
+            case 211:
+                $status = self::RESPONSE_RESULT_TAKEN;
+            case 210:
+                $status = self::RESPONSE_RESULT_FREE;
+        }
+        return $status;
+    }
+}

+ 22 - 0
app/Libs/Api/ENom/Parser.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\ENom;
+
+use ModulesGarden\ProxmoxAddon\Core\Interfaces\CurlParser;
+
+/**
+ * Description of Parser
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Parser implements CurlParser
+{
+
+    public function rebuild($head, $size)
+    {
+        return [
+            json_encode(simplexml_load_string(substr($head, 0))),
+            json_encode(simplexml_load_string(substr($head, 0)))
+        ];
+    }
+}

+ 97 - 0
app/Libs/Api/OpenSRS.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api;
+
+use ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Request;
+use ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Mso\IdnaConvert\IdnaConvert;
+
+/**
+ * Description of OpenSRS
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class OpenSRS extends AbstractApi
+{
+    const METHOD_API_CHECK_ONE_DOMAIN   = 'CheckOneDomain';
+    const METHOD_API_CHECK_MORE_DOMAINS = 'CheckMoreDomains';
+    protected $defineFields = [
+        'OSRS_USERNAME'        => '', // get with db
+        'OSRS_KEY'             => '', // get with db
+        'CRYPT_TYPE'           => 'ssl',
+        'OSRS_HOST'            => '', // get with db
+        'OSRS_SSL_PORT'        => '55443',
+        'OSRS_PROTOCOL'        => 'XCP',
+        'OSRS_VERSION'         => 'XML:0.1',
+        'OSRS_DEBUG'           => 0, // static field
+        'SRS_SPIN_COUNT'       => 0, // get with db
+        'OSRS_FASTLOOKUP_PORT' => '51000',
+        'MAIL_HOST'            => 'https://admin.hostedemail.com',
+        'MAIL_USERNAME'        => '', // null
+        'MAIL_PASSWORD'        => '', // null
+        'MAIL_ENV'             => 'LIVE',
+        'MAIL_CLIENT'          => 'OpenSRS PHP Toolkit',
+        'APP_MAIL_HOST'        => 'ssl://admin.hostedemail.com',
+        'APP_MAIL_USERNAME'    => '', // null
+        'APP_MAIL_PASSWORD'    => '', // null
+        'APP_MAIL_DOMAIN'      => '', // null
+        'APP_MAIL_PORT'        => '4449',
+        'APP_MAIL_PORTWAIT'    => '10'
+    ];
+    protected $settings;
+    protected $request;
+
+    /**
+     * @var IdnaConvert
+     */
+    protected $idnaConvert;
+
+    public function __construct(IdnaConvert $idnaConvert)
+    {
+        $this->idnaConvert = $idnaConvert;
+    }
+
+    public function setSettings($settings)
+    {
+        $this->settings = $settings;
+        $this->loadDefine();
+        $this->request = Helper\di(Request::class);
+
+        return $this;
+    }
+
+    protected function loadDefine()
+    {
+        $register                             = Helper\sl('register');
+        $this->defineFields['OSRS_USERNAME']  = $this->settings['username'];
+        $this->defineFields['OSRS_KEY']       = $this->settings['privateKey'];
+        $this->defineFields['OSRS_HOST']      = $this->settings['hostname'];
+        $this->defineFields['SRS_SPIN_COUNT'] = $this->settings['spinnerCount'];
+
+        foreach ($this->defineFields as $defineName => $value)
+        {
+            if ($register->isRegister($defineName) === false)
+            {
+                $register->register($defineName, $value);
+            }
+        }
+    }
+
+    public function checkOneDomain($record)
+    {
+        return $this->callToMethod(self::METHOD_API_CHECK_ONE_DOMAIN, $record);
+    }
+
+    protected function callToMethod($name, $params = null)
+    {
+        $namespace = "\\" . ModuleConstants::getRootNamespace() . "\App\Libs\Api\OpenSRS\Method\\" . $name;
+        return (new $namespace($this->request, $this->settings, $this->idnaConvert))->execute($params);
+    }
+
+    public function checkMoreDomain($records)
+    {
+        return $this->callToMethod(self::METHOD_API_CHECK_MORE_DOMAINS, $records);
+    }
+}

+ 19 - 0
app/Libs/Api/OpenSRS/APIException.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+class APIException extends \Exception
+{
+    protected $info;
+
+    public function __construct($msg, $info)
+    {
+        $this->info = $info;
+        parent::__construct($msg);
+    }
+
+    public function getInfo()
+    {
+        return $this->info;
+    }
+}

+ 534 - 0
app/Libs/Api/OpenSRS/AvailableExtensions.php

@@ -0,0 +1,534 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+use ModulesGarden\ProxmoxAddon\App\Helper\AbstractAvailableExtensions;
+
+/**
+ * Description of AvailableExtensions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AvailableExtensions extends AbstractAvailableExtensions
+{
+    protected $submoduleConfigKey = 'OpenSRS';
+    protected $tlds = [
+        '.abogado',
+        '.ac',
+        '.academy',
+        '.accountant',
+        '.accountants',
+        '.actor',
+        '.adult',
+        '.ae',
+        '.aero',
+        '.af',
+        '.ag',
+        '.agency',
+        '.ai',
+        '.airforce',
+        '.alsace',
+        '.am',
+        '.amsterdam',
+        '.apartments',
+        '.ar',
+        '.archi',
+        '.army',
+        '.as',
+        '.asia',
+        '.associates',
+        '.at',
+        '.attorney',
+        '.au',
+        '.auction',
+        '.audio',
+        '.auto',
+        '.band',
+        '.bar',
+        '.barcelona',
+        '.bargains',
+        '.bayern',
+        '.be',
+        '.beer',
+        '.berlin',
+        '.best',
+        '.bet',
+        '.bid',
+        '.bike',
+        '.bingo',
+        '.bio',
+        '.biz',
+        '.black',
+        '.blackfriday',
+        '.blue',
+        '.boutique',
+        '.br',
+        '.brussels',
+        '.build',
+        '.builders',
+        '.business',
+        '.buzz',
+        '.bz',
+        '.bzh',
+        '.ca',
+        '.cab',
+        '.cafe',
+        '.camera',
+        '.camp',
+        '.capetown',
+        '.capital',
+        '.car',
+        '.cars',
+        '.cards',
+        '.care',
+        '.careers',
+        '.casa',
+        '.cash',
+        '.casino',
+        '.cat',
+        '.catering',
+        '.cc',
+        '.cd',
+        '.center',
+        '.ceo',
+        '.ch',
+        '.chat',
+        '.cheap',
+        '.christmas',
+        '.church',
+        '.city',
+        '.cl',
+        '.claims',
+        '.cleaning',
+        '.click',
+        '.clinic',
+        '.clothing',
+        '.cloud',
+        '.club',
+        '.cm',
+        '.cn',
+        '.co',
+        '.coach',
+        '.codes',
+        '.coffee',
+        '.college',
+        '.cologne',
+        '.com',
+        '.community',
+        '.company',
+        '.computer',
+        '.condos',
+        '.construction',
+        '.consulting',
+        '.contractors',
+        '.cooking',
+        '.cool',
+        '.coop',
+        '.country',
+        '.coupons',
+        '.courses',
+        '.credit',
+        '.creditcard',
+        '.cricket',
+        '.cruises',
+        '.cx',
+        '.cymru',
+        '.cz',
+        '.dance',
+        '.date',
+        '.dating',
+        '.de',
+        '.deals',
+        '.degree',
+        '.delivery',
+        '.democrat',
+        '.dental',
+        '.dentist',
+        '.desi',
+        '.design',
+        '.diamonds',
+        '.diet',
+        '.digital',
+        '.direct',
+        '.directory',
+        '.discount',
+        '.dk',
+        '.dog',
+        '.domains',
+        '.download',
+        '.durban',
+        '.earth',
+        '.ec',
+        '.education',
+        '.email',
+        '.energy',
+        '.engineer',
+        '.engineering',
+        '.enterprises',
+        '.equipment',
+        '.es',
+        '.estate',
+        '.eu',
+        '.events',
+        '.exchange',
+        '.expert',
+        '.exposed',
+        '.express',
+        '.fail',
+        '.faith',
+        '.family',
+        '.fans',
+        '.farm',
+        '.fashion',
+        '.feedback',
+        '.fi',
+        '.finance',
+        '.financial',
+        '.fish',
+        '.fishing',
+        '.fit',
+        '.fitness',
+        '.flights',
+        '.florist',
+        '.flowers',
+        '.fm',
+        '.football',
+        '.forsale',
+        '.foundation',
+        '.fr',
+        '.frl',
+        '.fund',
+        '.furniture',
+        '.futbol',
+        '.fyi',
+        '.gallery',
+        '.game',
+        '.garden',
+        '.gd',
+        '.gg',
+        '.gift',
+        '.gifts',
+        '.gives',
+        '.gl',
+        '.glass',
+        '.global',
+        '.gmbh',
+        '.gold',
+        '.golf',
+        '.gr',
+        '.graphics',
+        '.gratis',
+        '.green',
+        '.gripe',
+        '.group',
+        '.gs',
+        '.guide',
+        '.guitars',
+        '.guru',
+        '.gy',
+        '.hamburg',
+        '.haus',
+        '.healthcare',
+        '.help',
+        '.hiphop',
+        '.hiv',
+        '.hk',
+        '.hm',
+        '.hn',
+        '.hockey',
+        '.holdings',
+        '.holiday',
+        '.horse',
+        '.host',
+        '.hosting',
+        '.house',
+        '.how',
+        '.hr',
+        '.ht',
+        '.hu',
+        '.il',
+        '.im',
+        '.immo',
+        '.immobilien',
+        '.in',
+        '.industries',
+        '.info',
+        '.ink',
+        '.institute',
+        '.insure',
+        '.international',
+        '.investments',
+        '.io',
+        '.irish',
+        '.is',
+        '.ist',
+        '.istanbul',
+        '.it',
+        '.je',
+        '.jetzt',
+        '.jewelry',
+        '.jobs',
+        '.joburg',
+        '.jp',
+        '.juegos',
+        '.kaufen',
+        '.kg',
+        '.ki',
+        '.kim',
+        '.kitchen',
+        '.kiwi',
+        '.koeln',
+        '.kr',
+        '.la',
+        '.land',
+        '.law',
+        '.lawyer',
+        '.lc',
+        '.lease',
+        '.legal',
+        '.lgbt',
+        '.li',
+        '.life',
+        '.lighting',
+        '.limited',
+        '.limo',
+        '.link',
+        '.live',
+        '.loan ',
+        '.loans',
+        '.lol',
+        '.london',
+        '.love',
+        '.lt',
+        '.ltd ',
+        '.ltda',
+        '.lu',
+        '.luxury',
+        '.lv',
+        '.ly',
+        '.ma',
+        '.maison',
+        '.management',
+        '.market',
+        '.marketing',
+        '.mba',
+        '.md',
+        '.me',
+        '.media',
+        '.melbourne',
+        '.memorial',
+        '.men',
+        '.menu',
+        '.miami',
+        '.mn',
+        '.mobi',
+        '.moda',
+        '.moe',
+        '.mom',
+        '.money',
+        '.mortgage',
+        '.movie',
+        '.ms',
+        '.mu',
+        '.mx',
+        '.my',
+        '.nagoya',
+        '.name',
+        '.navy',
+        '.net',
+        '.network',
+        '.news',
+        '.ngo/.ong',
+        '.ninja',
+        '.nl',
+        '.no',
+        '.nrw',
+        '.nu',
+        '.nyc',
+        '.nz',
+        '.one',
+        '.online',
+        '.org',
+        '.paris',
+        '.partners',
+        '.parts',
+        '.party',
+        '.pe',
+        '.pet',
+        '.ph',
+        '.photo',
+        '.photography',
+        '.photos',
+        '.physio',
+        '.pics',
+        '.pictures',
+        '.pink',
+        '.pizza',
+        '.pl',
+        '.place',
+        '.plumbing',
+        '.plus',
+        '.pm',
+        '.poker',
+        '.porn',
+        '.pr',
+        '.press',
+        '.pro',
+        '.productions',
+        '.promo',
+        '.properties',
+        '.property',
+        '.protection',
+        '.pt',
+        '.pub',
+        '.pw',
+        '.qa',
+        '.quebec',
+        '.racing',
+        '.re',
+        '.recipes',
+        '.red',
+        '.rehab',
+        '.reise',
+        '.reisen',
+        '.rent',
+        '.rentals',
+        '.repair',
+        '.report',
+        '.republican',
+        '.rest',
+        '.restaurant',
+        '.review',
+        '.reviews',
+        '.rip',
+        '.ro',
+        '.rocks',
+        '.rodeo',
+        '.ru',
+        '.ruhr',
+        '.run',
+        '.sale',
+        '.salon',
+        '.sarl',
+        '.sb',
+        '.sc',
+        '.school',
+        '.schule',
+        '.science',
+        '.scot',
+        '.se',
+        '.security',
+        '.services',
+        '.sexy',
+        '.sg',
+        '.sh',
+        '.shoes',
+        '.show',
+        '.si',
+        '.singles',
+        '.site',
+        '.ski',
+        '.so',
+        '.soccer',
+        '.social',
+        '.software',
+        '.solar',
+        '.solutions',
+        '.soy',
+        '.space',
+        '.sr',
+        '.st',
+        '.store',
+        '.stream',
+        '.studio',
+        '.study',
+        '.style',
+        '.sucks',
+        '.supplies',
+        '.supply',
+        '.support',
+        '.surf',
+        '.surgery',
+        '.sx',
+        '.sydney',
+        '.systems',
+        '.tattoo',
+        '.tax',
+        '.taxi',
+        '.tc',
+        '.team',
+        '.tech',
+        '.technology',
+        '.tel',
+        '.tennis',
+        '.tf',
+        '.theater',
+        '.theatre',
+        '.tienda',
+        '.tips',
+        '.tires',
+        '.tirol',
+        '.tk',
+        '.tl',
+        '.tm',
+        '.to',
+        '.today',
+        '.tokyo',
+        '.tools',
+        '.top',
+        '.tours',
+        '.town',
+        '.toys',
+        '.trade',
+        '.training',
+        '.travel',
+        '.tv',
+        '.tw',
+        '.ua',
+        '.uk',
+        '.university',
+        '.uno',
+        '.us',
+        '.uy',
+        '.vacations',
+        '.vc',
+        '.ve',
+        '.vegas',
+        '.ventures',
+        '.vet',
+        '.vg',
+        '.viajes',
+        '.video',
+        '.villas',
+        '.vin',
+        '.vip',
+        '.vision',
+        '.vlaanderen',
+        '.vodka',
+        '.vote',
+        '.voto',
+        '.voyage',
+        '.wales',
+        '.watch',
+        '.webcam',
+        '.website',
+        '.wedding',
+        '.wf',
+        '.wien',
+        '.wiki',
+        '.win',
+        '.wine',
+        '.work',
+        '.works',
+        '.world',
+        '.ws',
+        '.wtf',
+        '.xxx',
+        '.xyz',
+        '.yoga',
+        '.yokohama',
+        '.yt',
+        '.za',
+        '.zone'
+    ];
+}

+ 22 - 0
app/Libs/Api/OpenSRS/Exception.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+class Exception extends \Exception
+{
+
+    public static function notDefined($propertyName)
+    {
+        throw new self("oSRS Error - $propertyName is not defined.");
+    }
+
+    public static function classNotFound($class)
+    {
+        throw new self("The class $class does not exist or cannot be found");
+    }
+
+    public static function cannotSetOneCall($string)
+    {
+        throw new self("oSRS Error - $string cannot be set in one call");
+    }
+}

+ 176 - 0
app/Libs/Api/OpenSRS/FastLookup.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+
+class FastLookup
+{
+    private $_socket = false;
+    private $_socketErrorNum = false;
+    private $_socketErrorMsg = false;
+    private $_socketTimeout = 120;
+    private $dataObject;
+    private $dataFormat;
+
+    public function checkDomain($domain)
+    {
+        if (!$this->init_socket())
+        {
+            throw new APIException('oSRS Error - Unable to establish socket: (' . $this->_socketErrorNum . ') ' . $this->_socketErrorMsg);
+        }
+
+        $callCheck  = 'check_domain ' . $domain;
+        $callLength = strlen($callCheck);
+        fputs($this->_socket, $callCheck, $callLength);
+
+        $result = fread($this->_socket, 1024);
+        $data   = $this->parseResult($result);
+
+        // Close the socket
+        $this->close_socket();
+
+        return $data;
+    }
+
+    private function init_socket()
+    {
+        $register      = Helper\sl('register');
+        $this->_socket = pfsockopen($register->registry('OSRS_HOST'), $register->registry('OSRS_FASTLOOKUP_PORT'), $this->_socketErrorNum, $this->_socketErrorMsg, $this->_socketTimeout);
+        if (!$this->_socket)
+        {
+            return false;
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    private function parseResult($resString)
+    {
+        if ($resString != '')
+        {
+            $temArra = explode(' ', $resString);
+            if ($temArra[0] == 210)
+            {
+                $resultReturn = 'Available';
+            }
+            elseif ($temArra[0] == 211)
+            {
+                $resultReturn = 'Taken';
+            }
+            elseif ($temArra[0] == 701)
+            {
+                $resultReturn = 'Unknown TLD';
+            }
+            else
+            {
+                $resultReturn = 'Syntax error - ' . $temArra[0];
+            }
+        }
+        else
+        {
+            $resultReturn = 'Read error';
+        }
+
+        return $resultReturn | '';
+    }
+
+    private function close_socket()
+    {
+        fclose($this->_socket);
+        $this->_socket = false;
+    }
+
+    public function send($tlds = [])
+    {
+        $result = $this->checkDomainBunch($this->getDomain(), $tlds);
+
+        $this->resultFullRaw       = $result;
+        $this->resultRaw           = $result;
+        $this->resultFullFormatted = $this->convertArray2Formatted($this->_formatHolder, $this->resultFullRaw);
+        $this->resultFormatted     = $this->convertArray2Formatted($this->_formatHolder, $this->resultRaw);
+    }
+
+    public function checkDomainBunch($domain, $tlds)
+    {
+        if (!$this->init_socket())
+        {
+            throw new APIException('oSRS Error - Unable to establish socket: (' . $this->_socketErrorNum . ') ' . $this->_socketErrorMsg);
+        }
+
+        if (preg_match("/(^.+)\.(.+)\.(.+)/", $domain, $matches) > 0)
+        {
+            $domain = $matches[1];
+        }
+        elseif (preg_match("/(^.+)\.(.+)/", $domain, $matches) > 0)
+        {
+            $domain = $matches[1];
+        }
+
+        $resultArray = [];
+        foreach ($tlds as $tld)
+        {
+
+            $callCheck = 'check_domain ' . $domain . $tld;
+
+            $callLength = strlen($callCheck);
+            fputs($this->_socket, $callCheck, $callLength);
+
+            $result = fread($this->_socket, 1024);
+
+            $checkRes = $this->parseResult($result);
+
+            $loopAray           = [];
+            $loopAray['domain'] = $domain;
+            $loopAray['tld']    = $tld;
+            $loopAray['result'] = $checkRes;
+
+            array_push($resultArray, $loopAray);
+        }
+
+        $this->close_socket();
+
+        return $resultArray;
+    }
+
+    /**
+     * Get the domain from the dataObject.
+     */
+    public function getDomain()
+    {
+        return $this->dataObject->data->domain;
+    }
+
+    public function convertArray2Formatted($type = '', $data = '')
+    {
+        $resultString = '';
+        if ($type == 'json')
+        {
+            $resultString = json_encode($data);
+        }
+        if ($type == 'yaml')
+        {
+            $resultString = Spyc::YAMLDump($data);
+        }
+
+        return $resultString;
+    }
+
+    public function setDataObject($format, $dataObject)
+    {
+        $this->dataObject = $dataObject;
+        $this->dataFormat = $format;
+    }
+
+    /**
+     * Does the dataObject have a domain set?
+     *
+     * @return bool
+     */
+    public function hasDomain()
+    {
+        return isset($this->dataObject->data->domain);
+    }
+}

+ 69 - 0
app/Libs/Api/OpenSRS/Fastlookup/FastDomainLookup.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Fastlookup;
+
+use ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Exception;
+use ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\FastLookup;
+
+class FastDomainLookup extends FastLookup
+{
+    public $resultFullRaw;
+    public $resultRaw;
+    public $resultFullFormatted;
+    public $resultFormatted;
+    public $tlds;
+    protected $_domain = '';
+    protected $_tldSelect = [];
+    protected $_tldAll = [];
+    protected $_formatHolder = '';
+
+    public function __construct($formatString, $dataObject)
+    {
+        try
+        {
+            $this->_formatHolder = $formatString;
+
+            $this->_validateObject($dataObject);
+            $this->setDataObject($formatString, $dataObject);
+
+            $this->send($this->tlds);
+        }
+        catch (\ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Exception $exe)
+        {
+
+        }
+    }
+
+    // Validate the object
+    private function _validateObject($dataObject)
+    {
+        $domain = '';
+
+        // search domain must be definded
+        if (!isset($dataObject->data->domain))
+        {
+            Exception::notDefined('domain');
+        }
+
+        // Grab domain name
+        $domain = $dataObject->data->domain;
+
+        if (!isset($dataObject->data->selected))
+        {
+            Exception::notDefined('selected');
+        }
+
+        if (!isset($dataObject->data->alldomains))
+        {
+            Exception::notDefined('alldomains');
+        }
+
+        $selected   = explode(';', $dataObject->data->selected);
+        $this->tlds = explode(';', $dataObject->data->alldomains);
+
+        if (count(array_filter($selected)) >= 1)
+        {
+            $this->tlds = $selected;
+        }
+    }
+}

+ 25 - 0
app/Libs/Api/OpenSRS/Method/AbstractMethod.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Method;
+
+/**
+ * Description of AbstractMethod
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AbstractMethod
+{
+    const RESPONSE_RESULT_ERROR = 'error';
+    const RESPONSE_RESULT_TAKEN = 'taken';
+    const RESPONSE_RESULT_FREE  = 'free';
+    protected $request;
+    protected $settings;
+    protected $idnaConvert;
+
+    public function __construct($request, $settings, $idnaConvert)
+    {
+        $this->request     = $request;
+        $this->settings    = $settings;
+        $this->idnaConvert = $idnaConvert;
+    }
+}

+ 73 - 0
app/Libs/Api/OpenSRS/Method/CheckMoreDomains.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Method;
+
+/**
+ * Description of CheckMoreDomains
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CheckMoreDomains extends AbstractMethod
+{
+    protected $domainList = [];
+    protected $returnData = [];
+
+    public function execute($records)
+    {
+        foreach ($this->getDomainList($records) as $domain => $tlds)
+        {
+            $response = $this->request->process(
+                'json', json_encode([
+                    'func' => 'fastDomainLookup',
+                    'data' => [
+                        'domain'     => $this->idnaConvert->encode(strtolower($domain)),
+                        'selected'   => implode(';', $tlds),
+                        'alldomains' => implode(';', $tlds)
+                    ]
+                ])
+            )->resultFormatted;
+
+            $this->returnData = array_merge($this->returnData, json_decode($response));
+        }
+
+        return $this->formatResponse();
+    }
+
+    protected function getDomainList($records = [])
+    {
+        $this->domainList = [];
+        foreach ($records as $record)
+        {
+            $this->domainList[$record['sld']][] = "." . $this->idnaConvert->encode(strtolower($record['tld']));
+        }
+
+        return $this->domainList;
+    }
+
+    protected function formatResponse()
+    {
+        $returnData = [];
+        foreach ($this->returnData as $record)
+        {
+            $returnData[] = [
+                'sld'    => $this->idnaConvert->decode($record->domain),
+                'tld'    => $this->idnaConvert->decode($record->tld),
+                'status' => $this->choseResult($record->result)
+            ];
+        }
+        return $returnData;
+    }
+
+    protected function choseResult($return)
+    {
+        switch ($return)
+        {
+            case 'Available':
+                return self::RESPONSE_RESULT_FREE;
+            case 'Taken':
+                return self::RESPONSE_RESULT_TAKEN;
+            default:
+                return self::RESPONSE_RESULT_ERROR;
+        }
+    }
+}

+ 66 - 0
app/Libs/Api/OpenSRS/Method/CheckOneDomain.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\Method;
+
+/**
+ * Description of CheckOneDomain
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CheckOneDomain extends AbstractMethod
+{
+    protected $domain;
+    protected $tld;
+
+    public function execute($params)
+    {
+        $this->domain = str_replace(' ', '', $params['sld']);
+        $this->tld    = $params['tld'];
+        return $this->checkOneDomain();
+    }
+
+    public function checkOneDomain()
+    {
+        $tld = "." . $this->idnaConvert->encode(strtolower($this->tld));
+
+        $response = $this->request->process(
+            'json', json_encode([
+                'func' => 'fastDomainLookup',
+                'data' => [
+                    'domain'     => $this->idnaConvert->encode(strtolower($this->domain)),
+                    'selected'   => $tld,
+                    'alldomains' => $tld,
+                ]
+            ])
+        )->resultFormatted;
+
+        return $this->formatResponse($response);
+    }
+
+    protected function formatResponse($response)
+    {
+        $returnData = [];
+        foreach (json_decode($response) as $record)
+        {
+            $returnData[] = [
+                'sld'    => $this->domain,
+                'tld'    => $this->tld,
+                'status' => $this->choseResult($record->result)
+            ];
+        }
+        return $returnData;
+    }
+
+    protected function choseResult($return)
+    {
+        switch ($return)
+        {
+            case 'Available':
+                return self::RESPONSE_RESULT_FREE;
+            case 'Taken':
+                return self::RESPONSE_RESULT_TAKEN;
+            default:
+                return self::RESPONSE_RESULT_ERROR;
+        }
+    }
+}

+ 111 - 0
app/Libs/Api/OpenSRS/Request.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+use Spyc;
+
+class Request
+{
+
+    /**
+     * Process an OpenSRS Request.
+     *
+     * @param string $format input format (xml, json, array)
+     * @param string $data data
+     */
+    public function process($format = '', $data = '')
+    {
+        if (empty($data))
+        {
+            throw new Exception('OSRS Error - No data found.');
+
+            return;
+        }
+
+        $dataArray = [];
+        switch (strtolower($format))
+        {
+            case 'array':
+                $dataArray = $data;
+                break;
+            case 'json':
+                $json      = str_replace('\\"', '"', $data);   //  Replace  \"  with " for JSON that comes from Javascript
+                $dataArray = json_decode($json, true);
+                break;
+            case 'yaml':
+                $dataArray = Spyc::YAMLLoad($data);
+                break;
+            default:
+                $dataArray = $data;
+        }
+        // Convert associative array to object
+        $dataObject = $this->array2object($dataArray);
+        $classCall  = null;
+
+        $classCall = RequestFactory::build($dataObject->func, $format, $dataObject);
+
+        return $classCall;
+    }
+
+    /**
+     * Method to convert Array -> Object -> Array.
+     *
+     * @param hash $data Containing array object
+     *
+     * @return stdClass Object $object   Containing stdClass object
+     *
+     * @since    3.4
+     */
+    public function array2object($data)
+    {
+        if (is_array($data))
+        {
+            $data = json_decode(json_encode($data));
+        }
+
+        return $data;
+    }
+
+    public function convertArray2Formatted($type = '', $data = '')
+    {
+        $resultString = '';
+        if ($type == 'json')
+        {
+            $resultString = json_encode($data);
+        }
+        if ($type == 'yaml')
+        {
+            $resultString = Spyc::YAMLDump($data);
+        }
+
+        return $resultString;
+    }
+
+    public function convertFormatted2array($type = '', $data = '')
+    {
+        $resultArray = '';
+        if ($type == 'json')
+        {
+            $resultArray = json_decode($data, true);
+        }
+        if ($type == 'yaml')
+        {
+            $resultArray = Spyc::YAMLLoad($data);
+        }
+
+        return $resultArray;
+    }
+
+    public function array_filter_recursive($input)
+    {
+        foreach ($input as &$value)
+        {
+            if (is_array($value))
+            {
+                $value = $this->array_filter_recursive($value);
+            }
+        }
+
+        return array_filter($input);
+    }
+}

+ 45 - 0
app/Libs/Api/OpenSRS/RequestFactory.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS;
+
+class RequestFactory
+{
+    public static $RequestRoutes = [
+        'fastdomainlookup' => 'Fastlookup\FastDomainLookup'
+    ];
+
+    public static function build($func, $type, $dataObject)
+    {
+        $route              = '';
+        $routeKey           = strtolower($func);
+        $returnFullResponse = true;
+
+        if (array_key_exists($routeKey, self::$RequestRoutes))
+        {
+            $route = self::$RequestRoutes[$routeKey];
+        }
+
+        $class = '\ModulesGarden\ProxmoxAddon\App\Libs\Api\OpenSRS\\' . $route;
+
+        if (class_exists($class))
+        {
+            /* if (!isset($dataObject->attributes)) {
+              $dataconversionRoute = '\ModulesGarden\ProxmoxAddon\App\Api\OpenSRS\backwardcompatibility\dataconversion\\'.$route;
+
+              if (class_exists($dataconversionRoute)) {
+              $dc = new $dataconversionRoute();
+
+              $dataObject = $dc->convertDataObject($dataObject);
+
+              $returnFullResponse = false;
+              }
+              } */
+
+            return new $class($type, $dataObject, $returnFullResponse);
+        }
+        else
+        {
+            throw new \Exception("OSRS Error - $func is unsupported.");
+        }
+    }
+}

+ 4072 - 0
app/Libs/BigInteger.php

@@ -0,0 +1,4072 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs;
+
+/**
+ * Pure-PHP arbitrary precision integer arithmetic library.
+ *
+ * Supports base-2, base-10, base-16, and base-256 numbers.  Uses the GMP or BCMath extensions, if available,
+ * and an internal implementation, otherwise.
+ *
+ * PHP versions 4 and 5
+ *
+ * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the
+ * {@link MATH_BIGINTEGER_MODE_INTERNAL MATH_BIGINTEGER_MODE_INTERNAL} mode)
+ *
+ * BigInteger uses base-2**26 to perform operations such as multiplication and division and
+ * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction.  Because the largest possible
+ * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating
+ * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are
+ * used.  As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %,
+ * which only supports integers.  Although this fact will slow this library down, the fact that such a high
+ * base is being used should more than compensate.
+ *
+ * When PHP version 6 is officially released, we'll be able to use 64-bit integers.  This should, once again,
+ * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition /
+ * subtraction).
+ *
+ * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format.  ie.
+ * (new BigInteger(pow(2, 26)))->value = array(0, 1)
+ *
+ * Useful resources are as follows:
+ *
+ *  - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)}
+ *  - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)}
+ *  - Java's BigInteger classes.  See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip
+ *
+ * Here's an example of how to use this library:
+ * <code>
+ * <?php
+ *    include('Math/BigInteger.php');
+ *
+ *    $a = new BigInteger(2);
+ *    $b = new BigInteger(3);
+ *
+ *    $c = $a->add($b);
+ *
+ *    echo $c->toString(); // outputs 5
+ * ?>
+ * </code>
+ *
+ * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @category  Math
+ * @package   BigInteger
+ * @author    Jim Wigginton <terrafrost@php.net>
+ * @copyright MMVI Jim Wigginton
+ * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
+ * @link      http://pear.php.net/package/BigInteger
+ */
+/* * #@+
+ * Reduction constants
+ *
+ * @access private
+ * @see BigInteger::_reduce()
+ */
+/**
+ * @see BigInteger::_montgomery()
+ * @see BigInteger::_prepMontgomery()
+ */
+define('MATH_BIGINTEGER_MONTGOMERY', 0);
+/**
+ * @see BigInteger::_barrett()
+ */
+define('MATH_BIGINTEGER_BARRETT', 1);
+/**
+ * @see BigInteger::_mod2()
+ */
+define('MATH_BIGINTEGER_POWEROF2', 2);
+/**
+ * @see BigInteger::_remainder()
+ */
+define('MATH_BIGINTEGER_CLASSIC', 3);
+/**
+ * @see BigInteger::__clone()
+ */
+define('MATH_BIGINTEGER_NONE', 4);
+/* * #@- */
+
+/* * #@+
+ * Array constants
+ *
+ * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and
+ * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them.
+ *
+ * @access private
+ */
+/**
+ * $result[MATH_BIGINTEGER_VALUE] contains the value.
+ */
+define('MATH_BIGINTEGER_VALUE', 0);
+/**
+ * $result[MATH_BIGINTEGER_SIGN] contains the sign.
+ */
+define('MATH_BIGINTEGER_SIGN', 1);
+/* * #@- */
+
+/* * #@+
+ * @access private
+ * @see BigInteger::_montgomery()
+ * @see BigInteger::_barrett()
+ */
+/**
+ * Cache constants
+ *
+ * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid.
+ */
+define('MATH_BIGINTEGER_VARIABLE', 0);
+/**
+ * $cache[MATH_BIGINTEGER_DATA] contains the cached data.
+ */
+define('MATH_BIGINTEGER_DATA', 1);
+/* * #@- */
+
+/* * #@+
+ * Mode constants.
+ *
+ * @access private
+ * @see BigInteger::BigInteger()
+ */
+/**
+ * To use the pure-PHP implementation
+ */
+define('MATH_BIGINTEGER_MODE_INTERNAL', 1);
+/**
+ * To use the BCMath library
+ *
+ * (if enabled; otherwise, the internal implementation will be used)
+ */
+define('MATH_BIGINTEGER_MODE_BCMATH', 2);
+/**
+ * To use the GMP library
+ *
+ * (if present; otherwise, either the BCMath or the internal implementation will be used)
+ */
+define('MATH_BIGINTEGER_MODE_GMP', 3);
+/* * #@- */
+
+/**
+ * Karatsuba Cutoff
+ *
+ * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication?
+ *
+ * @access private
+ */
+define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25);
+
+/**
+ * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256
+ * numbers.
+ *
+ * @package BigInteger
+ * @author  Jim Wigginton <terrafrost@php.net>
+ * @version 1.0.0RC4
+ * @access  public
+ */
+class BigInteger
+{
+    /**
+     * Holds the BigInteger's value.
+     *
+     * @var Array
+     * @access private
+     */
+    var $value;
+
+    /**
+     * Holds the BigInteger's magnitude.
+     *
+     * @var Boolean
+     * @access private
+     */
+    var $is_negative = false;
+
+    /**
+     * Random number generator function
+     *
+     * @see setRandomGenerator()
+     * @access private
+     */
+    var $generator = 'mt_rand';
+
+    /**
+     * Precision
+     *
+     * @see setPrecision()
+     * @access private
+     */
+    var $precision = -1;
+
+    /**
+     * Precision Bitmask
+     *
+     * @see setPrecision()
+     * @access private
+     */
+    var $bitmask = false;
+
+    /**
+     * Mode independent value used for serialization.
+     *
+     * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for
+     * a variable that'll be serializable regardless of whether or not extensions are being used.  Unlike $this->value,
+     * however, $this->hex is only calculated when $this->__sleep() is called.
+     *
+     * @see __sleep()
+     * @see __wakeup()
+     * @var String
+     * @access private
+     */
+    var $hex;
+
+    /**
+     * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers.
+     *
+     * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using
+     * two's compliment.  The sole exception to this is -10, which is treated the same as 10 is.
+     *
+     * Here's an example:
+     * <code>
+     * &lt;?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('0x32', 16); // 50 in base-16
+     *
+     *    echo $a->toString(); // outputs 50
+     * ?&gt;
+     * </code>
+     *
+     * @param optional $x base-10 number or base-$base number if $base set.
+     * @param optional integer $base
+     * @return BigInteger
+     * @access public
+     */
+    function __construct($x = 0, $base = 10)
+    {
+        if (!defined('MATH_BIGINTEGER_MODE'))
+        {
+            switch (true)
+            {
+                case extension_loaded('gmp'):
+                    define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP);
+                    break;
+                case extension_loaded('bcmath'):
+                    define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH);
+                    break;
+                default:
+                    define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL);
+            }
+        }
+
+        if (function_exists('openssl_public_encrypt') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED'))
+        {
+            // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
+            ob_start();
+            phpinfo();
+            $content = ob_get_contents();
+            ob_end_clean();
+
+            preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);
+
+            $versions = [];
+            if (!empty($matches[1]))
+            {
+                for ($i = 0; $i < count($matches[1]); $i++)
+                {
+                    $versions[$matches[1][$i]] = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
+                }
+            }
+
+            // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
+            switch (true)
+            {
+                case!isset($versions['Header']):
+                case!isset($versions['Library']):
+                case $versions['Header'] == $versions['Library']:
+                    define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
+                    break;
+                default:
+                    define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
+            }
+        }
+
+        if (!defined('PHP_INT_SIZE'))
+        {
+            define('PHP_INT_SIZE', 4);
+        }
+
+        if (!defined('MATH_BIGINTEGER_BASE') && MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_INTERNAL)
+        {
+            switch (PHP_INT_SIZE)
+            {
+                case 8: // use 64-bit integers if int size is 8 bytes
+                    define('MATH_BIGINTEGER_BASE', 31);
+                    define('MATH_BIGINTEGER_BASE_FULL', 0x80000000);
+                    define('MATH_BIGINTEGER_MAX_DIGIT', 0x7FFFFFFF);
+                    define('MATH_BIGINTEGER_MSB', 0x40000000);
+                    // 10**9 is the closest we can get to 2**31 without passing it
+                    define('MATH_BIGINTEGER_MAX10', 1000000000);
+                    define('MATH_BIGINTEGER_MAX10_LEN', 9);
+                    // the largest digit that may be used in addition / subtraction
+                    define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 62));
+                    break;
+                //case 4: // use 64-bit floats if int size is 4 bytes
+                default:
+                    define('MATH_BIGINTEGER_BASE', 26);
+                    define('MATH_BIGINTEGER_BASE_FULL', 0x4000000);
+                    define('MATH_BIGINTEGER_MAX_DIGIT', 0x3FFFFFF);
+                    define('MATH_BIGINTEGER_MSB', 0x2000000);
+                    // 10**7 is the closest to 2**26 without passing it
+                    define('MATH_BIGINTEGER_MAX10', 10000000);
+                    define('MATH_BIGINTEGER_MAX10_LEN', 7);
+                    // the largest digit that may be used in addition / subtraction
+                    // we do pow(2, 52) instead of using 4503599627370496 directly because some
+                    // PHP installations will truncate 4503599627370496.
+                    define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 52));
+            }
+        }
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                if (is_resource($x) && get_resource_type($x) == 'GMP integer')
+                {
+                    $this->value = $x;
+                    return;
+                }
+                $this->value = gmp_init(0);
+                break;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $this->value = '0';
+                break;
+            default:
+                $this->value = [];
+        }
+
+        // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48
+        // '0' is the only value like this per http://php.net/empty
+        if (empty($x) && (abs($base) != 256 || $x !== '0'))
+        {
+            return;
+        }
+
+        switch ($base)
+        {
+            case -256:
+                if (ord($x[0]) & 0x80)
+                {
+                    $x                 = ~$x;
+                    $this->is_negative = true;
+                }
+            case 256:
+                switch (MATH_BIGINTEGER_MODE)
+                {
+                    case MATH_BIGINTEGER_MODE_GMP:
+                        $sign        = $this->is_negative ? '-' : '';
+                        $this->value = gmp_init($sign . '0x' . bin2hex($x));
+                        break;
+                    case MATH_BIGINTEGER_MODE_BCMATH:
+                        // round $len to the nearest 4 (thanks, DavidMJ!)
+                        $len = (strlen($x) + 3) & 0xFFFFFFFC;
+
+                        $x = str_pad($x, $len, chr(0), STR_PAD_LEFT);
+
+                        for ($i = 0; $i < $len; $i += 4)
+                        {
+                            $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32
+                            $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0);
+                        }
+
+                        if ($this->is_negative)
+                        {
+                            $this->value = '-' . $this->value;
+                        }
+
+                        break;
+                    // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb)
+                    default:
+                        while (strlen($x))
+                        {
+                            $this->value[] = $this->_bytes2int($this->_base256_rshift($x, MATH_BIGINTEGER_BASE));
+                        }
+                }
+
+                if ($this->is_negative)
+                {
+                    if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL)
+                    {
+                        $this->is_negative = false;
+                    }
+                    $temp        = $this->add(new BigInteger('-1'));
+                    $this->value = $temp->value;
+                }
+                break;
+            case 16:
+            case -16:
+                if ($base > 0 && $x[0] == '-')
+                {
+                    $this->is_negative = true;
+                    $x                 = substr($x, 1);
+                }
+
+                $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x);
+
+                $is_negative = false;
+                if ($base < 0 && hexdec($x[0]) >= 8)
+                {
+                    $this->is_negative = $is_negative = true;
+                    $x                 = bin2hex(~pack('H*', $x));
+                }
+
+                switch (MATH_BIGINTEGER_MODE)
+                {
+                    case MATH_BIGINTEGER_MODE_GMP:
+                        $temp              = $this->is_negative ? '-0x' . $x : '0x' . $x;
+                        $this->value       = gmp_init($temp);
+                        $this->is_negative = false;
+                        break;
+                    case MATH_BIGINTEGER_MODE_BCMATH:
+                        $x                 = (strlen($x) & 1) ? '0' . $x : $x;
+                        $temp              = new BigInteger(pack('H*', $x), 256);
+                        $this->value       = $this->is_negative ? '-' . $temp->value : $temp->value;
+                        $this->is_negative = false;
+                        break;
+                    default:
+                        $x           = (strlen($x) & 1) ? '0' . $x : $x;
+                        $temp        = new BigInteger(pack('H*', $x), 256);
+                        $this->value = $temp->value;
+                }
+
+                if ($is_negative)
+                {
+                    $temp        = $this->add(new BigInteger('-1'));
+                    $this->value = $temp->value;
+                }
+                break;
+            case 10:
+            case -10:
+                // (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that
+                // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals)
+                // [^-0-9].*: find any non-numeric characters and then any characters that follow that
+                $x = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#', '', $x);
+
+                switch (MATH_BIGINTEGER_MODE)
+                {
+                    case MATH_BIGINTEGER_MODE_GMP:
+                        $this->value = gmp_init($x);
+                        break;
+                    case MATH_BIGINTEGER_MODE_BCMATH:
+                        // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different
+                        // results then doing it on '-1' does (modInverse does $x[0])
+                        $this->value = $x === '-' ? '0' : (string)$x;
+                        break;
+                    default:
+                        $temp = new BigInteger();
+
+                        $multiplier        = new BigInteger();
+                        $multiplier->value = [MATH_BIGINTEGER_MAX10];
+
+                        if ($x[0] == '-')
+                        {
+                            $this->is_negative = true;
+                            $x                 = substr($x, 1);
+                        }
+
+                        $x = str_pad($x, strlen($x) + ((MATH_BIGINTEGER_MAX10_LEN - 1) * strlen($x)) % MATH_BIGINTEGER_MAX10_LEN, 0, STR_PAD_LEFT);
+                        while (strlen($x))
+                        {
+                            $temp = $temp->multiply($multiplier);
+                            $temp = $temp->add(new BigInteger($this->_int2bytes(substr($x, 0, MATH_BIGINTEGER_MAX10_LEN)), 256));
+                            $x    = substr($x, MATH_BIGINTEGER_MAX10_LEN);
+                        }
+
+                        $this->value = $temp->value;
+                }
+                break;
+            case 2: // base-2 support originally implemented by Lluis Pamies - thanks!
+            case -2:
+                if ($base > 0 && $x[0] == '-')
+                {
+                    $this->is_negative = true;
+                    $x                 = substr($x, 1);
+                }
+
+                $x = preg_replace('#^([01]*).*#', '$1', $x);
+                $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT);
+
+                $str = '0x';
+                while (strlen($x))
+                {
+                    $part = substr($x, 0, 4);
+                    $str  .= dechex(bindec($part));
+                    $x    = substr($x, 4);
+                }
+
+                if ($this->is_negative)
+                {
+                    $str = '-' . $str;
+                }
+
+                $temp              = new BigInteger($str, 8 * $base); // ie. either -16 or +16
+                $this->value       = $temp->value;
+                $this->is_negative = $temp->is_negative;
+
+                break;
+            default:
+                // base not supported, so we'll let $this == 0
+        }
+    }
+
+    /**
+     * Converts bytes to 32-bit integers
+     *
+     * @param String $x
+     * @return Integer
+     * @access private
+     */
+    function _bytes2int($x)
+    {
+        $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT));
+        return $temp['int'];
+    }
+
+    /**
+     * Logical Right Shift
+     *
+     * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder.
+     *
+     * @param $x String
+     * @param $shift Integer
+     * @return String
+     * @access private
+     */
+    function _base256_rshift(&$x, $shift)
+    {
+        if ($shift == 0)
+        {
+            $x = ltrim($x, chr(0));
+            return '';
+        }
+
+        $num_bytes = $shift >> 3; // eg. floor($shift/8)
+        $shift     &= 7; // eg. $shift % 8
+
+        $remainder = '';
+        if ($num_bytes)
+        {
+            $start     = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes;
+            $remainder = substr($x, $start);
+            $x         = substr($x, 0, -$num_bytes);
+        }
+
+        $carry       = 0;
+        $carry_shift = 8 - $shift;
+        for ($i = 0; $i < strlen($x); ++$i)
+        {
+            $temp  = (ord($x[$i]) >> $shift) | $carry;
+            $carry = (ord($x[$i]) << $carry_shift) & 0xFF;
+            $x[$i] = chr($temp);
+        }
+        $x = ltrim($x, chr(0));
+
+        $remainder = chr($carry >> $carry_shift) . $remainder;
+
+        return ltrim($remainder, chr(0));
+    }
+
+    /**
+     * Adds two BigIntegers.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('10');
+     *    $b = new BigInteger('20');
+     *
+     *    $c = $a->add($b);
+     *
+     *    echo $c->toString(); // outputs 30
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $y
+     * @return BigInteger
+     * @access public
+     * @internal Performs base-2**52 addition
+     */
+    function add($y)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_add($this->value, $y->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp        = new BigInteger();
+                $temp->value = bcadd($this->value, $y->value, 0);
+
+                return $this->_normalize($temp);
+        }
+
+        $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+        $result              = new BigInteger();
+        $result->value       = $temp[MATH_BIGINTEGER_VALUE];
+        $result->is_negative = $temp[MATH_BIGINTEGER_SIGN];
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Normalize
+     *
+     * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
+     *
+     * @param BigInteger
+     * @return BigInteger
+     * @see _trim()
+     * @access private
+     */
+    function _normalize($result)
+    {
+        $result->precision = $this->precision;
+        $result->bitmask   = $this->bitmask;
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                if (!empty($result->bitmask->value))
+                {
+                    $result->value = gmp_and($result->value, $result->bitmask->value);
+                }
+
+                return $result;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                if (!empty($result->bitmask->value))
+                {
+                    $result->value = bcmod($result->value, $result->bitmask->value);
+                }
+
+                return $result;
+        }
+
+        $value = &$result->value;
+
+        if (!count($value))
+        {
+            return $result;
+        }
+
+        $value = $this->_trim($value);
+
+        if (!empty($result->bitmask->value))
+        {
+            $length = min(count($value), count($this->bitmask->value));
+            $value  = array_slice($value, 0, $length);
+
+            for ($i = 0; $i < $length; ++$i)
+            {
+                $value[$i] = $value[$i] & $this->bitmask->value[$i];
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Trim
+     *
+     * Removes leading zeros
+     *
+     * @param Array $value
+     * @return BigInteger
+     * @access private
+     */
+    function _trim($value)
+    {
+        for ($i = count($value) - 1; $i >= 0; --$i)
+        {
+            if ($value[$i])
+            {
+                break;
+            }
+            unset($value[$i]);
+        }
+
+        return $value;
+    }
+
+    /**
+     * Performs addition.
+     *
+     * @param Array $x_value
+     * @param Boolean $x_negative
+     * @param Array $y_value
+     * @param Boolean $y_negative
+     * @return Array
+     * @access private
+     */
+    function _add($x_value, $x_negative, $y_value, $y_negative)
+    {
+        $x_size = count($x_value);
+        $y_size = count($y_value);
+
+        if ($x_size == 0)
+        {
+            return [
+                MATH_BIGINTEGER_VALUE => $y_value,
+                MATH_BIGINTEGER_SIGN  => $y_negative
+            ];
+        }
+        else
+        {
+            if ($y_size == 0)
+            {
+                return [
+                    MATH_BIGINTEGER_VALUE => $x_value,
+                    MATH_BIGINTEGER_SIGN  => $x_negative
+                ];
+            }
+        }
+
+        // subtract, if appropriate
+        if ($x_negative != $y_negative)
+        {
+            if ($x_value == $y_value)
+            {
+                return [
+                    MATH_BIGINTEGER_VALUE => [],
+                    MATH_BIGINTEGER_SIGN  => false
+                ];
+            }
+
+            $temp                       = $this->_subtract($x_value, false, $y_value, false);
+            $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ?
+                $x_negative : $y_negative;
+
+            return $temp;
+        }
+
+        if ($x_size < $y_size)
+        {
+            $size  = $x_size;
+            $value = $y_value;
+        }
+        else
+        {
+            $size  = $y_size;
+            $value = $x_value;
+        }
+
+        $value[] = 0; // just in case the carry adds an extra digit
+
+        $carry = 0;
+        for ($i = 0, $j = 1; $j < $size; $i += 2, $j += 2)
+        {
+            $sum   = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] + $y_value[$j] * MATH_BIGINTEGER_BASE_FULL + $y_value[$i] + $carry;
+            $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
+            $sum   = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT2 : $sum;
+
+            $temp = (int)($sum / MATH_BIGINTEGER_BASE_FULL);
+
+            $value[$i] = (int)($sum - MATH_BIGINTEGER_BASE_FULL * $temp); // eg. a faster alternative to fmod($sum, 0x4000000)
+            $value[$j] = $temp;
+        }
+
+        if ($j == $size)
+        { // ie. if $y_size is odd
+            $sum       = $x_value[$i] + $y_value[$i] + $carry;
+            $carry     = $sum >= MATH_BIGINTEGER_BASE_FULL;
+            $value[$i] = $carry ? $sum - MATH_BIGINTEGER_BASE_FULL : $sum;
+            ++$i; // ie. let $i = $j since we've just done $value[$i]
+        }
+
+        if ($carry)
+        {
+            for (; $value[$i] == MATH_BIGINTEGER_MAX_DIGIT; ++$i)
+            {
+                $value[$i] = 0;
+            }
+            ++$value[$i];
+        }
+
+        return [
+            MATH_BIGINTEGER_VALUE => $this->_trim($value),
+            MATH_BIGINTEGER_SIGN  => $x_negative
+        ];
+    }
+
+    /**
+     * Performs subtraction.
+     *
+     * @param Array $x_value
+     * @param Boolean $x_negative
+     * @param Array $y_value
+     * @param Boolean $y_negative
+     * @return Array
+     * @access private
+     */
+    function _subtract($x_value, $x_negative, $y_value, $y_negative)
+    {
+        $x_size = count($x_value);
+        $y_size = count($y_value);
+
+        if ($x_size == 0)
+        {
+            return [
+                MATH_BIGINTEGER_VALUE => $y_value,
+                MATH_BIGINTEGER_SIGN  => !$y_negative
+            ];
+        }
+        else
+        {
+            if ($y_size == 0)
+            {
+                return [
+                    MATH_BIGINTEGER_VALUE => $x_value,
+                    MATH_BIGINTEGER_SIGN  => $x_negative
+                ];
+            }
+        }
+
+        // add, if appropriate (ie. -$x - +$y or +$x - -$y)
+        if ($x_negative != $y_negative)
+        {
+            $temp                       = $this->_add($x_value, false, $y_value, false);
+            $temp[MATH_BIGINTEGER_SIGN] = $x_negative;
+
+            return $temp;
+        }
+
+        $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative);
+
+        if (!$diff)
+        {
+            return [
+                MATH_BIGINTEGER_VALUE => [],
+                MATH_BIGINTEGER_SIGN  => false
+            ];
+        }
+
+        // switch $x and $y around, if appropriate.
+        if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0))
+        {
+            $temp    = $x_value;
+            $x_value = $y_value;
+            $y_value = $temp;
+
+            $x_negative = !$x_negative;
+
+            $x_size = count($x_value);
+            $y_size = count($y_value);
+        }
+
+        // at this point, $x_value should be at least as big as - if not bigger than - $y_value
+
+        $carry = 0;
+        for ($i = 0, $j = 1; $j < $y_size; $i += 2, $j += 2)
+        {
+            $sum   = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] - $y_value[$j] * MATH_BIGINTEGER_BASE_FULL - $y_value[$i] - $carry;
+            $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
+            $sum   = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT2 : $sum;
+
+            $temp = (int)($sum / MATH_BIGINTEGER_BASE_FULL);
+
+            $x_value[$i] = (int)($sum - MATH_BIGINTEGER_BASE_FULL * $temp);
+            $x_value[$j] = $temp;
+        }
+
+        if ($j == $y_size)
+        { // ie. if $y_size is odd
+            $sum         = $x_value[$i] - $y_value[$i] - $carry;
+            $carry       = $sum < 0;
+            $x_value[$i] = $carry ? $sum + MATH_BIGINTEGER_BASE_FULL : $sum;
+            ++$i;
+        }
+
+        if ($carry)
+        {
+            for (; !$x_value[$i]; ++$i)
+            {
+                $x_value[$i] = MATH_BIGINTEGER_MAX_DIGIT;
+            }
+            --$x_value[$i];
+        }
+
+        return [
+            MATH_BIGINTEGER_VALUE => $this->_trim($x_value),
+            MATH_BIGINTEGER_SIGN  => $x_negative
+        ];
+    }
+
+    /**
+     * Compares two numbers.
+     *
+     * @param Array $x_value
+     * @param Boolean $x_negative
+     * @param Array $y_value
+     * @param Boolean $y_negative
+     * @return Integer
+     * @see compare()
+     * @access private
+     */
+    function _compare($x_value, $x_negative, $y_value, $y_negative)
+    {
+        if ($x_negative != $y_negative)
+        {
+            return (!$x_negative && $y_negative) ? 1 : -1;
+        }
+
+        $result = $x_negative ? -1 : 1;
+
+        if (count($x_value) != count($y_value))
+        {
+            return (count($x_value) > count($y_value)) ? $result : -$result;
+        }
+        $size = max(count($x_value), count($y_value));
+
+        $x_value = array_pad($x_value, $size, 0);
+        $y_value = array_pad($y_value, $size, 0);
+
+        for ($i = count($x_value) - 1; $i >= 0; --$i)
+        {
+            if ($x_value[$i] != $y_value[$i])
+            {
+                return ($x_value[$i] > $y_value[$i]) ? $result : -$result;
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * Multiplies two BigIntegers
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('10');
+     *    $b = new BigInteger('20');
+     *
+     *    $c = $a->multiply($b);
+     *
+     *    echo $c->toString(); // outputs 200
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $x
+     * @return BigInteger
+     * @access public
+     */
+    function multiply($x)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_mul($this->value, $x->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp        = new BigInteger();
+                $temp->value = bcmul($this->value, $x->value, 0);
+
+                return $this->_normalize($temp);
+        }
+
+        $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative);
+
+        $product              = new BigInteger();
+        $product->value       = $temp[MATH_BIGINTEGER_VALUE];
+        $product->is_negative = $temp[MATH_BIGINTEGER_SIGN];
+
+        return $this->_normalize($product);
+    }
+
+    /**
+     * Performs multiplication.
+     *
+     * @param Array $x_value
+     * @param Boolean $x_negative
+     * @param Array $y_value
+     * @param Boolean $y_negative
+     * @return Array
+     * @access private
+     */
+    function _multiply($x_value, $x_negative, $y_value, $y_negative)
+    {
+        //if ( $x_value == $y_value ) {
+        //    return array(
+        //        MATH_BIGINTEGER_VALUE => $this->_square($x_value),
+        //        MATH_BIGINTEGER_SIGN => $x_sign != $y_value
+        //    );
+        //}
+
+        $x_length = count($x_value);
+        $y_length = count($y_value);
+
+        if (!$x_length || !$y_length)
+        { // a 0 is being multiplied
+            return [
+                MATH_BIGINTEGER_VALUE => [],
+                MATH_BIGINTEGER_SIGN  => false
+            ];
+        }
+
+        return [
+            MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ?
+                $this->_trim($this->_regularMultiply($x_value, $y_value)) :
+                $this->_trim($this->_karatsuba($x_value, $y_value)),
+            MATH_BIGINTEGER_SIGN  => $x_negative != $y_negative
+        ];
+    }
+
+    /**
+     * Performs long multiplication on two BigIntegers
+     *
+     * Modeled after 'multiply' in MutableBigInteger.java.
+     *
+     * @param Array $x_value
+     * @param Array $y_value
+     * @return Array
+     * @access private
+     */
+    function _regularMultiply($x_value, $y_value)
+    {
+        $x_length = count($x_value);
+        $y_length = count($y_value);
+
+        if (!$x_length || !$y_length)
+        { // a 0 is being multiplied
+            return [];
+        }
+
+        if ($x_length < $y_length)
+        {
+            $temp    = $x_value;
+            $x_value = $y_value;
+            $y_value = $temp;
+
+            $x_length = count($x_value);
+            $y_length = count($y_value);
+        }
+
+        $product_value = $this->_array_repeat(0, $x_length + $y_length);
+
+        // the following for loop could be removed if the for loop following it
+        // (the one with nested for loops) initially set $i to 0, but
+        // doing so would also make the result in one set of unnecessary adds,
+        // since on the outermost loops first pass, $product->value[$k] is going
+        // to always be 0
+
+        $carry = 0;
+
+        for ($j = 0; $j < $x_length; ++$j)
+        { // ie. $i = 0
+            $temp              = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
+            $carry             = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+            $product_value[$j] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+        }
+
+        $product_value[$j] = $carry;
+
+        // the above for loop is what the previous comment was talking about.  the
+        // following for loop is the "one with nested for loops"
+        for ($i = 1; $i < $y_length; ++$i)
+        {
+            $carry = 0;
+
+            for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k)
+            {
+                $temp              = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
+                $carry             = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+                $product_value[$k] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+            }
+
+            $product_value[$k] = $carry;
+        }
+
+        return $product_value;
+    }
+
+    /**
+     * Array Repeat
+     *
+     * @param $input Array
+     * @param $multiplier mixed
+     * @return Array
+     * @access private
+     */
+    function _array_repeat($input, $multiplier)
+    {
+        return ($multiplier) ? array_fill(0, $multiplier, $input) : [];
+    }
+
+    /**
+     * Performs Karatsuba multiplication on two BigIntegers
+     *
+     * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}.
+     *
+     * @param Array $x_value
+     * @param Array $y_value
+     * @return Array
+     * @access private
+     */
+    function _karatsuba($x_value, $y_value)
+    {
+        $m = min(count($x_value) >> 1, count($y_value) >> 1);
+
+        if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF)
+        {
+            return $this->_regularMultiply($x_value, $y_value);
+        }
+
+        $x1 = array_slice($x_value, $m);
+        $x0 = array_slice($x_value, 0, $m);
+        $y1 = array_slice($y_value, $m);
+        $y0 = array_slice($y_value, 0, $m);
+
+        $z2 = $this->_karatsuba($x1, $y1);
+        $z0 = $this->_karatsuba($x0, $y0);
+
+        $z1   = $this->_add($x1, false, $x0, false);
+        $temp = $this->_add($y1, false, $y0, false);
+        $z1   = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]);
+        $temp = $this->_add($z2, false, $z0, false);
+        $z1   = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false);
+
+        $z2                        = array_merge(array_fill(0, 2 * $m, 0), $z2);
+        $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]);
+
+        $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]);
+        $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false);
+
+        return $xy[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * Converts 32-bit integers to bytes.
+     *
+     * @param Integer $x
+     * @return String
+     * @access private
+     */
+    function _int2bytes($x)
+    {
+        return ltrim(pack('N', $x), chr(0));
+    }
+
+    /**
+     * Converts a BigInteger to a bit string (eg. base-2).
+     *
+     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+     * saved as two's compliment.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('65');
+     *
+     *    echo $a->toBits(); // outputs '1000001'
+     * ?>
+     * </code>
+     *
+     * @param Boolean $twos_compliment
+     * @return String
+     * @access public
+     * @internal Converts a base-2**26 number to base-2**2
+     */
+    function toBits($twos_compliment = false)
+    {
+        $hex  = $this->toHex($twos_compliment);
+        $bits = '';
+        for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i -= 8)
+        {
+            $bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits;
+        }
+        if ($start)
+        { // hexdec('') == 0
+            $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits;
+        }
+        $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0');
+
+        if ($twos_compliment && $this->compare(new BigInteger()) > 0 && $this->precision <= 0)
+        {
+            return '0' . $result;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Converts a BigInteger to a hex string (eg. base-16)).
+     *
+     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+     * saved as two's compliment.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('65');
+     *
+     *    echo $a->toHex(); // outputs '41'
+     * ?>
+     * </code>
+     *
+     * @param Boolean $twos_compliment
+     * @return String
+     * @access public
+     * @internal Converts a base-2**26 number to base-2**8
+     */
+    function toHex($twos_compliment = false)
+    {
+        return bin2hex($this->toBytes($twos_compliment));
+    }
+
+    /**
+     * Converts a BigInteger to a byte string (eg. base-256).
+     *
+     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+     * saved as two's compliment.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('65');
+     *
+     *    echo $a->toBytes(); // outputs chr(65)
+     * ?>
+     * </code>
+     *
+     * @param Boolean $twos_compliment
+     * @return String
+     * @access public
+     * @internal Converts a base-2**26 number to base-2**8
+     */
+    function toBytes($twos_compliment = false)
+    {
+        if ($twos_compliment)
+        {
+            $comparison = $this->compare(new BigInteger());
+            if ($comparison == 0)
+            {
+                return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+            }
+
+            $temp  = $comparison < 0 ? $this->add(new BigInteger(1)) : $this->copy();
+            $bytes = $temp->toBytes();
+
+            if (empty($bytes))
+            { // eg. if the number we're trying to convert is -1
+                $bytes = chr(0);
+            }
+
+            if (ord($bytes[0]) & 0x80)
+            {
+                $bytes = chr(0) . $bytes;
+            }
+
+            return $comparison < 0 ? ~$bytes : $bytes;
+        }
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                if (gmp_cmp($this->value, gmp_init(0)) == 0)
+                {
+                    return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+                }
+
+                $temp = gmp_strval(gmp_abs($this->value), 16);
+                $temp = (strlen($temp) & 1) ? '0' . $temp : $temp;
+                $temp = pack('H*', $temp);
+
+                return $this->precision > 0 ?
+                    substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
+                    ltrim($temp, chr(0));
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                if ($this->value === '0')
+                {
+                    return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+                }
+
+                $value   = '';
+                $current = $this->value;
+
+                if ($current[0] == '-')
+                {
+                    $current = substr($current, 1);
+                }
+
+                while (bccomp($current, '0', 0) > 0)
+                {
+                    $temp    = bcmod($current, '16777216');
+                    $value   = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value;
+                    $current = bcdiv($current, '16777216', 0);
+                }
+
+                return $this->precision > 0 ?
+                    substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
+                    ltrim($value, chr(0));
+        }
+
+        if (!count($this->value))
+        {
+            return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+        }
+        $result = $this->_int2bytes($this->value[count($this->value) - 1]);
+
+        $temp = $this->copy();
+
+        for ($i = count($temp->value) - 2; $i >= 0; --$i)
+        {
+            $temp->_base256_lshift($result, MATH_BIGINTEGER_BASE);
+            $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT);
+        }
+
+        return $this->precision > 0 ?
+            str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) :
+            $result;
+    }
+
+    /**
+     * Compares two numbers.
+     *
+     * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite.  The reason for this is
+     * demonstrated thusly:
+     *
+     * $x  > $y: $x->compare($y)  > 0
+     * $x  < $y: $x->compare($y)  < 0
+     * $x == $y: $x->compare($y) == 0
+     *
+     * Note how the same comparison operator is used.  If you want to test for equality, use $x->equals($y).
+     *
+     * @param BigInteger $y
+     * @return Integer < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+     * @access public
+     * @see equals()
+     * @internal Could return $this->subtract($x), but that's not as fast as what we do do.
+     */
+    function compare($y)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                return gmp_cmp($this->value, $y->value);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                return bccomp($this->value, $y->value, 0);
+        }
+
+        return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative);
+    }
+
+    /**
+     * Copy an object
+     *
+     * PHP5 passes objects by reference while PHP4 passes by value.  As such, we need a function to guarantee
+     * that all objects are passed by value, when appropriate.  More information can be found here:
+     *
+     * {@link http://php.net/language.oop5.basic#51624}
+     *
+     * @access public
+     * @return BigInteger
+     * @see __clone()
+     */
+    function copy()
+    {
+        $temp              = new BigInteger();
+        $temp->value       = $this->value;
+        $temp->is_negative = $this->is_negative;
+        $temp->generator   = $this->generator;
+        $temp->precision   = $this->precision;
+        $temp->bitmask     = $this->bitmask;
+        return $temp;
+    }
+
+    /**
+     * Logical Left Shift
+     *
+     * Shifts binary strings $shift bits, essentially multiplying by 2**$shift.
+     *
+     * @param $x String
+     * @param $shift Integer
+     * @return String
+     * @access private
+     */
+    function _base256_lshift(&$x, $shift)
+    {
+        if ($shift == 0)
+        {
+            return;
+        }
+
+        $num_bytes = $shift >> 3; // eg. floor($shift/8)
+        $shift     &= 7; // eg. $shift % 8
+
+        $carry = 0;
+        for ($i = strlen($x) - 1; $i >= 0; --$i)
+        {
+            $temp  = ord($x[$i]) << $shift | $carry;
+            $x[$i] = chr($temp);
+            $carry = $temp >> 8;
+        }
+        $carry = ($carry != 0) ? chr($carry) : '';
+        $x     = $carry . $x . str_repeat(chr(0), $num_bytes);
+    }
+
+    /**
+     *  __toString() magic method
+     *
+     * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
+     * toString().
+     *
+     * @access public
+     * @internal Implemented per a suggestion by Techie-Michael - thanks!
+     */
+    function __toString()
+    {
+        return $this->toString();
+    }
+
+    /**
+     * Converts a BigInteger to a base-10 number.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('50');
+     *
+     *    echo $a->toString(); // outputs 50
+     * ?>
+     * </code>
+     *
+     * @return String
+     * @access public
+     * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10)
+     */
+    function toString()
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                return gmp_strval($this->value);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                if ($this->value === '0')
+                {
+                    return '0';
+                }
+
+                return ltrim($this->value, '0');
+        }
+
+        if (!count($this->value))
+        {
+            return '0';
+        }
+
+        $temp              = $this->copy();
+        $temp->is_negative = false;
+
+        $divisor        = new BigInteger();
+        $divisor->value = [MATH_BIGINTEGER_MAX10];
+        $result         = '';
+        while (count($temp->value))
+        {
+            list($temp, $mod) = $temp->divide($divisor);
+            $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', MATH_BIGINTEGER_MAX10_LEN, '0', STR_PAD_LEFT) . $result;
+        }
+        $result = ltrim($result, '0');
+        if (empty($result))
+        {
+            $result = '0';
+        }
+
+        if ($this->is_negative)
+        {
+            $result = '-' . $result;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Divides two BigIntegers.
+     *
+     * Returns an array whose first element contains the quotient and whose second element contains the
+     * "common residue".  If the remainder would be positive, the "common residue" and the remainder are the
+     * same.  If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+     * and the divisor (basically, the "common residue" is the first positive modulo).
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('10');
+     *    $b = new BigInteger('20');
+     *
+     *    list($quotient, $remainder) = $a->divide($b);
+     *
+     *    echo $quotient->toString(); // outputs 0
+     *    echo "\r\n";
+     *    echo $remainder->toString(); // outputs 10
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $y
+     * @return Array
+     * @access public
+     * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}.
+     */
+    function divide($y)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $quotient  = new BigInteger();
+                $remainder = new BigInteger();
+
+                list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value);
+
+                if (gmp_sign($remainder->value) < 0)
+                {
+                    $remainder->value = gmp_add($remainder->value, gmp_abs($y->value));
+                }
+
+                return [$this->_normalize($quotient), $this->_normalize($remainder)];
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $quotient  = new BigInteger();
+                $remainder = new BigInteger();
+
+                $quotient->value  = bcdiv($this->value, $y->value, 0);
+                $remainder->value = bcmod($this->value, $y->value);
+
+                if ($remainder->value[0] == '-')
+                {
+                    $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0);
+                }
+
+                return [$this->_normalize($quotient), $this->_normalize($remainder)];
+        }
+
+        if (count($y->value) == 1)
+        {
+            list($q, $r) = $this->_divide_digit($this->value, $y->value[0]);
+            $quotient              = new BigInteger();
+            $remainder             = new BigInteger();
+            $quotient->value       = $q;
+            $remainder->value      = [$r];
+            $quotient->is_negative = $this->is_negative != $y->is_negative;
+            return [$this->_normalize($quotient), $this->_normalize($remainder)];
+        }
+
+        static $zero;
+        if (!isset($zero))
+        {
+            $zero = new BigInteger();
+        }
+
+        $x = $this->copy();
+        $y = $y->copy();
+
+        $x_sign = $x->is_negative;
+        $y_sign = $y->is_negative;
+
+        $x->is_negative = $y->is_negative = false;
+
+        $diff = $x->compare($y);
+
+        if (!$diff)
+        {
+            $temp              = new BigInteger();
+            $temp->value       = [1];
+            $temp->is_negative = $x_sign != $y_sign;
+            return [$this->_normalize($temp), $this->_normalize(new BigInteger())];
+        }
+
+        if ($diff < 0)
+        {
+            // if $x is negative, "add" $y.
+            if ($x_sign)
+            {
+                $x = $y->subtract($x);
+            }
+            return [$this->_normalize(new BigInteger()), $this->_normalize($x)];
+        }
+
+        // normalize $x and $y as described in HAC 14.23 / 14.24
+        $msb = $y->value[count($y->value) - 1];
+        for ($shift = 0; !($msb & MATH_BIGINTEGER_MSB); ++$shift)
+        {
+            $msb <<= 1;
+        }
+        $x->_lshift($shift);
+        $y->_lshift($shift);
+        $y_value = &$y->value;
+
+        $x_max = count($x->value) - 1;
+        $y_max = count($y->value) - 1;
+
+        $quotient       = new BigInteger();
+        $quotient_value = &$quotient->value;
+        $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1);
+
+        static $temp, $lhs, $rhs;
+        if (!isset($temp))
+        {
+            $temp = new BigInteger();
+            $lhs  = new BigInteger();
+            $rhs  = new BigInteger();
+        }
+        $temp_value = &$temp->value;
+        $rhs_value  = &$rhs->value;
+
+        // $temp = $y << ($x_max - $y_max-1) in base 2**26
+        $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value);
+
+        while ($x->compare($temp) >= 0)
+        {
+            // calculate the "common residue"
+            ++$quotient_value[$x_max - $y_max];
+            $x     = $x->subtract($temp);
+            $x_max = count($x->value) - 1;
+        }
+
+        for ($i = $x_max; $i >= $y_max + 1; --$i)
+        {
+            $x_value  = &$x->value;
+            $x_window = [
+                isset($x_value[$i]) ? $x_value[$i] : 0,
+                isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0,
+                isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0
+            ];
+            $y_window = [
+                $y_value[$y_max],
+                ($y_max > 0) ? $y_value[$y_max - 1] : 0
+            ];
+
+            $q_index = $i - $y_max - 1;
+            if ($x_window[0] == $y_window[0])
+            {
+                $quotient_value[$q_index] = MATH_BIGINTEGER_MAX_DIGIT;
+            }
+            else
+            {
+                $quotient_value[$q_index] = (int)(
+                    ($x_window[0] * MATH_BIGINTEGER_BASE_FULL + $x_window[1]) /
+                    $y_window[0]
+                );
+            }
+
+            $temp_value = [$y_window[1], $y_window[0]];
+
+            $lhs->value = [$quotient_value[$q_index]];
+            $lhs        = $lhs->multiply($temp);
+
+            $rhs_value = [$x_window[2], $x_window[1], $x_window[0]];
+
+            while ($lhs->compare($rhs) > 0)
+            {
+                --$quotient_value[$q_index];
+
+                $lhs->value = [$quotient_value[$q_index]];
+                $lhs        = $lhs->multiply($temp);
+            }
+
+            $adjust     = $this->_array_repeat(0, $q_index);
+            $temp_value = [$quotient_value[$q_index]];
+            $temp       = $temp->multiply($y);
+            $temp_value = &$temp->value;
+            $temp_value = array_merge($adjust, $temp_value);
+
+            $x = $x->subtract($temp);
+
+            if ($x->compare($zero) < 0)
+            {
+                $temp_value = array_merge($adjust, $y_value);
+                $x          = $x->add($temp);
+
+                --$quotient_value[$q_index];
+            }
+
+            $x_max = count($x_value) - 1;
+        }
+
+        // unnormalize the remainder
+        $x->_rshift($shift);
+
+        $quotient->is_negative = $x_sign != $y_sign;
+
+        // calculate the "common residue", if appropriate
+        if ($x_sign)
+        {
+            $y->_rshift($shift);
+            $x = $y->subtract($x);
+        }
+
+        return [$this->_normalize($quotient), $this->_normalize($x)];
+    }
+
+    /**
+     * Divides a BigInteger by a regular integer
+     *
+     * abc / x = a00 / x + b0 / x + c / x
+     *
+     * @param Array $dividend
+     * @param Array $divisor
+     * @return Array
+     * @access private
+     */
+    function _divide_digit($dividend, $divisor)
+    {
+        $carry  = 0;
+        $result = [];
+
+        for ($i = count($dividend) - 1; $i >= 0; --$i)
+        {
+            $temp       = MATH_BIGINTEGER_BASE_FULL * $carry + $dividend[$i];
+            $result[$i] = (int)($temp / $divisor);
+            $carry      = (int)($temp - $divisor * $result[$i]);
+        }
+
+        return [$result, $carry];
+    }
+
+    /**
+     * Subtracts two BigIntegers.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('10');
+     *    $b = new BigInteger('20');
+     *
+     *    $c = $a->subtract($b);
+     *
+     *    echo $c->toString(); // outputs -10
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $y
+     * @return BigInteger
+     * @access public
+     * @internal Performs base-2**52 subtraction
+     */
+    function subtract($y)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_sub($this->value, $y->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp        = new BigInteger();
+                $temp->value = bcsub($this->value, $y->value, 0);
+
+                return $this->_normalize($temp);
+        }
+
+        $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+        $result              = new BigInteger();
+        $result->value       = $temp[MATH_BIGINTEGER_VALUE];
+        $result->is_negative = $temp[MATH_BIGINTEGER_SIGN];
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Logical Left Shift
+     *
+     * Shifts BigInteger's by $shift bits.
+     *
+     * @param Integer $shift
+     * @access private
+     */
+    function _lshift($shift)
+    {
+        if ($shift == 0)
+        {
+            return;
+        }
+
+        $num_digits = (int)($shift / MATH_BIGINTEGER_BASE);
+        $shift      %= MATH_BIGINTEGER_BASE;
+        $shift      = 1 << $shift;
+
+        $carry = 0;
+
+        for ($i = 0; $i < count($this->value); ++$i)
+        {
+            $temp            = $this->value[$i] * $shift + $carry;
+            $carry           = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+            $this->value[$i] = (int)($temp - $carry * MATH_BIGINTEGER_BASE_FULL);
+        }
+
+        if ($carry)
+        {
+            $this->value[] = $carry;
+        }
+
+        while ($num_digits--)
+        {
+            array_unshift($this->value, 0);
+        }
+    }
+
+    /**
+     * Logical Right Shift
+     *
+     * Shifts BigInteger's by $shift bits.
+     *
+     * @param Integer $shift
+     * @access private
+     */
+    function _rshift($shift)
+    {
+        if ($shift == 0)
+        {
+            return;
+        }
+
+        $num_digits  = (int)($shift / MATH_BIGINTEGER_BASE);
+        $shift       %= MATH_BIGINTEGER_BASE;
+        $carry_shift = MATH_BIGINTEGER_BASE - $shift;
+        $carry_mask  = (1 << $shift) - 1;
+
+        if ($num_digits)
+        {
+            $this->value = array_slice($this->value, $num_digits);
+        }
+
+        $carry = 0;
+
+        for ($i = count($this->value) - 1; $i >= 0; --$i)
+        {
+            $temp            = $this->value[$i] >> $shift | $carry;
+            $carry           = ($this->value[$i] & $carry_mask) << $carry_shift;
+            $this->value[$i] = $temp;
+        }
+
+        $this->value = $this->_trim($this->value);
+    }
+
+    /**
+     * __clone() magic method
+     *
+     * Although you can call BigInteger::__toString() directly in PHP5, you cannot call BigInteger::__clone()
+     * directly in PHP5.  You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5
+     * only syntax of $y = clone $x.  As such, if you're trying to write an application that works on both PHP4 and PHP5,
+     * call BigInteger::copy(), instead.
+     *
+     * @access public
+     * @return BigInteger
+     * @see copy()
+     */
+    function __clone()
+    {
+        return $this->copy();
+    }
+
+    /**
+     *  __sleep() magic method
+     *
+     * Will be called, automatically, when serialize() is called on a BigInteger object.
+     *
+     * @see __wakeup()
+     * @access public
+     */
+    function __sleep()
+    {
+        $this->hex = $this->toHex(true);
+        $vars      = ['hex'];
+        if ($this->generator != 'mt_rand')
+        {
+            $vars[] = 'generator';
+        }
+        if ($this->precision > 0)
+        {
+            $vars[] = 'precision';
+        }
+        return $vars;
+    }
+
+    /**
+     *  __wakeup() magic method
+     *
+     * Will be called, automatically, when unserialize() is called on a BigInteger object.
+     *
+     * @see __sleep()
+     * @access public
+     */
+    function __wakeup()
+    {
+        $temp              = new BigInteger($this->hex, -16);
+        $this->value       = $temp->value;
+        $this->is_negative = $temp->is_negative;
+        $this->setRandomGenerator($this->generator);
+        if ($this->precision > 0)
+        {
+            // recalculate $this->bitmask
+            $this->setPrecision($this->precision);
+        }
+    }
+
+    /**
+     * Set random number generator function
+     *
+     * This function is deprecated.
+     *
+     * @param String $generator
+     * @access public
+     */
+    function setRandomGenerator($generator)
+    {
+
+    }
+
+    /**
+     * Set Precision
+     *
+     * Some bitwise operations give different results depending on the precision being used.  Examples include left
+     * shift, not, and rotates.
+     *
+     * @param Integer $bits
+     * @access public
+     */
+    function setPrecision($bits)
+    {
+        $this->precision = $bits;
+        if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH)
+        {
+            $this->bitmask = new BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256);
+        }
+        else
+        {
+            $this->bitmask = new BigInteger(bcpow('2', $bits, 0));
+        }
+
+        $temp        = $this->_normalize($this);
+        $this->value = $temp->value;
+    }
+
+    /**
+     * Performs modular exponentiation.
+     *
+     * Alias for BigInteger::modPow()
+     *
+     * @param BigInteger $e
+     * @param BigInteger $n
+     * @return BigInteger
+     * @access public
+     */
+    function powMod($e, $n)
+    {
+        return $this->modPow($e, $n);
+    }
+
+    /**
+     * Performs modular exponentiation.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger('10');
+     *    $b = new BigInteger('20');
+     *    $c = new BigInteger('30');
+     *
+     *    $c = $a->modPow($b, $c);
+     *
+     *    echo $c->toString(); // outputs 10
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $e
+     * @param BigInteger $n
+     * @return BigInteger
+     * @access public
+     * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and
+     *    and although the approach involving repeated squaring does vastly better, it, too, is impractical
+     *    for our purposes.  The reason being that division - by far the most complicated and time-consuming
+     *    of the basic operations (eg. +,-,*,/) - occurs multiple times within it.
+     *
+     *    Modular reductions resolve this issue.  Although an individual modular reduction takes more time
+     *    then an individual division, when performed in succession (with the same modulo), they're a lot faster.
+     *
+     *    The two most commonly used modular reductions are Barrett and Montgomery reduction.  Montgomery reduction,
+     *    although faster, only works when the gcd of the modulo and of the base being used is 1.  In RSA, when the
+     *    base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because
+     *    the product of two odd numbers is odd), but what about when RSA isn't used?
+     *
+     *    In contrast, Barrett reduction has no such constraint.  As such, some bigint implementations perform a
+     *    Barrett reduction after every operation in the modpow function.  Others perform Barrett reductions when the
+     *    modulo is even and Montgomery reductions when the modulo is odd.  BigInteger.java's modPow method, however,
+     *    uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and
+     *    the other, a power of two - and recombine them, later.  This is the method that this modPow function uses.
+     *    {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates.
+     */
+    function modPow($e, $n)
+    {
+        $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs();
+
+        if ($e->compare(new BigInteger()) < 0)
+        {
+            $e = $e->abs();
+
+            $temp = $this->modInverse($n);
+            if ($temp === false)
+            {
+                return false;
+            }
+
+            return $this->_normalize($temp->modPow($e, $n));
+        }
+
+        if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP)
+        {
+            $temp        = new BigInteger();
+            $temp->value = gmp_powm($this->value, $e->value, $n->value);
+
+            return $this->_normalize($temp);
+        }
+
+        if ($this->compare(new BigInteger()) < 0 || $this->compare($n) > 0)
+        {
+            list(, $temp) = $this->divide($n);
+            return $temp->modPow($e, $n);
+        }
+
+        if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED'))
+        {
+            $components = [
+                'modulus'        => $n->toBytes(true),
+                'publicExponent' => $e->toBytes(true)
+            ];
+
+            $components = [
+                'modulus'        => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']),
+                'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent'])
+            ];
+
+            $RSAPublicKey = pack('Ca*a*a*', 48, $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']
+            );
+
+            $rsaOID       = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
+            $RSAPublicKey = chr(0) . $RSAPublicKey;
+            $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey;
+
+            $encapsulated = pack('Ca*a*', 48, $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey
+            );
+
+            $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
+                            chunk_split(base64_encode($encapsulated)) .
+                            '-----END PUBLIC KEY-----';
+
+            $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT);
+
+            if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING))
+            {
+                return new BigInteger($result, 256);
+            }
+        }
+
+        if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH)
+        {
+            $temp        = new BigInteger();
+            $temp->value = bcpowmod($this->value, $e->value, $n->value, 0);
+
+            return $this->_normalize($temp);
+        }
+
+        if (empty($e->value))
+        {
+            $temp        = new BigInteger();
+            $temp->value = [1];
+            return $this->_normalize($temp);
+        }
+
+        if ($e->value == [1])
+        {
+            list(, $temp) = $this->divide($n);
+            return $this->_normalize($temp);
+        }
+
+        if ($e->value == [2])
+        {
+            $temp        = new BigInteger();
+            $temp->value = $this->_square($this->value);
+            list(, $temp) = $temp->divide($n);
+            return $this->_normalize($temp);
+        }
+
+        return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT));
+
+        // is the modulo odd?
+        if ($n->value[0] & 1)
+        {
+            return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY));
+        }
+        // if it's not, it's even
+        // find the lowest set bit (eg. the max pow of 2 that divides $n)
+        for ($i = 0; $i < count($n->value); ++$i)
+        {
+            if ($n->value[$i])
+            {
+                $temp = decbin($n->value[$i]);
+                $j    = strlen($temp) - strrpos($temp, '1') - 1;
+                $j    += 26 * $i;
+                break;
+            }
+        }
+        // at this point, 2^$j * $n/(2^$j) == $n
+
+        $mod1 = $n->copy();
+        $mod1->_rshift($j);
+        $mod2        = new BigInteger();
+        $mod2->value = [1];
+        $mod2->_lshift($j);
+
+        $part1 = ($mod1->value != [1]) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new BigInteger();
+        $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2);
+
+        $y1 = $mod2->modInverse($mod1);
+        $y2 = $mod1->modInverse($mod2);
+
+        $result = $part1->multiply($mod2);
+        $result = $result->multiply($y1);
+
+        $temp = $part2->multiply($mod1);
+        $temp = $temp->multiply($y2);
+
+        $result = $result->add($temp);
+        list(, $result) = $result->divide($n);
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Absolute value.
+     *
+     * @return BigInteger
+     * @access public
+     */
+    function abs()
+    {
+        $temp = new BigInteger();
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp->value = gmp_abs($this->value);
+                break;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value;
+                break;
+            default:
+                $temp->value = $this->value;
+        }
+
+        return $temp;
+    }
+
+    /**
+     * Calculates modular inverses.
+     *
+     * Say you have (30 mod 17 * x mod 17) mod 17 == 1.  x can be found using modular inverses.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger(30);
+     *    $b = new BigInteger(17);
+     *
+     *    $c = $a->modInverse($b);
+     *    echo $c->toString(); // outputs 4
+     *
+     *    echo "\r\n";
+     *
+     *    $d = $a->multiply($c);
+     *    list(, $d) = $d->divide($b);
+     *    echo $d; // outputs 1 (as per the definition of modular inverse)
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $n
+     * @return mixed false, if no modular inverse exists, BigInteger, otherwise.
+     * @access public
+     * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.
+     */
+    function modInverse($n)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_invert($this->value, $n->value);
+
+                return ($temp->value === false) ? false : $this->_normalize($temp);
+        }
+
+        static $zero, $one;
+        if (!isset($zero))
+        {
+            $zero = new BigInteger();
+            $one  = new BigInteger(1);
+        }
+
+        // $x mod -$n == $x mod $n.
+        $n = $n->abs();
+
+        if ($this->compare($zero) < 0)
+        {
+            $temp = $this->abs();
+            $temp = $temp->modInverse($n);
+            return $this->_normalize($n->subtract($temp));
+        }
+
+        extract($this->extendedGCD($n));
+
+        if (!$gcd->equals($one))
+        {
+            return false;
+        }
+
+        $x = $x->compare($zero) < 0 ? $x->add($n) : $x;
+
+        return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x);
+    }
+
+    /**
+     * Calculates the greatest common divisor and Bezout's identity.
+     *
+     * Say you have 693 and 609.  The GCD is 21.  Bezout's identity states that there exist integers x and y such that
+     * 693*x + 609*y == 21.  In point of fact, there are actually an infinite number of x and y combinations and which
+     * combination is returned is dependant upon which mode is in use.  See
+     * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger(693);
+     *    $b = new BigInteger(609);
+     *
+     *    extract($a->extendedGCD($b));
+     *
+     *    echo $gcd->toString() . "\r\n"; // outputs 21
+     *    echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $n
+     * @return BigInteger
+     * @access public
+     * @internal Calculates the GCD using the binary xGCD algorithim described in
+     *    {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}.  As the text above 14.61 notes,
+     *    the more traditional algorithim requires "relatively costly multiple-precision divisions".
+     */
+    function extendedGCD($n)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                extract(gmp_gcdext($this->value, $n->value));
+
+                return [
+                    'gcd' => $this->_normalize(new BigInteger($g)),
+                    'x'   => $this->_normalize(new BigInteger($s)),
+                    'y'   => $this->_normalize(new BigInteger($t))
+                ];
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works
+                // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway.  as is,
+                // the basic extended euclidean algorithim is what we're using.
+
+                $u = $this->value;
+                $v = $n->value;
+
+                $a = '1';
+                $b = '0';
+                $c = '0';
+                $d = '1';
+
+                while (bccomp($v, '0', 0) != 0)
+                {
+                    $q = bcdiv($u, $v, 0);
+
+                    $temp = $u;
+                    $u    = $v;
+                    $v    = bcsub($temp, bcmul($v, $q, 0), 0);
+
+                    $temp = $a;
+                    $a    = $c;
+                    $c    = bcsub($temp, bcmul($a, $q, 0), 0);
+
+                    $temp = $b;
+                    $b    = $d;
+                    $d    = bcsub($temp, bcmul($b, $q, 0), 0);
+                }
+
+                return [
+                    'gcd' => $this->_normalize(new BigInteger($u)),
+                    'x'   => $this->_normalize(new BigInteger($a)),
+                    'y'   => $this->_normalize(new BigInteger($b))
+                ];
+        }
+
+        $y        = $n->copy();
+        $x        = $this->copy();
+        $g        = new BigInteger();
+        $g->value = [1];
+
+        while (!(($x->value[0] & 1) || ($y->value[0] & 1)))
+        {
+            $x->_rshift(1);
+            $y->_rshift(1);
+            $g->_lshift(1);
+        }
+
+        $u = $x->copy();
+        $v = $y->copy();
+
+        $a = new BigInteger();
+        $b = new BigInteger();
+        $c = new BigInteger();
+        $d = new BigInteger();
+
+        $a->value = $d->value = $g->value = [1];
+        $b->value = $c->value = [];
+
+        while (!empty($u->value))
+        {
+            while (!($u->value[0] & 1))
+            {
+                $u->_rshift(1);
+                if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)))
+                {
+                    $a = $a->add($y);
+                    $b = $b->subtract($x);
+                }
+                $a->_rshift(1);
+                $b->_rshift(1);
+            }
+
+            while (!($v->value[0] & 1))
+            {
+                $v->_rshift(1);
+                if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)))
+                {
+                    $c = $c->add($y);
+                    $d = $d->subtract($x);
+                }
+                $c->_rshift(1);
+                $d->_rshift(1);
+            }
+
+            if ($u->compare($v) >= 0)
+            {
+                $u = $u->subtract($v);
+                $a = $a->subtract($c);
+                $b = $b->subtract($d);
+            }
+            else
+            {
+                $v = $v->subtract($u);
+                $c = $c->subtract($a);
+                $d = $d->subtract($b);
+            }
+        }
+
+        return [
+            'gcd' => $this->_normalize($g->multiply($v)),
+            'x'   => $this->_normalize($c),
+            'y'   => $this->_normalize($d)
+        ];
+    }
+
+    /**
+     * DER-encode an integer
+     *
+     * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL
+     *
+     * @param Integer $length
+     * @return String
+     * @see modPow()
+     * @access private
+     */
+    function _encodeASN1Length($length)
+    {
+        if ($length <= 0x7F)
+        {
+            return chr($length);
+        }
+
+        $temp = ltrim(pack('N', $length), chr(0));
+        return pack('Ca*', 0x80 | strlen($temp), $temp);
+    }
+
+    /**
+     * Performs squaring
+     *
+     * @param Array $x
+     * @return Array
+     * @access private
+     */
+    function _square($x = false)
+    {
+        return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ?
+            $this->_trim($this->_baseSquare($x)) :
+            $this->_trim($this->_karatsubaSquare($x));
+    }
+
+    /**
+     * Performs traditional squaring on two BigIntegers
+     *
+     * Squaring can be done faster than multiplying a number by itself can be.  See
+     * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} /
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information.
+     *
+     * @param Array $value
+     * @return Array
+     * @access private
+     */
+    function _baseSquare($value)
+    {
+        if (empty($value))
+        {
+            return [];
+        }
+        $square_value = $this->_array_repeat(0, 2 * count($value));
+
+        for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i)
+        {
+            $i2 = $i << 1;
+
+            $temp              = $square_value[$i2] + $value[$i] * $value[$i];
+            $carry             = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+            $square_value[$i2] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+
+            // note how we start from $i+1 instead of 0 as we do in multiplication.
+            for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k)
+            {
+                $temp             = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry;
+                $carry            = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+                $square_value[$k] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+            }
+
+            // the following line can yield values larger 2**15.  at this point, PHP should switch
+            // over to floats.
+            $square_value[$i + $max_index + 1] = $carry;
+        }
+
+        return $square_value;
+    }
+
+    /**
+     * Performs Karatsuba "squaring" on two BigIntegers
+     *
+     * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}.
+     *
+     * @param Array $value
+     * @return Array
+     * @access private
+     */
+    function _karatsubaSquare($value)
+    {
+        $m = count($value) >> 1;
+
+        if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF)
+        {
+            return $this->_baseSquare($value);
+        }
+
+        $x1 = array_slice($value, $m);
+        $x0 = array_slice($value, 0, $m);
+
+        $z2 = $this->_karatsubaSquare($x1);
+        $z0 = $this->_karatsubaSquare($x0);
+
+        $z1   = $this->_add($x1, false, $x0, false);
+        $z1   = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]);
+        $temp = $this->_add($z2, false, $z0, false);
+        $z1   = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false);
+
+        $z2                        = array_merge(array_fill(0, 2 * $m, 0), $z2);
+        $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]);
+
+        $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]);
+        $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false);
+
+        return $xx[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * Sliding Window k-ary Modular Exponentiation
+     *
+     * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} /
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}.  In a departure from those algorithims,
+     * however, this function performs a modular reduction after every multiplication and squaring operation.
+     * As such, this function has the same preconditions that the reductions being used do.
+     *
+     * @param BigInteger $e
+     * @param BigInteger $n
+     * @param Integer $mode
+     * @return BigInteger
+     * @access private
+     */
+    function _slidingWindow($e, $n, $mode)
+    {
+        static $window_ranges = [7, 25, 81, 241, 673, 1793]; // from BigInteger.java's oddModPow function
+        //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1
+
+        $e_value  = $e->value;
+        $e_length = count($e_value) - 1;
+        $e_bits   = decbin($e_value[$e_length]);
+        for ($i = $e_length - 1; $i >= 0; --$i)
+        {
+            $e_bits .= str_pad(decbin($e_value[$i]), MATH_BIGINTEGER_BASE, '0', STR_PAD_LEFT);
+        }
+
+        $e_length = strlen($e_bits);
+
+        // calculate the appropriate window size.
+        // $window_size == 3 if $window_ranges is between 25 and 81, for example.
+        for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i)
+        {
+            ;
+        }
+
+        $n_value = $n->value;
+
+        // precompute $this^0 through $this^$window_size
+        $powers    = [];
+        $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode);
+        $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode);
+
+        // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end
+        // in a 1.  ie. it's supposed to be odd.
+        $temp = 1 << ($window_size - 1);
+        for ($i = 1; $i < $temp; ++$i)
+        {
+            $i2              = $i << 1;
+            $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode);
+        }
+
+        $result = [1];
+        $result = $this->_prepareReduce($result, $n_value, $mode);
+
+        for ($i = 0; $i < $e_length;)
+        {
+            if (!$e_bits[$i])
+            {
+                $result = $this->_squareReduce($result, $n_value, $mode);
+                ++$i;
+            }
+            else
+            {
+                for ($j = $window_size - 1; $j > 0; --$j)
+                {
+                    if (!empty($e_bits[$i + $j]))
+                    {
+                        break;
+                    }
+                }
+
+                for ($k = 0; $k <= $j; ++$k)
+                {// eg. the length of substr($e_bits, $i, $j+1)
+                    $result = $this->_squareReduce($result, $n_value, $mode);
+                }
+
+                $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode);
+
+                $i += $j + 1;
+            }
+        }
+
+        $temp        = new BigInteger();
+        $temp->value = $this->_reduce($result, $n_value, $mode);
+
+        return $temp;
+    }
+
+    /**
+     * Modular reduction preperation
+     *
+     * @param Array $x
+     * @param Array $n
+     * @param Integer $mode
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _prepareReduce($x, $n, $mode)
+    {
+        if ($mode == MATH_BIGINTEGER_MONTGOMERY)
+        {
+            return $this->_prepMontgomery($x, $n);
+        }
+        return $this->_reduce($x, $n, $mode);
+    }
+
+    /**
+     * Prepare a number for use in Montgomery Modular Reductions
+     *
+     * @param Array $x
+     * @param Array $n
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     * @see _montgomery()
+     */
+    function _prepMontgomery($x, $n)
+    {
+        $lhs        = new BigInteger();
+        $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x);
+        $rhs        = new BigInteger();
+        $rhs->value = $n;
+
+        list(, $temp) = $lhs->divide($rhs);
+        return $temp->value;
+    }
+
+    /**
+     * Modular reduction
+     *
+     * For most $modes this will return the remainder.
+     *
+     * @param Array $x
+     * @param Array $n
+     * @param Integer $mode
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _reduce($x, $n, $mode)
+    {
+        switch ($mode)
+        {
+            case MATH_BIGINTEGER_MONTGOMERY:
+                return $this->_montgomery($x, $n);
+            case MATH_BIGINTEGER_BARRETT:
+                return $this->_barrett($x, $n);
+            case MATH_BIGINTEGER_POWEROF2:
+                $lhs        = new BigInteger();
+                $lhs->value = $x;
+                $rhs        = new BigInteger();
+                $rhs->value = $n;
+                return $x->_mod2($n);
+            case MATH_BIGINTEGER_CLASSIC:
+                $lhs        = new BigInteger();
+                $lhs->value = $x;
+                $rhs        = new BigInteger();
+                $rhs->value = $n;
+                list(, $temp) = $lhs->divide($rhs);
+                return $temp->value;
+            case MATH_BIGINTEGER_NONE:
+                return $x;
+            default:
+                // an invalid $mode was provided
+        }
+    }
+
+    /**
+     * Montgomery Modular Reduction
+     *
+     * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n.
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be
+     * improved upon (basically, by using the comba method).  gcd($n, 2) must be equal to one for this function
+     * to work correctly.
+     *
+     * @param Array $x
+     * @param Array $n
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     * @see _prepMontgomery()
+     */
+    function _montgomery($x, $n)
+    {
+        static $cache = [
+            MATH_BIGINTEGER_VARIABLE => [],
+            MATH_BIGINTEGER_DATA     => []
+        ];
+
+        if (($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false)
+        {
+            $key                               = count($cache[MATH_BIGINTEGER_VARIABLE]);
+            $cache[MATH_BIGINTEGER_VARIABLE][] = $x;
+            $cache[MATH_BIGINTEGER_DATA][]     = $this->_modInverse67108864($n);
+        }
+
+        $k = count($n);
+
+        $result = [MATH_BIGINTEGER_VALUE => $x];
+
+        for ($i = 0; $i < $k; ++$i)
+        {
+            $temp   = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key];
+            $temp   = (int)($temp - MATH_BIGINTEGER_BASE_FULL * ((int)($temp / MATH_BIGINTEGER_BASE_FULL)));
+            $temp   = $this->_regularMultiply([$temp], $n);
+            $temp   = array_merge($this->_array_repeat(0, $i), $temp);
+            $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false);
+        }
+
+        $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k);
+
+        if ($this->_compare($result, false, $n, false) >= 0)
+        {
+            $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false);
+        }
+
+        return $result[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * Modular Inverse of a number mod 2**26 (eg. 67108864)
+     *
+     * Based off of the bnpInvDigit function implemented and justified in the following URL:
+     *
+     * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js}
+     *
+     * The following URL provides more info:
+     *
+     * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85}
+     *
+     * As for why we do all the bitmasking...  strange things can happen when converting from floats to ints. For
+     * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields
+     * int(-2147483648).  To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't
+     * auto-converted to floats.  The outermost bitmask is present because without it, there's no guarantee that
+     * the "residue" returned would be the so-called "common residue".  We use fmod, in the last step, because the
+     * maximum possible $x is 26 bits and the maximum $result is 16 bits.  Thus, we have to be able to handle up to
+     * 40 bits, which only 64-bit floating points will support.
+     *
+     * Thanks to Pedro Gimeno Fortea for input!
+     *
+     * @param Array $x
+     * @return Integer
+     * @see _montgomery()
+     * @access private
+     */
+    function _modInverse67108864($x) // 2**26 == 67,108,864
+    {
+        $x      = -$x[0];
+        $result = $x & 0x3; // x**-1 mod 2**2
+        $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4
+        $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8
+        $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16
+        $result = fmod($result * (2 - fmod($x * $result, MATH_BIGINTEGER_BASE_FULL)), MATH_BIGINTEGER_BASE_FULL); // x**-1 mod 2**26
+        return $result & MATH_BIGINTEGER_MAX_DIGIT;
+    }
+
+    /**
+     * Barrett Modular Reduction
+     *
+     * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} /
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information.  Modified slightly,
+     * so as not to require negative numbers (initially, this script didn't support negative numbers).
+     *
+     * Employs "folding", as described at
+     * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}.  To quote from
+     * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x."
+     *
+     * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that
+     * usable on account of (1) its not using reasonable radix points as discussed in
+     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable
+     * radix points, it only works when there are an even number of digits in the denominator.  The reason for (2) is that
+     * (x >> 1) + (x >> 1) != x / 2 + x / 2.  If x is even, they're the same, but if x is odd, they're not.  See the in-line
+     * comments for details.
+     *
+     * @param Array $n
+     * @param Array $m
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _barrett($n, $m)
+    {
+        static $cache = [
+            MATH_BIGINTEGER_VARIABLE => [],
+            MATH_BIGINTEGER_DATA     => []
+        ];
+
+        $m_length = count($m);
+
+        // if ($this->_compare($n, $this->_square($m)) >= 0) {
+        if (count($n) > 2 * $m_length)
+        {
+            $lhs        = new BigInteger();
+            $rhs        = new BigInteger();
+            $lhs->value = $n;
+            $rhs->value = $m;
+            list(, $temp) = $lhs->divide($rhs);
+            return $temp->value;
+        }
+
+        // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced
+        if ($m_length < 5)
+        {
+            return $this->_regularBarrett($n, $m);
+        }
+
+        // n = 2 * m.length
+
+        if (($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false)
+        {
+            $key                               = count($cache[MATH_BIGINTEGER_VARIABLE]);
+            $cache[MATH_BIGINTEGER_VARIABLE][] = $m;
+
+            $lhs         = new BigInteger();
+            $lhs_value   = &$lhs->value;
+            $lhs_value   = $this->_array_repeat(0, $m_length + ($m_length >> 1));
+            $lhs_value[] = 1;
+            $rhs         = new BigInteger();
+            $rhs->value  = $m;
+
+            list($u, $m1) = $lhs->divide($rhs);
+            $u  = $u->value;
+            $m1 = $m1->value;
+
+            $cache[MATH_BIGINTEGER_DATA][] = [
+                'u'  => $u, // m.length >> 1 (technically (m.length >> 1) + 1)
+                'm1' => $m1 // m.length
+            ];
+        }
+        else
+        {
+            extract($cache[MATH_BIGINTEGER_DATA][$key]);
+        }
+
+        $cutoff = $m_length + ($m_length >> 1);
+        $lsd    = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1)
+        $msd    = array_slice($n, $cutoff);    // m.length >> 1
+        $lsd    = $this->_trim($lsd);
+        $temp   = $this->_multiply($msd, false, $m1, false);
+        $n      = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1
+
+        if ($m_length & 1)
+        {
+            return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m);
+        }
+
+        // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2
+        $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1);
+        // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2
+        // if odd:  ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1
+        $temp = $this->_multiply($temp, false, $u, false);
+        // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1
+        // if odd:  (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1)
+        $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1);
+        // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1
+        // if odd:  (m.length - (m.length >> 1)) + m.length     = 2 * m.length - (m.length >> 1)
+        $temp = $this->_multiply($temp, false, $m, false);
+
+        // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit
+        // number from a m.length + (m.length >> 1) + 1 digit number.  ie. there'd be an extra digit and the while loop
+        // following this comment would loop a lot (hence our calling _regularBarrett() in that situation).
+
+        $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false);
+
+        while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0)
+        {
+            $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false);
+        }
+
+        return $result[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * (Regular) Barrett Modular Reduction
+     *
+     * For numbers with more than four digits BigInteger::_barrett() is faster.  The difference between that and this
+     * is that this function does not fold the denominator into a smaller form.
+     *
+     * @param Array $x
+     * @param Array $n
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _regularBarrett($x, $n)
+    {
+        static $cache = [
+            MATH_BIGINTEGER_VARIABLE => [],
+            MATH_BIGINTEGER_DATA     => []
+        ];
+
+        $n_length = count($n);
+
+        if (count($x) > 2 * $n_length)
+        {
+            $lhs        = new BigInteger();
+            $rhs        = new BigInteger();
+            $lhs->value = $x;
+            $rhs->value = $n;
+            list(, $temp) = $lhs->divide($rhs);
+            return $temp->value;
+        }
+
+        if (($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false)
+        {
+            $key                               = count($cache[MATH_BIGINTEGER_VARIABLE]);
+            $cache[MATH_BIGINTEGER_VARIABLE][] = $n;
+            $lhs                               = new BigInteger();
+            $lhs_value                         = &$lhs->value;
+            $lhs_value                         = $this->_array_repeat(0, 2 * $n_length);
+            $lhs_value[]                       = 1;
+            $rhs                               = new BigInteger();
+            $rhs->value                        = $n;
+            list($temp,) = $lhs->divide($rhs); // m.length
+            $cache[MATH_BIGINTEGER_DATA][] = $temp->value;
+        }
+
+        // 2 * m.length - (m.length - 1) = m.length + 1
+        $temp = array_slice($x, $n_length - 1);
+        // (m.length + 1) + m.length = 2 * m.length + 1
+        $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false);
+        // (2 * m.length + 1) - (m.length - 1) = m.length + 2
+        $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1);
+
+        // m.length + 1
+        $result = array_slice($x, 0, $n_length + 1);
+        // m.length + 1
+        $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1);
+        // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1)
+
+        if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0)
+        {
+            $corrector_value   = $this->_array_repeat(0, $n_length + 1);
+            $corrector_value[] = 1;
+            $result            = $this->_add($result, false, $corrector_value, false);
+            $result            = $result[MATH_BIGINTEGER_VALUE];
+        }
+
+        // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits
+        $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]);
+        while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0)
+        {
+            $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false);
+        }
+
+        return $result[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * Performs long multiplication up to $stop digits
+     *
+     * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved.
+     *
+     * @param Array $x_value
+     * @param Boolean $x_negative
+     * @param Array $y_value
+     * @param Boolean $y_negative
+     * @param Integer $stop
+     * @return Array
+     * @access private
+     * @see _regularBarrett()
+     */
+    function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop)
+    {
+        $x_length = count($x_value);
+        $y_length = count($y_value);
+
+        if (!$x_length || !$y_length)
+        { // a 0 is being multiplied
+            return [
+                MATH_BIGINTEGER_VALUE => [],
+                MATH_BIGINTEGER_SIGN  => false
+            ];
+        }
+
+        if ($x_length < $y_length)
+        {
+            $temp    = $x_value;
+            $x_value = $y_value;
+            $y_value = $temp;
+
+            $x_length = count($x_value);
+            $y_length = count($y_value);
+        }
+
+        $product_value = $this->_array_repeat(0, $x_length + $y_length);
+
+        // the following for loop could be removed if the for loop following it
+        // (the one with nested for loops) initially set $i to 0, but
+        // doing so would also make the result in one set of unnecessary adds,
+        // since on the outermost loops first pass, $product->value[$k] is going
+        // to always be 0
+
+        $carry = 0;
+
+        for ($j = 0; $j < $x_length; ++$j)
+        { // ie. $i = 0, $k = $i
+            $temp              = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
+            $carry             = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+            $product_value[$j] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+        }
+
+        if ($j < $stop)
+        {
+            $product_value[$j] = $carry;
+        }
+
+        // the above for loop is what the previous comment was talking about.  the
+        // following for loop is the "one with nested for loops"
+
+        for ($i = 1; $i < $y_length; ++$i)
+        {
+            $carry = 0;
+
+            for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k)
+            {
+                $temp              = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
+                $carry             = (int)($temp / MATH_BIGINTEGER_BASE_FULL);
+                $product_value[$k] = (int)($temp - MATH_BIGINTEGER_BASE_FULL * $carry);
+            }
+
+            if ($k < $stop)
+            {
+                $product_value[$k] = $carry;
+            }
+        }
+
+        return [
+            MATH_BIGINTEGER_VALUE => $this->_trim($product_value),
+            MATH_BIGINTEGER_SIGN  => $x_negative != $y_negative
+        ];
+    }
+
+    /**
+     * Modular square
+     *
+     * @param Array $x
+     * @param Array $n
+     * @param Integer $mode
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _squareReduce($x, $n, $mode)
+    {
+        if ($mode == MATH_BIGINTEGER_MONTGOMERY)
+        {
+            return $this->_montgomeryMultiply($x, $x, $n);
+        }
+        return $this->_reduce($this->_square($x), $n, $mode);
+    }
+
+    /**
+     * Montgomery Multiply
+     *
+     * Interleaves the montgomery reduction and long multiplication algorithms together as described in
+     * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36}
+     *
+     * @param Array $x
+     * @param Array $y
+     * @param Array $m
+     * @return Array
+     * @see _prepMontgomery()
+     * @see _montgomery()
+     * @access private
+     */
+    function _montgomeryMultiply($x, $y, $m)
+    {
+        $temp = $this->_multiply($x, false, $y, false);
+        return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m);
+
+        static $cache = [
+            MATH_BIGINTEGER_VARIABLE => [],
+            MATH_BIGINTEGER_DATA     => []
+        ];
+
+        if (($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false)
+        {
+            $key                               = count($cache[MATH_BIGINTEGER_VARIABLE]);
+            $cache[MATH_BIGINTEGER_VARIABLE][] = $m;
+            $cache[MATH_BIGINTEGER_DATA][]     = $this->_modInverse67108864($m);
+        }
+
+        $n = max(count($x), count($y), count($m));
+        $x = array_pad($x, $n, 0);
+        $y = array_pad($y, $n, 0);
+        $m = array_pad($m, $n, 0);
+        $a = [MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)];
+        for ($i = 0; $i < $n; ++$i)
+        {
+            $temp                     = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0];
+            $temp                     = (int)($temp - MATH_BIGINTEGER_BASE_FULL * ((int)($temp / MATH_BIGINTEGER_BASE_FULL)));
+            $temp                     = $temp * $cache[MATH_BIGINTEGER_DATA][$key];
+            $temp                     = (int)($temp - MATH_BIGINTEGER_BASE_FULL * ((int)($temp / MATH_BIGINTEGER_BASE_FULL)));
+            $temp                     = $this->_add($this->_regularMultiply([$x[$i]], $y), false, $this->_regularMultiply([$temp], $m), false);
+            $a                        = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false);
+            $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1);
+        }
+        if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0)
+        {
+            $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false);
+        }
+        return $a[MATH_BIGINTEGER_VALUE];
+    }
+
+    /**
+     * Modular multiply
+     *
+     * @param Array $x
+     * @param Array $y
+     * @param Array $n
+     * @param Integer $mode
+     * @return Array
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _multiplyReduce($x, $y, $n, $mode)
+    {
+        if ($mode == MATH_BIGINTEGER_MONTGOMERY)
+        {
+            return $this->_montgomeryMultiply($x, $y, $n);
+        }
+        $temp = $this->_multiply($x, false, $y, false);
+        return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode);
+    }
+
+    /**
+     * Modulos for Powers of Two
+     *
+     * Calculates $x%$n, where $n = 2**$e, for some $e.  Since this is basically the same as doing $x & ($n-1),
+     * we'll just use this function as a wrapper for doing that.
+     *
+     * @param BigInteger
+     * @return BigInteger
+     * @see _slidingWindow()
+     * @access private
+     */
+    function _mod2($n)
+    {
+        $temp        = new BigInteger();
+        $temp->value = [1];
+        return $this->bitwise_and($n->subtract($temp));
+    }
+
+    /**
+     * Logical And
+     *
+     * @param BigInteger $x
+     * @access public
+     * @return BigInteger
+     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
+     */
+    function bitwise_and($x)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_and($this->value, $x->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $left  = $this->toBytes();
+                $right = $x->toBytes();
+
+                $length = max(strlen($left), strlen($right));
+
+                $left  = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+
+                return $this->_normalize(new BigInteger($left & $right, 256));
+        }
+
+        $result = $this->copy();
+
+        $length = min(count($x->value), count($this->value));
+
+        $result->value = array_slice($result->value, 0, $length);
+
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $result->value[$i] &= $x->value[$i];
+        }
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Calculates the greatest common divisor
+     *
+     * Say you have 693 and 609.  The GCD is 21.
+     *
+     * Here's an example:
+     * <code>
+     * <?php
+     *    include('Math/BigInteger.php');
+     *
+     *    $a = new BigInteger(693);
+     *    $b = new BigInteger(609);
+     *
+     *    $gcd = a->extendedGCD($b);
+     *
+     *    echo $gcd->toString() . "\r\n"; // outputs 21
+     * ?>
+     * </code>
+     *
+     * @param BigInteger $n
+     * @return BigInteger
+     * @access public
+     */
+    function gcd($n)
+    {
+        extract($this->extendedGCD($n));
+        return $gcd;
+    }
+
+    /**
+     * Logical Exclusive-Or
+     *
+     * @param BigInteger $x
+     * @access public
+     * @return BigInteger
+     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
+     */
+    function bitwise_xor($x)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_xor($this->value, $x->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $left  = $this->toBytes();
+                $right = $x->toBytes();
+
+                $length = max(strlen($left), strlen($right));
+
+                $left  = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+
+                return $this->_normalize(new BigInteger($left ^ $right, 256));
+        }
+
+        $length        = max(count($this->value), count($x->value));
+        $result        = $this->copy();
+        $result->value = array_pad($result->value, $length, 0);
+        $x->value      = array_pad($x->value, $length, 0);
+
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $result->value[$i] ^= $x->value[$i];
+        }
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Logical Not
+     *
+     * @access public
+     * @return BigInteger
+     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
+     */
+    function bitwise_not()
+    {
+        // calculuate "not" without regard to $this->precision
+        // (will always result in a smaller number.  ie. ~1 isn't 1111 1110 - it's 0)
+        $temp    = $this->toBytes();
+        $pre_msb = decbin(ord($temp[0]));
+        $temp    = ~$temp;
+        $msb     = decbin(ord($temp[0]));
+        if (strlen($msb) == 8)
+        {
+            $msb = substr($msb, strpos($msb, '0'));
+        }
+        $temp[0] = chr(bindec($msb));
+
+        // see if we need to add extra leading 1's
+        $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8;
+        $new_bits     = $this->precision - $current_bits;
+        if ($new_bits <= 0)
+        {
+            return $this->_normalize(new BigInteger($temp, 256));
+        }
+
+        // generate as many leading 1's as we need to.
+        $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
+        $this->_base256_lshift($leading_ones, $current_bits);
+
+        $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT);
+
+        return $this->_normalize(new BigInteger($leading_ones | $temp, 256));
+    }
+
+    /**
+     * Logical Right Rotate
+     *
+     * Instead of the bottom x bits being dropped they're prepended to the shifted bit string.
+     *
+     * @param Integer $shift
+     * @return BigInteger
+     * @access public
+     */
+    function bitwise_rightRotate($shift)
+    {
+        return $this->bitwise_leftRotate(-$shift);
+    }
+
+    /**
+     * Logical Left Rotate
+     *
+     * Instead of the top x bits being dropped they're appended to the shifted bit string.
+     *
+     * @param Integer $shift
+     * @return BigInteger
+     * @access public
+     */
+    function bitwise_leftRotate($shift)
+    {
+        $bits = $this->toBytes();
+
+        if ($this->precision > 0)
+        {
+            $precision = $this->precision;
+            if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH)
+            {
+                $mask = $this->bitmask->subtract(new BigInteger(1));
+                $mask = $mask->toBytes();
+            }
+            else
+            {
+                $mask = $this->bitmask->toBytes();
+            }
+        }
+        else
+        {
+            $temp = ord($bits[0]);
+            for ($i = 0; $temp >> $i; ++$i)
+            {
+                ;
+            }
+            $precision = 8 * strlen($bits) - 8 + $i;
+            $mask      = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3);
+        }
+
+        if ($shift < 0)
+        {
+            $shift += $precision;
+        }
+        $shift %= $precision;
+
+        if (!$shift)
+        {
+            return $this->copy();
+        }
+
+        $left   = $this->bitwise_leftShift($shift);
+        $left   = $left->bitwise_and(new BigInteger($mask, 256));
+        $right  = $this->bitwise_rightShift($precision - $shift);
+        $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right);
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Logical Left Shift
+     *
+     * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
+     *
+     * @param Integer $shift
+     * @return BigInteger
+     * @access public
+     * @internal The only version that yields any speed increases is the internal version.
+     */
+    function bitwise_leftShift($shift)
+    {
+        $temp = new BigInteger();
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                static $two;
+
+                if (!isset($two))
+                {
+                    $two = gmp_init('2');
+                }
+
+                $temp->value = gmp_mul($this->value, gmp_pow($two, $shift));
+
+                break;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0);
+
+                break;
+            default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten
+                // and I don't want to do that...
+                $temp->value = $this->value;
+                $temp->_lshift($shift);
+        }
+
+        return $this->_normalize($temp);
+    }
+
+    /**
+     * Logical Right Shift
+     *
+     * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
+     *
+     * @param Integer $shift
+     * @return BigInteger
+     * @access public
+     * @internal The only version that yields any speed increases is the internal version.
+     */
+    function bitwise_rightShift($shift)
+    {
+        $temp = new BigInteger();
+
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                static $two;
+
+                if (!isset($two))
+                {
+                    $two = gmp_init('2');
+                }
+
+                $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift));
+
+                break;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0);
+
+                break;
+            default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten
+                // and I don't want to do that...
+                $temp->value = $this->value;
+                $temp->_rshift($shift);
+        }
+
+        return $this->_normalize($temp);
+    }
+
+    /**
+     * Logical Or
+     *
+     * @param BigInteger $x
+     * @access public
+     * @return BigInteger
+     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
+     */
+    function bitwise_or($x)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                $temp        = new BigInteger();
+                $temp->value = gmp_or($this->value, $x->value);
+
+                return $this->_normalize($temp);
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                $left  = $this->toBytes();
+                $right = $x->toBytes();
+
+                $length = max(strlen($left), strlen($right));
+
+                $left  = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+
+                return $this->_normalize(new BigInteger($left | $right, 256));
+        }
+
+        $length        = max(count($this->value), count($x->value));
+        $result        = $this->copy();
+        $result->value = array_pad($result->value, $length, 0);
+        $x->value      = array_pad($x->value, $length, 0);
+
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $result->value[$i] |= $x->value[$i];
+        }
+
+        return $this->_normalize($result);
+    }
+
+    /**
+     * Generate a random prime number.
+     *
+     * If there's not a prime within the given range, false will be returned.  If more than $timeout seconds have elapsed,
+     * give up and return false.
+     *
+     * @param optional Integer $min
+     * @param optional Integer $max
+     * @param optional Integer $timeout
+     * @return BigInteger
+     * @access public
+     * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}.
+     */
+    function randomPrime($min = false, $max = false, $timeout = false)
+    {
+        if ($min === false)
+        {
+            $min = new BigInteger(0);
+        }
+
+        if ($max === false)
+        {
+            $max = new BigInteger(0x7FFFFFFF);
+        }
+
+        $compare = $max->compare($min);
+
+        if (!$compare)
+        {
+            return $min->isPrime() ? $min : false;
+        }
+        else
+        {
+            if ($compare < 0)
+            {
+                // if $min is bigger then $max, swap $min and $max
+                $temp = $max;
+                $max  = $min;
+                $min  = $temp;
+            }
+        }
+
+        static $one, $two;
+        if (!isset($one))
+        {
+            $one = new BigInteger(1);
+            $two = new BigInteger(2);
+        }
+
+        $start = time();
+
+        $x = $this->random($min, $max);
+
+        // gmp_nextprime() requires PHP 5 >= 5.2.0 per <http://php.net/gmp-nextprime>.
+        if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime'))
+        {
+            $p        = new BigInteger();
+            $p->value = gmp_nextprime($x->value);
+
+            if ($p->compare($max) <= 0)
+            {
+                return $p;
+            }
+
+            if (!$min->equals($x))
+            {
+                $x = $x->subtract($one);
+            }
+
+            return $x->randomPrime($min, $x);
+        }
+
+        if ($x->equals($two))
+        {
+            return $x;
+        }
+
+        $x->_make_odd();
+        if ($x->compare($max) > 0)
+        {
+            // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range
+            if ($min->equals($max))
+            {
+                return false;
+            }
+            $x = $min->copy();
+            $x->_make_odd();
+        }
+
+        $initial_x = $x->copy();
+
+        while (true)
+        {
+            if ($timeout !== false && time() - $start > $timeout)
+            {
+                return false;
+            }
+
+            if ($x->isPrime())
+            {
+                return $x;
+            }
+
+            $x = $x->add($two);
+
+            if ($x->compare($max) > 0)
+            {
+                $x = $min->copy();
+                if ($x->equals($two))
+                {
+                    return $x;
+                }
+                $x->_make_odd();
+            }
+
+            if ($x->equals($initial_x))
+            {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Checks a numer to see if it's prime
+     *
+     * Assuming the $t parameter is not set, this function has an error rate of 2**-80.  The main motivation for the
+     * $t parameter is distributability.  BigInteger::randomPrime() can be distributed accross multiple pageloads
+     * on a website instead of just one.
+     *
+     * @param optional Integer $t
+     * @return Boolean
+     * @access public
+     * @internal Uses the
+     *     {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}.  See
+     *     {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}.
+     */
+    function isPrime($t = false)
+    {
+        $length = strlen($this->toBytes());
+
+        if (!$t)
+        {
+            // see HAC 4.49 "Note (controlling the error probability)"
+            // @codingStandardsIgnoreStart
+            if ($length >= 163)
+            {
+                $t = 2;
+            } // floor(1300 / 8)
+            else
+            {
+                if ($length >= 106)
+                {
+                    $t = 3;
+                } // floor( 850 / 8)
+                else
+                {
+                    if ($length >= 81)
+                    {
+                        $t = 4;
+                    } // floor( 650 / 8)
+                    else
+                    {
+                        if ($length >= 68)
+                        {
+                            $t = 5;
+                        } // floor( 550 / 8)
+                        else
+                        {
+                            if ($length >= 56)
+                            {
+                                $t = 6;
+                            } // floor( 450 / 8)
+                            else
+                            {
+                                if ($length >= 50)
+                                {
+                                    $t = 7;
+                                } // floor( 400 / 8)
+                                else
+                                {
+                                    if ($length >= 43)
+                                    {
+                                        $t = 8;
+                                    } // floor( 350 / 8)
+                                    else
+                                    {
+                                        if ($length >= 37)
+                                        {
+                                            $t = 9;
+                                        } // floor( 300 / 8)
+                                        else
+                                        {
+                                            if ($length >= 31)
+                                            {
+                                                $t = 12;
+                                            } // floor( 250 / 8)
+                                            else
+                                            {
+                                                if ($length >= 25)
+                                                {
+                                                    $t = 15;
+                                                } // floor( 200 / 8)
+                                                else
+                                                {
+                                                    if ($length >= 18)
+                                                    {
+                                                        $t = 18;
+                                                    } // floor( 150 / 8)
+                                                    else
+                                                    {
+                                                        $t = 27;
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            // @codingStandardsIgnoreEnd
+        }
+
+        // ie. gmp_testbit($this, 0)
+        // ie. isEven() or !isOdd()
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                return gmp_prob_prime($this->value, $t) != 0;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                if ($this->value === '2')
+                {
+                    return true;
+                }
+                if ($this->value[strlen($this->value) - 1] % 2 == 0)
+                {
+                    return false;
+                }
+                break;
+            default:
+                if ($this->value == [2])
+                {
+                    return true;
+                }
+                if (~$this->value[0] & 1)
+                {
+                    return false;
+                }
+        }
+
+        static $primes, $zero, $one, $two;
+
+        if (!isset($primes))
+        {
+            $primes = [
+                3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
+                61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
+                139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
+                229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313,
+                317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
+                421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
+                521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
+                619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727,
+                733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
+                839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947,
+                953, 967, 971, 977, 983, 991, 997
+            ];
+
+            if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL)
+            {
+                for ($i = 0; $i < count($primes); ++$i)
+                {
+                    $primes[$i] = new BigInteger($primes[$i]);
+                }
+            }
+
+            $zero = new BigInteger();
+            $one  = new BigInteger(1);
+            $two  = new BigInteger(2);
+        }
+
+        if ($this->equals($one))
+        {
+            return false;
+        }
+
+        // see HAC 4.4.1 "Random search for probable primes"
+        if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL)
+        {
+            foreach ($primes as $prime)
+            {
+                list(, $r) = $this->divide($prime);
+                if ($r->equals($zero))
+                {
+                    return $this->equals($prime);
+                }
+            }
+        }
+        else
+        {
+            $value = $this->value;
+            foreach ($primes as $prime)
+            {
+                list(, $r) = $this->_divide_digit($value, $prime);
+                if (!$r)
+                {
+                    return count($value) == 1 && $value[0] == $prime;
+                }
+            }
+        }
+
+        $n   = $this->copy();
+        $n_1 = $n->subtract($one);
+        $n_2 = $n->subtract($two);
+
+        $r       = $n_1->copy();
+        $r_value = $r->value;
+        // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
+        if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH)
+        {
+            $s = 0;
+            // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier
+            while ($r->value[strlen($r->value) - 1] % 2 == 0)
+            {
+                $r->value = bcdiv($r->value, '2', 0);
+                ++$s;
+            }
+        }
+        else
+        {
+            for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i)
+            {
+                $temp = ~$r_value[$i] & 0xFFFFFF;
+                for ($j = 1; ($temp >> $j) & 1; ++$j)
+                {
+                    ;
+                }
+                if ($j != 25)
+                {
+                    break;
+                }
+            }
+            $s = 26 * $i + $j - 1;
+            $r->_rshift($s);
+        }
+
+        for ($i = 0; $i < $t; ++$i)
+        {
+            $a = $this->random($two, $n_2);
+            $y = $a->modPow($r, $n);
+
+            if (!$y->equals($one) && !$y->equals($n_1))
+            {
+                for ($j = 1; $j < $s && !$y->equals($n_1); ++$j)
+                {
+                    $y = $y->modPow($two, $n);
+                    if ($y->equals($one))
+                    {
+                        return false;
+                    }
+                }
+
+                if (!$y->equals($n_1))
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests the equality of two numbers.
+     *
+     * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
+     *
+     * @param BigInteger $x
+     * @return Boolean
+     * @access public
+     * @see compare()
+     */
+    function equals($x)
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                return gmp_cmp($this->value, $x->value) == 0;
+            default:
+                return $this->value === $x->value && $this->is_negative == $x->is_negative;
+        }
+    }
+    // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long
+    // at 32-bits, while java's longs are 64-bits.
+
+    /**
+     * Generate a random number
+     *
+     * @param optional Integer $min
+     * @param optional Integer $max
+     * @return BigInteger
+     * @access public
+     */
+    function random($min = false, $max = false)
+    {
+        if ($min === false)
+        {
+            $min = new BigInteger(0);
+        }
+
+        if ($max === false)
+        {
+            $max = new BigInteger(0x7FFFFFFF);
+        }
+
+        $compare = $max->compare($min);
+
+        if (!$compare)
+        {
+            return $this->_normalize($min);
+        }
+        else
+        {
+            if ($compare < 0)
+            {
+                // if $min is bigger then $max, swap $min and $max
+                $temp = $max;
+                $max  = $min;
+                $min  = $temp;
+            }
+        }
+
+        static $one;
+        if (!isset($one))
+        {
+            $one = new BigInteger(1);
+        }
+
+        $max  = $max->subtract($min->subtract($one));
+        $size = strlen(ltrim($max->toBytes(), chr(0)));
+
+        /*
+          doing $random % $max doesn't work because some numbers will be more likely to occur than others.
+          eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145
+          would produce 5 whereas the only value of random that could produce 139 would be 139. ie.
+          not all numbers would be equally likely. some would be more likely than others.
+
+          creating a whole new random number until you find one that is within the range doesn't work
+          because, for sufficiently small ranges, the likelihood that you'd get a number within that range
+          would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability
+          would be pretty high that $random would be greater than $max.
+
+          phpseclib works around this using the technique described here:
+
+          http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string
+         */
+        $random_max = new BigInteger(chr(1) . str_repeat("\0", $size), 256);
+        $random     = $this->_random_number_helper($size);
+
+        list($max_multiple) = $random_max->divide($max);
+        $max_multiple = $max_multiple->multiply($max);
+
+        while ($random->compare($max_multiple) >= 0)
+        {
+            $random     = $random->subtract($max_multiple);
+            $random_max = $random_max->subtract($max_multiple);
+            $random     = $random->bitwise_leftShift(8);
+            $random     = $random->add($this->_random_number_helper(1));
+            $random_max = $random_max->bitwise_leftShift(8);
+            list($max_multiple) = $random_max->divide($max);
+            $max_multiple = $max_multiple->multiply($max);
+        }
+        list(, $random) = $random->divide($max);
+
+        return $this->_normalize($random->add($min));
+    }
+
+    /**
+     * Generates a random BigInteger
+     *
+     * Byte length is equal to $length. Uses crypt_random if it's loaded and mt_rand if it's not.
+     *
+     * @param Integer $length
+     * @return BigInteger
+     * @access private
+     */
+    function _random_number_helper($size)
+    {
+        $crypt_random = function_exists('crypt_random_string') || (!class_exists('Crypt_Random') && function_exists('crypt_random_string'));
+        if ($crypt_random)
+        {
+            $random = crypt_random_string($size);
+        }
+        else
+        {
+            $random = '';
+
+            if ($size & 1)
+            {
+                $random .= chr(mt_rand(0, 255));
+            }
+
+            $blocks = $size >> 1;
+            for ($i = 0; $i < $blocks; ++$i)
+            {
+                // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
+                $random .= pack('n', mt_rand(0, 0xFFFF));
+            }
+        }
+
+        return new BigInteger($random, 256);
+    }
+
+    /**
+     * Make the current number odd
+     *
+     * If the current number is odd it'll be unchanged.  If it's even, one will be added to it.
+     *
+     * @see randomPrime()
+     * @access private
+     */
+    function _make_odd()
+    {
+        switch (MATH_BIGINTEGER_MODE)
+        {
+            case MATH_BIGINTEGER_MODE_GMP:
+                gmp_setbit($this->value, 0);
+                break;
+            case MATH_BIGINTEGER_MODE_BCMATH:
+                if ($this->value[strlen($this->value) - 1] % 2 == 0)
+                {
+                    $this->value = bcadd($this->value, '1');
+                }
+                break;
+            default:
+                $this->value[0] |= 1;
+        }
+    }
+}

+ 74 - 0
app/Libs/Format.php

@@ -0,0 +1,74 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 13, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs;
+
+/**
+ * Description of Format
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class Format
+{
+
+    /**
+     * FUNCTION convert
+     * Format bytes
+     * @param int $bytes
+     * @param int $precision
+     * @return string
+     */
+    public static function convertBytes($bytes, $precision = 2)
+    {
+        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+
+        $bytes = max($bytes, 0);
+        $pow   = floor(($bytes ? log($bytes) : 0) / log(1024));
+        $pow   = min($pow, count($units) - 1);
+        $bytes /= (1 << (10 * $pow));
+
+        return round($bytes, $precision) . ' ' . $units[$pow];
+    }
+
+    public static function uptime($uptime)
+    {
+        if (!$uptime)
+        {
+            return false;
+        }
+        $days  = floor($uptime / 60 / 60 / 24);
+        $hours = $uptime / 60 / 60 % 24;
+        $mins  = $uptime / 60 % 60;
+        $secs  = $uptime % 60;
+
+        $hours = ($hours < 10) ? "0" . $hours : $hours;
+        $mins  = ($mins < 10) ? "0" . $mins : $mins;
+        $secs  = ($secs < 10) ? "0" . $secs : $secs;
+
+        if ($days)
+        {
+            return "{$days} days $hours:$mins:$secs";
+        }
+        else
+        {
+            return "$hours:$mins:$secs";
+        }
+    }
+}

+ 220 - 0
app/Libs/IPv6.php

@@ -0,0 +1,220 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon  Product developed. (2013-11-18)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Libs;
+
+if (!function_exists('ipv6_range'))
+{
+
+    function ipv6_range($prefix)
+    {
+        // Split in address and prefix length
+        list($firstaddrstr, $prefixlen) = explode('/', $prefix);
+
+        // Parse the address into a binary string
+        $firstaddrbin = inet_pton($firstaddrstr);
+
+        // Convert the binary string to a string with hexadecimal characters
+        # unpack() can be replaced with bin2hex()
+        # unpack() is used for symmetry with pack() below
+        $unpacked     = unpack('H*', $firstaddrbin);
+        $firstaddrhex = reset($unpacked);
+
+        // Overwriting first address string to make sure notation is optimal
+        $firstaddrstr = inet_ntop($firstaddrbin);
+
+        // Calculate the number of 'flexible' bits
+        $flexbits = 128 - $prefixlen;
+
+        // Build the hexadecimal string of the last address
+        $lastaddrhex = $firstaddrhex;
+
+        // We start at the end of the string (which is always 32 characters long)
+        $pos = 31;
+        while ($flexbits > 0)
+        {
+            // Get the character at this position
+            $orig = substr($lastaddrhex, $pos, 1);
+
+            // Convert it to an integer
+            $origval = hexdec($orig);
+
+            // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
+            $newval = $origval | (pow(2, min(4, $flexbits)) - 1);
+
+            // Convert it back to a hexadecimal character
+            $new = dechex($newval);
+
+            // And put that character back in the string
+            $lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
+
+            // We processed one nibble, move to previous position
+            $flexbits -= 4;
+            $pos      -= 1;
+        }
+
+        // Convert the hexadecimal string to a binary string
+        # Using pack() here
+        # Newer PHP version can use hex2bin()
+        $lastaddrbin = pack('H*', $lastaddrhex);
+
+        // And create an IPv6 address from the binary string
+        $lastaddrstr = inet_ntop($lastaddrbin);
+
+        return [$prefix, $firstaddrstr, $lastaddrstr];
+    }
+}
+
+/**
+ * Converts human readable representation to a 128bit int
+ * which can be stored in MySQL using DECIMAL(39,0).
+ *
+ * Requires PHP to be compiled with IPv6 support.
+ * This could be made to work without IPv6 support but
+ * I don't think there would be much use for it if PHP
+ * doesn't support IPv6.
+ *
+ * @param string $ip IPv4 or IPv6 address to convert
+ * @return string 128bit string that can be used with DECIMNAL(39,0) or false
+ */
+if (!function_exists('inet_ptoi'))
+{
+
+    function inet_ptoi($ip)
+    {
+        // make sure it is an ip
+        if (filter_var($ip, FILTER_VALIDATE_IP) === false)
+        {
+            return false;
+        }
+
+        $parts = unpack('N*', inet_pton($ip));
+
+        // fix IPv4
+        if (strpos($ip, '.') !== false)
+        {
+            $parts = [1 => 0, 2 => 0, 3 => 0, 4 => $parts[1]];
+        }
+
+        foreach ($parts as &$part)
+        {
+            // convert any unsigned ints to signed from unpack.
+            // this should be OK as it will be a PHP float not an int
+            if ($part < 0)
+            {
+                $part += 4294967296;
+            }
+        }
+
+        // use BCMath if the extension exists
+        if (function_exists('bcadd'))
+        {
+            $decimal = $parts[4];
+            $decimal = bcadd($decimal, bcmul($parts[3], '4294967296'));
+            $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616'));
+            $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336'));
+        }
+        // otherwise use the pure PHP BigInteger
+        else
+        {
+            $decimal = new BigInteger($parts[4]);
+            $part3   = new BigInteger($parts[3]);
+            $part2   = new BigInteger($parts[2]);
+            $part1   = new BigInteger($parts[1]);
+
+            $decimal = $decimal->add($part3->multiply(new BigInteger('4294967296')));
+            $decimal = $decimal->add($part2->multiply(new BigInteger('18446744073709551616')));
+            $decimal = $decimal->add($part1->multiply(new BigInteger('79228162514264337593543950336')));
+
+            $decimal = $decimal->toString();
+        }
+
+        return $decimal;
+    }
+}
+
+/**
+ * Converts a 128bit int to a human readable representation.
+ *
+ * Requires PHP to be compiled with IPv6 support.
+ * This could be made to work without IPv6 support but
+ * I don't think there would be much use for it if PHP
+ * doesn't support IPv6.
+ *
+ * @param string $decimal 128bit int
+ * @return string IPv4 or IPv6
+ */
+if (!function_exists('inet_itop'))
+{
+
+    function inet_itop($decimal)
+    {
+        $parts = [];
+
+        // use BCMath if the extension exists
+        if (function_exists('bcadd'))
+        {
+            $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0);
+            $decimal  = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
+            $parts[2] = bcdiv($decimal, '18446744073709551616', 0);
+            $decimal  = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
+            $parts[3] = bcdiv($decimal, '4294967296', 0);
+            $decimal  = bcsub($decimal, bcmul($parts[3], '4294967296'));
+            $parts[4] = $decimal;
+        }
+        // otherwise use the pure PHP BigInteger
+        else
+        {
+            $decimal = new BigInteger($decimal);
+            list($parts[1],) = $decimal->divide(new BigInteger('79228162514264337593543950336'));
+            $decimal = $decimal->subtract($parts[1]->multiply(new BigInteger('79228162514264337593543950336')));
+            list($parts[2],) = $decimal->divide(new BigInteger('18446744073709551616'));
+            $decimal = $decimal->subtract($parts[2]->multiply(new BigInteger('18446744073709551616')));
+            list($parts[3],) = $decimal->divide(new BigInteger('4294967296'));
+            $decimal  = $decimal->subtract($parts[3]->multiply(new BigInteger('4294967296')));
+            $parts[4] = $decimal;
+
+            $parts[1] = $parts[1]->toString();
+            $parts[2] = $parts[2]->toString();
+            $parts[3] = $parts[3]->toString();
+            $parts[4] = $parts[4]->toString();
+        }
+
+        foreach ($parts as &$part)
+        {
+            // convert any signed ints to unsigned for pack
+            // this should be fine as it will be treated as a float
+            if ($part > 2147483647)
+            {
+                $part -= 4294967296;
+            }
+        }
+
+        $ip = inet_ntop(pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]));
+
+        // fix IPv4 by removing :: from the beginning
+        if (strpos($ip, '.') !== false)
+        {
+            return substr($ip, 2);
+        }
+
+        return $ip;
+    }
+}

+ 11308 - 0
app/Libs/TLDList/tld.list

@@ -0,0 +1,11308 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// ===BEGIN ICANN DOMAINS===
+
+// ac : http://en.wikipedia.org/wiki/.ac
+ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+
+// ad : http://en.wikipedia.org/wiki/.ad
+ad
+nom.ad
+
+// ae : http://en.wikipedia.org/wiki/.ae
+// see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php
+ae
+co.ae
+net.ae
+org.ae
+sch.ae
+ac.ae
+gov.ae
+mil.ae
+
+// aero : see http://www.information.aero/index.php?id=66
+aero
+accident-investigation.aero
+accident-prevention.aero
+aerobatic.aero
+aeroclub.aero
+aerodrome.aero
+agents.aero
+aircraft.aero
+airline.aero
+airport.aero
+air-surveillance.aero
+airtraffic.aero
+air-traffic-control.aero
+ambulance.aero
+amusement.aero
+association.aero
+author.aero
+ballooning.aero
+broker.aero
+caa.aero
+cargo.aero
+catering.aero
+certification.aero
+championship.aero
+charter.aero
+civilaviation.aero
+club.aero
+conference.aero
+consultant.aero
+consulting.aero
+control.aero
+council.aero
+crew.aero
+design.aero
+dgca.aero
+educator.aero
+emergency.aero
+engine.aero
+engineer.aero
+entertainment.aero
+equipment.aero
+exchange.aero
+express.aero
+federation.aero
+flight.aero
+freight.aero
+fuel.aero
+gliding.aero
+government.aero
+groundhandling.aero
+group.aero
+hanggliding.aero
+homebuilt.aero
+insurance.aero
+journal.aero
+journalist.aero
+leasing.aero
+logistics.aero
+magazine.aero
+maintenance.aero
+media.aero
+microlight.aero
+modelling.aero
+navigation.aero
+parachuting.aero
+paragliding.aero
+passenger-association.aero
+pilot.aero
+press.aero
+production.aero
+recreation.aero
+repbody.aero
+res.aero
+research.aero
+rotorcraft.aero
+safety.aero
+scientist.aero
+services.aero
+show.aero
+skydiving.aero
+software.aero
+student.aero
+trader.aero
+trading.aero
+trainer.aero
+union.aero
+workinggroup.aero
+works.aero
+
+// af : http://www.nic.af/help.jsp
+af
+gov.af
+com.af
+org.af
+net.af
+edu.af
+
+// ag : http://www.nic.ag/prices.htm
+ag
+com.ag
+org.ag
+net.ag
+co.ag
+nom.ag
+
+// ai : http://nic.com.ai/
+ai
+off.ai
+com.ai
+net.ai
+org.ai
+
+// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31
+al
+com.al
+edu.al
+gov.al
+mil.al
+net.al
+org.al
+
+// am : http://en.wikipedia.org/wiki/.am
+am
+
+// ao : http://en.wikipedia.org/wiki/.ao
+// http://www.dns.ao/REGISTR.DOC
+ao
+ed.ao
+gv.ao
+og.ao
+co.ao
+pb.ao
+it.ao
+
+// aq : http://en.wikipedia.org/wiki/.aq
+aq
+
+// ar : https://nic.ar/normativa-vigente.xhtml
+ar
+com.ar
+edu.ar
+gob.ar
+gov.ar
+int.ar
+mil.ar
+net.ar
+org.ar
+tur.ar
+
+// arpa : http://en.wikipedia.org/wiki/.arpa
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+arpa
+e164.arpa
+in-addr.arpa
+ip6.arpa
+iris.arpa
+uri.arpa
+urn.arpa
+
+// as : http://en.wikipedia.org/wiki/.as
+as
+gov.as
+
+// asia : http://en.wikipedia.org/wiki/.asia
+asia
+
+// at : http://en.wikipedia.org/wiki/.at
+// Confirmed by registry <it@nic.at> 2008-06-17
+at
+ac.at
+co.at
+gv.at
+or.at
+
+// au : http://en.wikipedia.org/wiki/.au
+// http://www.auda.org.au/
+au
+// 2LDs
+com.au
+net.au
+org.au
+edu.au
+gov.au
+asn.au
+id.au
+// Historic 2LDs (closed to new registration, but sites still exist)
+info.au
+conf.au
+oz.au
+// CGDNs - http://www.cgdn.org.au/
+act.au
+nsw.au
+nt.au
+qld.au
+sa.au
+tas.au
+vic.au
+wa.au
+// 3LDs
+act.edu.au
+nsw.edu.au
+nt.edu.au
+qld.edu.au
+sa.edu.au
+tas.edu.au
+vic.edu.au
+wa.edu.au
+// act.gov.au  Bug 984824 - Removed at request of Greg Tankard
+// nsw.gov.au  Bug 547985 - Removed at request of <Shae.Donelan@services.nsw.gov.au>
+// nt.gov.au  Bug 940478 - Removed at request of Greg Connors <Greg.Connors@nt.gov.au>
+qld.gov.au
+sa.gov.au
+tas.gov.au
+vic.gov.au
+wa.gov.au
+
+// aw : http://en.wikipedia.org/wiki/.aw
+aw
+com.aw
+
+// ax : http://en.wikipedia.org/wiki/.ax
+ax
+
+// az : http://en.wikipedia.org/wiki/.az
+az
+com.az
+net.az
+int.az
+gov.az
+org.az
+edu.az
+info.az
+pp.az
+mil.az
+name.az
+pro.az
+biz.az
+
+// ba : http://en.wikipedia.org/wiki/.ba
+ba
+org.ba
+net.ba
+edu.ba
+gov.ba
+mil.ba
+unsa.ba
+unbi.ba
+co.ba
+com.ba
+rs.ba
+
+// bb : http://en.wikipedia.org/wiki/.bb
+bb
+biz.bb
+co.bb
+com.bb
+edu.bb
+gov.bb
+info.bb
+net.bb
+org.bb
+store.bb
+tv.bb
+
+// bd : http://en.wikipedia.org/wiki/.bd
+*.bd
+
+// be : http://en.wikipedia.org/wiki/.be
+// Confirmed by registry <tech@dns.be> 2008-06-08
+be
+ac.be
+
+// bf : http://en.wikipedia.org/wiki/.bf
+bf
+gov.bf
+
+// bg : http://en.wikipedia.org/wiki/.bg
+// https://www.register.bg/user/static/rules/en/index.html
+bg
+a.bg
+b.bg
+c.bg
+d.bg
+e.bg
+f.bg
+g.bg
+h.bg
+i.bg
+j.bg
+k.bg
+l.bg
+m.bg
+n.bg
+o.bg
+p.bg
+q.bg
+r.bg
+s.bg
+t.bg
+u.bg
+v.bg
+w.bg
+x.bg
+y.bg
+z.bg
+0.bg
+1.bg
+2.bg
+3.bg
+4.bg
+5.bg
+6.bg
+7.bg
+8.bg
+9.bg
+
+// bh : http://en.wikipedia.org/wiki/.bh
+bh
+com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
+
+// bi : http://en.wikipedia.org/wiki/.bi
+// http://whois.nic.bi/
+bi
+co.bi
+com.bi
+edu.bi
+or.bi
+org.bi
+
+// biz : http://en.wikipedia.org/wiki/.biz
+biz
+
+// bj : http://en.wikipedia.org/wiki/.bj
+bj
+asso.bj
+barreau.bj
+gouv.bj
+
+// bm : http://www.bermudanic.bm/dnr-text.txt
+bm
+com.bm
+edu.bm
+gov.bm
+net.bm
+org.bm
+
+// bn : http://en.wikipedia.org/wiki/.bn
+*.bn
+
+// bo : http://www.nic.bo/
+bo
+com.bo
+edu.bo
+gov.bo
+gob.bo
+int.bo
+org.bo
+net.bo
+mil.bo
+tv.bo
+
+// br : http://registro.br/dominio/categoria.html
+// Submitted by registry <fneves@registro.br> 2014-08-11
+br
+adm.br
+adv.br
+agr.br
+am.br
+arq.br
+art.br
+ato.br
+b.br
+bio.br
+blog.br
+bmd.br
+cim.br
+cng.br
+cnt.br
+com.br
+coop.br
+ecn.br
+eco.br
+edu.br
+emp.br
+eng.br
+esp.br
+etc.br
+eti.br
+far.br
+flog.br
+fm.br
+fnd.br
+fot.br
+fst.br
+g12.br
+ggf.br
+gov.br
+imb.br
+ind.br
+inf.br
+jor.br
+jus.br
+leg.br
+lel.br
+mat.br
+med.br
+mil.br
+mp.br
+mus.br
+net.br
+*.nom.br
+not.br
+ntr.br
+odo.br
+org.br
+ppg.br
+pro.br
+psc.br
+psi.br
+qsl.br
+radio.br
+rec.br
+slg.br
+srv.br
+taxi.br
+teo.br
+tmp.br
+trd.br
+tur.br
+tv.br
+vet.br
+vlog.br
+wiki.br
+zlg.br
+
+// bs : http://www.nic.bs/rules.html
+bs
+com.bs
+net.bs
+org.bs
+edu.bs
+gov.bs
+
+// bt : http://en.wikipedia.org/wiki/.bt
+bt
+com.bt
+edu.bt
+gov.bt
+net.bt
+org.bt
+
+// bv : No registrations at this time.
+// Submitted by registry <jarle@uninett.no> 2006-06-16
+bv
+
+// bw : http://en.wikipedia.org/wiki/.bw
+// http://www.gobin.info/domainname/bw.doc
+// list of other 2nd level tlds ?
+bw
+co.bw
+org.bw
+
+// by : http://en.wikipedia.org/wiki/.by
+// http://tld.by/rules_2006_en.html
+// list of other 2nd level tlds ?
+by
+gov.by
+mil.by
+// Official information does not indicate that com.by is a reserved
+// second-level domain, but it's being used as one (see www.google.com.by and
+// www.yahoo.com.by, for example), so we list it here for safety's sake.
+com.by
+
+// http://hoster.by/
+of.by
+
+// bz : http://en.wikipedia.org/wiki/.bz
+// http://www.belizenic.bz/
+bz
+com.bz
+net.bz
+org.bz
+edu.bz
+gov.bz
+
+// ca : http://en.wikipedia.org/wiki/.ca
+ca
+// ca geographical names
+ab.ca
+bc.ca
+mb.ca
+nb.ca
+nf.ca
+nl.ca
+ns.ca
+nt.ca
+nu.ca
+on.ca
+pe.ca
+qc.ca
+sk.ca
+yk.ca
+// gc.ca: http://en.wikipedia.org/wiki/.gc.ca
+// see also: http://registry.gc.ca/en/SubdomainFAQ
+gc.ca
+
+// cat : http://en.wikipedia.org/wiki/.cat
+cat
+
+// cc : http://en.wikipedia.org/wiki/.cc
+cc
+
+// cd : http://en.wikipedia.org/wiki/.cd
+// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1
+cd
+gov.cd
+
+// cf : http://en.wikipedia.org/wiki/.cf
+cf
+
+// cg : http://en.wikipedia.org/wiki/.cg
+cg
+
+// ch : http://en.wikipedia.org/wiki/.ch
+ch
+
+// ci : http://en.wikipedia.org/wiki/.ci
+// http://www.nic.ci/index.php?page=charte
+ci
+org.ci
+or.ci
+com.ci
+co.ci
+edu.ci
+ed.ci
+ac.ci
+net.ci
+go.ci
+asso.ci
+aéroport.ci
+int.ci
+presse.ci
+md.ci
+gouv.ci
+
+// ck : http://en.wikipedia.org/wiki/.ck
+*.ck
+!www.ck
+
+// cl : http://en.wikipedia.org/wiki/.cl
+cl
+gov.cl
+gob.cl
+co.cl
+mil.cl
+
+// cm : http://en.wikipedia.org/wiki/.cm plus bug 981927
+cm
+co.cm
+com.cm
+gov.cm
+net.cm
+
+// cn : http://en.wikipedia.org/wiki/.cn
+// Submitted by registry <tanyaling@cnnic.cn> 2008-06-11
+cn
+ac.cn
+com.cn
+edu.cn
+gov.cn
+net.cn
+org.cn
+mil.cn
+公司.cn
+网络.cn
+網絡.cn
+// cn geographic names
+ah.cn
+bj.cn
+cq.cn
+fj.cn
+gd.cn
+gs.cn
+gz.cn
+gx.cn
+ha.cn
+hb.cn
+he.cn
+hi.cn
+hl.cn
+hn.cn
+jl.cn
+js.cn
+jx.cn
+ln.cn
+nm.cn
+nx.cn
+qh.cn
+sc.cn
+sd.cn
+sh.cn
+sn.cn
+sx.cn
+tj.cn
+xj.cn
+xz.cn
+yn.cn
+zj.cn
+hk.cn
+mo.cn
+tw.cn
+
+// co : http://en.wikipedia.org/wiki/.co
+// Submitted by registry <tecnico@uniandes.edu.co> 2008-06-11
+co
+arts.co
+com.co
+edu.co
+firm.co
+gov.co
+info.co
+int.co
+mil.co
+net.co
+nom.co
+org.co
+rec.co
+web.co
+
+// com : http://en.wikipedia.org/wiki/.com
+com
+
+// coop : http://en.wikipedia.org/wiki/.coop
+coop
+
+// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do
+cr
+ac.cr
+co.cr
+ed.cr
+fi.cr
+go.cr
+or.cr
+sa.cr
+
+// cu : http://en.wikipedia.org/wiki/.cu
+cu
+com.cu
+edu.cu
+org.cu
+net.cu
+gov.cu
+inf.cu
+
+// cv : http://en.wikipedia.org/wiki/.cv
+cv
+
+// cw : http://www.una.cw/cw_registry/
+// Confirmed by registry <registry@una.net> 2013-03-26
+cw
+com.cw
+edu.cw
+net.cw
+org.cw
+
+// cx : http://en.wikipedia.org/wiki/.cx
+// list of other 2nd level tlds ?
+cx
+gov.cx
+
+// cy : http://en.wikipedia.org/wiki/.cy
+ac.cy
+biz.cy
+com.cy
+ekloges.cy
+gov.cy
+ltd.cy
+name.cy
+net.cy
+org.cy
+parliament.cy
+press.cy
+pro.cy
+tm.cy
+
+// cz : http://en.wikipedia.org/wiki/.cz
+cz
+
+// de : http://en.wikipedia.org/wiki/.de
+// Confirmed by registry <ops@denic.de> (with technical
+// reservations) 2008-07-01
+de
+
+// dj : http://en.wikipedia.org/wiki/.dj
+dj
+
+// dk : http://en.wikipedia.org/wiki/.dk
+// Confirmed by registry <robert@dk-hostmaster.dk> 2008-06-17
+dk
+
+// dm : http://en.wikipedia.org/wiki/.dm
+dm
+com.dm
+net.dm
+org.dm
+edu.dm
+gov.dm
+
+// do : http://en.wikipedia.org/wiki/.do
+do
+art.do
+com.do
+edu.do
+gob.do
+gov.do
+mil.do
+net.do
+org.do
+sld.do
+web.do
+
+// dz : http://en.wikipedia.org/wiki/.dz
+dz
+com.dz
+org.dz
+net.dz
+gov.dz
+edu.dz
+asso.dz
+pol.dz
+art.dz
+
+// ec : http://www.nic.ec/reg/paso1.asp
+// Submitted by registry <vabboud@nic.ec> 2008-07-04
+ec
+com.ec
+info.ec
+net.ec
+fin.ec
+k12.ec
+med.ec
+pro.ec
+org.ec
+edu.ec
+gov.ec
+gob.ec
+mil.ec
+
+// edu : http://en.wikipedia.org/wiki/.edu
+edu
+
+// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B
+ee
+edu.ee
+gov.ee
+riik.ee
+lib.ee
+med.ee
+com.ee
+pri.ee
+aip.ee
+org.ee
+fie.ee
+
+// eg : http://en.wikipedia.org/wiki/.eg
+eg
+com.eg
+edu.eg
+eun.eg
+gov.eg
+mil.eg
+name.eg
+net.eg
+org.eg
+sci.eg
+
+// er : http://en.wikipedia.org/wiki/.er
+*.er
+
+// es : https://www.nic.es/site_ingles/ingles/dominios/index.html
+es
+com.es
+nom.es
+org.es
+gob.es
+edu.es
+
+// et : http://en.wikipedia.org/wiki/.et
+et
+com.et
+gov.et
+org.et
+edu.et
+biz.et
+name.et
+info.et
+net.et
+
+// eu : http://en.wikipedia.org/wiki/.eu
+eu
+
+// fi : http://en.wikipedia.org/wiki/.fi
+fi
+// aland.fi : http://en.wikipedia.org/wiki/.ax
+// This domain is being phased out in favor of .ax. As there are still many
+// domains under aland.fi, we still keep it on the list until aland.fi is
+// completely removed.
+// TODO: Check for updates (expected to be phased out around Q1/2009)
+aland.fi
+
+// fj : http://en.wikipedia.org/wiki/.fj
+*.fj
+
+// fk : http://en.wikipedia.org/wiki/.fk
+*.fk
+
+// fm : http://en.wikipedia.org/wiki/.fm
+fm
+
+// fo : http://en.wikipedia.org/wiki/.fo
+fo
+
+// fr : http://www.afnic.fr/
+// domaines descriptifs : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-descriptifs
+fr
+com.fr
+asso.fr
+nom.fr
+prd.fr
+presse.fr
+tm.fr
+// domaines sectoriels : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-sectoriels
+aeroport.fr
+assedic.fr
+avocat.fr
+avoues.fr
+cci.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+geometre-expert.fr
+gouv.fr
+greta.fr
+huissier-justice.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.fr
+
+// ga : http://en.wikipedia.org/wiki/.ga
+ga
+
+// gb : This registry is effectively dormant
+// Submitted by registry <Damien.Shaw@ja.net> 2008-06-12
+gb
+
+// gd : http://en.wikipedia.org/wiki/.gd
+gd
+
+// ge : http://www.nic.net.ge/policy_en.pdf
+ge
+com.ge
+edu.ge
+gov.ge
+org.ge
+mil.ge
+net.ge
+pvt.ge
+
+// gf : http://en.wikipedia.org/wiki/.gf
+gf
+
+// gg : http://www.channelisles.net/register-domains/
+// Confirmed by registry <nigel@channelisles.net> 2013-11-28
+gg
+co.gg
+net.gg
+org.gg
+
+// gh : http://en.wikipedia.org/wiki/.gh
+// see also: http://www.nic.gh/reg_now.php
+// Although domains directly at second level are not possible at the moment,
+// they have been possible for some time and may come back.
+gh
+com.gh
+edu.gh
+gov.gh
+org.gh
+mil.gh
+
+// gi : http://www.nic.gi/rules.html
+gi
+com.gi
+ltd.gi
+gov.gi
+mod.gi
+edu.gi
+org.gi
+
+// gl : http://en.wikipedia.org/wiki/.gl
+// http://nic.gl
+gl
+co.gl
+com.gl
+edu.gl
+net.gl
+org.gl
+
+// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm
+gm
+
+// gn : http://psg.com/dns/gn/gn.txt
+// Submitted by registry <randy@psg.com> 2008-06-17
+gn
+ac.gn
+com.gn
+edu.gn
+gov.gn
+org.gn
+net.gn
+
+// gov : http://en.wikipedia.org/wiki/.gov
+gov
+
+// gp : http://www.nic.gp/index.php?lang=en
+gp
+com.gp
+net.gp
+mobi.gp
+edu.gp
+org.gp
+asso.gp
+
+// gq : http://en.wikipedia.org/wiki/.gq
+gq
+
+// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html
+// Submitted by registry <segred@ics.forth.gr> 2008-06-09
+gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+
+// gs : http://en.wikipedia.org/wiki/.gs
+gs
+
+// gt : http://www.gt/politicas_de_registro.html
+gt
+com.gt
+edu.gt
+gob.gt
+ind.gt
+mil.gt
+net.gt
+org.gt
+
+// gu : http://gadao.gov.gu/registration.txt
+*.gu
+
+// gw : http://en.wikipedia.org/wiki/.gw
+gw
+
+// gy : http://en.wikipedia.org/wiki/.gy
+// http://registry.gy/
+gy
+co.gy
+com.gy
+net.gy
+
+// hk : https://www.hkdnr.hk
+// Submitted by registry <hk.tech@hkirc.hk> 2008-06-11
+hk
+com.hk
+edu.hk
+gov.hk
+idv.hk
+net.hk
+org.hk
+公司.hk
+教育.hk
+敎育.hk
+政府.hk
+個人.hk
+个人.hk
+箇人.hk
+網络.hk
+网络.hk
+组織.hk
+網絡.hk
+网絡.hk
+组织.hk
+組織.hk
+組织.hk
+
+// hm : http://en.wikipedia.org/wiki/.hm
+hm
+
+// hn : http://www.nic.hn/politicas/ps02,,05.html
+hn
+com.hn
+edu.hn
+org.hn
+net.hn
+mil.hn
+gob.hn
+
+// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf
+hr
+iz.hr
+from.hr
+name.hr
+com.hr
+
+// ht : http://www.nic.ht/info/charte.cfm
+ht
+com.ht
+shop.ht
+firm.ht
+info.ht
+adult.ht
+net.ht
+pro.ht
+org.ht
+med.ht
+art.ht
+coop.ht
+pol.ht
+asso.ht
+edu.ht
+rel.ht
+gouv.ht
+perso.ht
+
+// hu : http://www.domain.hu/domain/English/sld.html
+// Confirmed by registry <pasztor@iszt.hu> 2008-06-12
+hu
+co.hu
+info.hu
+org.hu
+priv.hu
+sport.hu
+tm.hu
+2000.hu
+agrar.hu
+bolt.hu
+casino.hu
+city.hu
+erotica.hu
+erotika.hu
+film.hu
+forum.hu
+games.hu
+hotel.hu
+ingatlan.hu
+jogasz.hu
+konyvelo.hu
+lakas.hu
+media.hu
+news.hu
+reklam.hu
+sex.hu
+shop.hu
+suli.hu
+szex.hu
+tozsde.hu
+utazas.hu
+video.hu
+
+// id : https://register.pandi.or.id/
+id
+ac.id
+biz.id
+co.id
+desa.id
+go.id
+mil.id
+my.id
+net.id
+or.id
+sch.id
+web.id
+
+// ie : http://en.wikipedia.org/wiki/.ie
+ie
+gov.ie
+
+// il : http://www.isoc.org.il/domains/
+il
+ac.il
+co.il
+gov.il
+idf.il
+k12.il
+muni.il
+net.il
+org.il
+
+// im : https://www.nic.im/
+// Submitted by registry <info@nic.im> 2013-11-15
+im
+ac.im
+co.im
+com.im
+ltd.co.im
+net.im
+org.im
+plc.co.im
+tt.im
+tv.im
+
+// in : http://en.wikipedia.org/wiki/.in
+// see also: https://registry.in/Policies
+// Please note, that nic.in is not an official eTLD, but used by most
+// government institutions.
+in
+co.in
+firm.in
+net.in
+org.in
+gen.in
+ind.in
+nic.in
+ac.in
+edu.in
+res.in
+gov.in
+mil.in
+
+// info : http://en.wikipedia.org/wiki/.info
+info
+
+// int : http://en.wikipedia.org/wiki/.int
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+int
+eu.int
+
+// io : http://www.nic.io/rules.html
+// list of other 2nd level tlds ?
+io
+com.io
+
+// iq : http://www.cmc.iq/english/iq/iqregister1.htm
+iq
+gov.iq
+edu.iq
+mil.iq
+com.iq
+org.iq
+net.iq
+
+// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules
+// Also see http://www.nic.ir/Internationalized_Domain_Names
+// Two <iran>.ir entries added at request of <tech-team@nic.ir>, 2010-04-16
+ir
+ac.ir
+co.ir
+gov.ir
+id.ir
+net.ir
+org.ir
+sch.ir
+// xn--mgba3a4f16a.ir (<iran>.ir, Persian YEH)
+ایران.ir
+// xn--mgba3a4fra.ir (<iran>.ir, Arabic YEH)
+ايران.ir
+
+// is : http://www.isnic.is/domain/rules.php
+// Confirmed by registry <marius@isgate.is> 2008-12-06
+is
+net.is
+com.is
+edu.is
+gov.is
+org.is
+int.is
+
+// it : http://en.wikipedia.org/wiki/.it
+it
+gov.it
+edu.it
+// Reserved geo-names:
+// http://www.nic.it/documenti/regolamenti-e-linee-guida/regolamento-assegnazione-versione-6.0.pdf
+// There is also a list of reserved geo-names corresponding to Italian municipalities
+// http://www.nic.it/documenti/appendice-c.pdf, but it is not included here.
+// Regions
+abr.it
+abruzzo.it
+aosta-valley.it
+aostavalley.it
+bas.it
+basilicata.it
+cal.it
+calabria.it
+cam.it
+campania.it
+emilia-romagna.it
+emiliaromagna.it
+emr.it
+friuli-v-giulia.it
+friuli-ve-giulia.it
+friuli-vegiulia.it
+friuli-venezia-giulia.it
+friuli-veneziagiulia.it
+friuli-vgiulia.it
+friuliv-giulia.it
+friulive-giulia.it
+friulivegiulia.it
+friulivenezia-giulia.it
+friuliveneziagiulia.it
+friulivgiulia.it
+fvg.it
+laz.it
+lazio.it
+lig.it
+liguria.it
+lom.it
+lombardia.it
+lombardy.it
+lucania.it
+mar.it
+marche.it
+mol.it
+molise.it
+piedmont.it
+piemonte.it
+pmn.it
+pug.it
+puglia.it
+sar.it
+sardegna.it
+sardinia.it
+sic.it
+sicilia.it
+sicily.it
+taa.it
+tos.it
+toscana.it
+trentino-a-adige.it
+trentino-aadige.it
+trentino-alto-adige.it
+trentino-altoadige.it
+trentino-s-tirol.it
+trentino-stirol.it
+trentino-sud-tirol.it
+trentino-sudtirol.it
+trentino-sued-tirol.it
+trentino-suedtirol.it
+trentinoa-adige.it
+trentinoaadige.it
+trentinoalto-adige.it
+trentinoaltoadige.it
+trentinos-tirol.it
+trentinostirol.it
+trentinosud-tirol.it
+trentinosudtirol.it
+trentinosued-tirol.it
+trentinosuedtirol.it
+tuscany.it
+umb.it
+umbria.it
+val-d-aosta.it
+val-daosta.it
+vald-aosta.it
+valdaosta.it
+valle-aosta.it
+valle-d-aosta.it
+valle-daosta.it
+valleaosta.it
+valled-aosta.it
+valledaosta.it
+vallee-aoste.it
+valleeaoste.it
+vao.it
+vda.it
+ven.it
+veneto.it
+// Provinces
+ag.it
+agrigento.it
+al.it
+alessandria.it
+alto-adige.it
+altoadige.it
+an.it
+ancona.it
+andria-barletta-trani.it
+andria-trani-barletta.it
+andriabarlettatrani.it
+andriatranibarletta.it
+ao.it
+aosta.it
+aoste.it
+ap.it
+aq.it
+aquila.it
+ar.it
+arezzo.it
+ascoli-piceno.it
+ascolipiceno.it
+asti.it
+at.it
+av.it
+avellino.it
+ba.it
+balsan.it
+bari.it
+barletta-trani-andria.it
+barlettatraniandria.it
+belluno.it
+benevento.it
+bergamo.it
+bg.it
+bi.it
+biella.it
+bl.it
+bn.it
+bo.it
+bologna.it
+bolzano.it
+bozen.it
+br.it
+brescia.it
+brindisi.it
+bs.it
+bt.it
+bz.it
+ca.it
+cagliari.it
+caltanissetta.it
+campidano-medio.it
+campidanomedio.it
+campobasso.it
+carbonia-iglesias.it
+carboniaiglesias.it
+carrara-massa.it
+carraramassa.it
+caserta.it
+catania.it
+catanzaro.it
+cb.it
+ce.it
+cesena-forli.it
+cesenaforli.it
+ch.it
+chieti.it
+ci.it
+cl.it
+cn.it
+co.it
+como.it
+cosenza.it
+cr.it
+cremona.it
+crotone.it
+cs.it
+ct.it
+cuneo.it
+cz.it
+dell-ogliastra.it
+dellogliastra.it
+en.it
+enna.it
+fc.it
+fe.it
+fermo.it
+ferrara.it
+fg.it
+fi.it
+firenze.it
+florence.it
+fm.it
+foggia.it
+forli-cesena.it
+forlicesena.it
+fr.it
+frosinone.it
+ge.it
+genoa.it
+genova.it
+go.it
+gorizia.it
+gr.it
+grosseto.it
+iglesias-carbonia.it
+iglesiascarbonia.it
+im.it
+imperia.it
+is.it
+isernia.it
+kr.it
+la-spezia.it
+laquila.it
+laspezia.it
+latina.it
+lc.it
+le.it
+lecce.it
+lecco.it
+li.it
+livorno.it
+lo.it
+lodi.it
+lt.it
+lu.it
+lucca.it
+macerata.it
+mantova.it
+massa-carrara.it
+massacarrara.it
+matera.it
+mb.it
+mc.it
+me.it
+medio-campidano.it
+mediocampidano.it
+messina.it
+mi.it
+milan.it
+milano.it
+mn.it
+mo.it
+modena.it
+monza-brianza.it
+monza-e-della-brianza.it
+monza.it
+monzabrianza.it
+monzaebrianza.it
+monzaedellabrianza.it
+ms.it
+mt.it
+na.it
+naples.it
+napoli.it
+no.it
+novara.it
+nu.it
+nuoro.it
+og.it
+ogliastra.it
+olbia-tempio.it
+olbiatempio.it
+or.it
+oristano.it
+ot.it
+pa.it
+padova.it
+padua.it
+palermo.it
+parma.it
+pavia.it
+pc.it
+pd.it
+pe.it
+perugia.it
+pesaro-urbino.it
+pesarourbino.it
+pescara.it
+pg.it
+pi.it
+piacenza.it
+pisa.it
+pistoia.it
+pn.it
+po.it
+pordenone.it
+potenza.it
+pr.it
+prato.it
+pt.it
+pu.it
+pv.it
+pz.it
+ra.it
+ragusa.it
+ravenna.it
+rc.it
+re.it
+reggio-calabria.it
+reggio-emilia.it
+reggiocalabria.it
+reggioemilia.it
+rg.it
+ri.it
+rieti.it
+rimini.it
+rm.it
+rn.it
+ro.it
+roma.it
+rome.it
+rovigo.it
+sa.it
+salerno.it
+sassari.it
+savona.it
+si.it
+siena.it
+siracusa.it
+so.it
+sondrio.it
+sp.it
+sr.it
+ss.it
+suedtirol.it
+sv.it
+ta.it
+taranto.it
+te.it
+tempio-olbia.it
+tempioolbia.it
+teramo.it
+terni.it
+tn.it
+to.it
+torino.it
+tp.it
+tr.it
+trani-andria-barletta.it
+trani-barletta-andria.it
+traniandriabarletta.it
+tranibarlettaandria.it
+trapani.it
+trentino.it
+trento.it
+treviso.it
+trieste.it
+ts.it
+turin.it
+tv.it
+ud.it
+udine.it
+urbino-pesaro.it
+urbinopesaro.it
+va.it
+varese.it
+vb.it
+vc.it
+ve.it
+venezia.it
+venice.it
+verbania.it
+vercelli.it
+verona.it
+vi.it
+vibo-valentia.it
+vibovalentia.it
+vicenza.it
+viterbo.it
+vr.it
+vs.it
+vt.it
+vv.it
+
+// je : http://www.channelisles.net/register-domains/
+// Confirmed by registry <nigel@channelisles.net> 2013-11-28
+je
+co.je
+net.je
+org.je
+
+// jm : http://www.com.jm/register.html
+*.jm
+
+// jo : http://www.dns.jo/Registration_policy.aspx
+jo
+com.jo
+org.jo
+net.jo
+edu.jo
+sch.jo
+gov.jo
+mil.jo
+name.jo
+
+// jobs : http://en.wikipedia.org/wiki/.jobs
+jobs
+
+// jp : http://en.wikipedia.org/wiki/.jp
+// http://jprs.co.jp/en/jpdomain.html
+// Submitted by registry <info@jprs.jp> 2014-10-30
+jp
+// jp organizational type names
+ac.jp
+ad.jp
+co.jp
+ed.jp
+go.jp
+gr.jp
+lg.jp
+ne.jp
+or.jp
+// jp prefecture type names
+aichi.jp
+akita.jp
+aomori.jp
+chiba.jp
+ehime.jp
+fukui.jp
+fukuoka.jp
+fukushima.jp
+gifu.jp
+gunma.jp
+hiroshima.jp
+hokkaido.jp
+hyogo.jp
+ibaraki.jp
+ishikawa.jp
+iwate.jp
+kagawa.jp
+kagoshima.jp
+kanagawa.jp
+kochi.jp
+kumamoto.jp
+kyoto.jp
+mie.jp
+miyagi.jp
+miyazaki.jp
+nagano.jp
+nagasaki.jp
+nara.jp
+niigata.jp
+oita.jp
+okayama.jp
+okinawa.jp
+osaka.jp
+saga.jp
+saitama.jp
+shiga.jp
+shimane.jp
+shizuoka.jp
+tochigi.jp
+tokushima.jp
+tokyo.jp
+tottori.jp
+toyama.jp
+wakayama.jp
+yamagata.jp
+yamaguchi.jp
+yamanashi.jp
+栃木.jp
+愛知.jp
+愛媛.jp
+兵庫.jp
+熊本.jp
+茨城.jp
+北海道.jp
+千葉.jp
+和歌山.jp
+長崎.jp
+長野.jp
+新潟.jp
+青森.jp
+静岡.jp
+東京.jp
+石川.jp
+埼玉.jp
+三重.jp
+京都.jp
+佐賀.jp
+大分.jp
+大阪.jp
+奈良.jp
+宮城.jp
+宮崎.jp
+富山.jp
+山口.jp
+山形.jp
+山梨.jp
+岩手.jp
+岐阜.jp
+岡山.jp
+島根.jp
+広島.jp
+徳島.jp
+沖縄.jp
+滋賀.jp
+神奈川.jp
+福井.jp
+福岡.jp
+福島.jp
+秋田.jp
+群馬.jp
+香川.jp
+高知.jp
+鳥取.jp
+鹿児島.jp
+// jp geographic type names
+// http://jprs.jp/doc/rule/saisoku-1.html
+*.kawasaki.jp
+*.kitakyushu.jp
+*.kobe.jp
+*.nagoya.jp
+*.sapporo.jp
+*.sendai.jp
+*.yokohama.jp
+!city.kawasaki.jp
+!city.kitakyushu.jp
+!city.kobe.jp
+!city.nagoya.jp
+!city.sapporo.jp
+!city.sendai.jp
+!city.yokohama.jp
+// 4th level registration
+aisai.aichi.jp
+ama.aichi.jp
+anjo.aichi.jp
+asuke.aichi.jp
+chiryu.aichi.jp
+chita.aichi.jp
+fuso.aichi.jp
+gamagori.aichi.jp
+handa.aichi.jp
+hazu.aichi.jp
+hekinan.aichi.jp
+higashiura.aichi.jp
+ichinomiya.aichi.jp
+inazawa.aichi.jp
+inuyama.aichi.jp
+isshiki.aichi.jp
+iwakura.aichi.jp
+kanie.aichi.jp
+kariya.aichi.jp
+kasugai.aichi.jp
+kira.aichi.jp
+kiyosu.aichi.jp
+komaki.aichi.jp
+konan.aichi.jp
+kota.aichi.jp
+mihama.aichi.jp
+miyoshi.aichi.jp
+nishio.aichi.jp
+nisshin.aichi.jp
+obu.aichi.jp
+oguchi.aichi.jp
+oharu.aichi.jp
+okazaki.aichi.jp
+owariasahi.aichi.jp
+seto.aichi.jp
+shikatsu.aichi.jp
+shinshiro.aichi.jp
+shitara.aichi.jp
+tahara.aichi.jp
+takahama.aichi.jp
+tobishima.aichi.jp
+toei.aichi.jp
+togo.aichi.jp
+tokai.aichi.jp
+tokoname.aichi.jp
+toyoake.aichi.jp
+toyohashi.aichi.jp
+toyokawa.aichi.jp
+toyone.aichi.jp
+toyota.aichi.jp
+tsushima.aichi.jp
+yatomi.aichi.jp
+akita.akita.jp
+daisen.akita.jp
+fujisato.akita.jp
+gojome.akita.jp
+hachirogata.akita.jp
+happou.akita.jp
+higashinaruse.akita.jp
+honjo.akita.jp
+honjyo.akita.jp
+ikawa.akita.jp
+kamikoani.akita.jp
+kamioka.akita.jp
+katagami.akita.jp
+kazuno.akita.jp
+kitaakita.akita.jp
+kosaka.akita.jp
+kyowa.akita.jp
+misato.akita.jp
+mitane.akita.jp
+moriyoshi.akita.jp
+nikaho.akita.jp
+noshiro.akita.jp
+odate.akita.jp
+oga.akita.jp
+ogata.akita.jp
+semboku.akita.jp
+yokote.akita.jp
+yurihonjo.akita.jp
+aomori.aomori.jp
+gonohe.aomori.jp
+hachinohe.aomori.jp
+hashikami.aomori.jp
+hiranai.aomori.jp
+hirosaki.aomori.jp
+itayanagi.aomori.jp
+kuroishi.aomori.jp
+misawa.aomori.jp
+mutsu.aomori.jp
+nakadomari.aomori.jp
+noheji.aomori.jp
+oirase.aomori.jp
+owani.aomori.jp
+rokunohe.aomori.jp
+sannohe.aomori.jp
+shichinohe.aomori.jp
+shingo.aomori.jp
+takko.aomori.jp
+towada.aomori.jp
+tsugaru.aomori.jp
+tsuruta.aomori.jp
+abiko.chiba.jp
+asahi.chiba.jp
+chonan.chiba.jp
+chosei.chiba.jp
+choshi.chiba.jp
+chuo.chiba.jp
+funabashi.chiba.jp
+futtsu.chiba.jp
+hanamigawa.chiba.jp
+ichihara.chiba.jp
+ichikawa.chiba.jp
+ichinomiya.chiba.jp
+inzai.chiba.jp
+isumi.chiba.jp
+kamagaya.chiba.jp
+kamogawa.chiba.jp
+kashiwa.chiba.jp
+katori.chiba.jp
+katsuura.chiba.jp
+kimitsu.chiba.jp
+kisarazu.chiba.jp
+kozaki.chiba.jp
+kujukuri.chiba.jp
+kyonan.chiba.jp
+matsudo.chiba.jp
+midori.chiba.jp
+mihama.chiba.jp
+minamiboso.chiba.jp
+mobara.chiba.jp
+mutsuzawa.chiba.jp
+nagara.chiba.jp
+nagareyama.chiba.jp
+narashino.chiba.jp
+narita.chiba.jp
+noda.chiba.jp
+oamishirasato.chiba.jp
+omigawa.chiba.jp
+onjuku.chiba.jp
+otaki.chiba.jp
+sakae.chiba.jp
+sakura.chiba.jp
+shimofusa.chiba.jp
+shirako.chiba.jp
+shiroi.chiba.jp
+shisui.chiba.jp
+sodegaura.chiba.jp
+sosa.chiba.jp
+tako.chiba.jp
+tateyama.chiba.jp
+togane.chiba.jp
+tohnosho.chiba.jp
+tomisato.chiba.jp
+urayasu.chiba.jp
+yachimata.chiba.jp
+yachiyo.chiba.jp
+yokaichiba.chiba.jp
+yokoshibahikari.chiba.jp
+yotsukaido.chiba.jp
+ainan.ehime.jp
+honai.ehime.jp
+ikata.ehime.jp
+imabari.ehime.jp
+iyo.ehime.jp
+kamijima.ehime.jp
+kihoku.ehime.jp
+kumakogen.ehime.jp
+masaki.ehime.jp
+matsuno.ehime.jp
+matsuyama.ehime.jp
+namikata.ehime.jp
+niihama.ehime.jp
+ozu.ehime.jp
+saijo.ehime.jp
+seiyo.ehime.jp
+shikokuchuo.ehime.jp
+tobe.ehime.jp
+toon.ehime.jp
+uchiko.ehime.jp
+uwajima.ehime.jp
+yawatahama.ehime.jp
+echizen.fukui.jp
+eiheiji.fukui.jp
+fukui.fukui.jp
+ikeda.fukui.jp
+katsuyama.fukui.jp
+mihama.fukui.jp
+minamiechizen.fukui.jp
+obama.fukui.jp
+ohi.fukui.jp
+ono.fukui.jp
+sabae.fukui.jp
+sakai.fukui.jp
+takahama.fukui.jp
+tsuruga.fukui.jp
+wakasa.fukui.jp
+ashiya.fukuoka.jp
+buzen.fukuoka.jp
+chikugo.fukuoka.jp
+chikuho.fukuoka.jp
+chikujo.fukuoka.jp
+chikushino.fukuoka.jp
+chikuzen.fukuoka.jp
+chuo.fukuoka.jp
+dazaifu.fukuoka.jp
+fukuchi.fukuoka.jp
+hakata.fukuoka.jp
+higashi.fukuoka.jp
+hirokawa.fukuoka.jp
+hisayama.fukuoka.jp
+iizuka.fukuoka.jp
+inatsuki.fukuoka.jp
+kaho.fukuoka.jp
+kasuga.fukuoka.jp
+kasuya.fukuoka.jp
+kawara.fukuoka.jp
+keisen.fukuoka.jp
+koga.fukuoka.jp
+kurate.fukuoka.jp
+kurogi.fukuoka.jp
+kurume.fukuoka.jp
+minami.fukuoka.jp
+miyako.fukuoka.jp
+miyama.fukuoka.jp
+miyawaka.fukuoka.jp
+mizumaki.fukuoka.jp
+munakata.fukuoka.jp
+nakagawa.fukuoka.jp
+nakama.fukuoka.jp
+nishi.fukuoka.jp
+nogata.fukuoka.jp
+ogori.fukuoka.jp
+okagaki.fukuoka.jp
+okawa.fukuoka.jp
+oki.fukuoka.jp
+omuta.fukuoka.jp
+onga.fukuoka.jp
+onojo.fukuoka.jp
+oto.fukuoka.jp
+saigawa.fukuoka.jp
+sasaguri.fukuoka.jp
+shingu.fukuoka.jp
+shinyoshitomi.fukuoka.jp
+shonai.fukuoka.jp
+soeda.fukuoka.jp
+sue.fukuoka.jp
+tachiarai.fukuoka.jp
+tagawa.fukuoka.jp
+takata.fukuoka.jp
+toho.fukuoka.jp
+toyotsu.fukuoka.jp
+tsuiki.fukuoka.jp
+ukiha.fukuoka.jp
+umi.fukuoka.jp
+usui.fukuoka.jp
+yamada.fukuoka.jp
+yame.fukuoka.jp
+yanagawa.fukuoka.jp
+yukuhashi.fukuoka.jp
+aizubange.fukushima.jp
+aizumisato.fukushima.jp
+aizuwakamatsu.fukushima.jp
+asakawa.fukushima.jp
+bandai.fukushima.jp
+date.fukushima.jp
+fukushima.fukushima.jp
+furudono.fukushima.jp
+futaba.fukushima.jp
+hanawa.fukushima.jp
+higashi.fukushima.jp
+hirata.fukushima.jp
+hirono.fukushima.jp
+iitate.fukushima.jp
+inawashiro.fukushima.jp
+ishikawa.fukushima.jp
+iwaki.fukushima.jp
+izumizaki.fukushima.jp
+kagamiishi.fukushima.jp
+kaneyama.fukushima.jp
+kawamata.fukushima.jp
+kitakata.fukushima.jp
+kitashiobara.fukushima.jp
+koori.fukushima.jp
+koriyama.fukushima.jp
+kunimi.fukushima.jp
+miharu.fukushima.jp
+mishima.fukushima.jp
+namie.fukushima.jp
+nango.fukushima.jp
+nishiaizu.fukushima.jp
+nishigo.fukushima.jp
+okuma.fukushima.jp
+omotego.fukushima.jp
+ono.fukushima.jp
+otama.fukushima.jp
+samegawa.fukushima.jp
+shimogo.fukushima.jp
+shirakawa.fukushima.jp
+showa.fukushima.jp
+soma.fukushima.jp
+sukagawa.fukushima.jp
+taishin.fukushima.jp
+tamakawa.fukushima.jp
+tanagura.fukushima.jp
+tenei.fukushima.jp
+yabuki.fukushima.jp
+yamato.fukushima.jp
+yamatsuri.fukushima.jp
+yanaizu.fukushima.jp
+yugawa.fukushima.jp
+anpachi.gifu.jp
+ena.gifu.jp
+gifu.gifu.jp
+ginan.gifu.jp
+godo.gifu.jp
+gujo.gifu.jp
+hashima.gifu.jp
+hichiso.gifu.jp
+hida.gifu.jp
+higashishirakawa.gifu.jp
+ibigawa.gifu.jp
+ikeda.gifu.jp
+kakamigahara.gifu.jp
+kani.gifu.jp
+kasahara.gifu.jp
+kasamatsu.gifu.jp
+kawaue.gifu.jp
+kitagata.gifu.jp
+mino.gifu.jp
+minokamo.gifu.jp
+mitake.gifu.jp
+mizunami.gifu.jp
+motosu.gifu.jp
+nakatsugawa.gifu.jp
+ogaki.gifu.jp
+sakahogi.gifu.jp
+seki.gifu.jp
+sekigahara.gifu.jp
+shirakawa.gifu.jp
+tajimi.gifu.jp
+takayama.gifu.jp
+tarui.gifu.jp
+toki.gifu.jp
+tomika.gifu.jp
+wanouchi.gifu.jp
+yamagata.gifu.jp
+yaotsu.gifu.jp
+yoro.gifu.jp
+annaka.gunma.jp
+chiyoda.gunma.jp
+fujioka.gunma.jp
+higashiagatsuma.gunma.jp
+isesaki.gunma.jp
+itakura.gunma.jp
+kanna.gunma.jp
+kanra.gunma.jp
+katashina.gunma.jp
+kawaba.gunma.jp
+kiryu.gunma.jp
+kusatsu.gunma.jp
+maebashi.gunma.jp
+meiwa.gunma.jp
+midori.gunma.jp
+minakami.gunma.jp
+naganohara.gunma.jp
+nakanojo.gunma.jp
+nanmoku.gunma.jp
+numata.gunma.jp
+oizumi.gunma.jp
+ora.gunma.jp
+ota.gunma.jp
+shibukawa.gunma.jp
+shimonita.gunma.jp
+shinto.gunma.jp
+showa.gunma.jp
+takasaki.gunma.jp
+takayama.gunma.jp
+tamamura.gunma.jp
+tatebayashi.gunma.jp
+tomioka.gunma.jp
+tsukiyono.gunma.jp
+tsumagoi.gunma.jp
+ueno.gunma.jp
+yoshioka.gunma.jp
+asaminami.hiroshima.jp
+daiwa.hiroshima.jp
+etajima.hiroshima.jp
+fuchu.hiroshima.jp
+fukuyama.hiroshima.jp
+hatsukaichi.hiroshima.jp
+higashihiroshima.hiroshima.jp
+hongo.hiroshima.jp
+jinsekikogen.hiroshima.jp
+kaita.hiroshima.jp
+kui.hiroshima.jp
+kumano.hiroshima.jp
+kure.hiroshima.jp
+mihara.hiroshima.jp
+miyoshi.hiroshima.jp
+naka.hiroshima.jp
+onomichi.hiroshima.jp
+osakikamijima.hiroshima.jp
+otake.hiroshima.jp
+saka.hiroshima.jp
+sera.hiroshima.jp
+seranishi.hiroshima.jp
+shinichi.hiroshima.jp
+shobara.hiroshima.jp
+takehara.hiroshima.jp
+abashiri.hokkaido.jp
+abira.hokkaido.jp
+aibetsu.hokkaido.jp
+akabira.hokkaido.jp
+akkeshi.hokkaido.jp
+asahikawa.hokkaido.jp
+ashibetsu.hokkaido.jp
+ashoro.hokkaido.jp
+assabu.hokkaido.jp
+atsuma.hokkaido.jp
+bibai.hokkaido.jp
+biei.hokkaido.jp
+bifuka.hokkaido.jp
+bihoro.hokkaido.jp
+biratori.hokkaido.jp
+chippubetsu.hokkaido.jp
+chitose.hokkaido.jp
+date.hokkaido.jp
+ebetsu.hokkaido.jp
+embetsu.hokkaido.jp
+eniwa.hokkaido.jp
+erimo.hokkaido.jp
+esan.hokkaido.jp
+esashi.hokkaido.jp
+fukagawa.hokkaido.jp
+fukushima.hokkaido.jp
+furano.hokkaido.jp
+furubira.hokkaido.jp
+haboro.hokkaido.jp
+hakodate.hokkaido.jp
+hamatonbetsu.hokkaido.jp
+hidaka.hokkaido.jp
+higashikagura.hokkaido.jp
+higashikawa.hokkaido.jp
+hiroo.hokkaido.jp
+hokuryu.hokkaido.jp
+hokuto.hokkaido.jp
+honbetsu.hokkaido.jp
+horokanai.hokkaido.jp
+horonobe.hokkaido.jp
+ikeda.hokkaido.jp
+imakane.hokkaido.jp
+ishikari.hokkaido.jp
+iwamizawa.hokkaido.jp
+iwanai.hokkaido.jp
+kamifurano.hokkaido.jp
+kamikawa.hokkaido.jp
+kamishihoro.hokkaido.jp
+kamisunagawa.hokkaido.jp
+kamoenai.hokkaido.jp
+kayabe.hokkaido.jp
+kembuchi.hokkaido.jp
+kikonai.hokkaido.jp
+kimobetsu.hokkaido.jp
+kitahiroshima.hokkaido.jp
+kitami.hokkaido.jp
+kiyosato.hokkaido.jp
+koshimizu.hokkaido.jp
+kunneppu.hokkaido.jp
+kuriyama.hokkaido.jp
+kuromatsunai.hokkaido.jp
+kushiro.hokkaido.jp
+kutchan.hokkaido.jp
+kyowa.hokkaido.jp
+mashike.hokkaido.jp
+matsumae.hokkaido.jp
+mikasa.hokkaido.jp
+minamifurano.hokkaido.jp
+mombetsu.hokkaido.jp
+moseushi.hokkaido.jp
+mukawa.hokkaido.jp
+muroran.hokkaido.jp
+naie.hokkaido.jp
+nakagawa.hokkaido.jp
+nakasatsunai.hokkaido.jp
+nakatombetsu.hokkaido.jp
+nanae.hokkaido.jp
+nanporo.hokkaido.jp
+nayoro.hokkaido.jp
+nemuro.hokkaido.jp
+niikappu.hokkaido.jp
+niki.hokkaido.jp
+nishiokoppe.hokkaido.jp
+noboribetsu.hokkaido.jp
+numata.hokkaido.jp
+obihiro.hokkaido.jp
+obira.hokkaido.jp
+oketo.hokkaido.jp
+okoppe.hokkaido.jp
+otaru.hokkaido.jp
+otobe.hokkaido.jp
+otofuke.hokkaido.jp
+otoineppu.hokkaido.jp
+oumu.hokkaido.jp
+ozora.hokkaido.jp
+pippu.hokkaido.jp
+rankoshi.hokkaido.jp
+rebun.hokkaido.jp
+rikubetsu.hokkaido.jp
+rishiri.hokkaido.jp
+rishirifuji.hokkaido.jp
+saroma.hokkaido.jp
+sarufutsu.hokkaido.jp
+shakotan.hokkaido.jp
+shari.hokkaido.jp
+shibecha.hokkaido.jp
+shibetsu.hokkaido.jp
+shikabe.hokkaido.jp
+shikaoi.hokkaido.jp
+shimamaki.hokkaido.jp
+shimizu.hokkaido.jp
+shimokawa.hokkaido.jp
+shinshinotsu.hokkaido.jp
+shintoku.hokkaido.jp
+shiranuka.hokkaido.jp
+shiraoi.hokkaido.jp
+shiriuchi.hokkaido.jp
+sobetsu.hokkaido.jp
+sunagawa.hokkaido.jp
+taiki.hokkaido.jp
+takasu.hokkaido.jp
+takikawa.hokkaido.jp
+takinoue.hokkaido.jp
+teshikaga.hokkaido.jp
+tobetsu.hokkaido.jp
+tohma.hokkaido.jp
+tomakomai.hokkaido.jp
+tomari.hokkaido.jp
+toya.hokkaido.jp
+toyako.hokkaido.jp
+toyotomi.hokkaido.jp
+toyoura.hokkaido.jp
+tsubetsu.hokkaido.jp
+tsukigata.hokkaido.jp
+urakawa.hokkaido.jp
+urausu.hokkaido.jp
+uryu.hokkaido.jp
+utashinai.hokkaido.jp
+wakkanai.hokkaido.jp
+wassamu.hokkaido.jp
+yakumo.hokkaido.jp
+yoichi.hokkaido.jp
+aioi.hyogo.jp
+akashi.hyogo.jp
+ako.hyogo.jp
+amagasaki.hyogo.jp
+aogaki.hyogo.jp
+asago.hyogo.jp
+ashiya.hyogo.jp
+awaji.hyogo.jp
+fukusaki.hyogo.jp
+goshiki.hyogo.jp
+harima.hyogo.jp
+himeji.hyogo.jp
+ichikawa.hyogo.jp
+inagawa.hyogo.jp
+itami.hyogo.jp
+kakogawa.hyogo.jp
+kamigori.hyogo.jp
+kamikawa.hyogo.jp
+kasai.hyogo.jp
+kasuga.hyogo.jp
+kawanishi.hyogo.jp
+miki.hyogo.jp
+minamiawaji.hyogo.jp
+nishinomiya.hyogo.jp
+nishiwaki.hyogo.jp
+ono.hyogo.jp
+sanda.hyogo.jp
+sannan.hyogo.jp
+sasayama.hyogo.jp
+sayo.hyogo.jp
+shingu.hyogo.jp
+shinonsen.hyogo.jp
+shiso.hyogo.jp
+sumoto.hyogo.jp
+taishi.hyogo.jp
+taka.hyogo.jp
+takarazuka.hyogo.jp
+takasago.hyogo.jp
+takino.hyogo.jp
+tamba.hyogo.jp
+tatsuno.hyogo.jp
+toyooka.hyogo.jp
+yabu.hyogo.jp
+yashiro.hyogo.jp
+yoka.hyogo.jp
+yokawa.hyogo.jp
+ami.ibaraki.jp
+asahi.ibaraki.jp
+bando.ibaraki.jp
+chikusei.ibaraki.jp
+daigo.ibaraki.jp
+fujishiro.ibaraki.jp
+hitachi.ibaraki.jp
+hitachinaka.ibaraki.jp
+hitachiomiya.ibaraki.jp
+hitachiota.ibaraki.jp
+ibaraki.ibaraki.jp
+ina.ibaraki.jp
+inashiki.ibaraki.jp
+itako.ibaraki.jp
+iwama.ibaraki.jp
+joso.ibaraki.jp
+kamisu.ibaraki.jp
+kasama.ibaraki.jp
+kashima.ibaraki.jp
+kasumigaura.ibaraki.jp
+koga.ibaraki.jp
+miho.ibaraki.jp
+mito.ibaraki.jp
+moriya.ibaraki.jp
+naka.ibaraki.jp
+namegata.ibaraki.jp
+oarai.ibaraki.jp
+ogawa.ibaraki.jp
+omitama.ibaraki.jp
+ryugasaki.ibaraki.jp
+sakai.ibaraki.jp
+sakuragawa.ibaraki.jp
+shimodate.ibaraki.jp
+shimotsuma.ibaraki.jp
+shirosato.ibaraki.jp
+sowa.ibaraki.jp
+suifu.ibaraki.jp
+takahagi.ibaraki.jp
+tamatsukuri.ibaraki.jp
+tokai.ibaraki.jp
+tomobe.ibaraki.jp
+tone.ibaraki.jp
+toride.ibaraki.jp
+tsuchiura.ibaraki.jp
+tsukuba.ibaraki.jp
+uchihara.ibaraki.jp
+ushiku.ibaraki.jp
+yachiyo.ibaraki.jp
+yamagata.ibaraki.jp
+yawara.ibaraki.jp
+yuki.ibaraki.jp
+anamizu.ishikawa.jp
+hakui.ishikawa.jp
+hakusan.ishikawa.jp
+kaga.ishikawa.jp
+kahoku.ishikawa.jp
+kanazawa.ishikawa.jp
+kawakita.ishikawa.jp
+komatsu.ishikawa.jp
+nakanoto.ishikawa.jp
+nanao.ishikawa.jp
+nomi.ishikawa.jp
+nonoichi.ishikawa.jp
+noto.ishikawa.jp
+shika.ishikawa.jp
+suzu.ishikawa.jp
+tsubata.ishikawa.jp
+tsurugi.ishikawa.jp
+uchinada.ishikawa.jp
+wajima.ishikawa.jp
+fudai.iwate.jp
+fujisawa.iwate.jp
+hanamaki.iwate.jp
+hiraizumi.iwate.jp
+hirono.iwate.jp
+ichinohe.iwate.jp
+ichinoseki.iwate.jp
+iwaizumi.iwate.jp
+iwate.iwate.jp
+joboji.iwate.jp
+kamaishi.iwate.jp
+kanegasaki.iwate.jp
+karumai.iwate.jp
+kawai.iwate.jp
+kitakami.iwate.jp
+kuji.iwate.jp
+kunohe.iwate.jp
+kuzumaki.iwate.jp
+miyako.iwate.jp
+mizusawa.iwate.jp
+morioka.iwate.jp
+ninohe.iwate.jp
+noda.iwate.jp
+ofunato.iwate.jp
+oshu.iwate.jp
+otsuchi.iwate.jp
+rikuzentakata.iwate.jp
+shiwa.iwate.jp
+shizukuishi.iwate.jp
+sumita.iwate.jp
+tanohata.iwate.jp
+tono.iwate.jp
+yahaba.iwate.jp
+yamada.iwate.jp
+ayagawa.kagawa.jp
+higashikagawa.kagawa.jp
+kanonji.kagawa.jp
+kotohira.kagawa.jp
+manno.kagawa.jp
+marugame.kagawa.jp
+mitoyo.kagawa.jp
+naoshima.kagawa.jp
+sanuki.kagawa.jp
+tadotsu.kagawa.jp
+takamatsu.kagawa.jp
+tonosho.kagawa.jp
+uchinomi.kagawa.jp
+utazu.kagawa.jp
+zentsuji.kagawa.jp
+akune.kagoshima.jp
+amami.kagoshima.jp
+hioki.kagoshima.jp
+isa.kagoshima.jp
+isen.kagoshima.jp
+izumi.kagoshima.jp
+kagoshima.kagoshima.jp
+kanoya.kagoshima.jp
+kawanabe.kagoshima.jp
+kinko.kagoshima.jp
+kouyama.kagoshima.jp
+makurazaki.kagoshima.jp
+matsumoto.kagoshima.jp
+minamitane.kagoshima.jp
+nakatane.kagoshima.jp
+nishinoomote.kagoshima.jp
+satsumasendai.kagoshima.jp
+soo.kagoshima.jp
+tarumizu.kagoshima.jp
+yusui.kagoshima.jp
+aikawa.kanagawa.jp
+atsugi.kanagawa.jp
+ayase.kanagawa.jp
+chigasaki.kanagawa.jp
+ebina.kanagawa.jp
+fujisawa.kanagawa.jp
+hadano.kanagawa.jp
+hakone.kanagawa.jp
+hiratsuka.kanagawa.jp
+isehara.kanagawa.jp
+kaisei.kanagawa.jp
+kamakura.kanagawa.jp
+kiyokawa.kanagawa.jp
+matsuda.kanagawa.jp
+minamiashigara.kanagawa.jp
+miura.kanagawa.jp
+nakai.kanagawa.jp
+ninomiya.kanagawa.jp
+odawara.kanagawa.jp
+oi.kanagawa.jp
+oiso.kanagawa.jp
+sagamihara.kanagawa.jp
+samukawa.kanagawa.jp
+tsukui.kanagawa.jp
+yamakita.kanagawa.jp
+yamato.kanagawa.jp
+yokosuka.kanagawa.jp
+yugawara.kanagawa.jp
+zama.kanagawa.jp
+zushi.kanagawa.jp
+aki.kochi.jp
+geisei.kochi.jp
+hidaka.kochi.jp
+higashitsuno.kochi.jp
+ino.kochi.jp
+kagami.kochi.jp
+kami.kochi.jp
+kitagawa.kochi.jp
+kochi.kochi.jp
+mihara.kochi.jp
+motoyama.kochi.jp
+muroto.kochi.jp
+nahari.kochi.jp
+nakamura.kochi.jp
+nankoku.kochi.jp
+nishitosa.kochi.jp
+niyodogawa.kochi.jp
+ochi.kochi.jp
+okawa.kochi.jp
+otoyo.kochi.jp
+otsuki.kochi.jp
+sakawa.kochi.jp
+sukumo.kochi.jp
+susaki.kochi.jp
+tosa.kochi.jp
+tosashimizu.kochi.jp
+toyo.kochi.jp
+tsuno.kochi.jp
+umaji.kochi.jp
+yasuda.kochi.jp
+yusuhara.kochi.jp
+amakusa.kumamoto.jp
+arao.kumamoto.jp
+aso.kumamoto.jp
+choyo.kumamoto.jp
+gyokuto.kumamoto.jp
+hitoyoshi.kumamoto.jp
+kamiamakusa.kumamoto.jp
+kashima.kumamoto.jp
+kikuchi.kumamoto.jp
+kosa.kumamoto.jp
+kumamoto.kumamoto.jp
+mashiki.kumamoto.jp
+mifune.kumamoto.jp
+minamata.kumamoto.jp
+minamioguni.kumamoto.jp
+nagasu.kumamoto.jp
+nishihara.kumamoto.jp
+oguni.kumamoto.jp
+ozu.kumamoto.jp
+sumoto.kumamoto.jp
+takamori.kumamoto.jp
+uki.kumamoto.jp
+uto.kumamoto.jp
+yamaga.kumamoto.jp
+yamato.kumamoto.jp
+yatsushiro.kumamoto.jp
+ayabe.kyoto.jp
+fukuchiyama.kyoto.jp
+higashiyama.kyoto.jp
+ide.kyoto.jp
+ine.kyoto.jp
+joyo.kyoto.jp
+kameoka.kyoto.jp
+kamo.kyoto.jp
+kita.kyoto.jp
+kizu.kyoto.jp
+kumiyama.kyoto.jp
+kyotamba.kyoto.jp
+kyotanabe.kyoto.jp
+kyotango.kyoto.jp
+maizuru.kyoto.jp
+minami.kyoto.jp
+minamiyamashiro.kyoto.jp
+miyazu.kyoto.jp
+muko.kyoto.jp
+nagaokakyo.kyoto.jp
+nakagyo.kyoto.jp
+nantan.kyoto.jp
+oyamazaki.kyoto.jp
+sakyo.kyoto.jp
+seika.kyoto.jp
+tanabe.kyoto.jp
+uji.kyoto.jp
+ujitawara.kyoto.jp
+wazuka.kyoto.jp
+yamashina.kyoto.jp
+yawata.kyoto.jp
+asahi.mie.jp
+inabe.mie.jp
+ise.mie.jp
+kameyama.mie.jp
+kawagoe.mie.jp
+kiho.mie.jp
+kisosaki.mie.jp
+kiwa.mie.jp
+komono.mie.jp
+kumano.mie.jp
+kuwana.mie.jp
+matsusaka.mie.jp
+meiwa.mie.jp
+mihama.mie.jp
+minamiise.mie.jp
+misugi.mie.jp
+miyama.mie.jp
+nabari.mie.jp
+shima.mie.jp
+suzuka.mie.jp
+tado.mie.jp
+taiki.mie.jp
+taki.mie.jp
+tamaki.mie.jp
+toba.mie.jp
+tsu.mie.jp
+udono.mie.jp
+ureshino.mie.jp
+watarai.mie.jp
+yokkaichi.mie.jp
+furukawa.miyagi.jp
+higashimatsushima.miyagi.jp
+ishinomaki.miyagi.jp
+iwanuma.miyagi.jp
+kakuda.miyagi.jp
+kami.miyagi.jp
+kawasaki.miyagi.jp
+kesennuma.miyagi.jp
+marumori.miyagi.jp
+matsushima.miyagi.jp
+minamisanriku.miyagi.jp
+misato.miyagi.jp
+murata.miyagi.jp
+natori.miyagi.jp
+ogawara.miyagi.jp
+ohira.miyagi.jp
+onagawa.miyagi.jp
+osaki.miyagi.jp
+rifu.miyagi.jp
+semine.miyagi.jp
+shibata.miyagi.jp
+shichikashuku.miyagi.jp
+shikama.miyagi.jp
+shiogama.miyagi.jp
+shiroishi.miyagi.jp
+tagajo.miyagi.jp
+taiwa.miyagi.jp
+tome.miyagi.jp
+tomiya.miyagi.jp
+wakuya.miyagi.jp
+watari.miyagi.jp
+yamamoto.miyagi.jp
+zao.miyagi.jp
+aya.miyazaki.jp
+ebino.miyazaki.jp
+gokase.miyazaki.jp
+hyuga.miyazaki.jp
+kadogawa.miyazaki.jp
+kawaminami.miyazaki.jp
+kijo.miyazaki.jp
+kitagawa.miyazaki.jp
+kitakata.miyazaki.jp
+kitaura.miyazaki.jp
+kobayashi.miyazaki.jp
+kunitomi.miyazaki.jp
+kushima.miyazaki.jp
+mimata.miyazaki.jp
+miyakonojo.miyazaki.jp
+miyazaki.miyazaki.jp
+morotsuka.miyazaki.jp
+nichinan.miyazaki.jp
+nishimera.miyazaki.jp
+nobeoka.miyazaki.jp
+saito.miyazaki.jp
+shiiba.miyazaki.jp
+shintomi.miyazaki.jp
+takaharu.miyazaki.jp
+takanabe.miyazaki.jp
+takazaki.miyazaki.jp
+tsuno.miyazaki.jp
+achi.nagano.jp
+agematsu.nagano.jp
+anan.nagano.jp
+aoki.nagano.jp
+asahi.nagano.jp
+azumino.nagano.jp
+chikuhoku.nagano.jp
+chikuma.nagano.jp
+chino.nagano.jp
+fujimi.nagano.jp
+hakuba.nagano.jp
+hara.nagano.jp
+hiraya.nagano.jp
+iida.nagano.jp
+iijima.nagano.jp
+iiyama.nagano.jp
+iizuna.nagano.jp
+ikeda.nagano.jp
+ikusaka.nagano.jp
+ina.nagano.jp
+karuizawa.nagano.jp
+kawakami.nagano.jp
+kiso.nagano.jp
+kisofukushima.nagano.jp
+kitaaiki.nagano.jp
+komagane.nagano.jp
+komoro.nagano.jp
+matsukawa.nagano.jp
+matsumoto.nagano.jp
+miasa.nagano.jp
+minamiaiki.nagano.jp
+minamimaki.nagano.jp
+minamiminowa.nagano.jp
+minowa.nagano.jp
+miyada.nagano.jp
+miyota.nagano.jp
+mochizuki.nagano.jp
+nagano.nagano.jp
+nagawa.nagano.jp
+nagiso.nagano.jp
+nakagawa.nagano.jp
+nakano.nagano.jp
+nozawaonsen.nagano.jp
+obuse.nagano.jp
+ogawa.nagano.jp
+okaya.nagano.jp
+omachi.nagano.jp
+omi.nagano.jp
+ookuwa.nagano.jp
+ooshika.nagano.jp
+otaki.nagano.jp
+otari.nagano.jp
+sakae.nagano.jp
+sakaki.nagano.jp
+saku.nagano.jp
+sakuho.nagano.jp
+shimosuwa.nagano.jp
+shinanomachi.nagano.jp
+shiojiri.nagano.jp
+suwa.nagano.jp
+suzaka.nagano.jp
+takagi.nagano.jp
+takamori.nagano.jp
+takayama.nagano.jp
+tateshina.nagano.jp
+tatsuno.nagano.jp
+togakushi.nagano.jp
+togura.nagano.jp
+tomi.nagano.jp
+ueda.nagano.jp
+wada.nagano.jp
+yamagata.nagano.jp
+yamanouchi.nagano.jp
+yasaka.nagano.jp
+yasuoka.nagano.jp
+chijiwa.nagasaki.jp
+futsu.nagasaki.jp
+goto.nagasaki.jp
+hasami.nagasaki.jp
+hirado.nagasaki.jp
+iki.nagasaki.jp
+isahaya.nagasaki.jp
+kawatana.nagasaki.jp
+kuchinotsu.nagasaki.jp
+matsuura.nagasaki.jp
+nagasaki.nagasaki.jp
+obama.nagasaki.jp
+omura.nagasaki.jp
+oseto.nagasaki.jp
+saikai.nagasaki.jp
+sasebo.nagasaki.jp
+seihi.nagasaki.jp
+shimabara.nagasaki.jp
+shinkamigoto.nagasaki.jp
+togitsu.nagasaki.jp
+tsushima.nagasaki.jp
+unzen.nagasaki.jp
+ando.nara.jp
+gose.nara.jp
+heguri.nara.jp
+higashiyoshino.nara.jp
+ikaruga.nara.jp
+ikoma.nara.jp
+kamikitayama.nara.jp
+kanmaki.nara.jp
+kashiba.nara.jp
+kashihara.nara.jp
+katsuragi.nara.jp
+kawai.nara.jp
+kawakami.nara.jp
+kawanishi.nara.jp
+koryo.nara.jp
+kurotaki.nara.jp
+mitsue.nara.jp
+miyake.nara.jp
+nara.nara.jp
+nosegawa.nara.jp
+oji.nara.jp
+ouda.nara.jp
+oyodo.nara.jp
+sakurai.nara.jp
+sango.nara.jp
+shimoichi.nara.jp
+shimokitayama.nara.jp
+shinjo.nara.jp
+soni.nara.jp
+takatori.nara.jp
+tawaramoto.nara.jp
+tenkawa.nara.jp
+tenri.nara.jp
+uda.nara.jp
+yamatokoriyama.nara.jp
+yamatotakada.nara.jp
+yamazoe.nara.jp
+yoshino.nara.jp
+aga.niigata.jp
+agano.niigata.jp
+gosen.niigata.jp
+itoigawa.niigata.jp
+izumozaki.niigata.jp
+joetsu.niigata.jp
+kamo.niigata.jp
+kariwa.niigata.jp
+kashiwazaki.niigata.jp
+minamiuonuma.niigata.jp
+mitsuke.niigata.jp
+muika.niigata.jp
+murakami.niigata.jp
+myoko.niigata.jp
+nagaoka.niigata.jp
+niigata.niigata.jp
+ojiya.niigata.jp
+omi.niigata.jp
+sado.niigata.jp
+sanjo.niigata.jp
+seiro.niigata.jp
+seirou.niigata.jp
+sekikawa.niigata.jp
+shibata.niigata.jp
+tagami.niigata.jp
+tainai.niigata.jp
+tochio.niigata.jp
+tokamachi.niigata.jp
+tsubame.niigata.jp
+tsunan.niigata.jp
+uonuma.niigata.jp
+yahiko.niigata.jp
+yoita.niigata.jp
+yuzawa.niigata.jp
+beppu.oita.jp
+bungoono.oita.jp
+bungotakada.oita.jp
+hasama.oita.jp
+hiji.oita.jp
+himeshima.oita.jp
+hita.oita.jp
+kamitsue.oita.jp
+kokonoe.oita.jp
+kuju.oita.jp
+kunisaki.oita.jp
+kusu.oita.jp
+oita.oita.jp
+saiki.oita.jp
+taketa.oita.jp
+tsukumi.oita.jp
+usa.oita.jp
+usuki.oita.jp
+yufu.oita.jp
+akaiwa.okayama.jp
+asakuchi.okayama.jp
+bizen.okayama.jp
+hayashima.okayama.jp
+ibara.okayama.jp
+kagamino.okayama.jp
+kasaoka.okayama.jp
+kibichuo.okayama.jp
+kumenan.okayama.jp
+kurashiki.okayama.jp
+maniwa.okayama.jp
+misaki.okayama.jp
+nagi.okayama.jp
+niimi.okayama.jp
+nishiawakura.okayama.jp
+okayama.okayama.jp
+satosho.okayama.jp
+setouchi.okayama.jp
+shinjo.okayama.jp
+shoo.okayama.jp
+soja.okayama.jp
+takahashi.okayama.jp
+tamano.okayama.jp
+tsuyama.okayama.jp
+wake.okayama.jp
+yakage.okayama.jp
+aguni.okinawa.jp
+ginowan.okinawa.jp
+ginoza.okinawa.jp
+gushikami.okinawa.jp
+haebaru.okinawa.jp
+higashi.okinawa.jp
+hirara.okinawa.jp
+iheya.okinawa.jp
+ishigaki.okinawa.jp
+ishikawa.okinawa.jp
+itoman.okinawa.jp
+izena.okinawa.jp
+kadena.okinawa.jp
+kin.okinawa.jp
+kitadaito.okinawa.jp
+kitanakagusuku.okinawa.jp
+kumejima.okinawa.jp
+kunigami.okinawa.jp
+minamidaito.okinawa.jp
+motobu.okinawa.jp
+nago.okinawa.jp
+naha.okinawa.jp
+nakagusuku.okinawa.jp
+nakijin.okinawa.jp
+nanjo.okinawa.jp
+nishihara.okinawa.jp
+ogimi.okinawa.jp
+okinawa.okinawa.jp
+onna.okinawa.jp
+shimoji.okinawa.jp
+taketomi.okinawa.jp
+tarama.okinawa.jp
+tokashiki.okinawa.jp
+tomigusuku.okinawa.jp
+tonaki.okinawa.jp
+urasoe.okinawa.jp
+uruma.okinawa.jp
+yaese.okinawa.jp
+yomitan.okinawa.jp
+yonabaru.okinawa.jp
+yonaguni.okinawa.jp
+zamami.okinawa.jp
+abeno.osaka.jp
+chihayaakasaka.osaka.jp
+chuo.osaka.jp
+daito.osaka.jp
+fujiidera.osaka.jp
+habikino.osaka.jp
+hannan.osaka.jp
+higashiosaka.osaka.jp
+higashisumiyoshi.osaka.jp
+higashiyodogawa.osaka.jp
+hirakata.osaka.jp
+ibaraki.osaka.jp
+ikeda.osaka.jp
+izumi.osaka.jp
+izumiotsu.osaka.jp
+izumisano.osaka.jp
+kadoma.osaka.jp
+kaizuka.osaka.jp
+kanan.osaka.jp
+kashiwara.osaka.jp
+katano.osaka.jp
+kawachinagano.osaka.jp
+kishiwada.osaka.jp
+kita.osaka.jp
+kumatori.osaka.jp
+matsubara.osaka.jp
+minato.osaka.jp
+minoh.osaka.jp
+misaki.osaka.jp
+moriguchi.osaka.jp
+neyagawa.osaka.jp
+nishi.osaka.jp
+nose.osaka.jp
+osakasayama.osaka.jp
+sakai.osaka.jp
+sayama.osaka.jp
+sennan.osaka.jp
+settsu.osaka.jp
+shijonawate.osaka.jp
+shimamoto.osaka.jp
+suita.osaka.jp
+tadaoka.osaka.jp
+taishi.osaka.jp
+tajiri.osaka.jp
+takaishi.osaka.jp
+takatsuki.osaka.jp
+tondabayashi.osaka.jp
+toyonaka.osaka.jp
+toyono.osaka.jp
+yao.osaka.jp
+ariake.saga.jp
+arita.saga.jp
+fukudomi.saga.jp
+genkai.saga.jp
+hamatama.saga.jp
+hizen.saga.jp
+imari.saga.jp
+kamimine.saga.jp
+kanzaki.saga.jp
+karatsu.saga.jp
+kashima.saga.jp
+kitagata.saga.jp
+kitahata.saga.jp
+kiyama.saga.jp
+kouhoku.saga.jp
+kyuragi.saga.jp
+nishiarita.saga.jp
+ogi.saga.jp
+omachi.saga.jp
+ouchi.saga.jp
+saga.saga.jp
+shiroishi.saga.jp
+taku.saga.jp
+tara.saga.jp
+tosu.saga.jp
+yoshinogari.saga.jp
+arakawa.saitama.jp
+asaka.saitama.jp
+chichibu.saitama.jp
+fujimi.saitama.jp
+fujimino.saitama.jp
+fukaya.saitama.jp
+hanno.saitama.jp
+hanyu.saitama.jp
+hasuda.saitama.jp
+hatogaya.saitama.jp
+hatoyama.saitama.jp
+hidaka.saitama.jp
+higashichichibu.saitama.jp
+higashimatsuyama.saitama.jp
+honjo.saitama.jp
+ina.saitama.jp
+iruma.saitama.jp
+iwatsuki.saitama.jp
+kamiizumi.saitama.jp
+kamikawa.saitama.jp
+kamisato.saitama.jp
+kasukabe.saitama.jp
+kawagoe.saitama.jp
+kawaguchi.saitama.jp
+kawajima.saitama.jp
+kazo.saitama.jp
+kitamoto.saitama.jp
+koshigaya.saitama.jp
+kounosu.saitama.jp
+kuki.saitama.jp
+kumagaya.saitama.jp
+matsubushi.saitama.jp
+minano.saitama.jp
+misato.saitama.jp
+miyashiro.saitama.jp
+miyoshi.saitama.jp
+moroyama.saitama.jp
+nagatoro.saitama.jp
+namegawa.saitama.jp
+niiza.saitama.jp
+ogano.saitama.jp
+ogawa.saitama.jp
+ogose.saitama.jp
+okegawa.saitama.jp
+omiya.saitama.jp
+otaki.saitama.jp
+ranzan.saitama.jp
+ryokami.saitama.jp
+saitama.saitama.jp
+sakado.saitama.jp
+satte.saitama.jp
+sayama.saitama.jp
+shiki.saitama.jp
+shiraoka.saitama.jp
+soka.saitama.jp
+sugito.saitama.jp
+toda.saitama.jp
+tokigawa.saitama.jp
+tokorozawa.saitama.jp
+tsurugashima.saitama.jp
+urawa.saitama.jp
+warabi.saitama.jp
+yashio.saitama.jp
+yokoze.saitama.jp
+yono.saitama.jp
+yorii.saitama.jp
+yoshida.saitama.jp
+yoshikawa.saitama.jp
+yoshimi.saitama.jp
+aisho.shiga.jp
+gamo.shiga.jp
+higashiomi.shiga.jp
+hikone.shiga.jp
+koka.shiga.jp
+konan.shiga.jp
+kosei.shiga.jp
+koto.shiga.jp
+kusatsu.shiga.jp
+maibara.shiga.jp
+moriyama.shiga.jp
+nagahama.shiga.jp
+nishiazai.shiga.jp
+notogawa.shiga.jp
+omihachiman.shiga.jp
+otsu.shiga.jp
+ritto.shiga.jp
+ryuoh.shiga.jp
+takashima.shiga.jp
+takatsuki.shiga.jp
+torahime.shiga.jp
+toyosato.shiga.jp
+yasu.shiga.jp
+akagi.shimane.jp
+ama.shimane.jp
+gotsu.shimane.jp
+hamada.shimane.jp
+higashiizumo.shimane.jp
+hikawa.shimane.jp
+hikimi.shimane.jp
+izumo.shimane.jp
+kakinoki.shimane.jp
+masuda.shimane.jp
+matsue.shimane.jp
+misato.shimane.jp
+nishinoshima.shimane.jp
+ohda.shimane.jp
+okinoshima.shimane.jp
+okuizumo.shimane.jp
+shimane.shimane.jp
+tamayu.shimane.jp
+tsuwano.shimane.jp
+unnan.shimane.jp
+yakumo.shimane.jp
+yasugi.shimane.jp
+yatsuka.shimane.jp
+arai.shizuoka.jp
+atami.shizuoka.jp
+fuji.shizuoka.jp
+fujieda.shizuoka.jp
+fujikawa.shizuoka.jp
+fujinomiya.shizuoka.jp
+fukuroi.shizuoka.jp
+gotemba.shizuoka.jp
+haibara.shizuoka.jp
+hamamatsu.shizuoka.jp
+higashiizu.shizuoka.jp
+ito.shizuoka.jp
+iwata.shizuoka.jp
+izu.shizuoka.jp
+izunokuni.shizuoka.jp
+kakegawa.shizuoka.jp
+kannami.shizuoka.jp
+kawanehon.shizuoka.jp
+kawazu.shizuoka.jp
+kikugawa.shizuoka.jp
+kosai.shizuoka.jp
+makinohara.shizuoka.jp
+matsuzaki.shizuoka.jp
+minamiizu.shizuoka.jp
+mishima.shizuoka.jp
+morimachi.shizuoka.jp
+nishiizu.shizuoka.jp
+numazu.shizuoka.jp
+omaezaki.shizuoka.jp
+shimada.shizuoka.jp
+shimizu.shizuoka.jp
+shimoda.shizuoka.jp
+shizuoka.shizuoka.jp
+susono.shizuoka.jp
+yaizu.shizuoka.jp
+yoshida.shizuoka.jp
+ashikaga.tochigi.jp
+bato.tochigi.jp
+haga.tochigi.jp
+ichikai.tochigi.jp
+iwafune.tochigi.jp
+kaminokawa.tochigi.jp
+kanuma.tochigi.jp
+karasuyama.tochigi.jp
+kuroiso.tochigi.jp
+mashiko.tochigi.jp
+mibu.tochigi.jp
+moka.tochigi.jp
+motegi.tochigi.jp
+nasu.tochigi.jp
+nasushiobara.tochigi.jp
+nikko.tochigi.jp
+nishikata.tochigi.jp
+nogi.tochigi.jp
+ohira.tochigi.jp
+ohtawara.tochigi.jp
+oyama.tochigi.jp
+sakura.tochigi.jp
+sano.tochigi.jp
+shimotsuke.tochigi.jp
+shioya.tochigi.jp
+takanezawa.tochigi.jp
+tochigi.tochigi.jp
+tsuga.tochigi.jp
+ujiie.tochigi.jp
+utsunomiya.tochigi.jp
+yaita.tochigi.jp
+aizumi.tokushima.jp
+anan.tokushima.jp
+ichiba.tokushima.jp
+itano.tokushima.jp
+kainan.tokushima.jp
+komatsushima.tokushima.jp
+matsushige.tokushima.jp
+mima.tokushima.jp
+minami.tokushima.jp
+miyoshi.tokushima.jp
+mugi.tokushima.jp
+nakagawa.tokushima.jp
+naruto.tokushima.jp
+sanagochi.tokushima.jp
+shishikui.tokushima.jp
+tokushima.tokushima.jp
+wajiki.tokushima.jp
+adachi.tokyo.jp
+akiruno.tokyo.jp
+akishima.tokyo.jp
+aogashima.tokyo.jp
+arakawa.tokyo.jp
+bunkyo.tokyo.jp
+chiyoda.tokyo.jp
+chofu.tokyo.jp
+chuo.tokyo.jp
+edogawa.tokyo.jp
+fuchu.tokyo.jp
+fussa.tokyo.jp
+hachijo.tokyo.jp
+hachioji.tokyo.jp
+hamura.tokyo.jp
+higashikurume.tokyo.jp
+higashimurayama.tokyo.jp
+higashiyamato.tokyo.jp
+hino.tokyo.jp
+hinode.tokyo.jp
+hinohara.tokyo.jp
+inagi.tokyo.jp
+itabashi.tokyo.jp
+katsushika.tokyo.jp
+kita.tokyo.jp
+kiyose.tokyo.jp
+kodaira.tokyo.jp
+koganei.tokyo.jp
+kokubunji.tokyo.jp
+komae.tokyo.jp
+koto.tokyo.jp
+kouzushima.tokyo.jp
+kunitachi.tokyo.jp
+machida.tokyo.jp
+meguro.tokyo.jp
+minato.tokyo.jp
+mitaka.tokyo.jp
+mizuho.tokyo.jp
+musashimurayama.tokyo.jp
+musashino.tokyo.jp
+nakano.tokyo.jp
+nerima.tokyo.jp
+ogasawara.tokyo.jp
+okutama.tokyo.jp
+ome.tokyo.jp
+oshima.tokyo.jp
+ota.tokyo.jp
+setagaya.tokyo.jp
+shibuya.tokyo.jp
+shinagawa.tokyo.jp
+shinjuku.tokyo.jp
+suginami.tokyo.jp
+sumida.tokyo.jp
+tachikawa.tokyo.jp
+taito.tokyo.jp
+tama.tokyo.jp
+toshima.tokyo.jp
+chizu.tottori.jp
+hino.tottori.jp
+kawahara.tottori.jp
+koge.tottori.jp
+kotoura.tottori.jp
+misasa.tottori.jp
+nanbu.tottori.jp
+nichinan.tottori.jp
+sakaiminato.tottori.jp
+tottori.tottori.jp
+wakasa.tottori.jp
+yazu.tottori.jp
+yonago.tottori.jp
+asahi.toyama.jp
+fuchu.toyama.jp
+fukumitsu.toyama.jp
+funahashi.toyama.jp
+himi.toyama.jp
+imizu.toyama.jp
+inami.toyama.jp
+johana.toyama.jp
+kamiichi.toyama.jp
+kurobe.toyama.jp
+nakaniikawa.toyama.jp
+namerikawa.toyama.jp
+nanto.toyama.jp
+nyuzen.toyama.jp
+oyabe.toyama.jp
+taira.toyama.jp
+takaoka.toyama.jp
+tateyama.toyama.jp
+toga.toyama.jp
+tonami.toyama.jp
+toyama.toyama.jp
+unazuki.toyama.jp
+uozu.toyama.jp
+yamada.toyama.jp
+arida.wakayama.jp
+aridagawa.wakayama.jp
+gobo.wakayama.jp
+hashimoto.wakayama.jp
+hidaka.wakayama.jp
+hirogawa.wakayama.jp
+inami.wakayama.jp
+iwade.wakayama.jp
+kainan.wakayama.jp
+kamitonda.wakayama.jp
+katsuragi.wakayama.jp
+kimino.wakayama.jp
+kinokawa.wakayama.jp
+kitayama.wakayama.jp
+koya.wakayama.jp
+koza.wakayama.jp
+kozagawa.wakayama.jp
+kudoyama.wakayama.jp
+kushimoto.wakayama.jp
+mihama.wakayama.jp
+misato.wakayama.jp
+nachikatsuura.wakayama.jp
+shingu.wakayama.jp
+shirahama.wakayama.jp
+taiji.wakayama.jp
+tanabe.wakayama.jp
+wakayama.wakayama.jp
+yuasa.wakayama.jp
+yura.wakayama.jp
+asahi.yamagata.jp
+funagata.yamagata.jp
+higashine.yamagata.jp
+iide.yamagata.jp
+kahoku.yamagata.jp
+kaminoyama.yamagata.jp
+kaneyama.yamagata.jp
+kawanishi.yamagata.jp
+mamurogawa.yamagata.jp
+mikawa.yamagata.jp
+murayama.yamagata.jp
+nagai.yamagata.jp
+nakayama.yamagata.jp
+nanyo.yamagata.jp
+nishikawa.yamagata.jp
+obanazawa.yamagata.jp
+oe.yamagata.jp
+oguni.yamagata.jp
+ohkura.yamagata.jp
+oishida.yamagata.jp
+sagae.yamagata.jp
+sakata.yamagata.jp
+sakegawa.yamagata.jp
+shinjo.yamagata.jp
+shirataka.yamagata.jp
+shonai.yamagata.jp
+takahata.yamagata.jp
+tendo.yamagata.jp
+tozawa.yamagata.jp
+tsuruoka.yamagata.jp
+yamagata.yamagata.jp
+yamanobe.yamagata.jp
+yonezawa.yamagata.jp
+yuza.yamagata.jp
+abu.yamaguchi.jp
+hagi.yamaguchi.jp
+hikari.yamaguchi.jp
+hofu.yamaguchi.jp
+iwakuni.yamaguchi.jp
+kudamatsu.yamaguchi.jp
+mitou.yamaguchi.jp
+nagato.yamaguchi.jp
+oshima.yamaguchi.jp
+shimonoseki.yamaguchi.jp
+shunan.yamaguchi.jp
+tabuse.yamaguchi.jp
+tokuyama.yamaguchi.jp
+toyota.yamaguchi.jp
+ube.yamaguchi.jp
+yuu.yamaguchi.jp
+chuo.yamanashi.jp
+doshi.yamanashi.jp
+fuefuki.yamanashi.jp
+fujikawa.yamanashi.jp
+fujikawaguchiko.yamanashi.jp
+fujiyoshida.yamanashi.jp
+hayakawa.yamanashi.jp
+hokuto.yamanashi.jp
+ichikawamisato.yamanashi.jp
+kai.yamanashi.jp
+kofu.yamanashi.jp
+koshu.yamanashi.jp
+kosuge.yamanashi.jp
+minami-alps.yamanashi.jp
+minobu.yamanashi.jp
+nakamichi.yamanashi.jp
+nanbu.yamanashi.jp
+narusawa.yamanashi.jp
+nirasaki.yamanashi.jp
+nishikatsura.yamanashi.jp
+oshino.yamanashi.jp
+otsuki.yamanashi.jp
+showa.yamanashi.jp
+tabayama.yamanashi.jp
+tsuru.yamanashi.jp
+uenohara.yamanashi.jp
+yamanakako.yamanashi.jp
+yamanashi.yamanashi.jp
+
+// ke : http://www.kenic.or.ke/index.php?option=com_content&task=view&id=117&Itemid=145
+*.ke
+
+// kg : http://www.domain.kg/dmn_n.html
+kg
+org.kg
+net.kg
+com.kg
+edu.kg
+gov.kg
+mil.kg
+
+// kh : http://www.mptc.gov.kh/dns_registration.htm
+*.kh
+
+// ki : http://www.ki/dns/index.html
+ki
+edu.ki
+biz.ki
+net.ki
+org.ki
+gov.ki
+info.ki
+com.ki
+
+// km : http://en.wikipedia.org/wiki/.km
+// http://www.domaine.km/documents/charte.doc
+km
+org.km
+nom.km
+gov.km
+prd.km
+tm.km
+edu.km
+mil.km
+ass.km
+com.km
+// These are only mentioned as proposed suggestions at domaine.km, but
+// http://en.wikipedia.org/wiki/.km says they're available for registration:
+coop.km
+asso.km
+presse.km
+medecin.km
+notaires.km
+pharmaciens.km
+veterinaire.km
+gouv.km
+
+// kn : http://en.wikipedia.org/wiki/.kn
+// http://www.dot.kn/domainRules.html
+kn
+net.kn
+org.kn
+edu.kn
+gov.kn
+
+// kp : http://www.kcce.kp/en_index.php
+kp
+com.kp
+edu.kp
+gov.kp
+org.kp
+rep.kp
+tra.kp
+
+// kr : http://en.wikipedia.org/wiki/.kr
+// see also: http://domain.nida.or.kr/eng/registration.jsp
+kr
+ac.kr
+co.kr
+es.kr
+go.kr
+hs.kr
+kg.kr
+mil.kr
+ms.kr
+ne.kr
+or.kr
+pe.kr
+re.kr
+sc.kr
+// kr geographical names
+busan.kr
+chungbuk.kr
+chungnam.kr
+daegu.kr
+daejeon.kr
+gangwon.kr
+gwangju.kr
+gyeongbuk.kr
+gyeonggi.kr
+gyeongnam.kr
+incheon.kr
+jeju.kr
+jeonbuk.kr
+jeonnam.kr
+seoul.kr
+ulsan.kr
+
+// kw : http://en.wikipedia.org/wiki/.kw
+*.kw
+
+// ky : http://www.icta.ky/da_ky_reg_dom.php
+// Confirmed by registry <kysupport@perimeterusa.com> 2008-06-17
+ky
+edu.ky
+gov.ky
+com.ky
+org.ky
+net.ky
+
+// kz : http://en.wikipedia.org/wiki/.kz
+// see also: http://www.nic.kz/rules/index.jsp
+kz
+org.kz
+edu.kz
+net.kz
+gov.kz
+mil.kz
+com.kz
+
+// la : http://en.wikipedia.org/wiki/.la
+// Submitted by registry <gavin.brown@nic.la> 2008-06-10
+la
+int.la
+net.la
+info.la
+edu.la
+gov.la
+per.la
+com.la
+org.la
+
+// lb : http://en.wikipedia.org/wiki/.lb
+// Submitted by registry <randy@psg.com> 2008-06-17
+lb
+com.lb
+edu.lb
+gov.lb
+net.lb
+org.lb
+
+// lc : http://en.wikipedia.org/wiki/.lc
+// see also: http://www.nic.lc/rules.htm
+lc
+com.lc
+net.lc
+co.lc
+org.lc
+edu.lc
+gov.lc
+
+// li : http://en.wikipedia.org/wiki/.li
+li
+
+// lk : http://www.nic.lk/seclevpr.html
+lk
+gov.lk
+sch.lk
+net.lk
+int.lk
+com.lk
+org.lk
+edu.lk
+ngo.lk
+soc.lk
+web.lk
+ltd.lk
+assn.lk
+grp.lk
+hotel.lk
+ac.lk
+
+// lr : http://psg.com/dns/lr/lr.txt
+// Submitted by registry <randy@psg.com> 2008-06-17
+lr
+com.lr
+edu.lr
+gov.lr
+org.lr
+net.lr
+
+// ls : http://en.wikipedia.org/wiki/.ls
+ls
+co.ls
+org.ls
+
+// lt : http://en.wikipedia.org/wiki/.lt
+lt
+// gov.lt : http://www.gov.lt/index_en.php
+gov.lt
+
+// lu : http://www.dns.lu/en/
+lu
+
+// lv : http://www.nic.lv/DNS/En/generic.php
+lv
+com.lv
+edu.lv
+gov.lv
+org.lv
+mil.lv
+id.lv
+net.lv
+asn.lv
+conf.lv
+
+// ly : http://www.nic.ly/regulations.php
+ly
+com.ly
+net.ly
+gov.ly
+plc.ly
+edu.ly
+sch.ly
+med.ly
+org.ly
+id.ly
+
+// ma : http://en.wikipedia.org/wiki/.ma
+// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf
+ma
+co.ma
+net.ma
+gov.ma
+org.ma
+ac.ma
+press.ma
+
+// mc : http://www.nic.mc/
+mc
+tm.mc
+asso.mc
+
+// md : http://en.wikipedia.org/wiki/.md
+md
+
+// me : http://en.wikipedia.org/wiki/.me
+me
+co.me
+net.me
+org.me
+edu.me
+ac.me
+gov.me
+its.me
+priv.me
+
+// mg : http://nic.mg/nicmg/?page_id=39
+mg
+org.mg
+nom.mg
+gov.mg
+prd.mg
+tm.mg
+edu.mg
+mil.mg
+com.mg
+co.mg
+
+// mh : http://en.wikipedia.org/wiki/.mh
+mh
+
+// mil : http://en.wikipedia.org/wiki/.mil
+mil
+
+// mk : http://en.wikipedia.org/wiki/.mk
+// see also: http://dns.marnet.net.mk/postapka.php
+mk
+com.mk
+org.mk
+net.mk
+edu.mk
+gov.mk
+inf.mk
+name.mk
+
+// ml : http://www.gobin.info/domainname/ml-template.doc
+// see also: http://en.wikipedia.org/wiki/.ml
+ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
+
+// mm : http://en.wikipedia.org/wiki/.mm
+*.mm
+
+// mn : http://en.wikipedia.org/wiki/.mn
+mn
+gov.mn
+edu.mn
+org.mn
+
+// mo : http://www.monic.net.mo/
+mo
+com.mo
+net.mo
+org.mo
+edu.mo
+gov.mo
+
+// mobi : http://en.wikipedia.org/wiki/.mobi
+mobi
+
+// mp : http://www.dot.mp/
+// Confirmed by registry <dcamacho@saipan.com> 2008-06-17
+mp
+
+// mq : http://en.wikipedia.org/wiki/.mq
+mq
+
+// mr : http://en.wikipedia.org/wiki/.mr
+mr
+gov.mr
+
+// ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf
+ms
+com.ms
+edu.ms
+gov.ms
+net.ms
+org.ms
+
+// mt : https://www.nic.org.mt/go/policy
+// Submitted by registry <help@nic.org.mt> 2013-11-19
+mt
+com.mt
+edu.mt
+net.mt
+org.mt
+
+// mu : http://en.wikipedia.org/wiki/.mu
+mu
+com.mu
+net.mu
+org.mu
+gov.mu
+ac.mu
+co.mu
+or.mu
+
+// museum : http://about.museum/naming/
+// http://index.museum/
+museum
+academy.museum
+agriculture.museum
+air.museum
+airguard.museum
+alabama.museum
+alaska.museum
+amber.museum
+ambulance.museum
+american.museum
+americana.museum
+americanantiques.museum
+americanart.museum
+amsterdam.museum
+and.museum
+annefrank.museum
+anthro.museum
+anthropology.museum
+antiques.museum
+aquarium.museum
+arboretum.museum
+archaeological.museum
+archaeology.museum
+architecture.museum
+art.museum
+artanddesign.museum
+artcenter.museum
+artdeco.museum
+arteducation.museum
+artgallery.museum
+arts.museum
+artsandcrafts.museum
+asmatart.museum
+assassination.museum
+assisi.museum
+association.museum
+astronomy.museum
+atlanta.museum
+austin.museum
+australia.museum
+automotive.museum
+aviation.museum
+axis.museum
+badajoz.museum
+baghdad.museum
+bahn.museum
+bale.museum
+baltimore.museum
+barcelona.museum
+baseball.museum
+basel.museum
+baths.museum
+bauern.museum
+beauxarts.museum
+beeldengeluid.museum
+bellevue.museum
+bergbau.museum
+berkeley.museum
+berlin.museum
+bern.museum
+bible.museum
+bilbao.museum
+bill.museum
+birdart.museum
+birthplace.museum
+bonn.museum
+boston.museum
+botanical.museum
+botanicalgarden.museum
+botanicgarden.museum
+botany.museum
+brandywinevalley.museum
+brasil.museum
+bristol.museum
+british.museum
+britishcolumbia.museum
+broadcast.museum
+brunel.museum
+brussel.museum
+brussels.museum
+bruxelles.museum
+building.museum
+burghof.museum
+bus.museum
+bushey.museum
+cadaques.museum
+california.museum
+cambridge.museum
+can.museum
+canada.museum
+capebreton.museum
+carrier.museum
+cartoonart.museum
+casadelamoneda.museum
+castle.museum
+castres.museum
+celtic.museum
+center.museum
+chattanooga.museum
+cheltenham.museum
+chesapeakebay.museum
+chicago.museum
+children.museum
+childrens.museum
+childrensgarden.museum
+chiropractic.museum
+chocolate.museum
+christiansburg.museum
+cincinnati.museum
+cinema.museum
+circus.museum
+civilisation.museum
+civilization.museum
+civilwar.museum
+clinton.museum
+clock.museum
+coal.museum
+coastaldefence.museum
+cody.museum
+coldwar.museum
+collection.museum
+colonialwilliamsburg.museum
+coloradoplateau.museum
+columbia.museum
+columbus.museum
+communication.museum
+communications.museum
+community.museum
+computer.museum
+computerhistory.museum
+comunicações.museum
+contemporary.museum
+contemporaryart.museum
+convent.museum
+copenhagen.museum
+corporation.museum
+correios-e-telecomunicações.museum
+corvette.museum
+costume.museum
+countryestate.museum
+county.museum
+crafts.museum
+cranbrook.museum
+creation.museum
+cultural.museum
+culturalcenter.museum
+culture.museum
+cyber.museum
+cymru.museum
+dali.museum
+dallas.museum
+database.museum
+ddr.museum
+decorativearts.museum
+delaware.museum
+delmenhorst.museum
+denmark.museum
+depot.museum
+design.museum
+detroit.museum
+dinosaur.museum
+discovery.museum
+dolls.museum
+donostia.museum
+durham.museum
+eastafrica.museum
+eastcoast.museum
+education.museum
+educational.museum
+egyptian.museum
+eisenbahn.museum
+elburg.museum
+elvendrell.museum
+embroidery.museum
+encyclopedic.museum
+england.museum
+entomology.museum
+environment.museum
+environmentalconservation.museum
+epilepsy.museum
+essex.museum
+estate.museum
+ethnology.museum
+exeter.museum
+exhibition.museum
+family.museum
+farm.museum
+farmequipment.museum
+farmers.museum
+farmstead.museum
+field.museum
+figueres.museum
+filatelia.museum
+film.museum
+fineart.museum
+finearts.museum
+finland.museum
+flanders.museum
+florida.museum
+force.museum
+fortmissoula.museum
+fortworth.museum
+foundation.museum
+francaise.museum
+frankfurt.museum
+franziskaner.museum
+freemasonry.museum
+freiburg.museum
+fribourg.museum
+frog.museum
+fundacio.museum
+furniture.museum
+gallery.museum
+garden.museum
+gateway.museum
+geelvinck.museum
+gemological.museum
+geology.museum
+georgia.museum
+giessen.museum
+glas.museum
+glass.museum
+gorge.museum
+grandrapids.museum
+graz.museum
+guernsey.museum
+halloffame.museum
+hamburg.museum
+handson.museum
+harvestcelebration.museum
+hawaii.museum
+health.museum
+heimatunduhren.museum
+hellas.museum
+helsinki.museum
+hembygdsforbund.museum
+heritage.museum
+histoire.museum
+historical.museum
+historicalsociety.museum
+historichouses.museum
+historisch.museum
+historisches.museum
+history.museum
+historyofscience.museum
+horology.museum
+house.museum
+humanities.museum
+illustration.museum
+imageandsound.museum
+indian.museum
+indiana.museum
+indianapolis.museum
+indianmarket.museum
+intelligence.museum
+interactive.museum
+iraq.museum
+iron.museum
+isleofman.museum
+jamison.museum
+jefferson.museum
+jerusalem.museum
+jewelry.museum
+jewish.museum
+jewishart.museum
+jfk.museum
+journalism.museum
+judaica.museum
+judygarland.museum
+juedisches.museum
+juif.museum
+karate.museum
+karikatur.museum
+kids.museum
+koebenhavn.museum
+koeln.museum
+kunst.museum
+kunstsammlung.museum
+kunstunddesign.museum
+labor.museum
+labour.museum
+lajolla.museum
+lancashire.museum
+landes.museum
+lans.museum
+läns.museum
+larsson.museum
+lewismiller.museum
+lincoln.museum
+linz.museum
+living.museum
+livinghistory.museum
+localhistory.museum
+london.museum
+losangeles.museum
+louvre.museum
+loyalist.museum
+lucerne.museum
+luxembourg.museum
+luzern.museum
+mad.museum
+madrid.museum
+mallorca.museum
+manchester.museum
+mansion.museum
+mansions.museum
+manx.museum
+marburg.museum
+maritime.museum
+maritimo.museum
+maryland.museum
+marylhurst.museum
+media.museum
+medical.museum
+medizinhistorisches.museum
+meeres.museum
+memorial.museum
+mesaverde.museum
+michigan.museum
+midatlantic.museum
+military.museum
+mill.museum
+miners.museum
+mining.museum
+minnesota.museum
+missile.museum
+missoula.museum
+modern.museum
+moma.museum
+money.museum
+monmouth.museum
+monticello.museum
+montreal.museum
+moscow.museum
+motorcycle.museum
+muenchen.museum
+muenster.museum
+mulhouse.museum
+muncie.museum
+museet.museum
+museumcenter.museum
+museumvereniging.museum
+music.museum
+national.museum
+nationalfirearms.museum
+nationalheritage.museum
+nativeamerican.museum
+naturalhistory.museum
+naturalhistorymuseum.museum
+naturalsciences.museum
+nature.museum
+naturhistorisches.museum
+natuurwetenschappen.museum
+naumburg.museum
+naval.museum
+nebraska.museum
+neues.museum
+newhampshire.museum
+newjersey.museum
+newmexico.museum
+newport.museum
+newspaper.museum
+newyork.museum
+niepce.museum
+norfolk.museum
+north.museum
+nrw.museum
+nuernberg.museum
+nuremberg.museum
+nyc.museum
+nyny.museum
+oceanographic.museum
+oceanographique.museum
+omaha.museum
+online.museum
+ontario.museum
+openair.museum
+oregon.museum
+oregontrail.museum
+otago.museum
+oxford.museum
+pacific.museum
+paderborn.museum
+palace.museum
+paleo.museum
+palmsprings.museum
+panama.museum
+paris.museum
+pasadena.museum
+pharmacy.museum
+philadelphia.museum
+philadelphiaarea.museum
+philately.museum
+phoenix.museum
+photography.museum
+pilots.museum
+pittsburgh.museum
+planetarium.museum
+plantation.museum
+plants.museum
+plaza.museum
+portal.museum
+portland.museum
+portlligat.museum
+posts-and-telecommunications.museum
+preservation.museum
+presidio.museum
+press.museum
+project.museum
+public.museum
+pubol.museum
+quebec.museum
+railroad.museum
+railway.museum
+research.museum
+resistance.museum
+riodejaneiro.museum
+rochester.museum
+rockart.museum
+roma.museum
+russia.museum
+saintlouis.museum
+salem.museum
+salvadordali.museum
+salzburg.museum
+sandiego.museum
+sanfrancisco.museum
+santabarbara.museum
+santacruz.museum
+santafe.museum
+saskatchewan.museum
+satx.museum
+savannahga.museum
+schlesisches.museum
+schoenbrunn.museum
+schokoladen.museum
+school.museum
+schweiz.museum
+science.museum
+scienceandhistory.museum
+scienceandindustry.museum
+sciencecenter.museum
+sciencecenters.museum
+science-fiction.museum
+sciencehistory.museum
+sciences.museum
+sciencesnaturelles.museum
+scotland.museum
+seaport.museum
+settlement.museum
+settlers.museum
+shell.museum
+sherbrooke.museum
+sibenik.museum
+silk.museum
+ski.museum
+skole.museum
+society.museum
+sologne.museum
+soundandvision.museum
+southcarolina.museum
+southwest.museum
+space.museum
+spy.museum
+square.museum
+stadt.museum
+stalbans.museum
+starnberg.museum
+state.museum
+stateofdelaware.museum
+station.museum
+steam.museum
+steiermark.museum
+stjohn.museum
+stockholm.museum
+stpetersburg.museum
+stuttgart.museum
+suisse.museum
+surgeonshall.museum
+surrey.museum
+svizzera.museum
+sweden.museum
+sydney.museum
+tank.museum
+tcm.museum
+technology.museum
+telekommunikation.museum
+television.museum
+texas.museum
+textile.museum
+theater.museum
+time.museum
+timekeeping.museum
+topology.museum
+torino.museum
+touch.museum
+town.museum
+transport.museum
+tree.museum
+trolley.museum
+trust.museum
+trustee.museum
+uhren.museum
+ulm.museum
+undersea.museum
+university.museum
+usa.museum
+usantiques.museum
+usarts.museum
+uscountryestate.museum
+usculture.museum
+usdecorativearts.museum
+usgarden.museum
+ushistory.museum
+ushuaia.museum
+uslivinghistory.museum
+utah.museum
+uvic.museum
+valley.museum
+vantaa.museum
+versailles.museum
+viking.museum
+village.museum
+virginia.museum
+virtual.museum
+virtuel.museum
+vlaanderen.museum
+volkenkunde.museum
+wales.museum
+wallonie.museum
+war.museum
+washingtondc.museum
+watchandclock.museum
+watch-and-clock.museum
+western.museum
+westfalen.museum
+whaling.museum
+wildlife.museum
+williamsburg.museum
+windmill.museum
+workshop.museum
+york.museum
+yorkshire.museum
+yosemite.museum
+youth.museum
+zoological.museum
+zoology.museum
+ירושלים.museum
+иком.museum
+
+// mv : http://en.wikipedia.org/wiki/.mv
+// "mv" included because, contra Wikipedia, google.mv exists.
+mv
+aero.mv
+biz.mv
+com.mv
+coop.mv
+edu.mv
+gov.mv
+info.mv
+int.mv
+mil.mv
+museum.mv
+name.mv
+net.mv
+org.mv
+pro.mv
+
+// mw : http://www.registrar.mw/
+mw
+ac.mw
+biz.mw
+co.mw
+com.mw
+coop.mw
+edu.mw
+gov.mw
+int.mw
+museum.mw
+net.mw
+org.mw
+
+// mx : http://www.nic.mx/
+// Submitted by registry <farias@nic.mx> 2008-06-19
+mx
+com.mx
+org.mx
+gob.mx
+edu.mx
+net.mx
+
+// my : http://www.mynic.net.my/
+my
+com.my
+net.my
+org.my
+gov.my
+edu.my
+mil.my
+name.my
+
+// mz : http://www.gobin.info/domainname/mz-template.doc
+*.mz
+!teledata.mz
+
+// na : http://www.na-nic.com.na/
+// http://www.info.na/domain/
+na
+info.na
+pro.na
+name.na
+school.na
+or.na
+dr.na
+us.na
+mx.na
+ca.na
+in.na
+cc.na
+tv.na
+ws.na
+mobi.na
+co.na
+com.na
+org.na
+
+// name : has 2nd-level tlds, but there's no list of them
+name
+
+// nc : http://www.cctld.nc/
+nc
+asso.nc
+
+// ne : http://en.wikipedia.org/wiki/.ne
+ne
+
+// net : http://en.wikipedia.org/wiki/.net
+net
+
+// nf : http://en.wikipedia.org/wiki/.nf
+nf
+com.nf
+net.nf
+per.nf
+rec.nf
+web.nf
+arts.nf
+firm.nf
+info.nf
+other.nf
+store.nf
+
+// ng : http://psg.com/dns/ng/
+ng
+com.ng
+edu.ng
+name.ng
+net.ng
+org.ng
+sch.ng
+gov.ng
+mil.ng
+mobi.ng
+
+// ni : http://www.nic.ni/
+com.ni
+gob.ni
+edu.ni
+org.ni
+nom.ni
+net.ni
+mil.ni
+co.ni
+biz.ni
+web.ni
+int.ni
+ac.ni
+in.ni
+info.ni
+
+// nl : http://en.wikipedia.org/wiki/.nl
+//      https://www.sidn.nl/
+//      ccTLD for the Netherlands
+nl
+
+// BV.nl will be a registry for dutch BV's (besloten vennootschap)
+bv.nl
+
+// no : http://www.norid.no/regelverk/index.en.html
+// The Norwegian registry has declined to notify us of updates. The web pages
+// referenced below are the official source of the data. There is also an
+// announce mailing list:
+// https://postlister.uninett.no/sympa/info/norid-diskusjon
+no
+// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+priv.no
+// Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+// no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html
+// counties
+aa.no
+ah.no
+bu.no
+fm.no
+hl.no
+hm.no
+jan-mayen.no
+mr.no
+nl.no
+nt.no
+of.no
+ol.no
+oslo.no
+rl.no
+sf.no
+st.no
+svalbard.no
+tm.no
+tr.no
+va.no
+vf.no
+// primary and lower secondary schools per county
+gs.aa.no
+gs.ah.no
+gs.bu.no
+gs.fm.no
+gs.hl.no
+gs.hm.no
+gs.jan-mayen.no
+gs.mr.no
+gs.nl.no
+gs.nt.no
+gs.of.no
+gs.ol.no
+gs.oslo.no
+gs.rl.no
+gs.sf.no
+gs.st.no
+gs.svalbard.no
+gs.tm.no
+gs.tr.no
+gs.va.no
+gs.vf.no
+// cities
+akrehamn.no
+åkrehamn.no
+algard.no
+ålgård.no
+arna.no
+brumunddal.no
+bryne.no
+bronnoysund.no
+brønnøysund.no
+drobak.no
+drøbak.no
+egersund.no
+fetsund.no
+floro.no
+florø.no
+fredrikstad.no
+hokksund.no
+honefoss.no
+hønefoss.no
+jessheim.no
+jorpeland.no
+jørpeland.no
+kirkenes.no
+kopervik.no
+krokstadelva.no
+langevag.no
+langevåg.no
+leirvik.no
+mjondalen.no
+mjøndalen.no
+mo-i-rana.no
+mosjoen.no
+mosjøen.no
+nesoddtangen.no
+orkanger.no
+osoyro.no
+osøyro.no
+raholt.no
+råholt.no
+sandnessjoen.no
+sandnessjøen.no
+skedsmokorset.no
+slattum.no
+spjelkavik.no
+stathelle.no
+stavern.no
+stjordalshalsen.no
+stjørdalshalsen.no
+tananger.no
+tranby.no
+vossevangen.no
+// communities
+afjord.no
+åfjord.no
+agdenes.no
+al.no
+ål.no
+alesund.no
+ålesund.no
+alstahaug.no
+alta.no
+áltá.no
+alaheadju.no
+álaheadju.no
+alvdal.no
+amli.no
+åmli.no
+amot.no
+åmot.no
+andebu.no
+andoy.no
+andøy.no
+andasuolo.no
+ardal.no
+årdal.no
+aremark.no
+arendal.no
+ås.no
+aseral.no
+åseral.no
+asker.no
+askim.no
+askvoll.no
+askoy.no
+askøy.no
+asnes.no
+åsnes.no
+audnedaln.no
+aukra.no
+aure.no
+aurland.no
+aurskog-holand.no
+aurskog-høland.no
+austevoll.no
+austrheim.no
+averoy.no
+averøy.no
+balestrand.no
+ballangen.no
+balat.no
+bálát.no
+balsfjord.no
+bahccavuotna.no
+báhccavuotna.no
+bamble.no
+bardu.no
+beardu.no
+beiarn.no
+bajddar.no
+bájddar.no
+baidar.no
+báidár.no
+berg.no
+bergen.no
+berlevag.no
+berlevåg.no
+bearalvahki.no
+bearalváhki.no
+bindal.no
+birkenes.no
+bjarkoy.no
+bjarkøy.no
+bjerkreim.no
+bjugn.no
+bodo.no
+bodø.no
+badaddja.no
+bådåddjå.no
+budejju.no
+bokn.no
+bremanger.no
+bronnoy.no
+brønnøy.no
+bygland.no
+bykle.no
+barum.no
+bærum.no
+bo.telemark.no
+bø.telemark.no
+bo.nordland.no
+bø.nordland.no
+bievat.no
+bievát.no
+bomlo.no
+bømlo.no
+batsfjord.no
+båtsfjord.no
+bahcavuotna.no
+báhcavuotna.no
+dovre.no
+drammen.no
+drangedal.no
+dyroy.no
+dyrøy.no
+donna.no
+dønna.no
+eid.no
+eidfjord.no
+eidsberg.no
+eidskog.no
+eidsvoll.no
+eigersund.no
+elverum.no
+enebakk.no
+engerdal.no
+etne.no
+etnedal.no
+evenes.no
+evenassi.no
+evenášši.no
+evje-og-hornnes.no
+farsund.no
+fauske.no
+fuossko.no
+fuoisku.no
+fedje.no
+fet.no
+finnoy.no
+finnøy.no
+fitjar.no
+fjaler.no
+fjell.no
+flakstad.no
+flatanger.no
+flekkefjord.no
+flesberg.no
+flora.no
+fla.no
+flå.no
+folldal.no
+forsand.no
+fosnes.no
+frei.no
+frogn.no
+froland.no
+frosta.no
+frana.no
+fræna.no
+froya.no
+frøya.no
+fusa.no
+fyresdal.no
+forde.no
+førde.no
+gamvik.no
+gangaviika.no
+gáŋgaviika.no
+gaular.no
+gausdal.no
+gildeskal.no
+gildeskål.no
+giske.no
+gjemnes.no
+gjerdrum.no
+gjerstad.no
+gjesdal.no
+gjovik.no
+gjøvik.no
+gloppen.no
+gol.no
+gran.no
+grane.no
+granvin.no
+gratangen.no
+grimstad.no
+grong.no
+kraanghke.no
+kråanghke.no
+grue.no
+gulen.no
+hadsel.no
+halden.no
+halsa.no
+hamar.no
+hamaroy.no
+habmer.no
+hábmer.no
+hapmir.no
+hápmir.no
+hammerfest.no
+hammarfeasta.no
+hámmárfeasta.no
+haram.no
+hareid.no
+harstad.no
+hasvik.no
+aknoluokta.no
+ákŋoluokta.no
+hattfjelldal.no
+aarborte.no
+haugesund.no
+hemne.no
+hemnes.no
+hemsedal.no
+heroy.more-og-romsdal.no
+herøy.møre-og-romsdal.no
+heroy.nordland.no
+herøy.nordland.no
+hitra.no
+hjartdal.no
+hjelmeland.no
+hobol.no
+hobøl.no
+hof.no
+hol.no
+hole.no
+holmestrand.no
+holtalen.no
+holtålen.no
+hornindal.no
+horten.no
+hurdal.no
+hurum.no
+hvaler.no
+hyllestad.no
+hagebostad.no
+hægebostad.no
+hoyanger.no
+høyanger.no
+hoylandet.no
+høylandet.no
+ha.no
+hå.no
+ibestad.no
+inderoy.no
+inderøy.no
+iveland.no
+jevnaker.no
+jondal.no
+jolster.no
+jølster.no
+karasjok.no
+karasjohka.no
+kárášjohka.no
+karlsoy.no
+galsa.no
+gálsá.no
+karmoy.no
+karmøy.no
+kautokeino.no
+guovdageaidnu.no
+klepp.no
+klabu.no
+klæbu.no
+kongsberg.no
+kongsvinger.no
+kragero.no
+kragerø.no
+kristiansand.no
+kristiansund.no
+krodsherad.no
+krødsherad.no
+kvalsund.no
+rahkkeravju.no
+ráhkkerávju.no
+kvam.no
+kvinesdal.no
+kvinnherad.no
+kviteseid.no
+kvitsoy.no
+kvitsøy.no
+kvafjord.no
+kvæfjord.no
+giehtavuoatna.no
+kvanangen.no
+kvænangen.no
+navuotna.no
+návuotna.no
+kafjord.no
+kåfjord.no
+gaivuotna.no
+gáivuotna.no
+larvik.no
+lavangen.no
+lavagis.no
+loabat.no
+loabát.no
+lebesby.no
+davvesiida.no
+leikanger.no
+leirfjord.no
+leka.no
+leksvik.no
+lenvik.no
+leangaviika.no
+leaŋgaviika.no
+lesja.no
+levanger.no
+lier.no
+lierne.no
+lillehammer.no
+lillesand.no
+lindesnes.no
+lindas.no
+lindås.no
+lom.no
+loppa.no
+lahppi.no
+láhppi.no
+lund.no
+lunner.no
+luroy.no
+lurøy.no
+luster.no
+lyngdal.no
+lyngen.no
+ivgu.no
+lardal.no
+lerdal.no
+lærdal.no
+lodingen.no
+lødingen.no
+lorenskog.no
+lørenskog.no
+loten.no
+løten.no
+malvik.no
+masoy.no
+måsøy.no
+muosat.no
+muosát.no
+mandal.no
+marker.no
+marnardal.no
+masfjorden.no
+meland.no
+meldal.no
+melhus.no
+meloy.no
+meløy.no
+meraker.no
+meråker.no
+moareke.no
+moåreke.no
+midsund.no
+midtre-gauldal.no
+modalen.no
+modum.no
+molde.no
+moskenes.no
+moss.no
+mosvik.no
+malselv.no
+målselv.no
+malatvuopmi.no
+málatvuopmi.no
+namdalseid.no
+aejrie.no
+namsos.no
+namsskogan.no
+naamesjevuemie.no
+nååmesjevuemie.no
+laakesvuemie.no
+nannestad.no
+narvik.no
+narviika.no
+naustdal.no
+nedre-eiker.no
+nes.akershus.no
+nes.buskerud.no
+nesna.no
+nesodden.no
+nesseby.no
+unjarga.no
+unjárga.no
+nesset.no
+nissedal.no
+nittedal.no
+nord-aurdal.no
+nord-fron.no
+nord-odal.no
+norddal.no
+nordkapp.no
+davvenjarga.no
+davvenjárga.no
+nordre-land.no
+nordreisa.no
+raisa.no
+ráisa.no
+nore-og-uvdal.no
+notodden.no
+naroy.no
+nærøy.no
+notteroy.no
+nøtterøy.no
+odda.no
+oksnes.no
+øksnes.no
+oppdal.no
+oppegard.no
+oppegård.no
+orkdal.no
+orland.no
+ørland.no
+orskog.no
+ørskog.no
+orsta.no
+ørsta.no
+os.hedmark.no
+os.hordaland.no
+osen.no
+osteroy.no
+osterøy.no
+ostre-toten.no
+østre-toten.no
+overhalla.no
+ovre-eiker.no
+øvre-eiker.no
+oyer.no
+øyer.no
+oygarden.no
+øygarden.no
+oystre-slidre.no
+øystre-slidre.no
+porsanger.no
+porsangu.no
+porsáŋgu.no
+porsgrunn.no
+radoy.no
+radøy.no
+rakkestad.no
+rana.no
+ruovat.no
+randaberg.no
+rauma.no
+rendalen.no
+rennebu.no
+rennesoy.no
+rennesøy.no
+rindal.no
+ringebu.no
+ringerike.no
+ringsaker.no
+rissa.no
+risor.no
+risør.no
+roan.no
+rollag.no
+rygge.no
+ralingen.no
+rælingen.no
+rodoy.no
+rødøy.no
+romskog.no
+rømskog.no
+roros.no
+røros.no
+rost.no
+røst.no
+royken.no
+røyken.no
+royrvik.no
+røyrvik.no
+rade.no
+råde.no
+salangen.no
+siellak.no
+saltdal.no
+salat.no
+sálát.no
+sálat.no
+samnanger.no
+sande.more-og-romsdal.no
+sande.møre-og-romsdal.no
+sande.vestfold.no
+sandefjord.no
+sandnes.no
+sandoy.no
+sandøy.no
+sarpsborg.no
+sauda.no
+sauherad.no
+sel.no
+selbu.no
+selje.no
+seljord.no
+sigdal.no
+siljan.no
+sirdal.no
+skaun.no
+skedsmo.no
+ski.no
+skien.no
+skiptvet.no
+skjervoy.no
+skjervøy.no
+skierva.no
+skiervá.no
+skjak.no
+skjåk.no
+skodje.no
+skanland.no
+skånland.no
+skanit.no
+skánit.no
+smola.no
+smøla.no
+snillfjord.no
+snasa.no
+snåsa.no
+snoasa.no
+snaase.no
+snåase.no
+sogndal.no
+sokndal.no
+sola.no
+solund.no
+songdalen.no
+sortland.no
+spydeberg.no
+stange.no
+stavanger.no
+steigen.no
+steinkjer.no
+stjordal.no
+stjørdal.no
+stokke.no
+stor-elvdal.no
+stord.no
+stordal.no
+storfjord.no
+omasvuotna.no
+strand.no
+stranda.no
+stryn.no
+sula.no
+suldal.no
+sund.no
+sunndal.no
+surnadal.no
+sveio.no
+svelvik.no
+sykkylven.no
+sogne.no
+søgne.no
+somna.no
+sømna.no
+sondre-land.no
+søndre-land.no
+sor-aurdal.no
+sør-aurdal.no
+sor-fron.no
+sør-fron.no
+sor-odal.no
+sør-odal.no
+sor-varanger.no
+sør-varanger.no
+matta-varjjat.no
+mátta-várjjat.no
+sorfold.no
+sørfold.no
+sorreisa.no
+sørreisa.no
+sorum.no
+sørum.no
+tana.no
+deatnu.no
+time.no
+tingvoll.no
+tinn.no
+tjeldsund.no
+dielddanuorri.no
+tjome.no
+tjøme.no
+tokke.no
+tolga.no
+torsken.no
+tranoy.no
+tranøy.no
+tromso.no
+tromsø.no
+tromsa.no
+romsa.no
+trondheim.no
+troandin.no
+trysil.no
+trana.no
+træna.no
+trogstad.no
+trøgstad.no
+tvedestrand.no
+tydal.no
+tynset.no
+tysfjord.no
+divtasvuodna.no
+divttasvuotna.no
+tysnes.no
+tysvar.no
+tysvær.no
+tonsberg.no
+tønsberg.no
+ullensaker.no
+ullensvang.no
+ulvik.no
+utsira.no
+vadso.no
+vadsø.no
+cahcesuolo.no
+čáhcesuolo.no
+vaksdal.no
+valle.no
+vang.no
+vanylven.no
+vardo.no
+vardø.no
+varggat.no
+várggát.no
+vefsn.no
+vaapste.no
+vega.no
+vegarshei.no
+vegårshei.no
+vennesla.no
+verdal.no
+verran.no
+vestby.no
+vestnes.no
+vestre-slidre.no
+vestre-toten.no
+vestvagoy.no
+vestvågøy.no
+vevelstad.no
+vik.no
+vikna.no
+vindafjord.no
+volda.no
+voss.no
+varoy.no
+værøy.no
+vagan.no
+vågan.no
+voagat.no
+vagsoy.no
+vågsøy.no
+vaga.no
+vågå.no
+valer.ostfold.no
+våler.østfold.no
+valer.hedmark.no
+våler.hedmark.no
+
+// np : http://www.mos.com.np/register.html
+*.np
+
+// nr : http://cenpac.net.nr/dns/index.html
+// Confirmed by registry <technician@cenpac.net.nr> 2008-06-17
+nr
+biz.nr
+info.nr
+gov.nr
+edu.nr
+org.nr
+net.nr
+com.nr
+
+// nu : http://en.wikipedia.org/wiki/.nu
+nu
+
+// nz : http://en.wikipedia.org/wiki/.nz
+// Confirmed by registry <jay@nzrs.net.nz> 2014-05-19
+nz
+ac.nz
+co.nz
+cri.nz
+geek.nz
+gen.nz
+govt.nz
+health.nz
+iwi.nz
+kiwi.nz
+maori.nz
+mil.nz
+māori.nz
+net.nz
+org.nz
+parliament.nz
+school.nz
+
+// om : http://en.wikipedia.org/wiki/.om
+om
+co.om
+com.om
+edu.om
+gov.om
+med.om
+museum.om
+net.om
+org.om
+pro.om
+
+// org : http://en.wikipedia.org/wiki/.org
+org
+
+// pa : http://www.nic.pa/
+// Some additional second level "domains" resolve directly as hostnames, such as
+// pannet.pa, so we add a rule for "pa".
+pa
+ac.pa
+gob.pa
+com.pa
+org.pa
+sld.pa
+edu.pa
+net.pa
+ing.pa
+abo.pa
+med.pa
+nom.pa
+
+// pe : https://www.nic.pe/InformeFinalComision.pdf
+pe
+edu.pe
+gob.pe
+nom.pe
+mil.pe
+org.pe
+com.pe
+net.pe
+
+// pf : http://www.gobin.info/domainname/formulaire-pf.pdf
+pf
+com.pf
+org.pf
+edu.pf
+
+// pg : http://en.wikipedia.org/wiki/.pg
+*.pg
+
+// ph : http://www.domains.ph/FAQ2.asp
+// Submitted by registry <jed@email.com.ph> 2008-06-13
+ph
+com.ph
+net.ph
+org.ph
+gov.ph
+edu.ph
+ngo.ph
+mil.ph
+i.ph
+
+// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK
+pk
+com.pk
+net.pk
+edu.pk
+org.pk
+fam.pk
+biz.pk
+web.pk
+gov.pk
+gob.pk
+gok.pk
+gon.pk
+gop.pk
+gos.pk
+info.pk
+
+// pl http://www.dns.pl/english/index.html
+// updated by .PL registry on 2015-04-28
+pl
+com.pl
+net.pl
+org.pl
+// pl functional domains (http://www.dns.pl/english/index.html)
+aid.pl
+agro.pl
+atm.pl
+auto.pl
+biz.pl
+edu.pl
+gmina.pl
+gsm.pl
+info.pl
+mail.pl
+miasta.pl
+media.pl
+mil.pl
+nieruchomosci.pl
+nom.pl
+pc.pl
+powiat.pl
+priv.pl
+realestate.pl
+rel.pl
+sex.pl
+shop.pl
+sklep.pl
+sos.pl
+szkola.pl
+targi.pl
+tm.pl
+tourism.pl
+travel.pl
+turystyka.pl
+// Government domains
+gov.pl
+ap.gov.pl
+ic.gov.pl
+is.gov.pl
+us.gov.pl
+kmpsp.gov.pl
+kppsp.gov.pl
+kwpsp.gov.pl
+psp.gov.pl
+wskr.gov.pl
+kwp.gov.pl
+mw.gov.pl
+ug.gov.pl
+um.gov.pl
+umig.gov.pl
+ugim.gov.pl
+upow.gov.pl
+uw.gov.pl
+starostwo.gov.pl
+pa.gov.pl
+po.gov.pl
+psse.gov.pl
+pup.gov.pl
+rzgw.gov.pl
+sa.gov.pl
+so.gov.pl
+sr.gov.pl
+wsa.gov.pl
+sko.gov.pl
+uzs.gov.pl
+wiih.gov.pl
+winb.gov.pl
+pinb.gov.pl
+wios.gov.pl
+witd.gov.pl
+wzmiuw.gov.pl
+piw.gov.pl
+wiw.gov.pl
+griw.gov.pl
+wif.gov.pl
+oum.gov.pl
+sdn.gov.pl
+zp.gov.pl
+uppo.gov.pl
+mup.gov.pl
+wuoz.gov.pl
+konsulat.gov.pl
+oirm.gov.pl
+// pl regional domains (http://www.dns.pl/english/index.html)
+augustow.pl
+babia-gora.pl
+bedzin.pl
+beskidy.pl
+bialowieza.pl
+bialystok.pl
+bielawa.pl
+bieszczady.pl
+boleslawiec.pl
+bydgoszcz.pl
+bytom.pl
+cieszyn.pl
+czeladz.pl
+czest.pl
+dlugoleka.pl
+elblag.pl
+elk.pl
+glogow.pl
+gniezno.pl
+gorlice.pl
+grajewo.pl
+ilawa.pl
+jaworzno.pl
+jelenia-gora.pl
+jgora.pl
+kalisz.pl
+kazimierz-dolny.pl
+karpacz.pl
+kartuzy.pl
+kaszuby.pl
+katowice.pl
+kepno.pl
+ketrzyn.pl
+klodzko.pl
+kobierzyce.pl
+kolobrzeg.pl
+konin.pl
+konskowola.pl
+kutno.pl
+lapy.pl
+lebork.pl
+legnica.pl
+lezajsk.pl
+limanowa.pl
+lomza.pl
+lowicz.pl
+lubin.pl
+lukow.pl
+malbork.pl
+malopolska.pl
+mazowsze.pl
+mazury.pl
+mielec.pl
+mielno.pl
+mragowo.pl
+naklo.pl
+nowaruda.pl
+nysa.pl
+olawa.pl
+olecko.pl
+olkusz.pl
+olsztyn.pl
+opoczno.pl
+opole.pl
+ostroda.pl
+ostroleka.pl
+ostrowiec.pl
+ostrowwlkp.pl
+pila.pl
+pisz.pl
+podhale.pl
+podlasie.pl
+polkowice.pl
+pomorze.pl
+pomorskie.pl
+prochowice.pl
+pruszkow.pl
+przeworsk.pl
+pulawy.pl
+radom.pl
+rawa-maz.pl
+rybnik.pl
+rzeszow.pl
+sanok.pl
+sejny.pl
+slask.pl
+slupsk.pl
+sosnowiec.pl
+stalowa-wola.pl
+skoczow.pl
+starachowice.pl
+stargard.pl
+suwalki.pl
+swidnica.pl
+swiebodzin.pl
+swinoujscie.pl
+szczecin.pl
+szczytno.pl
+tarnobrzeg.pl
+tgory.pl
+turek.pl
+tychy.pl
+ustka.pl
+walbrzych.pl
+warmia.pl
+warszawa.pl
+waw.pl
+wegrow.pl
+wielun.pl
+wlocl.pl
+wloclawek.pl
+wodzislaw.pl
+wolomin.pl
+wroclaw.pl
+zachpomor.pl
+zagan.pl
+zarow.pl
+zgora.pl
+zgorzelec.pl
+
+// pm : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+pm
+
+// pn : http://www.government.pn/PnRegistry/policies.htm
+pn
+gov.pn
+co.pn
+org.pn
+edu.pn
+net.pn
+
+// post : http://en.wikipedia.org/wiki/.post
+post
+
+// pr : http://www.nic.pr/index.asp?f=1
+pr
+com.pr
+net.pr
+org.pr
+gov.pr
+edu.pr
+isla.pr
+pro.pr
+biz.pr
+info.pr
+name.pr
+// these aren't mentioned on nic.pr, but on http://en.wikipedia.org/wiki/.pr
+est.pr
+prof.pr
+ac.pr
+
+// pro : http://www.nic.pro/support_faq.htm
+pro
+aca.pro
+bar.pro
+cpa.pro
+jur.pro
+law.pro
+med.pro
+eng.pro
+
+// ps : http://en.wikipedia.org/wiki/.ps
+// http://www.nic.ps/registration/policy.html#reg
+ps
+edu.ps
+gov.ps
+sec.ps
+plo.ps
+com.ps
+org.ps
+net.ps
+
+// pt : http://online.dns.pt/dns/start_dns
+pt
+net.pt
+gov.pt
+org.pt
+edu.pt
+int.pt
+publ.pt
+com.pt
+nome.pt
+
+// pw : http://en.wikipedia.org/wiki/.pw
+pw
+co.pw
+ne.pw
+or.pw
+ed.pw
+go.pw
+belau.pw
+
+// py : http://www.nic.py/pautas.html#seccion_9
+// Confirmed by registry 2012-10-03
+py
+com.py
+coop.py
+edu.py
+gov.py
+mil.py
+net.py
+org.py
+
+// qa : http://domains.qa/en/
+qa
+com.qa
+edu.qa
+gov.qa
+mil.qa
+name.qa
+net.qa
+org.qa
+sch.qa
+
+// re : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs
+re
+com.re
+asso.re
+nom.re
+
+// ro : http://www.rotld.ro/
+ro
+com.ro
+org.ro
+tm.ro
+nt.ro
+nom.ro
+info.ro
+rec.ro
+arts.ro
+firm.ro
+store.ro
+www.ro
+
+// rs : http://en.wikipedia.org/wiki/.rs
+rs
+co.rs
+org.rs
+edu.rs
+ac.rs
+gov.rs
+in.rs
+
+// ru : http://www.cctld.ru/ru/docs/aktiv_8.php
+// Industry domains
+ru
+ac.ru
+com.ru
+edu.ru
+int.ru
+net.ru
+org.ru
+pp.ru
+// Geographical domains
+adygeya.ru
+altai.ru
+amur.ru
+arkhangelsk.ru
+astrakhan.ru
+bashkiria.ru
+belgorod.ru
+bir.ru
+bryansk.ru
+buryatia.ru
+cbg.ru
+chel.ru
+chelyabinsk.ru
+chita.ru
+chukotka.ru
+chuvashia.ru
+dagestan.ru
+dudinka.ru
+e-burg.ru
+grozny.ru
+irkutsk.ru
+ivanovo.ru
+izhevsk.ru
+jar.ru
+joshkar-ola.ru
+kalmykia.ru
+kaluga.ru
+kamchatka.ru
+karelia.ru
+kazan.ru
+kchr.ru
+kemerovo.ru
+khabarovsk.ru
+khakassia.ru
+khv.ru
+kirov.ru
+koenig.ru
+komi.ru
+kostroma.ru
+krasnoyarsk.ru
+kuban.ru
+kurgan.ru
+kursk.ru
+lipetsk.ru
+magadan.ru
+mari.ru
+mari-el.ru
+marine.ru
+mordovia.ru
+// mosreg.ru  Bug 1090800 - removed at request of Aleksey Konstantinov <konstantinovav@mosreg.ru>
+msk.ru
+murmansk.ru
+nalchik.ru
+nnov.ru
+nov.ru
+novosibirsk.ru
+nsk.ru
+omsk.ru
+orenburg.ru
+oryol.ru
+palana.ru
+penza.ru
+perm.ru
+ptz.ru
+rnd.ru
+ryazan.ru
+sakhalin.ru
+samara.ru
+saratov.ru
+simbirsk.ru
+smolensk.ru
+spb.ru
+stavropol.ru
+stv.ru
+surgut.ru
+tambov.ru
+tatarstan.ru
+tom.ru
+tomsk.ru
+tsaritsyn.ru
+tsk.ru
+tula.ru
+tuva.ru
+tver.ru
+tyumen.ru
+udm.ru
+udmurtia.ru
+ulan-ude.ru
+vladikavkaz.ru
+vladimir.ru
+vladivostok.ru
+volgograd.ru
+vologda.ru
+voronezh.ru
+vrn.ru
+vyatka.ru
+yakutia.ru
+yamal.ru
+yaroslavl.ru
+yekaterinburg.ru
+yuzhno-sakhalinsk.ru
+// More geographical domains
+amursk.ru
+baikal.ru
+cmw.ru
+fareast.ru
+jamal.ru
+kms.ru
+k-uralsk.ru
+kustanai.ru
+kuzbass.ru
+mytis.ru
+nakhodka.ru
+nkz.ru
+norilsk.ru
+oskol.ru
+pyatigorsk.ru
+rubtsovsk.ru
+snz.ru
+syzran.ru
+vdonsk.ru
+zgrad.ru
+// State domains
+gov.ru
+mil.ru
+// Technical domains
+test.ru
+
+// rw : http://www.nic.rw/cgi-bin/policy.pl
+rw
+gov.rw
+net.rw
+edu.rw
+ac.rw
+com.rw
+co.rw
+int.rw
+mil.rw
+gouv.rw
+
+// sa : http://www.nic.net.sa/
+sa
+com.sa
+net.sa
+org.sa
+gov.sa
+med.sa
+pub.sa
+edu.sa
+sch.sa
+
+// sb : http://www.sbnic.net.sb/
+// Submitted by registry <lee.humphries@telekom.com.sb> 2008-06-08
+sb
+com.sb
+edu.sb
+gov.sb
+net.sb
+org.sb
+
+// sc : http://www.nic.sc/
+sc
+com.sc
+gov.sc
+net.sc
+org.sc
+edu.sc
+
+// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm
+// Submitted by registry <admin@isoc.sd> 2008-06-17
+sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+
+// se : http://en.wikipedia.org/wiki/.se
+// Submitted by registry <patrik.wallstrom@iis.se> 2014-03-18
+se
+a.se
+ac.se
+b.se
+bd.se
+brand.se
+c.se
+d.se
+e.se
+f.se
+fh.se
+fhsk.se
+fhv.se
+g.se
+h.se
+i.se
+k.se
+komforb.se
+kommunalforbund.se
+komvux.se
+l.se
+lanbib.se
+m.se
+n.se
+naturbruksgymn.se
+o.se
+org.se
+p.se
+parti.se
+pp.se
+press.se
+r.se
+s.se
+t.se
+tm.se
+u.se
+w.se
+x.se
+y.se
+z.se
+
+// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines
+sg
+com.sg
+net.sg
+org.sg
+gov.sg
+edu.sg
+per.sg
+
+// sh : http://www.nic.sh/registrar.html
+sh
+com.sh
+net.sh
+gov.sh
+org.sh
+mil.sh
+
+// si : http://en.wikipedia.org/wiki/.si
+si
+
+// sj : No registrations at this time.
+// Submitted by registry <jarle@uninett.no> 2008-06-16
+sj
+
+// sk : http://en.wikipedia.org/wiki/.sk
+// list of 2nd level domains ?
+sk
+
+// sl : http://www.nic.sl
+// Submitted by registry <adam@neoip.com> 2008-06-12
+sl
+com.sl
+net.sl
+edu.sl
+gov.sl
+org.sl
+
+// sm : http://en.wikipedia.org/wiki/.sm
+sm
+
+// sn : http://en.wikipedia.org/wiki/.sn
+sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
+
+// so : http://www.soregistry.com/
+so
+com.so
+net.so
+org.so
+
+// sr : http://en.wikipedia.org/wiki/.sr
+sr
+
+// st : http://www.nic.st/html/policyrules/
+st
+co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+gov.st
+mil.st
+net.st
+org.st
+principe.st
+saotome.st
+store.st
+
+// su : http://en.wikipedia.org/wiki/.su
+su
+adygeya.su
+arkhangelsk.su
+balashov.su
+bashkiria.su
+bryansk.su
+dagestan.su
+grozny.su
+ivanovo.su
+kalmykia.su
+kaluga.su
+karelia.su
+khakassia.su
+krasnodar.su
+kurgan.su
+lenug.su
+mordovia.su
+msk.su
+murmansk.su
+nalchik.su
+nov.su
+obninsk.su
+penza.su
+pokrovsk.su
+sochi.su
+spb.su
+togliatti.su
+troitsk.su
+tula.su
+tuva.su
+vladikavkaz.su
+vladimir.su
+vologda.su
+
+// sv : http://www.svnet.org.sv/niveldos.pdf
+sv
+com.sv
+edu.sv
+gob.sv
+org.sv
+red.sv
+
+// sx : http://en.wikipedia.org/wiki/.sx
+// Confirmed by registry <jcvignes@openregistry.com> 2012-05-31
+sx
+gov.sx
+
+// sy : http://en.wikipedia.org/wiki/.sy
+// see also: http://www.gobin.info/domainname/sy.doc
+sy
+edu.sy
+gov.sy
+net.sy
+mil.sy
+com.sy
+org.sy
+
+// sz : http://en.wikipedia.org/wiki/.sz
+// http://www.sispa.org.sz/
+sz
+co.sz
+ac.sz
+org.sz
+
+// tc : http://en.wikipedia.org/wiki/.tc
+tc
+
+// td : http://en.wikipedia.org/wiki/.td
+td
+
+// tel: http://en.wikipedia.org/wiki/.tel
+// http://www.telnic.org/
+tel
+
+// tf : http://en.wikipedia.org/wiki/.tf
+tf
+
+// tg : http://en.wikipedia.org/wiki/.tg
+// http://www.nic.tg/
+tg
+
+// th : http://en.wikipedia.org/wiki/.th
+// Submitted by registry <krit@thains.co.th> 2008-06-17
+th
+ac.th
+co.th
+go.th
+in.th
+mi.th
+net.th
+or.th
+
+// tj : http://www.nic.tj/policy.html
+tj
+ac.tj
+biz.tj
+co.tj
+com.tj
+edu.tj
+go.tj
+gov.tj
+int.tj
+mil.tj
+name.tj
+net.tj
+nic.tj
+org.tj
+test.tj
+web.tj
+
+// tk : http://en.wikipedia.org/wiki/.tk
+tk
+
+// tl : http://en.wikipedia.org/wiki/.tl
+tl
+gov.tl
+
+// tm : http://www.nic.tm/local.html
+tm
+com.tm
+co.tm
+org.tm
+net.tm
+nom.tm
+gov.tm
+mil.tm
+edu.tm
+
+// tn : http://en.wikipedia.org/wiki/.tn
+// http://whois.ati.tn/
+tn
+com.tn
+ens.tn
+fin.tn
+gov.tn
+ind.tn
+intl.tn
+nat.tn
+net.tn
+org.tn
+info.tn
+perso.tn
+tourism.tn
+edunet.tn
+rnrt.tn
+rns.tn
+rnu.tn
+mincom.tn
+agrinet.tn
+defense.tn
+turen.tn
+
+// to : http://en.wikipedia.org/wiki/.to
+// Submitted by registry <egullich@colo.to> 2008-06-17
+to
+com.to
+gov.to
+net.to
+org.to
+edu.to
+mil.to
+
+// tp : No registrations at this time.
+// Submitted by Ryan Sleevi <ryan.sleevi@gmail.com> 2014-01-03
+tp
+
+// subTLDs: https://www.nic.tr/forms/eng/policies.pdf
+//     and: https://www.nic.tr/forms/politikalar.pdf
+// Submitted by <mehmetgurevin@gmail.com> 2014-07-19
+tr
+com.tr
+info.tr
+biz.tr
+net.tr
+org.tr
+web.tr
+gen.tr
+tv.tr
+av.tr
+dr.tr
+bbs.tr
+name.tr
+tel.tr
+gov.tr
+bel.tr
+pol.tr
+mil.tr
+k12.tr
+edu.tr
+kep.tr
+
+// Used by Northern Cyprus
+nc.tr
+
+// Used by government agencies of Northern Cyprus
+gov.nc.tr
+
+// travel : http://en.wikipedia.org/wiki/.travel
+travel
+
+// tt : http://www.nic.tt/
+tt
+co.tt
+com.tt
+org.tt
+net.tt
+biz.tt
+info.tt
+pro.tt
+int.tt
+coop.tt
+jobs.tt
+mobi.tt
+travel.tt
+museum.tt
+aero.tt
+name.tt
+gov.tt
+edu.tt
+
+// tv : http://en.wikipedia.org/wiki/.tv
+// Not listing any 2LDs as reserved since none seem to exist in practice,
+// Wikipedia notwithstanding.
+tv
+
+// tw : http://en.wikipedia.org/wiki/.tw
+tw
+edu.tw
+gov.tw
+mil.tw
+com.tw
+net.tw
+org.tw
+idv.tw
+game.tw
+ebiz.tw
+club.tw
+網路.tw
+組織.tw
+商業.tw
+
+// tz : http://www.tznic.or.tz/index.php/domains
+// Confirmed by registry <manager@tznic.or.tz> 2013-01-22
+tz
+ac.tz
+co.tz
+go.tz
+hotel.tz
+info.tz
+me.tz
+mil.tz
+mobi.tz
+ne.tz
+or.tz
+sc.tz
+tv.tz
+
+// ua : https://hostmaster.ua/policy/?ua
+// Submitted by registry <dk@cctld.ua> 2012-04-27
+ua
+// ua 2LD
+com.ua
+edu.ua
+gov.ua
+in.ua
+net.ua
+org.ua
+// ua geographic names
+// https://hostmaster.ua/2ld/
+cherkassy.ua
+cherkasy.ua
+chernigov.ua
+chernihiv.ua
+chernivtsi.ua
+chernovtsy.ua
+ck.ua
+cn.ua
+cr.ua
+crimea.ua
+cv.ua
+dn.ua
+dnepropetrovsk.ua
+dnipropetrovsk.ua
+dominic.ua
+donetsk.ua
+dp.ua
+if.ua
+ivano-frankivsk.ua
+kh.ua
+kharkiv.ua
+kharkov.ua
+kherson.ua
+khmelnitskiy.ua
+khmelnytskyi.ua
+kiev.ua
+kirovograd.ua
+km.ua
+kr.ua
+krym.ua
+ks.ua
+kv.ua
+kyiv.ua
+lg.ua
+lt.ua
+lugansk.ua
+lutsk.ua
+lv.ua
+lviv.ua
+mk.ua
+mykolaiv.ua
+nikolaev.ua
+od.ua
+odesa.ua
+odessa.ua
+pl.ua
+poltava.ua
+rivne.ua
+rovno.ua
+rv.ua
+sb.ua
+sebastopol.ua
+sevastopol.ua
+sm.ua
+sumy.ua
+te.ua
+ternopil.ua
+uz.ua
+uzhgorod.ua
+vinnica.ua
+vinnytsia.ua
+vn.ua
+volyn.ua
+yalta.ua
+zaporizhzhe.ua
+zaporizhzhia.ua
+zhitomir.ua
+zhytomyr.ua
+zp.ua
+zt.ua
+
+// ug : https://www.registry.co.ug/
+ug
+co.ug
+or.ug
+ac.ug
+sc.ug
+go.ug
+ne.ug
+com.ug
+org.ug
+
+// uk : http://en.wikipedia.org/wiki/.uk
+// Submitted by registry <Michael.Daly@nominet.org.uk>
+uk
+ac.uk
+co.uk
+gov.uk
+ltd.uk
+me.uk
+net.uk
+nhs.uk
+org.uk
+plc.uk
+police.uk
+*.sch.uk
+
+// us : http://en.wikipedia.org/wiki/.us
+us
+dni.us
+fed.us
+isa.us
+kids.us
+nsn.us
+// us geographic names
+ak.us
+al.us
+ar.us
+as.us
+az.us
+ca.us
+co.us
+ct.us
+dc.us
+de.us
+fl.us
+ga.us
+gu.us
+hi.us
+ia.us
+id.us
+il.us
+in.us
+ks.us
+ky.us
+la.us
+ma.us
+md.us
+me.us
+mi.us
+mn.us
+mo.us
+ms.us
+mt.us
+nc.us
+nd.us
+ne.us
+nh.us
+nj.us
+nm.us
+nv.us
+ny.us
+oh.us
+ok.us
+or.us
+pa.us
+pr.us
+ri.us
+sc.us
+sd.us
+tn.us
+tx.us
+ut.us
+vi.us
+vt.us
+va.us
+wa.us
+wi.us
+wv.us
+wy.us
+// The registrar notes several more specific domains available in each state,
+// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat
+// haphazard; in some states these domains resolve as addresses, while in others
+// only subdomains are available, or even nothing at all. We include the
+// most common ones where it's clear that different sites are different
+// entities.
+k12.ak.us
+k12.al.us
+k12.ar.us
+k12.as.us
+k12.az.us
+k12.ca.us
+k12.co.us
+k12.ct.us
+k12.dc.us
+k12.de.us
+k12.fl.us
+k12.ga.us
+k12.gu.us
+// k12.hi.us  Bug 614565 - Hawaii has a state-wide DOE login
+k12.ia.us
+k12.id.us
+k12.il.us
+k12.in.us
+k12.ks.us
+k12.ky.us
+k12.la.us
+k12.ma.us
+k12.md.us
+k12.me.us
+k12.mi.us
+k12.mn.us
+k12.mo.us
+k12.ms.us
+k12.mt.us
+k12.nc.us
+// k12.nd.us  Bug 1028347 - Removed at request of Travis Rosso <trossow@nd.gov>
+k12.ne.us
+k12.nh.us
+k12.nj.us
+k12.nm.us
+k12.nv.us
+k12.ny.us
+k12.oh.us
+k12.ok.us
+k12.or.us
+k12.pa.us
+k12.pr.us
+k12.ri.us
+k12.sc.us
+// k12.sd.us  Bug 934131 - Removed at request of James Booze <James.Booze@k12.sd.us>
+k12.tn.us
+k12.tx.us
+k12.ut.us
+k12.vi.us
+k12.vt.us
+k12.va.us
+k12.wa.us
+k12.wi.us
+// k12.wv.us  Bug 947705 - Removed at request of Verne Britton <verne@wvnet.edu>
+k12.wy.us
+cc.ak.us
+cc.al.us
+cc.ar.us
+cc.as.us
+cc.az.us
+cc.ca.us
+cc.co.us
+cc.ct.us
+cc.dc.us
+cc.de.us
+cc.fl.us
+cc.ga.us
+cc.gu.us
+cc.hi.us
+cc.ia.us
+cc.id.us
+cc.il.us
+cc.in.us
+cc.ks.us
+cc.ky.us
+cc.la.us
+cc.ma.us
+cc.md.us
+cc.me.us
+cc.mi.us
+cc.mn.us
+cc.mo.us
+cc.ms.us
+cc.mt.us
+cc.nc.us
+cc.nd.us
+cc.ne.us
+cc.nh.us
+cc.nj.us
+cc.nm.us
+cc.nv.us
+cc.ny.us
+cc.oh.us
+cc.ok.us
+cc.or.us
+cc.pa.us
+cc.pr.us
+cc.ri.us
+cc.sc.us
+cc.sd.us
+cc.tn.us
+cc.tx.us
+cc.ut.us
+cc.vi.us
+cc.vt.us
+cc.va.us
+cc.wa.us
+cc.wi.us
+cc.wv.us
+cc.wy.us
+lib.ak.us
+lib.al.us
+lib.ar.us
+lib.as.us
+lib.az.us
+lib.ca.us
+lib.co.us
+lib.ct.us
+lib.dc.us
+lib.de.us
+lib.fl.us
+lib.ga.us
+lib.gu.us
+lib.hi.us
+lib.ia.us
+lib.id.us
+lib.il.us
+lib.in.us
+lib.ks.us
+lib.ky.us
+lib.la.us
+lib.ma.us
+lib.md.us
+lib.me.us
+lib.mi.us
+lib.mn.us
+lib.mo.us
+lib.ms.us
+lib.mt.us
+lib.nc.us
+lib.nd.us
+lib.ne.us
+lib.nh.us
+lib.nj.us
+lib.nm.us
+lib.nv.us
+lib.ny.us
+lib.oh.us
+lib.ok.us
+lib.or.us
+lib.pa.us
+lib.pr.us
+lib.ri.us
+lib.sc.us
+lib.sd.us
+lib.tn.us
+lib.tx.us
+lib.ut.us
+lib.vi.us
+lib.vt.us
+lib.va.us
+lib.wa.us
+lib.wi.us
+// lib.wv.us  Bug 941670 - Removed at request of Larry W Arnold <arnold@wvlc.lib.wv.us>
+lib.wy.us
+// k12.ma.us contains school districts in Massachusetts. The 4LDs are
+//  managed indepedently except for private (PVT), charter (CHTR) and
+//  parochial (PAROCH) schools.  Those are delegated dorectly to the
+//  5LD operators.   <k12-ma-hostmaster _ at _ rsuc.gweep.net>
+pvt.k12.ma.us
+chtr.k12.ma.us
+paroch.k12.ma.us
+
+// uy : http://www.nic.org.uy/
+uy
+com.uy
+edu.uy
+gub.uy
+mil.uy
+net.uy
+org.uy
+
+// uz : http://www.reg.uz/
+uz
+co.uz
+com.uz
+net.uz
+org.uz
+
+// va : http://en.wikipedia.org/wiki/.va
+va
+
+// vc : http://en.wikipedia.org/wiki/.vc
+// Submitted by registry <kshah@ca.afilias.info> 2008-06-13
+vc
+com.vc
+net.vc
+org.vc
+gov.vc
+mil.vc
+edu.vc
+
+// ve : https://registro.nic.ve/
+// Confirmed by registry 2012-10-04
+// Updated 2014-05-20 - Bug 940478
+ve
+arts.ve
+co.ve
+com.ve
+e12.ve
+edu.ve
+firm.ve
+gob.ve
+gov.ve
+info.ve
+int.ve
+mil.ve
+net.ve
+org.ve
+rec.ve
+store.ve
+tec.ve
+web.ve
+
+// vg : http://en.wikipedia.org/wiki/.vg
+vg
+
+// vi : http://www.nic.vi/newdomainform.htm
+// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other
+// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they
+// are available for registration (which they do not seem to be).
+vi
+co.vi
+com.vi
+k12.vi
+net.vi
+org.vi
+
+// vn : https://www.dot.vn/vnnic/vnnic/domainregistration.jsp
+vn
+com.vn
+net.vn
+org.vn
+edu.vn
+gov.vn
+int.vn
+ac.vn
+biz.vn
+info.vn
+name.vn
+pro.vn
+health.vn
+
+// vu : http://en.wikipedia.org/wiki/.vu
+// http://www.vunic.vu/
+vu
+com.vu
+edu.vu
+net.vu
+org.vu
+
+// wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+wf
+
+// ws : http://en.wikipedia.org/wiki/.ws
+// http://samoanic.ws/index.dhtml
+ws
+com.ws
+net.ws
+org.ws
+gov.ws
+edu.ws
+
+// yt : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+yt
+
+// IDN ccTLDs
+// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then
+// U-label, and follow this format:
+// // A-Label ("<Latin renderings>", <language name>[, variant info]) : <ISO 3166 ccTLD>
+// // [sponsoring org]
+// U-Label
+
+// xn--mgbaam7a8h ("Emerat", Arabic) : AE
+// http://nic.ae/english/arabicdomain/rules.jsp
+امارات
+
+// xn--y9a3aq ("hye", Armenian) : AM
+// ISOC AM (operated by .am Registry)
+հայ
+
+// xn--54b7fta0cc ("Bangla", Bangla) : BD
+বাংলা
+
+// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY
+// Operated by .by registry
+бел
+
+// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中国
+
+// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中國
+
+// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ
+الجزائر
+
+// xn--wgbh1c ("Egypt/Masr", Arabic) : EG
+// http://www.dotmasr.eg/
+مصر
+
+// xn--node ("ge", Georgian Mkhedruli) : GE
+გე
+
+// xn--qxam ("el", Greek) : GR
+// Hellenic Ministry of Infrastructure, Transport, and Networks
+ελ
+
+// xn--j6w193g ("Hong Kong", Chinese) : HK
+// https://www2.hkirc.hk/register/rules.jsp
+香港
+
+// xn--h2brj9c ("Bharat", Devanagari) : IN
+// India
+भारत
+
+// xn--mgbbh1a71e ("Bharat", Arabic) : IN
+// India
+بھارت
+
+// xn--fpcrj9c3d ("Bharat", Telugu) : IN
+// India
+భారత్
+
+// xn--gecrj9c ("Bharat", Gujarati) : IN
+// India
+ભારત
+
+// xn--s9brj9c ("Bharat", Gurmukhi) : IN
+// India
+ਭਾਰਤ
+
+// xn--45brj9c ("Bharat", Bengali) : IN
+// India
+ভারত
+
+// xn--xkc2dl3a5ee0h ("India", Tamil) : IN
+// India
+இந்தியா
+
+// xn--mgba3a4f16a ("Iran", Persian) : IR
+ایران
+
+// xn--mgba3a4fra ("Iran", Arabic) : IR
+ايران
+
+// xn--mgbtx2b ("Iraq", Arabic) : IQ
+// Communications and Media Commission
+عراق
+
+// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO
+// National Information Technology Center (NITC)
+// Royal Scientific Society, Al-Jubeiha
+الاردن
+
+// xn--3e0b707e ("Republic of Korea", Hangul) : KR
+한국
+
+// xn--80ao21a ("Kaz", Kazakh) : KZ
+қаз
+
+// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK
+// http://nic.lk
+ලංකා
+
+// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK
+// http://nic.lk
+இலங்கை
+
+// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA
+المغرب
+
+// xn--d1alf ("mkd", Macedonian) : MK
+// MARnet
+мкд
+
+// xn--l1acc ("mon", Mongolian) : MN
+мон
+
+// xn--mix891f ("Macao", Chinese, Traditional) : MO
+// MONIC / HNET Asia (Registry Operator for .mo)
+澳門
+
+// xn--mix082f ("Macao", Chinese, Simplified) : MO
+澳门
+
+// xn--mgbx4cd0ab ("Malaysia", Malay) : MY
+مليسيا
+
+// xn--mgb9awbf ("Oman", Arabic) : OM
+عمان
+
+// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK
+پاکستان
+
+// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK
+پاكستان
+
+// xn--ygbi2ammx ("Falasteen", Arabic) : PS
+// The Palestinian National Internet Naming Authority (PNINA)
+// http://www.pnina.ps
+فلسطين
+
+// xn--90a3ac ("srb", Cyrillic) : RS
+// http://www.rnids.rs/en/the-.срб-domain
+срб
+пр.срб
+орг.срб
+обр.срб
+од.срб
+упр.срб
+ак.срб
+
+// xn--p1ai ("rf", Russian-Cyrillic) : RU
+// http://www.cctld.ru/en/docs/rulesrf.php
+рф
+
+// xn--wgbl6a ("Qatar", Arabic) : QA
+// http://www.ict.gov.qa/
+قطر
+
+// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA
+// http://www.nic.net.sa/
+السعودية
+
+// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant)  : SA
+السعودیة
+
+// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA
+السعودیۃ
+
+// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA
+السعوديه
+
+// xn--mgbpl2fh ("sudan", Arabic) : SD
+// Operated by .sd registry
+سودان
+
+// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG
+新加坡
+
+// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG
+சிங்கப்பூர்
+
+// xn--ogbpf8fl ("Syria", Arabic) : SY
+سورية
+
+// xn--mgbtf8fl ("Syria", Arabic, variant) : SY
+سوريا
+
+// xn--o3cw4h ("Thai", Thai) : TH
+// http://www.thnic.co.th
+ไทย
+
+// xn--pgbs0dh ("Tunisia", Arabic) : TN
+// http://nic.tn
+تونس
+
+// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+台灣
+
+// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+台湾
+
+// xn--nnx388a ("Taiwan", Chinese, variant) : TW
+臺灣
+
+// xn--j1amh ("ukr", Cyrillic) : UA
+укр
+
+// xn--mgb2ddes ("AlYemen", Arabic) : YE
+اليمن
+
+// xxx : http://icmregistry.com
+xxx
+
+// ye : http://www.y.net.ye/services/domain_name.htm
+*.ye
+
+// za : http://www.zadna.org.za/content/page/domain-information
+ac.za
+agrica.za
+alt.za
+co.za
+edu.za
+gov.za
+grondar.za
+law.za
+mil.za
+net.za
+ngo.za
+nis.za
+nom.za
+org.za
+school.za
+tm.za
+web.za
+
+// zm : http://en.wikipedia.org/wiki/.zm
+*.zm
+
+// zw : http://en.wikipedia.org/wiki/.zw
+*.zw
+
+
+// List of new gTLDs imported from https://newgtlds.icann.org/newgtlds.csv on 2015-11-12T22:43:48Z
+
+// aaa : 2015-02-26 American Automobile Association, Inc.
+aaa
+
+// aarp : 2015-05-21 AARP
+aarp
+
+// abarth : 2015-07-30 Fiat Chrysler Automobiles N.V.
+abarth
+
+// abb : 2014-10-24 ABB Ltd
+abb
+
+// abbott : 2014-07-24 Abbott Laboratories, Inc.
+abbott
+
+// abbvie : 2015-07-30 AbbVie Inc.
+abbvie
+
+// abc : 2015-07-30 Disney Enterprises, Inc.
+abc
+
+// able : 2015-06-25 Able Inc.
+able
+
+// abogado : 2014-04-24 Top Level Domain Holdings Limited
+abogado
+
+// abudhabi : 2015-07-30 Abu Dhabi Systems and Information Centre
+abudhabi
+
+// academy : 2013-11-07 Half Oaks, LLC
+academy
+
+// accenture : 2014-08-15 Accenture plc
+accenture
+
+// accountant : 2014-11-20 dot Accountant Limited
+accountant
+
+// accountants : 2014-03-20 Knob Town, LLC
+accountants
+
+// aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG
+aco
+
+// active : 2014-05-01 The Active Network, Inc
+active
+
+// actor : 2013-12-12 United TLD Holdco Ltd.
+actor
+
+// adac : 2015-07-16 Allgemeiner Deutscher Automobil-Club e.V. (ADAC)
+adac
+
+// ads : 2014-12-04 Charleston Road Registry Inc.
+ads
+
+// adult : 2014-10-16 ICM Registry AD LLC
+adult
+
+// aeg : 2015-03-19 Aktiebolaget Electrolux
+aeg
+
+// aetna : 2015-05-21 Aetna Life Insurance Company
+aetna
+
+// afamilycompany : 2015-07-23 Johnson Shareholdings, Inc.
+afamilycompany
+
+// afl : 2014-10-02 Australian Football League
+afl
+
+// africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa
+africa
+
+// africamagic : 2015-03-05 Electronic Media Network (Pty) Ltd
+africamagic
+
+// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+agakhan
+
+// agency : 2013-11-14 Steel Falls, LLC
+agency
+
+// aig : 2014-12-18 American International Group, Inc.
+aig
+
+// aigo : 2015-08-06 aigo Digital Technology Co,Ltd.
+aigo
+
+// airbus : 2015-07-30 Airbus S.A.S.
+airbus
+
+// airforce : 2014-03-06 United TLD Holdco Ltd.
+airforce
+
+// airtel : 2014-10-24 Bharti Airtel Limited
+airtel
+
+// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+akdn
+
+// alfaromeo : 2015-07-31 Fiat Chrysler Automobiles N.V.
+alfaromeo
+
+// alibaba : 2015-01-15 Alibaba Group Holding Limited
+alibaba
+
+// alipay : 2015-01-15 Alibaba Group Holding Limited
+alipay
+
+// allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft
+allfinanz
+
+// allstate : 2015-07-31 Allstate Fire and Casualty Insurance Company
+allstate
+
+// ally : 2015-06-18 Ally Financial Inc.
+ally
+
+// alsace : 2014-07-02 REGION D ALSACE
+alsace
+
+// alstom : 2015-07-30 ALSTOM
+alstom
+
+// americanexpress : 2015-07-31 American Express Travel Related Services Company, Inc.
+americanexpress
+
+// americanfamily : 2015-07-23 AmFam, Inc.
+americanfamily
+
+// amex : 2015-07-31 American Express Travel Related Services Company, Inc.
+amex
+
+// amfam : 2015-07-23 AmFam, Inc.
+amfam
+
+// amica : 2015-05-28 Amica Mutual Insurance Company
+amica
+
+// amsterdam : 2014-07-24 Gemeente Amsterdam
+amsterdam
+
+// analytics : 2014-12-18 Campus IP LLC
+analytics
+
+// android : 2014-08-07 Charleston Road Registry Inc.
+android
+
+// anquan : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+anquan
+
+// anz : 2015-07-31 Australia and New Zealand Banking Group Limited
+anz
+
+// aol : 2015-09-17 AOL Inc.
+aol
+
+// apartments : 2014-12-11 June Maple, LLC
+apartments
+
+// app : 2015-05-14 Charleston Road Registry Inc.
+app
+
+// apple : 2015-05-14 Apple Inc.
+apple
+
+// aquarelle : 2014-07-24 Aquarelle.com
+aquarelle
+
+// aramco : 2014-11-20 Aramco Services Company
+aramco
+
+// archi : 2014-02-06 STARTING DOT LIMITED
+archi
+
+// army : 2014-03-06 United TLD Holdco Ltd.
+army
+
+// arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E.
+arte
+
+// asda : 2015-07-31 Wal-Mart Stores, Inc.
+asda
+
+// associates : 2014-03-06 Baxter Hill, LLC
+associates
+
+// athleta : 2015-07-30 The Gap, Inc.
+athleta
+
+// attorney : 2014-03-20
+attorney
+
+// auction : 2014-03-20
+auction
+
+// audi : 2015-05-21 AUDI Aktiengesellschaft
+audi
+
+// audible : 2015-06-25 Amazon EU S.à r.l.
+audible
+
+// audio : 2014-03-20 Uniregistry, Corp.
+audio
+
+// auspost : 2015-08-13 Australian Postal Corporation
+auspost
+
+// author : 2014-12-18 Amazon EU S.à r.l.
+author
+
+// auto : 2014-11-13
+auto
+
+// autos : 2014-01-09 DERAutos, LLC
+autos
+
+// avianca : 2015-01-08 Aerovias del Continente Americano S.A. Avianca
+avianca
+
+// aws : 2015-06-25 Amazon EU S.à r.l.
+aws
+
+// axa : 2013-12-19 AXA SA
+axa
+
+// azure : 2014-12-18 Microsoft Corporation
+azure
+
+// baby : 2015-04-09 Johnson & Johnson Services, Inc.
+baby
+
+// baidu : 2015-01-08 Baidu, Inc.
+baidu
+
+// banamex : 2015-07-30 Citigroup Inc.
+banamex
+
+// bananarepublic : 2015-07-31 The Gap, Inc.
+bananarepublic
+
+// band : 2014-06-12
+band
+
+// bank : 2014-09-25 fTLD Registry Services LLC
+bank
+
+// bar : 2013-12-12 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+bar
+
+// barcelona : 2014-07-24 Municipi de Barcelona
+barcelona
+
+// barclaycard : 2014-11-20 Barclays Bank PLC
+barclaycard
+
+// barclays : 2014-11-20 Barclays Bank PLC
+barclays
+
+// barefoot : 2015-06-11 Gallo Vineyards, Inc.
+barefoot
+
+// bargains : 2013-11-14 Half Hallow, LLC
+bargains
+
+// baseball : 2015-10-29 MLB Advanced Media DH, LLC
+baseball
+
+// basketball : 2015-08-20 Fédération Internationale de Basketball (FIBA)
+basketball
+
+// bauhaus : 2014-04-17 Werkhaus GmbH
+bauhaus
+
+// bayern : 2014-01-23 Bayern Connect GmbH
+bayern
+
+// bbc : 2014-12-18 British Broadcasting Corporation
+bbc
+
+// bbt : 2015-07-23 BB&T Corporation
+bbt
+
+// bbva : 2014-10-02 BANCO BILBAO VIZCAYA ARGENTARIA, S.A.
+bbva
+
+// bcg : 2015-04-02 The Boston Consulting Group, Inc.
+bcg
+
+// bcn : 2014-07-24 Municipi de Barcelona
+bcn
+
+// beats : 2015-05-14 Beats Electronics, LLC
+beats
+
+// beer : 2014-01-09 Top Level Domain Holdings Limited
+beer
+
+// bentley : 2014-12-18 Bentley Motors Limited
+bentley
+
+// berlin : 2013-10-31 dotBERLIN GmbH & Co. KG
+berlin
+
+// best : 2013-12-19 BestTLD Pty Ltd
+best
+
+// bestbuy : 2015-07-31 BBY Solutions, Inc.
+bestbuy
+
+// bet : 2015-05-07 Afilias plc
+bet
+
+// bharti : 2014-01-09 Bharti Enterprises (Holding) Private Limited
+bharti
+
+// bible : 2014-06-19 American Bible Society
+bible
+
+// bid : 2013-12-19 dot Bid Limited
+bid
+
+// bike : 2013-08-27 Grand Hollow, LLC
+bike
+
+// bing : 2014-12-18 Microsoft Corporation
+bing
+
+// bingo : 2014-12-04 Sand Cedar, LLC
+bingo
+
+// bio : 2014-03-06 STARTING DOT LIMITED
+bio
+
+// black : 2014-01-16 Afilias Limited
+black
+
+// blackfriday : 2014-01-16 Uniregistry, Corp.
+blackfriday
+
+// blanco : 2015-07-16 BLANCO GmbH + Co KG
+blanco
+
+// blockbuster : 2015-07-30 Dish DBS Corporation
+blockbuster
+
+// blog : 2015-05-14 PRIMER NIVEL S.A.
+blog
+
+// bloomberg : 2014-07-17 Bloomberg IP Holdings LLC
+bloomberg
+
+// blue : 2013-11-07 Afilias Limited
+blue
+
+// bms : 2014-10-30 Bristol-Myers Squibb Company
+bms
+
+// bmw : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
+bmw
+
+// bnl : 2014-07-24 Banca Nazionale del Lavoro
+bnl
+
+// bnpparibas : 2014-05-29 BNP Paribas
+bnpparibas
+
+// boats : 2014-12-04 DERBoats, LLC
+boats
+
+// boehringer : 2015-07-09 Boehringer Ingelheim International GmbH
+boehringer
+
+// bofa : 2015-07-31 NMS Services, Inc.
+bofa
+
+// bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+bom
+
+// bond : 2014-06-05 Bond University Limited
+bond
+
+// boo : 2014-01-30 Charleston Road Registry Inc.
+boo
+
+// book : 2015-08-27 Amazon EU S.à r.l.
+book
+
+// booking : 2015-07-16 Booking.com B.V.
+booking
+
+// boots : 2015-01-08 THE BOOTS COMPANY PLC
+boots
+
+// bosch : 2015-06-18 Robert Bosch GMBH
+bosch
+
+// bostik : 2015-05-28 Bostik SA
+bostik
+
+// bot : 2014-12-18 Amazon EU S.à r.l.
+bot
+
+// boutique : 2013-11-14 Over Galley, LLC
+boutique
+
+// bradesco : 2014-12-18 Banco Bradesco S.A.
+bradesco
+
+// bridgestone : 2014-12-18 Bridgestone Corporation
+bridgestone
+
+// broadway : 2014-12-22 Celebrate Broadway, Inc.
+broadway
+
+// broker : 2014-12-11 IG Group Holdings PLC
+broker
+
+// brother : 2015-01-29 Brother Industries, Ltd.
+brother
+
+// brussels : 2014-02-06 DNS.be vzw
+brussels
+
+// budapest : 2013-11-21 Top Level Domain Holdings Limited
+budapest
+
+// bugatti : 2015-07-23 Bugatti International SA
+bugatti
+
+// build : 2013-11-07 Plan Bee LLC
+build
+
+// builders : 2013-11-07 Atomic Madison, LLC
+builders
+
+// business : 2013-11-07 Spring Cross, LLC
+business
+
+// buy : 2014-12-18 Amazon EU S.à r.l.
+buy
+
+// buzz : 2013-10-02 DOTSTRATEGY CO.
+buzz
+
+// bzh : 2014-02-27 Association www.bzh
+bzh
+
+// cab : 2013-10-24 Half Sunset, LLC
+cab
+
+// cafe : 2015-02-11 Pioneer Canyon, LLC
+cafe
+
+// cal : 2014-07-24 Charleston Road Registry Inc.
+cal
+
+// call : 2014-12-18 Amazon EU S.à r.l.
+call
+
+// calvinklein : 2015-07-30 PVH gTLD Holdings LLC
+calvinklein
+
+// camera : 2013-08-27 Atomic Maple, LLC
+camera
+
+// camp : 2013-11-07 Delta Dynamite, LLC
+camp
+
+// cancerresearch : 2014-05-15 Australian Cancer Research Foundation
+cancerresearch
+
+// canon : 2014-09-12 Canon Inc.
+canon
+
+// capetown : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+capetown
+
+// capital : 2014-03-06 Delta Mill, LLC
+capital
+
+// capitalone : 2015-08-06 Capital One Financial Corporation
+capitalone
+
+// car : 2015-01-22
+car
+
+// caravan : 2013-12-12 Caravan International, Inc.
+caravan
+
+// cards : 2013-12-05 Foggy Hollow, LLC
+cards
+
+// care : 2014-03-06 Goose Cross
+care
+
+// career : 2013-10-09 dotCareer LLC
+career
+
+// careers : 2013-10-02 Wild Corner, LLC
+careers
+
+// cars : 2014-11-13
+cars
+
+// cartier : 2014-06-23 Richemont DNS Inc.
+cartier
+
+// casa : 2013-11-21 Top Level Domain Holdings Limited
+casa
+
+// case : 2015-09-03 CNH Industrial N.V.
+case
+
+// caseih : 2015-09-03 CNH Industrial N.V.
+caseih
+
+// cash : 2014-03-06 Delta Lake, LLC
+cash
+
+// casino : 2014-12-18 Binky Sky, LLC
+casino
+
+// catering : 2013-12-05 New Falls. LLC
+catering
+
+// catholic : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+catholic
+
+// cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+cba
+
+// cbn : 2014-08-22 The Christian Broadcasting Network, Inc.
+cbn
+
+// cbre : 2015-07-02 CBRE, Inc.
+cbre
+
+// cbs : 2015-08-06 CBS Domains Inc.
+cbs
+
+// ceb : 2015-04-09 The Corporate Executive Board Company
+ceb
+
+// center : 2013-11-07 Tin Mill, LLC
+center
+
+// ceo : 2013-11-07 CEOTLD Pty Ltd
+ceo
+
+// cern : 2014-06-05 European Organization for Nuclear Research ("CERN")
+cern
+
+// cfa : 2014-08-28 CFA Institute
+cfa
+
+// cfd : 2014-12-11 IG Group Holdings PLC
+cfd
+
+// chanel : 2015-04-09 Chanel International B.V.
+chanel
+
+// channel : 2014-05-08 Charleston Road Registry Inc.
+channel
+
+// chase : 2015-04-30 JPMorgan Chase & Co.
+chase
+
+// chat : 2014-12-04 Sand Fields, LLC
+chat
+
+// cheap : 2013-11-14 Sand Cover, LLC
+cheap
+
+// chintai : 2015-06-11 CHINTAI Corporation
+chintai
+
+// chloe : 2014-10-16 Richemont DNS Inc.
+chloe
+
+// christmas : 2013-11-21 Uniregistry, Corp.
+christmas
+
+// chrome : 2014-07-24 Charleston Road Registry Inc.
+chrome
+
+// chrysler : 2015-07-30 FCA US LLC.
+chrysler
+
+// church : 2014-02-06 Holly Fields, LLC
+church
+
+// cipriani : 2015-02-19 Hotel Cipriani Srl
+cipriani
+
+// circle : 2014-12-18 Amazon EU S.à r.l.
+circle
+
+// cisco : 2014-12-22 Cisco Technology, Inc.
+cisco
+
+// citadel : 2015-07-23 Citadel Domain LLC
+citadel
+
+// citi : 2015-07-30 Citigroup Inc.
+citi
+
+// citic : 2014-01-09 CITIC Group Corporation
+citic
+
+// city : 2014-05-29 Snow Sky, LLC
+city
+
+// cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc.
+cityeats
+
+// claims : 2014-03-20 Black Corner, LLC
+claims
+
+// cleaning : 2013-12-05 Fox Shadow, LLC
+cleaning
+
+// click : 2014-06-05 Uniregistry, Corp.
+click
+
+// clinic : 2014-03-20 Goose Park, LLC
+clinic
+
+// clinique : 2015-10-01 The Estée Lauder Companies Inc.
+clinique
+
+// clothing : 2013-08-27 Steel Lake, LLC
+clothing
+
+// cloud : 2015-04-16 ARUBA S.p.A.
+cloud
+
+// club : 2013-11-08 .CLUB DOMAINS, LLC
+club
+
+// clubmed : 2015-06-25 Club Méditerranée S.A.
+clubmed
+
+// coach : 2014-10-09 Koko Island, LLC
+coach
+
+// codes : 2013-10-31 Puff Willow, LLC
+codes
+
+// coffee : 2013-10-17 Trixy Cover, LLC
+coffee
+
+// college : 2014-01-16 XYZ.COM LLC
+college
+
+// cologne : 2014-02-05 NetCologne Gesellschaft für Telekommunikation mbH
+cologne
+
+// comcast : 2015-07-23 Comcast IP Holdings I, LLC
+comcast
+
+// commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+commbank
+
+// community : 2013-12-05 Fox Orchard, LLC
+community
+
+// company : 2013-11-07 Silver Avenue, LLC
+company
+
+// compare : 2015-10-08 iSelect Ltd
+compare
+
+// computer : 2013-10-24 Pine Mill, LLC
+computer
+
+// comsec : 2015-01-08 VeriSign, Inc.
+comsec
+
+// condos : 2013-12-05 Pine House, LLC
+condos
+
+// construction : 2013-09-16 Fox Dynamite, LLC
+construction
+
+// consulting : 2013-12-05
+consulting
+
+// contact : 2015-01-08 Top Level Spectrum, Inc.
+contact
+
+// contractors : 2013-09-10 Magic Woods, LLC
+contractors
+
+// cooking : 2013-11-21 Top Level Domain Holdings Limited
+cooking
+
+// cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc.
+cookingchannel
+
+// cool : 2013-11-14 Koko Lake, LLC
+cool
+
+// corsica : 2014-09-25 Collectivité Territoriale de Corse
+corsica
+
+// country : 2013-12-19 Top Level Domain Holdings Limited
+country
+
+// coupon : 2015-02-26 Amazon EU S.à r.l.
+coupon
+
+// coupons : 2015-03-26 Black Island, LLC
+coupons
+
+// courses : 2014-12-04 OPEN UNIVERSITIES AUSTRALIA PTY LTD
+courses
+
+// credit : 2014-03-20 Snow Shadow, LLC
+credit
+
+// creditcard : 2014-03-20 Binky Frostbite, LLC
+creditcard
+
+// creditunion : 2015-01-22 CUNA Performance Resources, LLC
+creditunion
+
+// cricket : 2014-10-09 dot Cricket Limited
+cricket
+
+// crown : 2014-10-24 Crown Equipment Corporation
+crown
+
+// crs : 2014-04-03 Federated Co-operatives Limited
+crs
+
+// cruises : 2013-12-05 Spring Way, LLC
+cruises
+
+// csc : 2014-09-25 Alliance-One Services, Inc.
+csc
+
+// cuisinella : 2014-04-03 SALM S.A.S.
+cuisinella
+
+// cymru : 2014-05-08 Nominet UK
+cymru
+
+// cyou : 2015-01-22 Beijing Gamease Age Digital Technology Co., Ltd.
+cyou
+
+// dabur : 2014-02-06 Dabur India Limited
+dabur
+
+// dad : 2014-01-23 Charleston Road Registry Inc.
+dad
+
+// dance : 2013-10-24 United TLD Holdco Ltd.
+dance
+
+// date : 2014-11-20 dot Date Limited
+date
+
+// dating : 2013-12-05 Pine Fest, LLC
+dating
+
+// datsun : 2014-03-27 NISSAN MOTOR CO., LTD.
+datsun
+
+// day : 2014-01-30 Charleston Road Registry Inc.
+day
+
+// dclk : 2014-11-20 Charleston Road Registry Inc.
+dclk
+
+// dds : 2015-05-07 Top Level Domain Holdings Limited
+dds
+
+// deal : 2015-06-25 Amazon EU S.à r.l.
+deal
+
+// dealer : 2014-12-22 Dealer Dot Com, Inc.
+dealer
+
+// deals : 2014-05-22 Sand Sunset, LLC
+deals
+
+// degree : 2014-03-06
+degree
+
+// delivery : 2014-09-11 Steel Station, LLC
+delivery
+
+// dell : 2014-10-24 Dell Inc.
+dell
+
+// deloitte : 2015-07-31 Deloitte Touche Tohmatsu
+deloitte
+
+// delta : 2015-02-19 Delta Air Lines, Inc.
+delta
+
+// democrat : 2013-10-24 United TLD Holdco Ltd.
+democrat
+
+// dental : 2014-03-20 Tin Birch, LLC
+dental
+
+// dentist : 2014-03-20
+dentist
+
+// desi : 2013-11-14 Desi Networks LLC
+desi
+
+// design : 2014-11-07 Top Level Design, LLC
+design
+
+// dev : 2014-10-16 Charleston Road Registry Inc.
+dev
+
+// dhl : 2015-07-23 Deutsche Post AG
+dhl
+
+// diamonds : 2013-09-22 John Edge, LLC
+diamonds
+
+// diet : 2014-06-26 Uniregistry, Corp.
+diet
+
+// digital : 2014-03-06 Dash Park, LLC
+digital
+
+// direct : 2014-04-10 Half Trail, LLC
+direct
+
+// directory : 2013-09-20 Extra Madison, LLC
+directory
+
+// discount : 2014-03-06 Holly Hill, LLC
+discount
+
+// discover : 2015-07-23 Discover Financial Services
+discover
+
+// dish : 2015-07-30 Dish DBS Corporation
+dish
+
+// diy : 2015-11-05 Lifestyle Domain Holdings, Inc.
+diy
+
+// dnp : 2013-12-13 Dai Nippon Printing Co., Ltd.
+dnp
+
+// docs : 2014-10-16 Charleston Road Registry Inc.
+docs
+
+// dodge : 2015-07-30 FCA US LLC.
+dodge
+
+// dog : 2014-12-04 Koko Mill, LLC
+dog
+
+// doha : 2014-09-18 Communications Regulatory Authority (CRA)
+doha
+
+// domains : 2013-10-17 Sugar Cross, LLC
+domains
+
+// doosan : 2014-04-03 Doosan Corporation
+doosan
+
+// dot : 2015-05-21 Dish DBS Corporation
+dot
+
+// download : 2014-11-20 dot Support Limited
+download
+
+// drive : 2015-03-05 Charleston Road Registry Inc.
+drive
+
+// dstv : 2015-03-12 MultiChoice (Proprietary) Limited
+dstv
+
+// dtv : 2015-06-04 Dish DBS Corporation
+dtv
+
+// dubai : 2015-01-01 Dubai Smart Government Department
+dubai
+
+// duck : 2015-07-23 Johnson Shareholdings, Inc.
+duck
+
+// dunlop : 2015-07-02 The Goodyear Tire & Rubber Company
+dunlop
+
+// duns : 2015-08-06 The Dun & Bradstreet Corporation
+duns
+
+// dupont : 2015-06-25 E.I. du Pont de Nemours and Company
+dupont
+
+// durban : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+durban
+
+// dvag : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+dvag
+
+// dwg : 2015-07-23 Autodesk, Inc.
+dwg
+
+// earth : 2014-12-04 Interlink Co., Ltd.
+earth
+
+// eat : 2014-01-23 Charleston Road Registry Inc.
+eat
+
+// edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V.
+edeka
+
+// education : 2013-11-07 Brice Way, LLC
+education
+
+// email : 2013-10-31 Spring Madison, LLC
+email
+
+// emerck : 2014-04-03 Merck KGaA
+emerck
+
+// emerson : 2015-07-23 Emerson Electric Co.
+emerson
+
+// energy : 2014-09-11 Binky Birch, LLC
+energy
+
+// engineer : 2014-03-06 United TLD Holdco Ltd.
+engineer
+
+// engineering : 2014-03-06 Romeo Canyon
+engineering
+
+// enterprises : 2013-09-20 Snow Oaks, LLC
+enterprises
+
+// epost : 2015-07-23 Deutsche Post AG
+epost
+
+// epson : 2014-12-04 Seiko Epson Corporation
+epson
+
+// equipment : 2013-08-27 Corn Station, LLC
+equipment
+
+// ericsson : 2015-07-09 Telefonaktiebolaget L M Ericsson
+ericsson
+
+// erni : 2014-04-03 ERNI Group Holding AG
+erni
+
+// esq : 2014-05-08 Charleston Road Registry Inc.
+esq
+
+// estate : 2013-08-27 Trixy Park, LLC
+estate
+
+// esurance : 2015-07-23 Esurance Insurance Company
+esurance
+
+// etisalat : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat)
+etisalat
+
+// eurovision : 2014-04-24 European Broadcasting Union (EBU)
+eurovision
+
+// eus : 2013-12-12 Puntueus Fundazioa
+eus
+
+// events : 2013-12-05 Pioneer Maple, LLC
+events
+
+// everbank : 2014-05-15 EverBank
+everbank
+
+// exchange : 2014-03-06 Spring Falls, LLC
+exchange
+
+// expert : 2013-11-21 Magic Pass, LLC
+expert
+
+// exposed : 2013-12-05 Victor Beach, LLC
+exposed
+
+// express : 2015-02-11 Sea Sunset, LLC
+express
+
+// extraspace : 2015-05-14 Extra Space Storage LLC
+extraspace
+
+// fage : 2014-12-18 Fage International S.A.
+fage
+
+// fail : 2014-03-06 Atomic Pipe, LLC
+fail
+
+// fairwinds : 2014-11-13 FairWinds Partners, LLC
+fairwinds
+
+// faith : 2014-11-20 dot Faith Limited
+faith
+
+// family : 2015-04-02
+family
+
+// fan : 2014-03-06
+fan
+
+// fans : 2014-11-07 Asiamix Digital Limited
+fans
+
+// farm : 2013-11-07 Just Maple, LLC
+farm
+
+// farmers : 2015-07-09 Farmers Insurance Exchange
+farmers
+
+// fashion : 2014-07-03 Top Level Domain Holdings Limited
+fashion
+
+// fast : 2014-12-18 Amazon EU S.à r.l.
+fast
+
+// fedex : 2015-08-06 Federal Express Corporation
+fedex
+
+// feedback : 2013-12-19 Top Level Spectrum, Inc.
+feedback
+
+// ferrari : 2015-07-31 Fiat Chrysler Automobiles N.V.
+ferrari
+
+// ferrero : 2014-12-18 Ferrero Trading Lux S.A.
+ferrero
+
+// fiat : 2015-07-31 Fiat Chrysler Automobiles N.V.
+fiat
+
+// fidelity : 2015-07-30 Fidelity Brokerage Services LLC
+fidelity
+
+// fido : 2015-08-06 Rogers Communications Partnership
+fido
+
+// film : 2015-01-08 Motion Picture Domain Registry Pty Ltd
+film
+
+// final : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+final
+
+// finance : 2014-03-20 Cotton Cypress, LLC
+finance
+
+// financial : 2014-03-06 Just Cover, LLC
+financial
+
+// fire : 2015-06-25 Amazon EU S.à r.l.
+fire
+
+// firestone : 2014-12-18 Bridgestone Corporation
+firestone
+
+// firmdale : 2014-03-27 Firmdale Holdings Limited
+firmdale
+
+// fish : 2013-12-12 Fox Woods, LLC
+fish
+
+// fishing : 2013-11-21 Top Level Domain Holdings Limited
+fishing
+
+// fit : 2014-11-07 Top Level Domain Holdings Limited
+fit
+
+// fitness : 2014-03-06 Brice Orchard, LLC
+fitness
+
+// flickr : 2015-04-02 Yahoo! Domain Services Inc.
+flickr
+
+// flights : 2013-12-05 Fox Station, LLC
+flights
+
+// flir : 2015-07-23 FLIR Systems, Inc.
+flir
+
+// florist : 2013-11-07 Half Cypress, LLC
+florist
+
+// flowers : 2014-10-09 Uniregistry, Corp.
+flowers
+
+// flsmidth : 2014-07-24 FLSmidth A/S
+flsmidth
+
+// fly : 2014-05-08 Charleston Road Registry Inc.
+fly
+
+// foo : 2014-01-23 Charleston Road Registry Inc.
+foo
+
+// foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc.
+foodnetwork
+
+// football : 2014-12-18 Foggy Farms, LLC
+football
+
+// ford : 2014-11-13 Ford Motor Company
+ford
+
+// forex : 2014-12-11 IG Group Holdings PLC
+forex
+
+// forsale : 2014-05-22
+forsale
+
+// forum : 2015-04-02 Fegistry, LLC
+forum
+
+// foundation : 2013-12-05 John Dale, LLC
+foundation
+
+// fox : 2015-09-11 FOX Registry, LLC
+fox
+
+// fresenius : 2015-07-30 Fresenius Immobilien-Verwaltungs-GmbH
+fresenius
+
+// frl : 2014-05-15 FRLregistry B.V.
+frl
+
+// frogans : 2013-12-19 OP3FT
+frogans
+
+// frontdoor : 2015-07-02 Lifestyle Domain Holdings, Inc.
+frontdoor
+
+// frontier : 2015-02-05 Frontier Communications Corporation
+frontier
+
+// ftr : 2015-07-16 Frontier Communications Corporation
+ftr
+
+// fujitsu : 2015-07-30 Fujitsu Limited
+fujitsu
+
+// fujixerox : 2015-07-23 Xerox DNHC LLC
+fujixerox
+
+// fund : 2014-03-20 John Castle, LLC
+fund
+
+// furniture : 2014-03-20 Lone Fields, LLC
+furniture
+
+// futbol : 2013-09-20
+futbol
+
+// fyi : 2015-04-02 Silver Tigers, LLC
+fyi
+
+// gal : 2013-11-07 Asociación puntoGAL
+gal
+
+// gallery : 2013-09-13 Sugar House, LLC
+gallery
+
+// gallo : 2015-06-11 Gallo Vineyards, Inc.
+gallo
+
+// gallup : 2015-02-19 Gallup, Inc.
+gallup
+
+// game : 2015-05-28 Uniregistry, Corp.
+game
+
+// games : 2015-05-28 Foggy Beach, LLC
+games
+
+// gap : 2015-07-31 The Gap, Inc.
+gap
+
+// garden : 2014-06-26 Top Level Domain Holdings Limited
+garden
+
+// gbiz : 2014-07-17 Charleston Road Registry Inc.
+gbiz
+
+// gdn : 2014-07-31 Joint Stock Company "Navigation-information systems"
+gdn
+
+// gea : 2014-12-04 GEA Group Aktiengesellschaft
+gea
+
+// gent : 2014-01-23 COMBELL GROUP NV/SA
+gent
+
+// genting : 2015-03-12 Resorts World Inc Pte. Ltd.
+genting
+
+// george : 2015-07-31 Wal-Mart Stores, Inc.
+george
+
+// ggee : 2014-01-09 GMO Internet, Inc.
+ggee
+
+// gift : 2013-10-17 Uniregistry, Corp.
+gift
+
+// gifts : 2014-07-03 Goose Sky, LLC
+gifts
+
+// gives : 2014-03-06 United TLD Holdco Ltd.
+gives
+
+// giving : 2014-11-13 Giving Limited
+giving
+
+// glade : 2015-07-23 Johnson Shareholdings, Inc.
+glade
+
+// glass : 2013-11-07 Black Cover, LLC
+glass
+
+// gle : 2014-07-24 Charleston Road Registry Inc.
+gle
+
+// global : 2014-04-17 Dot GLOBAL AS
+global
+
+// globo : 2013-12-19 Globo Comunicação e Participações S.A
+globo
+
+// gmail : 2014-05-01 Charleston Road Registry Inc.
+gmail
+
+// gmo : 2014-01-09 GMO Internet, Inc.
+gmo
+
+// gmx : 2014-04-24 1&1 Mail & Media GmbH
+gmx
+
+// godaddy : 2015-07-23 Go Daddy East, LLC
+godaddy
+
+// gold : 2015-01-22 June Edge, LLC
+gold
+
+// goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+goldpoint
+
+// golf : 2014-12-18 Lone falls, LLC
+golf
+
+// goo : 2014-12-18 NTT Resonant Inc.
+goo
+
+// goodhands : 2015-07-31 Allstate Fire and Casualty Insurance Company
+goodhands
+
+// goodyear : 2015-07-02 The Goodyear Tire & Rubber Company
+goodyear
+
+// goog : 2014-11-20 Charleston Road Registry Inc.
+goog
+
+// google : 2014-07-24 Charleston Road Registry Inc.
+google
+
+// gop : 2014-01-16 Republican State Leadership Committee, Inc.
+gop
+
+// got : 2014-12-18 Amazon EU S.à r.l.
+got
+
+// gotv : 2015-03-12 MultiChoice (Proprietary) Limited
+gotv
+
+// grainger : 2015-05-07 Grainger Registry Services, LLC
+grainger
+
+// graphics : 2013-09-13 Over Madison, LLC
+graphics
+
+// gratis : 2014-03-20 Pioneer Tigers, LLC
+gratis
+
+// green : 2014-05-08 Afilias Limited
+green
+
+// gripe : 2014-03-06 Corn Sunset, LLC
+gripe
+
+// group : 2014-08-15 Romeo Town, LLC
+group
+
+// guardian : 2015-07-30 The Guardian Life Insurance Company of America
+guardian
+
+// gucci : 2014-11-13 Guccio Gucci S.p.a.
+gucci
+
+// guge : 2014-08-28 Charleston Road Registry Inc.
+guge
+
+// guide : 2013-09-13 Snow Moon, LLC
+guide
+
+// guitars : 2013-11-14 Uniregistry, Corp.
+guitars
+
+// guru : 2013-08-27 Pioneer Cypress, LLC
+guru
+
+// hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH
+hamburg
+
+// hangout : 2014-11-13 Charleston Road Registry Inc.
+hangout
+
+// haus : 2013-12-05
+haus
+
+// hbo : 2015-07-30 HBO Registry Services, Inc.
+hbo
+
+// hdfc : 2015-07-30 HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED
+hdfc
+
+// hdfcbank : 2015-02-12 HDFC Bank Limited
+hdfcbank
+
+// health : 2015-02-11 DotHealth, LLC
+health
+
+// healthcare : 2014-06-12 Silver Glen, LLC
+healthcare
+
+// help : 2014-06-26 Uniregistry, Corp.
+help
+
+// helsinki : 2015-02-05 City of Helsinki
+helsinki
+
+// here : 2014-02-06 Charleston Road Registry Inc.
+here
+
+// hermes : 2014-07-10 HERMES INTERNATIONAL
+hermes
+
+// hgtv : 2015-07-02 Lifestyle Domain Holdings, Inc.
+hgtv
+
+// hiphop : 2014-03-06 Uniregistry, Corp.
+hiphop
+
+// hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc.
+hisamitsu
+
+// hitachi : 2014-10-31 Hitachi, Ltd.
+hitachi
+
+// hiv : 2014-03-13 dotHIV gemeinnuetziger e.V.
+hiv
+
+// hkt : 2015-05-14 PCCW-HKT DataCom Services Limited
+hkt
+
+// hockey : 2015-03-19 Half Willow, LLC
+hockey
+
+// holdings : 2013-08-27 John Madison, LLC
+holdings
+
+// holiday : 2013-11-07 Goose Woods, LLC
+holiday
+
+// homedepot : 2015-04-02 Homer TLC, Inc.
+homedepot
+
+// homegoods : 2015-07-16 The TJX Companies, Inc.
+homegoods
+
+// homes : 2014-01-09 DERHomes, LLC
+homes
+
+// homesense : 2015-07-16 The TJX Companies, Inc.
+homesense
+
+// honda : 2014-12-18 Honda Motor Co., Ltd.
+honda
+
+// honeywell : 2015-07-23 Honeywell GTLD LLC
+honeywell
+
+// horse : 2013-11-21 Top Level Domain Holdings Limited
+horse
+
+// host : 2014-04-17 DotHost Inc.
+host
+
+// hosting : 2014-05-29 Uniregistry, Corp.
+hosting
+
+// hot : 2015-08-27 Amazon EU S.à r.l.
+hot
+
+// hoteles : 2015-03-05 Travel Reservations SRL
+hoteles
+
+// hotmail : 2014-12-18 Microsoft Corporation
+hotmail
+
+// house : 2013-11-07 Sugar Park, LLC
+house
+
+// how : 2014-01-23 Charleston Road Registry Inc.
+how
+
+// hsbc : 2014-10-24 HSBC Holdings PLC
+hsbc
+
+// htc : 2015-04-02 HTC corporation
+htc
+
+// hughes : 2015-07-30 Hughes Satellite Systems Corporation
+hughes
+
+// hyatt : 2015-07-30 Hyatt GTLD, L.L.C.
+hyatt
+
+// hyundai : 2015-07-09 Hyundai Motor Company
+hyundai
+
+// ibm : 2014-07-31 International Business Machines Corporation
+ibm
+
+// icbc : 2015-02-19 Industrial and Commercial Bank of China Limited
+icbc
+
+// ice : 2014-10-30 IntercontinentalExchange, Inc.
+ice
+
+// icu : 2015-01-08 One.com A/S
+icu
+
+// ieee : 2015-07-23 IEEE Global LLC
+ieee
+
+// ifm : 2014-01-30 ifm electronic gmbh
+ifm
+
+// iinet : 2014-07-03 Connect West Pty. Ltd.
+iinet
+
+// ikano : 2015-07-09 Ikano S.A.
+ikano
+
+// imamat : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation)
+imamat
+
+// imdb : 2015-06-25 Amazon EU S.à r.l.
+imdb
+
+// immo : 2014-07-10 Auburn Bloom, LLC
+immo
+
+// immobilien : 2013-11-07 United TLD Holdco Ltd.
+immobilien
+
+// industries : 2013-12-05 Outer House, LLC
+industries
+
+// infiniti : 2014-03-27 NISSAN MOTOR CO., LTD.
+infiniti
+
+// ing : 2014-01-23 Charleston Road Registry Inc.
+ing
+
+// ink : 2013-12-05 Top Level Design, LLC
+ink
+
+// institute : 2013-11-07 Outer Maple, LLC
+institute
+
+// insurance : 2015-02-19 fTLD Registry Services LLC
+insurance
+
+// insure : 2014-03-20 Pioneer Willow, LLC
+insure
+
+// intel : 2015-08-06 Intel Corporation
+intel
+
+// international : 2013-11-07 Wild Way, LLC
+international
+
+// intuit : 2015-07-30 Intuit Administrative Services, Inc.
+intuit
+
+// investments : 2014-03-20 Holly Glen, LLC
+investments
+
+// ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A.
+ipiranga
+
+// irish : 2014-08-07 Dot-Irish LLC
+irish
+
+// iselect : 2015-02-11 iSelect Ltd
+iselect
+
+// ismaili : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation)
+ismaili
+
+// ist : 2014-08-28 Istanbul Metropolitan Municipality
+ist
+
+// istanbul : 2014-08-28 Istanbul Metropolitan Municipality
+istanbul
+
+// itau : 2014-10-02 Itau Unibanco Holding S.A.
+itau
+
+// itv : 2015-07-09 ITV Services Limited
+itv
+
+// iveco : 2015-09-03 CNH Industrial N.V.
+iveco
+
+// iwc : 2014-06-23 Richemont DNS Inc.
+iwc
+
+// jaguar : 2014-11-13 Jaguar Land Rover Ltd
+jaguar
+
+// java : 2014-06-19 Oracle Corporation
+java
+
+// jcb : 2014-11-20 JCB Co., Ltd.
+jcb
+
+// jcp : 2015-04-23 JCP Media, Inc.
+jcp
+
+// jeep : 2015-07-30 FCA US LLC.
+jeep
+
+// jetzt : 2014-01-09 New TLD Company AB
+jetzt
+
+// jewelry : 2015-03-05 Wild Bloom, LLC
+jewelry
+
+// jio : 2015-04-02 Affinity Names, Inc.
+jio
+
+// jlc : 2014-12-04 Richemont DNS Inc.
+jlc
+
+// jll : 2015-04-02 Jones Lang LaSalle Incorporated
+jll
+
+// jmp : 2015-03-26 Matrix IP LLC
+jmp
+
+// jnj : 2015-06-18 Johnson & Johnson Services, Inc.
+jnj
+
+// joburg : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+joburg
+
+// jot : 2014-12-18 Amazon EU S.à r.l.
+jot
+
+// joy : 2014-12-18 Amazon EU S.à r.l.
+joy
+
+// jpmorgan : 2015-04-30 JPMorgan Chase & Co.
+jpmorgan
+
+// jprs : 2014-09-18 Japan Registry Services Co., Ltd.
+jprs
+
+// juegos : 2014-03-20 Uniregistry, Corp.
+juegos
+
+// juniper : 2015-07-30 JUNIPER NETWORKS, INC.
+juniper
+
+// kaufen : 2013-11-07 United TLD Holdco Ltd.
+kaufen
+
+// kddi : 2014-09-12 KDDI CORPORATION
+kddi
+
+// kerryhotels : 2015-04-30 Kerry Trading Co. Limited
+kerryhotels
+
+// kerrylogistics : 2015-04-09 Kerry Trading Co. Limited
+kerrylogistics
+
+// kerryproperties : 2015-04-09 Kerry Trading Co. Limited
+kerryproperties
+
+// kfh : 2014-12-04 Kuwait Finance House
+kfh
+
+// kia : 2015-07-09 KIA MOTORS CORPORATION
+kia
+
+// kim : 2013-09-23 Afilias Limited
+kim
+
+// kinder : 2014-11-07 Ferrero Trading Lux S.A.
+kinder
+
+// kindle : 2015-06-25 Amazon EU S.à r.l.
+kindle
+
+// kitchen : 2013-09-20 Just Goodbye, LLC
+kitchen
+
+// kiwi : 2013-09-20 DOT KIWI LIMITED
+kiwi
+
+// koeln : 2014-01-09 NetCologne Gesellschaft für Telekommunikation mbH
+koeln
+
+// komatsu : 2015-01-08 Komatsu Ltd.
+komatsu
+
+// kosher : 2015-08-20 Kosher Marketing Assets LLC
+kosher
+
+// kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft)
+kpmg
+
+// kpn : 2015-01-08 Koninklijke KPN N.V.
+kpn
+
+// krd : 2013-12-05 KRG Department of Information Technology
+krd
+
+// kred : 2013-12-19 KredTLD Pty Ltd
+kred
+
+// kuokgroup : 2015-04-09 Kerry Trading Co. Limited
+kuokgroup
+
+// kyknet : 2015-03-05 Electronic Media Network (Pty) Ltd
+kyknet
+
+// kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen
+kyoto
+
+// lacaixa : 2014-01-09 CAIXA D'ESTALVIS I PENSIONS DE BARCELONA
+lacaixa
+
+// ladbrokes : 2015-08-06 LADBROKES INTERNATIONAL PLC
+ladbrokes
+
+// lamborghini : 2015-06-04 Automobili Lamborghini S.p.A.
+lamborghini
+
+// lamer : 2015-10-01 The Estée Lauder Companies Inc.
+lamer
+
+// lancaster : 2015-02-12 LANCASTER
+lancaster
+
+// lancia : 2015-07-31 Fiat Chrysler Automobiles N.V.
+lancia
+
+// lancome : 2015-07-23 L'Oréal
+lancome
+
+// land : 2013-09-10 Pine Moon, LLC
+land
+
+// landrover : 2014-11-13 Jaguar Land Rover Ltd
+landrover
+
+// lanxess : 2015-07-30 LANXESS Corporation
+lanxess
+
+// lasalle : 2015-04-02 Jones Lang LaSalle Incorporated
+lasalle
+
+// lat : 2014-10-16 ECOM-LAC Federaciòn de Latinoamèrica y el Caribe para Internet y el Comercio Electrònico
+lat
+
+// latino : 2015-07-30 Dish DBS Corporation
+latino
+
+// latrobe : 2014-06-16 La Trobe University
+latrobe
+
+// law : 2015-01-22 Minds + Machines Group Limited
+law
+
+// lawyer : 2014-03-20
+lawyer
+
+// lds : 2014-03-20 IRI Domain Management, LLC ("Applicant")
+lds
+
+// lease : 2014-03-06 Victor Trail, LLC
+lease
+
+// leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc
+leclerc
+
+// lefrak : 2015-07-16 LeFrak Organization, Inc.
+lefrak
+
+// legal : 2014-10-16 Blue Falls, LLC
+legal
+
+// lego : 2015-07-16 LEGO Juris A/S
+lego
+
+// lexus : 2015-04-23 TOYOTA MOTOR CORPORATION
+lexus
+
+// lgbt : 2014-05-08 Afilias Limited
+lgbt
+
+// liaison : 2014-10-02 Liaison Technologies, Incorporated
+liaison
+
+// lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
+lidl
+
+// life : 2014-02-06 Trixy Oaks, LLC
+life
+
+// lifeinsurance : 2015-01-15 American Council of Life Insurers
+lifeinsurance
+
+// lifestyle : 2014-12-11 Lifestyle Domain Holdings, Inc.
+lifestyle
+
+// lighting : 2013-08-27 John McCook, LLC
+lighting
+
+// like : 2014-12-18 Amazon EU S.à r.l.
+like
+
+// lilly : 2015-07-31 Eli Lilly and Company
+lilly
+
+// limited : 2014-03-06 Big Fest, LLC
+limited
+
+// limo : 2013-10-17 Hidden Frostbite, LLC
+limo
+
+// lincoln : 2014-11-13 Ford Motor Company
+lincoln
+
+// linde : 2014-12-04 Linde Aktiengesellschaft
+linde
+
+// link : 2013-11-14 Uniregistry, Corp.
+link
+
+// lipsy : 2015-06-25 Lipsy Ltd
+lipsy
+
+// live : 2014-12-04
+live
+
+// living : 2015-07-30 Lifestyle Domain Holdings, Inc.
+living
+
+// lixil : 2015-03-19 LIXIL Group Corporation
+lixil
+
+// loan : 2014-11-20 dot Loan Limited
+loan
+
+// loans : 2014-03-20 June Woods, LLC
+loans
+
+// locker : 2015-06-04 Dish DBS Corporation
+locker
+
+// locus : 2015-06-25 Locus Analytics LLC
+locus
+
+// loft : 2015-07-30 Annco, Inc.
+loft
+
+// lol : 2015-01-30 Uniregistry, Corp.
+lol
+
+// london : 2013-11-14 Dot London Domains Limited
+london
+
+// lotte : 2014-11-07 Lotte Holdings Co., Ltd.
+lotte
+
+// lotto : 2014-04-10 Afilias Limited
+lotto
+
+// love : 2014-12-22 Merchant Law Group LLP
+love
+
+// lpl : 2015-07-30 LPL Holdings, Inc.
+lpl
+
+// lplfinancial : 2015-07-30 LPL Holdings, Inc.
+lplfinancial
+
+// ltd : 2014-09-25 Over Corner, LLC
+ltd
+
+// ltda : 2014-04-17 DOMAIN ROBOT SERVICOS DE HOSPEDAGEM NA INTERNET LTDA
+ltda
+
+// lundbeck : 2015-08-06 H. Lundbeck A/S
+lundbeck
+
+// lupin : 2014-11-07 LUPIN LIMITED
+lupin
+
+// luxe : 2014-01-09 Top Level Domain Holdings Limited
+luxe
+
+// luxury : 2013-10-17 Luxury Partners, LLC
+luxury
+
+// macys : 2015-07-31 Macys, Inc.
+macys
+
+// madrid : 2014-05-01 Comunidad de Madrid
+madrid
+
+// maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF)
+maif
+
+// maison : 2013-12-05 Victor Frostbite, LLC
+maison
+
+// makeup : 2015-01-15 L'Oréal
+makeup
+
+// man : 2014-12-04 MAN SE
+man
+
+// management : 2013-11-07 John Goodbye, LLC
+management
+
+// mango : 2013-10-24 PUNTO FA S.L.
+mango
+
+// market : 2014-03-06
+market
+
+// marketing : 2013-11-07 Fern Pass, LLC
+marketing
+
+// markets : 2014-12-11 IG Group Holdings PLC
+markets
+
+// marriott : 2014-10-09 Marriott Worldwide Corporation
+marriott
+
+// marshalls : 2015-07-16 The TJX Companies, Inc.
+marshalls
+
+// maserati : 2015-07-31 Fiat Chrysler Automobiles N.V.
+maserati
+
+// mattel : 2015-08-06 Mattel Sites, Inc.
+mattel
+
+// mba : 2015-04-02 Lone Hollow, LLC
+mba
+
+// mcd : 2015-07-30 McDonald’s Corporation
+mcd
+
+// mcdonalds : 2015-07-30 McDonald’s Corporation
+mcdonalds
+
+// mckinsey : 2015-07-31 McKinsey Holdings, Inc.
+mckinsey
+
+// med : 2015-08-06 Medistry LLC
+med
+
+// media : 2014-03-06 Grand Glen, LLC
+media
+
+// meet : 2014-01-16
+meet
+
+// melbourne : 2014-05-29 The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation
+melbourne
+
+// meme : 2014-01-30 Charleston Road Registry Inc.
+meme
+
+// memorial : 2014-10-16 Dog Beach, LLC
+memorial
+
+// men : 2015-02-26 Exclusive Registry Limited
+men
+
+// menu : 2013-09-11 Wedding TLD2, LLC
+menu
+
+// meo : 2014-11-07 PT Comunicacoes S.A.
+meo
+
+// metlife : 2015-05-07 MetLife Services and Solutions, LLC
+metlife
+
+// miami : 2013-12-19 Top Level Domain Holdings Limited
+miami
+
+// microsoft : 2014-12-18 Microsoft Corporation
+microsoft
+
+// mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
+mini
+
+// mint : 2015-07-30 Intuit Administrative Services, Inc.
+mint
+
+// mit : 2015-07-02 Massachusetts Institute of Technology
+mit
+
+// mitsubishi : 2015-07-23 Mitsubishi Corporation
+mitsubishi
+
+// mlb : 2015-05-21 MLB Advanced Media DH, LLC
+mlb
+
+// mls : 2015-04-23 The Canadian Real Estate Association
+mls
+
+// mma : 2014-11-07 MMA IARD
+mma
+
+// mnet : 2015-03-05 Electronic Media Network (Pty) Ltd
+mnet
+
+// mobily : 2014-12-18 GreenTech Consultancy Company W.L.L.
+mobily
+
+// moda : 2013-11-07 United TLD Holdco Ltd.
+moda
+
+// moe : 2013-11-13 Interlink Co., Ltd.
+moe
+
+// moi : 2014-12-18 Amazon EU S.à r.l.
+moi
+
+// mom : 2015-04-16 Uniregistry, Corp.
+mom
+
+// monash : 2013-09-30 Monash University
+monash
+
+// money : 2014-10-16 Outer McCook, LLC
+money
+
+// monster : 2015-09-11 Monster Worldwide, Inc.
+monster
+
+// montblanc : 2014-06-23 Richemont DNS Inc.
+montblanc
+
+// mopar : 2015-07-30 FCA US LLC.
+mopar
+
+// mormon : 2013-12-05 IRI Domain Management, LLC ("Applicant")
+mormon
+
+// mortgage : 2014-03-20
+mortgage
+
+// moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+moscow
+
+// moto : 2015-06-04 Charleston Road Registry Inc.
+moto
+
+// motorcycles : 2014-01-09 DERMotorcycles, LLC
+motorcycles
+
+// mov : 2014-01-30 Charleston Road Registry Inc.
+mov
+
+// movie : 2015-02-05 New Frostbite, LLC
+movie
+
+// movistar : 2014-10-16 Telefónica S.A.
+movistar
+
+// msd : 2015-07-23 MSD Registry Holdings, Inc.
+msd
+
+// mtn : 2014-12-04 MTN Dubai Limited
+mtn
+
+// mtpc : 2014-11-20 Mitsubishi Tanabe Pharma Corporation
+mtpc
+
+// mtr : 2015-03-12 MTR Corporation Limited
+mtr
+
+// multichoice : 2015-03-12 MultiChoice (Proprietary) Limited
+multichoice
+
+// mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC
+mutual
+
+// mutuelle : 2015-06-18 Fédération Nationale de la Mutualité Française
+mutuelle
+
+// mzansimagic : 2015-03-05 Electronic Media Network (Pty) Ltd
+mzansimagic
+
+// nab : 2015-08-20 National Australia Bank Limited
+nab
+
+// nadex : 2014-12-11 IG Group Holdings PLC
+nadex
+
+// nagoya : 2013-10-24 GMO Registry, Inc.
+nagoya
+
+// naspers : 2015-02-12 Intelprop (Proprietary) Limited
+naspers
+
+// nationwide : 2015-07-23 Nationwide Mutual Insurance Company
+nationwide
+
+// natura : 2015-03-12 NATURA COSMÉTICOS S.A.
+natura
+
+// navy : 2014-03-06 United TLD Holdco Ltd.
+navy
+
+// nba : 2015-07-31 NBA REGISTRY, LLC
+nba
+
+// nec : 2015-01-08 NEC Corporation
+nec
+
+// netbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+netbank
+
+// netflix : 2015-06-18 Netflix, Inc.
+netflix
+
+// network : 2013-11-14 Trixy Manor, LLC
+network
+
+// neustar : 2013-12-05 NeuStar, Inc.
+neustar
+
+// new : 2014-01-30 Charleston Road Registry Inc.
+new
+
+// newholland : 2015-09-03 CNH Industrial N.V.
+newholland
+
+// news : 2014-12-18
+news
+
+// next : 2015-06-18 Next plc
+next
+
+// nextdirect : 2015-06-18 Next plc
+nextdirect
+
+// nexus : 2014-07-24 Charleston Road Registry Inc.
+nexus
+
+// nfl : 2015-07-23 NFL Reg Ops LLC
+nfl
+
+// ngo : 2014-03-06 Public Interest Registry
+ngo
+
+// nhk : 2014-02-13 Japan Broadcasting Corporation (NHK)
+nhk
+
+// nico : 2014-12-04 DWANGO Co., Ltd.
+nico
+
+// nike : 2015-07-23 NIKE, Inc.
+nike
+
+// nikon : 2015-05-21 NIKON CORPORATION
+nikon
+
+// ninja : 2013-11-07 United TLD Holdco Ltd.
+ninja
+
+// nissan : 2014-03-27 NISSAN MOTOR CO., LTD.
+nissan
+
+// nissay : 2015-10-29 Nippon Life Insurance Company
+nissay
+
+// nokia : 2015-01-08 Nokia Corporation
+nokia
+
+// northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC
+northwesternmutual
+
+// norton : 2014-12-04 Symantec Corporation
+norton
+
+// now : 2015-06-25 Amazon EU S.à r.l.
+now
+
+// nowruz : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+nowruz
+
+// nowtv : 2015-05-14 Starbucks (HK) Limited
+nowtv
+
+// nra : 2014-05-22 NRA Holdings Company, INC.
+nra
+
+// nrw : 2013-11-21 Minds + Machines GmbH
+nrw
+
+// ntt : 2014-10-31 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ntt
+
+// nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications
+nyc
+
+// obi : 2014-09-25 OBI Group Holding SE & Co. KGaA
+obi
+
+// observer : 2015-04-30 Guardian News and Media Limited
+observer
+
+// off : 2015-07-23 Johnson Shareholdings, Inc.
+off
+
+// office : 2015-03-12 Microsoft Corporation
+office
+
+// okinawa : 2013-12-05 BusinessRalliart Inc.
+okinawa
+
+// olayan : 2015-05-14 Crescent Holding GmbH
+olayan
+
+// olayangroup : 2015-05-14 Crescent Holding GmbH
+olayangroup
+
+// oldnavy : 2015-07-31 The Gap, Inc.
+oldnavy
+
+// ollo : 2015-06-04 Dish DBS Corporation
+ollo
+
+// omega : 2015-01-08 The Swatch Group Ltd
+omega
+
+// one : 2014-11-07 One.com A/S
+one
+
+// ong : 2014-03-06 Public Interest Registry
+ong
+
+// onl : 2013-09-16 I-Registry Ltd.
+onl
+
+// online : 2015-01-15 DotOnline Inc.
+online
+
+// onyourside : 2015-07-23 Nationwide Mutual Insurance Company
+onyourside
+
+// ooo : 2014-01-09 INFIBEAM INCORPORATION LIMITED
+ooo
+
+// open : 2015-07-31 American Express Travel Related Services Company, Inc.
+open
+
+// oracle : 2014-06-19 Oracle Corporation
+oracle
+
+// orange : 2015-03-12 Orange Brand Services Limited
+orange
+
+// organic : 2014-03-27 Afilias Limited
+organic
+
+// orientexpress : 2015-02-05 Belmond Ltd.
+orientexpress
+
+// origins : 2015-10-01 The Estée Lauder Companies Inc.
+origins
+
+// osaka : 2014-09-04 Interlink Co., Ltd.
+osaka
+
+// otsuka : 2013-10-11 Otsuka Holdings Co., Ltd.
+otsuka
+
+// ott : 2015-06-04 Dish DBS Corporation
+ott
+
+// ovh : 2014-01-16 OVH SAS
+ovh
+
+// page : 2014-12-04 Charleston Road Registry Inc.
+page
+
+// pamperedchef : 2015-02-05 The Pampered Chef, Ltd.
+pamperedchef
+
+// panasonic : 2015-07-30 Panasonic Corporation
+panasonic
+
+// panerai : 2014-11-07 Richemont DNS Inc.
+panerai
+
+// paris : 2014-01-30 City of Paris
+paris
+
+// pars : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+pars
+
+// partners : 2013-12-05 Magic Glen, LLC
+partners
+
+// parts : 2013-12-05 Sea Goodbye, LLC
+parts
+
+// party : 2014-09-11 Blue Sky Registry Limited
+party
+
+// passagens : 2015-03-05 Travel Reservations SRL
+passagens
+
+// pay : 2015-08-27 Amazon EU S.à r.l.
+pay
+
+// payu : 2015-02-12 MIH PayU B.V.
+payu
+
+// pccw : 2015-05-14 PCCW Enterprises Limited
+pccw
+
+// pet : 2015-05-07 Afilias plc
+pet
+
+// pfizer : 2015-09-11 Pfizer Inc.
+pfizer
+
+// pharmacy : 2014-06-19 National Association of Boards of Pharmacy
+pharmacy
+
+// philips : 2014-11-07 Koninklijke Philips N.V.
+philips
+
+// photo : 2013-11-14 Uniregistry, Corp.
+photo
+
+// photography : 2013-09-20 Sugar Glen, LLC
+photography
+
+// photos : 2013-10-17 Sea Corner, LLC
+photos
+
+// physio : 2014-05-01 PhysBiz Pty Ltd
+physio
+
+// piaget : 2014-10-16 Richemont DNS Inc.
+piaget
+
+// pics : 2013-11-14 Uniregistry, Corp.
+pics
+
+// pictet : 2014-06-26 Pictet Europe S.A.
+pictet
+
+// pictures : 2014-03-06 Foggy Sky, LLC
+pictures
+
+// pid : 2015-01-08 Top Level Spectrum, Inc.
+pid
+
+// pin : 2014-12-18 Amazon EU S.à r.l.
+pin
+
+// ping : 2015-06-11 Ping Registry Provider, Inc.
+ping
+
+// pink : 2013-10-01 Afilias Limited
+pink
+
+// pioneer : 2015-07-16 Pioneer Corporation
+pioneer
+
+// pizza : 2014-06-26 Foggy Moon, LLC
+pizza
+
+// place : 2014-04-24 Snow Galley, LLC
+place
+
+// play : 2015-03-05 Charleston Road Registry Inc.
+play
+
+// playstation : 2015-07-02 Sony Computer Entertainment Inc.
+playstation
+
+// plumbing : 2013-09-10 Spring Tigers, LLC
+plumbing
+
+// plus : 2015-02-05 Sugar Mill, LLC
+plus
+
+// pnc : 2015-07-02 PNC Domain Co., LLC
+pnc
+
+// pohl : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+pohl
+
+// poker : 2014-07-03 Afilias Domains No. 5 Limited
+poker
+
+// politie : 2015-08-20 Politie Nederland
+politie
+
+// porn : 2014-10-16 ICM Registry PN LLC
+porn
+
+// pramerica : 2015-07-30 Prudential Financial, Inc.
+pramerica
+
+// praxi : 2013-12-05 Praxi S.p.A.
+praxi
+
+// press : 2014-04-03 DotPress Inc.
+press
+
+// prime : 2015-06-25 Amazon EU S.à r.l.
+prime
+
+// prod : 2014-01-23 Charleston Road Registry Inc.
+prod
+
+// productions : 2013-12-05 Magic Birch, LLC
+productions
+
+// prof : 2014-07-24 Charleston Road Registry Inc.
+prof
+
+// progressive : 2015-07-23 Progressive Casualty Insurance Company
+progressive
+
+// promo : 2014-12-18 Play.PROMO Oy
+promo
+
+// properties : 2013-12-05 Big Pass, LLC
+properties
+
+// property : 2014-05-22 Uniregistry, Corp.
+property
+
+// protection : 2015-04-23
+protection
+
+// pru : 2015-07-30 Prudential Financial, Inc.
+pru
+
+// prudential : 2015-07-30 Prudential Financial, Inc.
+prudential
+
+// pub : 2013-12-12 United TLD Holdco Ltd.
+pub
+
+// pwc : 2015-10-29 PricewaterhouseCoopers LLP
+pwc
+
+// qpon : 2013-11-14 dotCOOL, Inc.
+qpon
+
+// quebec : 2013-12-19 PointQuébec Inc
+quebec
+
+// quest : 2015-03-26 Quest ION Limited
+quest
+
+// qvc : 2015-07-30 QVC, Inc.
+qvc
+
+// racing : 2014-12-04 Premier Registry Limited
+racing
+
+// raid : 2015-07-23 Johnson Shareholdings, Inc.
+raid
+
+// read : 2014-12-18 Amazon EU S.à r.l.
+read
+
+// realestate : 2015-09-11 dotRealEstate LLC
+realestate
+
+// realtor : 2014-05-29 Real Estate Domains LLC
+realtor
+
+// realty : 2015-03-19 Fegistry, LLC
+realty
+
+// recipes : 2013-10-17 Grand Island, LLC
+recipes
+
+// red : 2013-11-07 Afilias Limited
+red
+
+// redstone : 2014-10-31 Redstone Haute Couture Co., Ltd.
+redstone
+
+// redumbrella : 2015-03-26 Travelers TLD, LLC
+redumbrella
+
+// rehab : 2014-03-06 United TLD Holdco Ltd.
+rehab
+
+// reise : 2014-03-13
+reise
+
+// reisen : 2014-03-06 New Cypress, LLC
+reisen
+
+// reit : 2014-09-04 National Association of Real Estate Investment Trusts, Inc.
+reit
+
+// reliance : 2015-04-02 Reliance Industries Limited
+reliance
+
+// ren : 2013-12-12 Beijing Qianxiang Wangjing Technology Development Co., Ltd.
+ren
+
+// rent : 2014-12-04 DERRent, LLC
+rent
+
+// rentals : 2013-12-05 Big Hollow,LLC
+rentals
+
+// repair : 2013-11-07 Lone Sunset, LLC
+repair
+
+// report : 2013-12-05 Binky Glen, LLC
+report
+
+// republican : 2014-03-20 United TLD Holdco Ltd.
+republican
+
+// rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+rest
+
+// restaurant : 2014-07-03 Snow Avenue, LLC
+restaurant
+
+// review : 2014-11-20 dot Review Limited
+review
+
+// reviews : 2013-09-13
+reviews
+
+// rexroth : 2015-06-18 Robert Bosch GMBH
+rexroth
+
+// rich : 2013-11-21 I-Registry Ltd.
+rich
+
+// richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited
+richardli
+
+// ricoh : 2014-11-20 Ricoh Company, Ltd.
+ricoh
+
+// rightathome : 2015-07-23 Johnson Shareholdings, Inc.
+rightathome
+
+// ril : 2015-04-02 Reliance Industries Limited
+ril
+
+// rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO
+rio
+
+// rip : 2014-07-10 United TLD Holdco Ltd.
+rip
+
+// rocher : 2014-12-18 Ferrero Trading Lux S.A.
+rocher
+
+// rocks : 2013-11-14
+rocks
+
+// rodeo : 2013-12-19 Top Level Domain Holdings Limited
+rodeo
+
+// rogers : 2015-08-06 Rogers Communications Partnership
+rogers
+
+// room : 2014-12-18 Amazon EU S.à r.l.
+room
+
+// rsvp : 2014-05-08 Charleston Road Registry Inc.
+rsvp
+
+// ruhr : 2013-10-02 regiodot GmbH & Co. KG
+ruhr
+
+// run : 2015-03-19 Snow Park, LLC
+run
+
+// rwe : 2015-04-02 RWE AG
+rwe
+
+// ryukyu : 2014-01-09 BusinessRalliart Inc.
+ryukyu
+
+// saarland : 2013-12-12 dotSaarland GmbH
+saarland
+
+// safe : 2014-12-18 Amazon EU S.à r.l.
+safe
+
+// safety : 2015-01-08 Safety Registry Services, LLC.
+safety
+
+// sakura : 2014-12-18 SAKURA Internet Inc.
+sakura
+
+// sale : 2014-10-16
+sale
+
+// salon : 2014-12-11 Outer Orchard, LLC
+salon
+
+// samsclub : 2015-07-31 Wal-Mart Stores, Inc.
+samsclub
+
+// samsung : 2014-04-03 SAMSUNG SDS CO., LTD
+samsung
+
+// sandvik : 2014-11-13 Sandvik AB
+sandvik
+
+// sandvikcoromant : 2014-11-07 Sandvik AB
+sandvikcoromant
+
+// sanofi : 2014-10-09 Sanofi
+sanofi
+
+// sap : 2014-03-27 SAP AG
+sap
+
+// sapo : 2014-11-07 PT Comunicacoes S.A.
+sapo
+
+// sarl : 2014-07-03 Delta Orchard, LLC
+sarl
+
+// sas : 2015-04-02 Research IP LLC
+sas
+
+// save : 2015-06-25 Amazon EU S.à r.l.
+save
+
+// saxo : 2014-10-31 Saxo Bank A/S
+saxo
+
+// sbi : 2015-03-12 STATE BANK OF INDIA
+sbi
+
+// sbs : 2014-11-07 SPECIAL BROADCASTING SERVICE CORPORATION
+sbs
+
+// sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ)
+sca
+
+// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB")
+scb
+
+// schaeffler : 2015-08-06 Schaeffler Technologies AG & Co. KG
+schaeffler
+
+// schmidt : 2014-04-03 SALM S.A.S.
+schmidt
+
+// scholarships : 2014-04-24 Scholarships.com, LLC
+scholarships
+
+// school : 2014-12-18 Little Galley, LLC
+school
+
+// schule : 2014-03-06 Outer Moon, LLC
+schule
+
+// schwarz : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
+schwarz
+
+// science : 2014-09-11 dot Science Limited
+science
+
+// scjohnson : 2015-07-23 Johnson Shareholdings, Inc.
+scjohnson
+
+// scor : 2014-10-31 SCOR SE
+scor
+
+// scot : 2014-01-23 Dot Scot Registry Limited
+scot
+
+// seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal)
+seat
+
+// secure : 2015-08-27 Amazon EU S.à r.l.
+secure
+
+// security : 2015-05-14
+security
+
+// seek : 2014-12-04 Seek Limited
+seek
+
+// select : 2015-10-08 iSelect Ltd
+select
+
+// sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A.
+sener
+
+// services : 2014-02-27 Fox Castle, LLC
+services
+
+// ses : 2015-07-23 SES
+ses
+
+// seven : 2015-08-06 Seven West Media Ltd
+seven
+
+// sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG
+sew
+
+// sex : 2014-11-13 ICM Registry SX LLC
+sex
+
+// sexy : 2013-09-11 Uniregistry, Corp.
+sexy
+
+// sfr : 2015-08-13 Societe Francaise du Radiotelephone - SFR
+sfr
+
+// shangrila : 2015-09-03 Shangri‐La International Hotel Management Limited
+shangrila
+
+// sharp : 2014-05-01 Sharp Corporation
+sharp
+
+// shaw : 2015-04-23 Shaw Cablesystems G.P.
+shaw
+
+// shell : 2015-07-30 Shell Information Technology International Inc
+shell
+
+// shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+shia
+
+// shiksha : 2013-11-14 Afilias Limited
+shiksha
+
+// shoes : 2013-10-02 Binky Galley, LLC
+shoes
+
+// shouji : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+shouji
+
+// show : 2015-03-05 Snow Beach, LLC
+show
+
+// showtime : 2015-08-06 CBS Domains Inc.
+showtime
+
+// shriram : 2014-01-23 Shriram Capital Ltd.
+shriram
+
+// silk : 2015-06-25 Amazon EU S.à r.l.
+silk
+
+// sina : 2015-03-12 Sina Corporation
+sina
+
+// singles : 2013-08-27 Fern Madison, LLC
+singles
+
+// site : 2015-01-15 DotSite Inc.
+site
+
+// ski : 2015-04-09 STARTING DOT LIMITED
+ski
+
+// skin : 2015-01-15 L'Oréal
+skin
+
+// sky : 2014-06-19 Sky IP International Ltd, a company incorporated in England and Wales, operating via its registered Swiss branch
+sky
+
+// skype : 2014-12-18 Microsoft Corporation
+skype
+
+// sling : 2015-07-30 Hughes Satellite Systems Corporation
+sling
+
+// smart : 2015-07-09 Smart Communications, Inc. (SMART)
+smart
+
+// smile : 2014-12-18 Amazon EU S.à r.l.
+smile
+
+// sncf : 2015-02-19 Société Nationale des Chemins de fer Francais S N C F
+sncf
+
+// soccer : 2015-03-26 Foggy Shadow, LLC
+soccer
+
+// social : 2013-11-07 United TLD Holdco Ltd.
+social
+
+// softbank : 2015-07-02 SoftBank Corp.
+softbank
+
+// software : 2014-03-20
+software
+
+// sohu : 2013-12-19 Sohu.com Limited
+sohu
+
+// solar : 2013-11-07 Ruby Town, LLC
+solar
+
+// solutions : 2013-11-07 Silver Cover, LLC
+solutions
+
+// song : 2015-02-26 Amazon EU S.à r.l.
+song
+
+// sony : 2015-01-08 Sony Corporation
+sony
+
+// soy : 2014-01-23 Charleston Road Registry Inc.
+soy
+
+// space : 2014-04-03 DotSpace Inc.
+space
+
+// spiegel : 2014-02-05 SPIEGEL-Verlag Rudolf Augstein GmbH & Co. KG
+spiegel
+
+// spot : 2015-02-26 Amazon EU S.à r.l.
+spot
+
+// spreadbetting : 2014-12-11 IG Group Holdings PLC
+spreadbetting
+
+// srl : 2015-05-07 mySRL GmbH
+srl
+
+// srt : 2015-07-30 FCA US LLC.
+srt
+
+// stada : 2014-11-13 STADA Arzneimittel AG
+stada
+
+// staples : 2015-07-30 Staples, Inc.
+staples
+
+// star : 2015-01-08 Star India Private Limited
+star
+
+// starhub : 2015-02-05 StarHub Ltd
+starhub
+
+// statebank : 2015-03-12 STATE BANK OF INDIA
+statebank
+
+// statefarm : 2015-07-30 State Farm Mutual Automobile Insurance Company
+statefarm
+
+// statoil : 2014-12-04 Statoil ASA
+statoil
+
+// stc : 2014-10-09 Saudi Telecom Company
+stc
+
+// stcgroup : 2014-10-09 Saudi Telecom Company
+stcgroup
+
+// stockholm : 2014-12-18 Stockholms kommun
+stockholm
+
+// storage : 2014-12-22 Self Storage Company LLC
+storage
+
+// store : 2015-04-09 DotStore Inc.
+store
+
+// studio : 2015-02-11
+studio
+
+// study : 2014-12-11 OPEN UNIVERSITIES AUSTRALIA PTY LTD
+study
+
+// style : 2014-12-04 Binky Moon, LLC
+style
+
+// sucks : 2014-12-22 Vox Populi Registry Inc.
+sucks
+
+// supersport : 2015-03-05 SuperSport International Holdings Proprietary Limited
+supersport
+
+// supplies : 2013-12-19 Atomic Fields, LLC
+supplies
+
+// supply : 2013-12-19 Half Falls, LLC
+supply
+
+// support : 2013-10-24 Grand Orchard, LLC
+support
+
+// surf : 2014-01-09 Top Level Domain Holdings Limited
+surf
+
+// surgery : 2014-03-20 Tin Avenue, LLC
+surgery
+
+// suzuki : 2014-02-20 SUZUKI MOTOR CORPORATION
+suzuki
+
+// swatch : 2015-01-08 The Swatch Group Ltd
+swatch
+
+// swiftcover : 2015-07-23 Swiftcover Insurance Services Limited
+swiftcover
+
+// swiss : 2014-10-16 Swiss Confederation
+swiss
+
+// sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet
+sydney
+
+// symantec : 2014-12-04 Symantec Corporation
+symantec
+
+// systems : 2013-11-07 Dash Cypress, LLC
+systems
+
+// tab : 2014-12-04 Tabcorp Holdings Limited
+tab
+
+// taipei : 2014-07-10 Taipei City Government
+taipei
+
+// talk : 2015-04-09 Amazon EU S.à r.l.
+talk
+
+// taobao : 2015-01-15 Alibaba Group Holding Limited
+taobao
+
+// target : 2015-07-31 Target Domain Holdings, LLC
+target
+
+// tatamotors : 2015-03-12 Tata Motors Ltd
+tatamotors
+
+// tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic"
+tatar
+
+// tattoo : 2013-08-30 Uniregistry, Corp.
+tattoo
+
+// tax : 2014-03-20 Storm Orchard, LLC
+tax
+
+// taxi : 2015-03-19 Pine Falls, LLC
+taxi
+
+// tci : 2014-09-12 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+tci
+
+// tdk : 2015-06-11 TDK Corporation
+tdk
+
+// team : 2015-03-05 Atomic Lake, LLC
+team
+
+// tech : 2015-01-30 Dot Tech LLC
+tech
+
+// technology : 2013-09-13 Auburn Falls
+technology
+
+// telecity : 2015-02-19 TelecityGroup International Limited
+telecity
+
+// telefonica : 2014-10-16 Telefónica S.A.
+telefonica
+
+// temasek : 2014-08-07 Temasek Holdings (Private) Limited
+temasek
+
+// tennis : 2014-12-04 Cotton Bloom, LLC
+tennis
+
+// teva : 2015-07-02 Teva Pharmaceutical Industries Limited
+teva
+
+// thd : 2015-04-02 Homer TLC, Inc.
+thd
+
+// theater : 2015-03-19 Blue Tigers, LLC
+theater
+
+// theatre : 2015-05-07
+theatre
+
+// theguardian : 2015-04-30 Guardian News and Media Limited
+theguardian
+
+// tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America
+tiaa
+
+// tickets : 2015-02-05 Accent Media Limited
+tickets
+
+// tienda : 2013-11-14 Victor Manor, LLC
+tienda
+
+// tiffany : 2015-01-30 Tiffany and Company
+tiffany
+
+// tips : 2013-09-20 Corn Willow, LLC
+tips
+
+// tires : 2014-11-07 Dog Edge, LLC
+tires
+
+// tirol : 2014-04-24 punkt Tirol GmbH
+tirol
+
+// tjmaxx : 2015-07-16 The TJX Companies, Inc.
+tjmaxx
+
+// tjx : 2015-07-16 The TJX Companies, Inc.
+tjx
+
+// tkmaxx : 2015-07-16 The TJX Companies, Inc.
+tkmaxx
+
+// tmall : 2015-01-15 Alibaba Group Holding Limited
+tmall
+
+// today : 2013-09-20 Pearl Woods, LLC
+today
+
+// tokyo : 2013-11-13 GMO Registry, Inc.
+tokyo
+
+// tools : 2013-11-21 Pioneer North, LLC
+tools
+
+// top : 2014-03-20 Jiangsu Bangning Science & Technology Co.,Ltd.
+top
+
+// toray : 2014-12-18 Toray Industries, Inc.
+toray
+
+// toshiba : 2014-04-10 TOSHIBA Corporation
+toshiba
+
+// total : 2015-08-06 Total SA
+total
+
+// tours : 2015-01-22 Sugar Station, LLC
+tours
+
+// town : 2014-03-06 Koko Moon, LLC
+town
+
+// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION
+toyota
+
+// toys : 2014-03-06 Pioneer Orchard, LLC
+toys
+
+// trade : 2014-01-23 Elite Registry Limited
+trade
+
+// trading : 2014-12-11 IG Group Holdings PLC
+trading
+
+// training : 2013-11-07 Wild Willow, LLC
+training
+
+// travelchannel : 2015-07-02 Lifestyle Domain Holdings, Inc.
+travelchannel
+
+// travelers : 2015-03-26 Travelers TLD, LLC
+travelers
+
+// travelersinsurance : 2015-03-26 Travelers TLD, LLC
+travelersinsurance
+
+// trust : 2014-10-16
+trust
+
+// trv : 2015-03-26 Travelers TLD, LLC
+trv
+
+// tube : 2015-06-11 Latin American Telecom LLC
+tube
+
+// tui : 2014-07-03 TUI AG
+tui
+
+// tunes : 2015-02-26 Amazon EU S.à r.l.
+tunes
+
+// tushu : 2014-12-18 Amazon EU S.à r.l.
+tushu
+
+// tvs : 2015-02-19 T V SUNDRAM IYENGAR  & SONS LIMITED
+tvs
+
+// ubank : 2015-08-20 National Australia Bank Limited
+ubank
+
+// ubs : 2014-12-11 UBS AG
+ubs
+
+// uconnect : 2015-07-30 FCA US LLC.
+uconnect
+
+// unicom : 2015-10-15 China United Network Communications Corporation Limited
+unicom
+
+// university : 2014-03-06 Little Station, LLC
+university
+
+// uno : 2013-09-11 Dot Latin LLC
+uno
+
+// uol : 2014-05-01 UBN INTERNET LTDA.
+uol
+
+// ups : 2015-06-25 UPS Market Driver, Inc.
+ups
+
+// vacations : 2013-12-05 Atomic Tigers, LLC
+vacations
+
+// vana : 2014-12-11 Lifestyle Domain Holdings, Inc.
+vana
+
+// vanguard : 2015-09-03 The Vanguard Group, Inc.
+vanguard
+
+// vegas : 2014-01-16 Dot Vegas, Inc.
+vegas
+
+// ventures : 2013-08-27 Binky Lake, LLC
+ventures
+
+// verisign : 2015-08-13 VeriSign, Inc.
+verisign
+
+// versicherung : 2014-03-20 dotversicherung-registry GmbH
+versicherung
+
+// vet : 2014-03-06
+vet
+
+// viajes : 2013-10-17 Black Madison, LLC
+viajes
+
+// video : 2014-10-16
+video
+
+// vig : 2015-05-14 VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe
+vig
+
+// viking : 2015-04-02 Viking River Cruises (Bermuda) Ltd.
+viking
+
+// villas : 2013-12-05 New Sky, LLC
+villas
+
+// vin : 2015-06-18 Holly Shadow, LLC
+vin
+
+// vip : 2015-01-22 Minds + Machines Group Limited
+vip
+
+// virgin : 2014-09-25 Virgin Enterprises Limited
+virgin
+
+// visa : 2015-07-30 Visa Worldwide Pte. Limited
+visa
+
+// vision : 2013-12-05 Koko Station, LLC
+vision
+
+// vista : 2014-09-18 Vistaprint Limited
+vista
+
+// vistaprint : 2014-09-18 Vistaprint Limited
+vistaprint
+
+// viva : 2014-11-07 Saudi Telecom Company
+viva
+
+// vivo : 2015-07-31 Telefonica Brasil S.A.
+vivo
+
+// vlaanderen : 2014-02-06 DNS.be vzw
+vlaanderen
+
+// vodka : 2013-12-19 Top Level Domain Holdings Limited
+vodka
+
+// volkswagen : 2015-05-14 Volkswagen Group of America Inc.
+volkswagen
+
+// vote : 2013-11-21 Monolith Registry LLC
+vote
+
+// voting : 2013-11-13 Valuetainment Corp.
+voting
+
+// voto : 2013-11-21 Monolith Registry LLC
+voto
+
+// voyage : 2013-08-27 Ruby House, LLC
+voyage
+
+// vuelos : 2015-03-05 Travel Reservations SRL
+vuelos
+
+// wales : 2014-05-08 Nominet UK
+wales
+
+// walmart : 2015-07-31 Wal-Mart Stores, Inc.
+walmart
+
+// walter : 2014-11-13 Sandvik AB
+walter
+
+// wang : 2013-10-24 Zodiac Leo Limited
+wang
+
+// wanggou : 2014-12-18 Amazon EU S.à r.l.
+wanggou
+
+// warman : 2015-06-18 Weir Group IP Limited
+warman
+
+// watch : 2013-11-14 Sand Shadow, LLC
+watch
+
+// watches : 2014-12-22 Richemont DNS Inc.
+watches
+
+// weather : 2015-01-08 The Weather Channel, LLC
+weather
+
+// weatherchannel : 2015-03-12 The Weather Channel, LLC
+weatherchannel
+
+// webcam : 2014-01-23 dot Webcam Limited
+webcam
+
+// weber : 2015-06-04 Saint-Gobain Weber SA
+weber
+
+// website : 2014-04-03 DotWebsite Inc.
+website
+
+// wed : 2013-10-01 Atgron, Inc.
+wed
+
+// wedding : 2014-04-24 Top Level Domain Holdings Limited
+wedding
+
+// weibo : 2015-03-05 Sina Corporation
+weibo
+
+// weir : 2015-01-29 Weir Group IP Limited
+weir
+
+// whoswho : 2014-02-20 Who's Who Registry
+whoswho
+
+// wien : 2013-10-28 punkt.wien GmbH
+wien
+
+// wiki : 2013-11-07 Top Level Design, LLC
+wiki
+
+// williamhill : 2014-03-13 William Hill Organization Limited
+williamhill
+
+// win : 2014-11-20 First Registry Limited
+win
+
+// windows : 2014-12-18 Microsoft Corporation
+windows
+
+// wine : 2015-06-18 June Station, LLC
+wine
+
+// winners : 2015-07-16 The TJX Companies, Inc.
+winners
+
+// wme : 2014-02-13 William Morris Endeavor Entertainment, LLC
+wme
+
+// wolterskluwer : 2015-08-06 Wolters Kluwer N.V.
+wolterskluwer
+
+// woodside : 2015-07-09 Woodside Petroleum Limited
+woodside
+
+// work : 2013-12-19 Top Level Domain Holdings Limited
+work
+
+// works : 2013-11-14 Little Dynamite, LLC
+works
+
+// world : 2014-06-12 Bitter Fields, LLC
+world
+
+// wow : 2015-10-08 Amazon EU S.à r.l.
+wow
+
+// wtc : 2013-12-19 World Trade Centers Association, Inc.
+wtc
+
+// wtf : 2014-03-06 Hidden Way, LLC
+wtf
+
+// xbox : 2014-12-18 Microsoft Corporation
+xbox
+
+// xerox : 2014-10-24 Xerox DNHC LLC
+xerox
+
+// xfinity : 2015-07-09 Comcast IP Holdings I, LLC
+xfinity
+
+// xihuan : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+xihuan
+
+// xin : 2014-12-11 Elegant Leader Limited
+xin
+
+// xn--11b4c3d : 2015-01-15 VeriSign Sarl
+कॉम
+
+// xn--1ck2e1b : 2015-02-26 Amazon EU S.à r.l.
+セール
+
+// xn--1qqw23a : 2014-01-09 Guangzhou YU Wei Information Technology Co., Ltd.
+佛山
+
+// xn--30rr7y : 2014-06-12 Excellent First Limited
+慈善
+
+// xn--3bst00m : 2013-09-13 Eagle Horizon Limited
+集团
+
+// xn--3ds443g : 2013-09-08 TLD REGISTRY LIMITED
+在线
+
+// xn--3oq18vl8pn36a : 2015-07-02 Volkswagen (China) Investment Co., Ltd.
+大众汽车
+
+// xn--3pxu8k : 2015-01-15 VeriSign Sarl
+点看
+
+// xn--42c2d9a : 2015-01-15 VeriSign Sarl
+คอม
+
+// xn--45q11c : 2013-11-21 Zodiac Scorpio Limited
+八卦
+
+// xn--4gbrim : 2013-10-04 Suhub Electronic Establishment
+موقع
+
+// xn--4gq48lf9j : 2015-07-31 Wal-Mart Stores, Inc.
+一号店
+
+// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center
+公益
+
+// xn--55qx5d : 2013-11-14 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center)
+公司
+
+// xn--5su34j936bgsg : 2015-09-03 Shangri‐La International Hotel Management Limited
+香格里拉
+
+// xn--5tzm5g : 2014-12-22 Global Website TLD Asia Limited
+网站
+
+// xn--6frz82g : 2013-09-23 Afilias Limited
+移动
+
+// xn--6qq986b3xl : 2013-09-13 Tycoon Treasure Limited
+我爱你
+
+// xn--80adxhks : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+москва
+
+// xn--80aqecdr1a : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+католик
+
+// xn--80asehdb : 2013-07-14 CORE Association
+онлайн
+
+// xn--80aswg : 2013-07-14 CORE Association
+сайт
+
+// xn--8y0a063a : 2015-03-26 China United Network Communications Corporation Limited
+联通
+
+// xn--9dbq2a : 2015-01-15 VeriSign Sarl
+קום
+
+// xn--9et52u : 2014-06-12 RISE VICTORY LIMITED
+时尚
+
+// xn--9krt00a : 2015-03-12 Sina Corporation
+微博
+
+// xn--b4w605ferd : 2014-08-07 Temasek Holdings (Private) Limited
+淡马锡
+
+// xn--bck1b9a5dre4c : 2015-02-26 Amazon EU S.à r.l.
+ファッション
+
+// xn--c1avg : 2013-11-14 Public Interest Registry
+орг
+
+// xn--c2br7g : 2015-01-15 VeriSign Sarl
+नेट
+
+// xn--cck2b3b : 2015-02-26 Amazon EU S.à r.l.
+ストア
+
+// xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD
+삼성
+
+// xn--czr694b : 2014-01-16 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED
+商标
+
+// xn--czrs0t : 2013-12-19 Wild Island, LLC
+商店
+
+// xn--czru2d : 2013-11-21 Zodiac Capricorn Limited
+商城
+
+// xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet”
+дети
+
+// xn--eckvdtc9d : 2014-12-18 Amazon EU S.à r.l.
+ポイント
+
+// xn--efvy88h : 2014-08-22 Xinhua News Agency Guangdong Branch 新华通讯社广东分社
+新闻
+
+// xn--estv75g : 2015-02-19 Industrial and Commercial Bank of China Limited
+工行
+
+// xn--fct429k : 2015-04-09 Amazon EU S.à r.l.
+家電
+
+// xn--fhbei : 2015-01-15 VeriSign Sarl
+كوم
+
+// xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED
+中文网
+
+// xn--fiq64b : 2013-10-14 CITIC Group Corporation
+中信
+
+// xn--fjq720a : 2014-05-22 Will Bloom, LLC
+娱乐
+
+// xn--flw351e : 2014-07-31 Charleston Road Registry Inc.
+谷歌
+
+// xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited
+電訊盈科
+
+// xn--g2xx48c : 2015-01-30 Minds + Machines Group Limited
+购物
+
+// xn--gckr3f0f : 2015-02-26 Amazon EU S.à r.l.
+クラウド
+
+// xn--gk3at1e : 2015-10-08 Amazon EU S.à r.l.
+通販
+
+// xn--hxt814e : 2014-05-15 Zodiac Libra Limited
+网店
+
+// xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry
+संगठन
+
+// xn--imr513n : 2014-12-11 HU YI GLOBAL INFORMATION RESOURCES (HOLDING) COMPANY. HONGKONG LIMITED
+餐厅
+
+// xn--io0a7i : 2013-11-14 Computer Network Information Center of Chinese Academy of Sciences (China Internet Network Information Center)
+网络
+
+// xn--j1aef : 2015-01-15 VeriSign Sarl
+ком
+
+// xn--jlq61u9w7b : 2015-01-08 Nokia Corporation
+诺基亚
+
+// xn--jvr189m : 2015-02-26 Amazon EU S.à r.l.
+食品
+
+// xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V.
+飞利浦
+
+// xn--kpu716f : 2014-12-22 Richemont DNS Inc.
+手表
+
+// xn--kput3i : 2014-02-13 Beijing RITT-Net Technology Development Co., Ltd
+手机
+
+// xn--mgba3a3ejt : 2014-11-20 Aramco Services Company
+ارامكو
+
+// xn--mgba7c0bbn0a : 2015-05-14 Crescent Holding GmbH
+العليان
+
+// xn--mgbaakc7dvf : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat)
+اتصالات
+
+// xn--mgbab2bd : 2013-10-31 CORE Association
+بازار
+
+// xn--mgbb9fbpob : 2014-12-18 GreenTech Consultancy Company W.L.L.
+موبايلي
+
+// xn--mgbca7dzdo : 2015-07-30 Abu Dhabi Systems and Information Centre
+ابوظبي
+
+// xn--mgbi4ecexp : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+كاثوليك
+
+// xn--mgbt3dhd : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+همراه
+
+// xn--mk1bu44c : 2015-01-15 VeriSign Sarl
+닷컴
+
+// xn--mxtq1m : 2014-03-06 Net-Chinese Co., Ltd.
+政府
+
+// xn--ngbc5azd : 2013-07-13 International Domain Registry Pty. Ltd.
+شبكة
+
+// xn--ngbe9e0a : 2014-12-04 Kuwait Finance House
+بيتك
+
+// xn--nqv7f : 2013-11-14 Public Interest Registry
+机构
+
+// xn--nqv7fs00ema : 2013-11-14 Public Interest Registry
+组织机构
+
+// xn--nyqy26a : 2014-11-07 Stable Tone Limited
+健康
+
+// xn--p1acf : 2013-12-12 Rusnames Limited
+рус
+
+// xn--pbt977c : 2014-12-22 Richemont DNS Inc.
+珠宝
+
+// xn--pssy2u : 2015-01-15 VeriSign Sarl
+大拿
+
+// xn--q9jyb4c : 2013-09-17 Charleston Road Registry Inc.
+みんな
+
+// xn--qcka1pmc : 2014-07-31 Charleston Road Registry Inc.
+グーグル
+
+// xn--rhqv96g : 2013-09-11 Stable Tone Limited
+世界
+
+// xn--rovu88b : 2015-02-26 Amazon EU S.à r.l.
+書籍
+
+// xn--ses554g : 2014-01-16
+网址
+
+// xn--t60b56a : 2015-01-15 VeriSign Sarl
+닷넷
+
+// xn--tckwe : 2015-01-15 VeriSign Sarl
+コム
+
+// xn--tiq49xqyj : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+天主教
+
+// xn--unup4y : 2013-07-14 Spring Fields, LLC
+游戏
+
+// xn--vermgensberater-ctb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+vermögensberater
+
+// xn--vermgensberatung-pwb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+vermögensberatung
+
+// xn--vhquv : 2013-08-27 Dash McCook, LLC
+企业
+
+// xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd.
+信息
+
+// xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited
+嘉里大酒店
+
+// xn--w4rs40l : 2015-07-30 Kerry Trading Co. Limited
+嘉里
+
+// xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd.
+广东
+
+// xn--zfr164b : 2013-11-08 China Organizational Name Administration Center
+政务
+
+// xperia : 2015-05-14 Sony Mobile Communications AB
+xperia
+
+// xyz : 2013-12-05 XYZ.COM LLC
+xyz
+
+// yachts : 2014-01-09 DERYachts, LLC
+yachts
+
+// yahoo : 2015-04-02 Yahoo! Domain Services Inc.
+yahoo
+
+// yamaxun : 2014-12-18 Amazon EU S.à r.l.
+yamaxun
+
+// yandex : 2014-04-10 YANDEX, LLC
+yandex
+
+// yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+yodobashi
+
+// yoga : 2014-05-29 Top Level Domain Holdings Limited
+yoga
+
+// yokohama : 2013-12-12 GMO Registry, Inc.
+yokohama
+
+// you : 2015-04-09 Amazon EU S.à r.l.
+you
+
+// youtube : 2014-05-01 Charleston Road Registry Inc.
+youtube
+
+// yun : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
+yun
+
+// zappos : 2015-06-25 Amazon EU S.à r.l.
+zappos
+
+// zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.)
+zara
+
+// zero : 2014-12-18 Amazon EU S.à r.l.
+zero
+
+// zip : 2014-05-08 Charleston Road Registry Inc.
+zip
+
+// zippo : 2015-07-02 Zadco Company
+zippo
+
+// zone : 2013-11-14 Outer Falls, LLC
+zone
+
+// zuerich : 2014-11-07 Kanton Zürich (Canton of Zurich)
+zuerich
+
+
+// ===END ICANN DOMAINS===
+// ===BEGIN PRIVATE DOMAINS===
+// (Note: these are in alphabetical order by company name)
+
+// Amazon CloudFront : https://aws.amazon.com/cloudfront/
+// Submitted by Donavan Miller <donavanm@amazon.com> 2013-03-22
+cloudfront.net
+
+// Amazon Elastic Compute Cloud: https://aws.amazon.com/ec2/
+// Submitted by Osman Surkatty <osmans@amazon.com> 2014-12-16
+ap-northeast-1.compute.amazonaws.com
+ap-southeast-1.compute.amazonaws.com
+ap-southeast-2.compute.amazonaws.com
+cn-north-1.compute.amazonaws.cn
+compute.amazonaws.cn
+compute.amazonaws.com
+compute-1.amazonaws.com
+eu-west-1.compute.amazonaws.com
+eu-central-1.compute.amazonaws.com
+sa-east-1.compute.amazonaws.com
+us-east-1.amazonaws.com
+us-gov-west-1.compute.amazonaws.com
+us-west-1.compute.amazonaws.com
+us-west-2.compute.amazonaws.com
+z-1.compute-1.amazonaws.com
+z-2.compute-1.amazonaws.com
+
+// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/
+// Submitted by Adam Stein <astein@amazon.com> 2013-04-02
+elasticbeanstalk.com
+
+// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/
+// Submitted by Scott Vidmar <svidmar@amazon.com> 2013-03-27
+elb.amazonaws.com
+
+// Amazon S3 : https://aws.amazon.com/s3/
+// Submitted by Eric Kinolik <kilo@amazon.com> 2015-04-08
+s3.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-ap-southeast-1.amazonaws.com
+s3-ap-southeast-2.amazonaws.com
+s3-external-1.amazonaws.com
+s3-external-2.amazonaws.com
+s3-fips-us-gov-west-1.amazonaws.com
+s3-eu-central-1.amazonaws.com
+s3-eu-west-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3.cn-north-1.amazonaws.com.cn
+s3.eu-central-1.amazonaws.com
+
+// BetaInABox
+// Submitted by adrian@betainabox.com 2012-09-13
+betainabox.com
+
+// CentralNic : http://www.centralnic.com/names/domains
+// Submitted by registry <gavin.brown@centralnic.com> 2012-09-27
+ae.org
+ar.com
+br.com
+cn.com
+com.de
+com.se
+de.com
+eu.com
+gb.com
+gb.net
+hu.com
+hu.net
+jp.net
+jpn.com
+kr.com
+mex.com
+no.com
+qc.com
+ru.com
+sa.com
+se.com
+se.net
+uk.com
+uk.net
+us.com
+uy.com
+za.bz
+za.com
+
+// Africa.com Web Solutions Ltd : https://registry.africa.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+africa.com
+
+// iDOT Services Limited : http://www.domain.gr.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+gr.com
+
+// Radix FZC : http://domains.in.net
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+in.net
+
+// US REGISTRY LLC : http://us.org
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+us.org
+
+// co.com Registry, LLC : https://registry.co.com
+// Submitted by Gavin Brown <gavin.brown@centralnic.com> 2014-02-04
+co.com
+
+// c.la : http://www.c.la/
+c.la
+
+// cloudControl : https://www.cloudcontrol.com/
+// Submitted by Tobias Wilken <tw@cloudcontrol.com> 2013-07-23
+cloudcontrolled.com
+cloudcontrolapp.com
+
+// co.ca : http://registry.co.ca/
+co.ca
+
+// CDN77.com : http://www.cdn77.com
+// Submitted by Jan Krpes <jan.krpes@cdn77.com> 2015-07-13
+c.cdn77.org
+cdn77-ssl.net
+r.cdn77.net
+rsc.cdn77.org
+ssl.origin.cdn77-secure.org
+
+// CoDNS B.V.
+co.nl
+co.no
+
+// Commerce Guys, SAS
+// Submitted by Damien Tournoud <damien@commerceguys.com> 2015-01-22
+*.platform.sh
+
+// Cupcake : https://cupcake.io/
+// Submitted by Jonathan Rudenberg <jonathan@cupcake.io> 2013-10-08
+cupcake.is
+
+// DreamHost : http://www.dreamhost.com/
+// Submitted by Andrew Farmer <andrew.farmer@dreamhost.com> 2012-10-02
+dreamhosters.com
+
+// DuckDNS : http://www.duckdns.org/
+// Submitted by Richard Harper <richard@duckdns.org> 2015-05-17
+duckdns.org
+
+// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/
+dyndns-at-home.com
+dyndns-at-work.com
+dyndns-blog.com
+dyndns-free.com
+dyndns-home.com
+dyndns-ip.com
+dyndns-mail.com
+dyndns-office.com
+dyndns-pics.com
+dyndns-remote.com
+dyndns-server.com
+dyndns-web.com
+dyndns-wiki.com
+dyndns-work.com
+dyndns.biz
+dyndns.info
+dyndns.org
+dyndns.tv
+at-band-camp.net
+ath.cx
+barrel-of-knowledge.info
+barrell-of-knowledge.info
+better-than.tv
+blogdns.com
+blogdns.net
+blogdns.org
+blogsite.org
+boldlygoingnowhere.org
+broke-it.net
+buyshouses.net
+cechire.com
+dnsalias.com
+dnsalias.net
+dnsalias.org
+dnsdojo.com
+dnsdojo.net
+dnsdojo.org
+does-it.net
+doesntexist.com
+doesntexist.org
+dontexist.com
+dontexist.net
+dontexist.org
+doomdns.com
+doomdns.org
+dvrdns.org
+dyn-o-saur.com
+dynalias.com
+dynalias.net
+dynalias.org
+dynathome.net
+dyndns.ws
+endofinternet.net
+endofinternet.org
+endoftheinternet.org
+est-a-la-maison.com
+est-a-la-masion.com
+est-le-patron.com
+est-mon-blogueur.com
+for-better.biz
+for-more.biz
+for-our.info
+for-some.biz
+for-the.biz
+forgot.her.name
+forgot.his.name
+from-ak.com
+from-al.com
+from-ar.com
+from-az.net
+from-ca.com
+from-co.net
+from-ct.com
+from-dc.com
+from-de.com
+from-fl.com
+from-ga.com
+from-hi.com
+from-ia.com
+from-id.com
+from-il.com
+from-in.com
+from-ks.com
+from-ky.com
+from-la.net
+from-ma.com
+from-md.com
+from-me.org
+from-mi.com
+from-mn.com
+from-mo.com
+from-ms.com
+from-mt.com
+from-nc.com
+from-nd.com
+from-ne.com
+from-nh.com
+from-nj.com
+from-nm.com
+from-nv.com
+from-ny.net
+from-oh.com
+from-ok.com
+from-or.com
+from-pa.com
+from-pr.com
+from-ri.com
+from-sc.com
+from-sd.com
+from-tn.com
+from-tx.com
+from-ut.com
+from-va.com
+from-vt.com
+from-wa.com
+from-wi.com
+from-wv.com
+from-wy.com
+ftpaccess.cc
+fuettertdasnetz.de
+game-host.org
+game-server.cc
+getmyip.com
+gets-it.net
+go.dyndns.org
+gotdns.com
+gotdns.org
+groks-the.info
+groks-this.info
+ham-radio-op.net
+here-for-more.info
+hobby-site.com
+hobby-site.org
+home.dyndns.org
+homedns.org
+homeftp.net
+homeftp.org
+homeip.net
+homelinux.com
+homelinux.net
+homelinux.org
+homeunix.com
+homeunix.net
+homeunix.org
+iamallama.com
+in-the-band.net
+is-a-anarchist.com
+is-a-blogger.com
+is-a-bookkeeper.com
+is-a-bruinsfan.org
+is-a-bulls-fan.com
+is-a-candidate.org
+is-a-caterer.com
+is-a-celticsfan.org
+is-a-chef.com
+is-a-chef.net
+is-a-chef.org
+is-a-conservative.com
+is-a-cpa.com
+is-a-cubicle-slave.com
+is-a-democrat.com
+is-a-designer.com
+is-a-doctor.com
+is-a-financialadvisor.com
+is-a-geek.com
+is-a-geek.net
+is-a-geek.org
+is-a-green.com
+is-a-guru.com
+is-a-hard-worker.com
+is-a-hunter.com
+is-a-knight.org
+is-a-landscaper.com
+is-a-lawyer.com
+is-a-liberal.com
+is-a-libertarian.com
+is-a-linux-user.org
+is-a-llama.com
+is-a-musician.com
+is-a-nascarfan.com
+is-a-nurse.com
+is-a-painter.com
+is-a-patsfan.org
+is-a-personaltrainer.com
+is-a-photographer.com
+is-a-player.com
+is-a-republican.com
+is-a-rockstar.com
+is-a-socialist.com
+is-a-soxfan.org
+is-a-student.com
+is-a-teacher.com
+is-a-techie.com
+is-a-therapist.com
+is-an-accountant.com
+is-an-actor.com
+is-an-actress.com
+is-an-anarchist.com
+is-an-artist.com
+is-an-engineer.com
+is-an-entertainer.com
+is-by.us
+is-certified.com
+is-found.org
+is-gone.com
+is-into-anime.com
+is-into-cars.com
+is-into-cartoons.com
+is-into-games.com
+is-leet.com
+is-lost.org
+is-not-certified.com
+is-saved.org
+is-slick.com
+is-uberleet.com
+is-very-bad.org
+is-very-evil.org
+is-very-good.org
+is-very-nice.org
+is-very-sweet.org
+is-with-theband.com
+isa-geek.com
+isa-geek.net
+isa-geek.org
+isa-hockeynut.com
+issmarterthanyou.com
+isteingeek.de
+istmein.de
+kicks-ass.net
+kicks-ass.org
+knowsitall.info
+land-4-sale.us
+lebtimnetz.de
+leitungsen.de
+likes-pie.com
+likescandy.com
+merseine.nu
+mine.nu
+misconfused.org
+mypets.ws
+myphotos.cc
+neat-url.com
+office-on-the.net
+on-the-web.tv
+podzone.net
+podzone.org
+readmyblog.org
+saves-the-whales.com
+scrapper-site.net
+scrapping.cc
+selfip.biz
+selfip.com
+selfip.info
+selfip.net
+selfip.org
+sells-for-less.com
+sells-for-u.com
+sells-it.net
+sellsyourhome.org
+servebbs.com
+servebbs.net
+servebbs.org
+serveftp.net
+serveftp.org
+servegame.org
+shacknet.nu
+simple-url.com
+space-to-rent.com
+stuff-4-sale.org
+stuff-4-sale.us
+teaches-yoga.com
+thruhere.net
+traeumtgerade.de
+webhop.biz
+webhop.info
+webhop.net
+webhop.org
+worse-than.tv
+writesthisblog.com
+
+// EU.org https://eu.org/
+// Submitted by Pierre Beyssac <hostmaster@eu.org> 2015-04-17
+
+eu.org
+al.eu.org
+asso.eu.org
+at.eu.org
+au.eu.org
+be.eu.org
+bg.eu.org
+ca.eu.org
+cd.eu.org
+ch.eu.org
+cn.eu.org
+cy.eu.org
+cz.eu.org
+de.eu.org
+dk.eu.org
+edu.eu.org
+ee.eu.org
+es.eu.org
+fi.eu.org
+fr.eu.org
+gr.eu.org
+hr.eu.org
+hu.eu.org
+ie.eu.org
+il.eu.org
+in.eu.org
+int.eu.org
+is.eu.org
+it.eu.org
+jp.eu.org
+kr.eu.org
+lt.eu.org
+lu.eu.org
+lv.eu.org
+mc.eu.org
+me.eu.org
+mk.eu.org
+mt.eu.org
+my.eu.org
+net.eu.org
+ng.eu.org
+nl.eu.org
+no.eu.org
+nz.eu.org
+paris.eu.org
+pl.eu.org
+pt.eu.org
+q-a.eu.org
+ro.eu.org
+ru.eu.org
+se.eu.org
+si.eu.org
+sk.eu.org
+tr.eu.org
+uk.eu.org
+us.eu.org
+
+// Fastly Inc. http://www.fastly.com/
+// Submitted by Vladimir Vuksan <vladimir@fastly.com> 2013-05-31
+a.ssl.fastly.net
+b.ssl.fastly.net
+global.ssl.fastly.net
+a.prod.fastly.net
+global.prod.fastly.net
+
+// Firebase, Inc.
+// Submitted by Chris Raynor <chris@firebase.com> 2014-01-21
+firebaseapp.com
+
+// Flynn : https://flynn.io
+// Submitted by Jonathan Rudenberg <jonathan@flynn.io> 2014-07-12
+flynnhub.com
+
+// GDS : https://www.gov.uk/service-manual/operations/operating-servicegovuk-subdomains
+// Submitted by David Illsley <david.illsley@digital.cabinet-office.gov.uk> 2014-08-28
+service.gov.uk
+
+// GitHub, Inc.
+// Submitted by Ben Toews <btoews@github.com> 2014-02-06
+github.io
+githubusercontent.com
+
+// GlobeHosting, Inc.
+// Submitted by Zoltan Egresi <egresi@globehosting.com> 2013-07-12
+ro.com
+
+// Google, Inc.
+// Submitted by Eduardo Vela <evn@google.com> 2014-12-19
+appspot.com
+blogspot.ae
+blogspot.al
+blogspot.am
+blogspot.ba
+blogspot.be
+blogspot.bg
+blogspot.bj
+blogspot.ca
+blogspot.cf
+blogspot.ch
+blogspot.cl
+blogspot.co.at
+blogspot.co.id
+blogspot.co.il
+blogspot.co.ke
+blogspot.co.nz
+blogspot.co.uk
+blogspot.co.za
+blogspot.com
+blogspot.com.ar
+blogspot.com.au
+blogspot.com.br
+blogspot.com.by
+blogspot.com.co
+blogspot.com.cy
+blogspot.com.ee
+blogspot.com.eg
+blogspot.com.es
+blogspot.com.mt
+blogspot.com.ng
+blogspot.com.tr
+blogspot.com.uy
+blogspot.cv
+blogspot.cz
+blogspot.de
+blogspot.dk
+blogspot.fi
+blogspot.fr
+blogspot.gr
+blogspot.hk
+blogspot.hr
+blogspot.hu
+blogspot.ie
+blogspot.in
+blogspot.is
+blogspot.it
+blogspot.jp
+blogspot.kr
+blogspot.li
+blogspot.lt
+blogspot.lu
+blogspot.md
+blogspot.mk
+blogspot.mr
+blogspot.mx
+blogspot.my
+blogspot.nl
+blogspot.no
+blogspot.pe
+blogspot.pt
+blogspot.qa
+blogspot.re
+blogspot.ro
+blogspot.rs
+blogspot.ru
+blogspot.se
+blogspot.sg
+blogspot.si
+blogspot.sk
+blogspot.sn
+blogspot.td
+blogspot.tw
+blogspot.ug
+blogspot.vn
+codespot.com
+googleapis.com
+googlecode.com
+pagespeedmobilizer.com
+withgoogle.com
+withyoutube.com
+
+// Hashbang : https://hashbang.sh
+// Hashbang is a non-profit that provides shells and various other services.
+hashbang.sh
+
+// Heroku : https://www.heroku.com/
+// Submitted by Tom Maher <tmaher@heroku.com> 2013-05-02
+herokuapp.com
+herokussl.com
+
+// iki.fi
+// Submitted by Hannu Aronsson <haa@iki.fi> 2009-11-05
+iki.fi
+
+// info.at : http://www.info.at/
+biz.at
+info.at
+
+// Michau Enterprises Limited : http://www.co.pl/
+co.pl
+
+// Microsoft : http://microsoft.com
+// Submitted by Barry Dorrans <bdorrans@microsoft.com> 2014-01-24
+azurewebsites.net
+azure-mobile.net
+cloudapp.net
+
+// Mozilla Foundation : https://mozilla.org/
+// Submited by glob <glob@mozilla.com> 2015-07-06
+bmoattachments.org
+
+// Neustar Inc.
+// Submitted by Trung Tran <Trung.Tran@neustar.biz> 2015-04-23
+4u.com
+
+// ngrok : https://ngrok.com/
+// Submitted by Alan Shreve <alan@ngrok.com> 2015-11-10
+ngrok.io
+
+// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/
+// Submitted by Jeff Wheelhouse <support@nearlyfreespeech.net> 2014-02-02
+nfshost.com
+
+// NYC.mn : http://www.information.nyc.mn
+// Submitted by Matthew Brown <mattbrown@nyc.mn> 2013-03-11
+nyc.mn
+
+// One Fold Media : http://www.onefoldmedia.com/
+// Submitted by Eddie Jones <eddie@onefoldmedia.com> 2014-06-10
+nid.io
+
+// Opera Software, A.S.A.
+// Submitted by Yngve Pettersen <yngve@opera.com> 2009-11-26
+operaunite.com
+
+// OutSystems
+// Submitted by Duarte Santos <domain-admin@outsystemscloud.com> 2014-03-11
+outsystemscloud.com
+
+// Pagefront : https://www.pagefronthq.com/
+// Submitted by Jason Kriss <jason@pagefronthq.com> 2015-12-02
+pagefrontapp.com
+
+// .pl domains (grandfathered)
+art.pl
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
+// Pantheon Systems, Inc. : https://pantheon.io/
+// Submitted by Gary Dylina <gary@pantheon.io> 2015-09-14
+pantheon.io
+gotpantheon.com
+
+// priv.at : http://www.nic.priv.at/
+// Submitted by registry <lendl@nic.at> 2008-06-09
+priv.at
+
+// QA2
+// Submitted by Daniel Dent (https://www.danieldent.com/) 2015-07-16
+qa2.com
+
+// Rackmaze LLC : https://www.rackmaze.com
+// Submitted by Kirill Pertsev <kika@rackmaze.com> 2015-12-02
+rackmaze.com
+rackmaze.net
+
+// Red Hat, Inc. OpenShift : https://openshift.redhat.com/
+// Submitted by Tim Kramer <tkramer@rhcloud.com> 2012-10-24
+rhcloud.com
+
+// Sandstorm Development Group, Inc. : https://sandcats.io/
+// Submitted by Asheesh Laroia <asheesh@sandstorm.io> 2015-07-21
+sandcats.io
+
+// Service Online LLC : http://drs.ua/
+// Submitted by Serhii Bulakh <support@drs.ua> 2015-07-30
+biz.ua
+co.ua
+pp.ua
+
+// SinaAppEngine : http://sae.sina.com.cn/
+// Submitted by SinaAppEngine <saesupport@sinacloud.com> 2015-02-02
+sinaapp.com
+vipsinaapp.com
+1kapp.com
+
+// Synology, Inc. : https://www.synology.com/
+// Submitted by Rony Weng <ronyweng@synology.com> 2015-12-02
+synology.me
+diskstation.me
+i234.me
+myds.me
+dscloud.biz
+dscloud.me
+dscloud.mobi
+dsmynas.com
+dsmynas.net
+dsmynas.org
+familyds.com
+familyds.net
+familyds.org
+
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+
+// UDR Limited : http://www.udr.hk.com
+// Submitted by registry <hostmaster@udr.hk.com> 2014-11-07
+hk.com
+hk.org
+ltd.hk
+inc.hk
+
+// Yola : https://www.yola.com/
+// Submitted by Stefano Rivera <stefano@yola.com> 2014-07-09
+yolasite.com
+
+// ZaNiC : http://www.za.net/
+// Submitted by registry <hostmaster@nic.za.net> 2009-10-03
+za.net
+za.org
+
+// ===END PRIVATE DOMAINS===

+ 185 - 0
app/Listeners/Cloud/LxcUpdateListener.php

@@ -0,0 +1,185 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Features;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\VmNetwork;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class LxcUpdateListener extends VmListener
+{
+    use VmNetwork;
+
+    public function handle(LxcUpdateEvent $event)
+    {
+        $this->event    = $event;
+        $this->api();
+        $this->initVmAnVmModel();
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //Disk size
+            $diskSize = $this->event->getConfig()['disk'];
+            $masterHardDiskSize = sl('Vm')->getVm()->getMasterHddSize();
+            if ($diskSize && $diskSize - $masterHardDiskSize > 0)
+            {
+                $disk = ['disk' => "rootfs", 'size' => sprintf('+%sG', $diskSize - $masterHardDiskSize)];
+                sl('Vm')->getVm()->resize($disk);
+            }
+            //init container
+            $container = [];
+            //hostname
+            if ($hostname = $this->event->getVmModel()->name)
+            {
+                $container['hostname'] = $this->event->getVmModel()->name;
+            }
+            //description
+            $container['description'] =  htmlspecialchars($this->event->getConfig()['description'], ENT_QUOTES);
+            if ($this->event->getVmModel()->cpulimit )
+            {
+                $container['cpulimit'] = $this->event->getVmModel()->cpulimit ;
+            }
+            //Memory
+            $container['memory'] = $this->event->getVmModel()->memory;
+            //SWAP
+            $container['swap'] = $this->event->getVmModel()->swap;
+            //cpuunits
+            $container['cpuunits'] = $this->event->getVmModel()->cpuunits;
+            //Name servers
+            $ns = [];
+            for ($i = 0; $i <= 1; $i++)
+            {
+                $n = $this->event->getConfig()['nameserver'][$i];
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            if (sl('Vm')->getVm()->isRunning() && sl('Vm')->getVm()->config()['nameserver'] == $container['nameserver'])
+            {
+                unset($container['nameserver']);
+            }
+            //cores
+            if ($this->event->getVmModel()->cores)
+            {
+                $container['cores'] = $this->event->getVmModel()->cores;
+            }
+            //arch
+            if ($this->configuration()->getArch())
+            {
+                $container['arch'] = $this->configuration()->getArch();
+            }
+            //cmode
+            if ($this->configuration()->getCmode() && !sl('Vm')->getVm()->isRunning())
+            {
+                $container['cmode'] = $this->configuration()->getCmode() ;
+            }
+            //console
+            if ($this->configuration()->isConsole() && !sl('Vm')->getVm()->isRunning())
+            {
+                $container['console'] = 1;
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //protection
+            if ($this->configuration()->isProtection())
+            {
+                $container['protection'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tty
+            if ($this->configuration()->getTty() && !sl('Vm')->getVm()->isRunning())
+            {
+                $container['tty'] = $this->configuration()->getTty();
+            }
+            //Features
+            $features = new Features();
+            //Keyctl
+            if($this->configuration()->isFeatureKeyctl() && sl('Vm')->getVm()->config()['unprivileged']){
+                $features->setKeyctl(1);
+            }
+            //Nesting
+            if($this->configuration()->isFeatureNesting() ){
+                $features->setNesting(1);
+            }
+            //NFS
+            if($this->configuration()->isFeatureNfs() ){
+                $features->addNfs();
+            }
+            //CIFS
+            if($this->configuration()->isFeatureCifs() ){
+                $features->addCifs();
+            }
+            //Fuse
+            if($this->configuration()->isFeatureFuse() ){
+                $features->setFuse(1);
+            }
+            //Mknod
+            if($this->configuration()->isFeatureMknod() ){
+                $features->setMknod(1);
+            }
+            $container[Features::ID] = $features->asConfig();
+            //Network create & update
+            $container += $this->createNetwork();
+            $container += $this->updateNetwork();
+            //Update container
+            sl('Vm')->getVm()->updateConfig($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            echo $ex->getMessage();
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+
+    }
+
+    private function updateNetwork()
+    {
+        $container         = [];
+        $updateNetworkRate = !$this->configuration()->getMinimumRate() || !$this->hosting()->isBandwidthOverageUsage();
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        foreach (sl('Vm')->getVm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($updateNetworkRate && $entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        return $container;
+    }
+
+
+}

+ 443 - 0
app/Listeners/Cloud/QemuUpdateListener.php

@@ -0,0 +1,443 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\VirtualInterfaceConverter;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\SnippetTrait;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\VmNetwork;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class QemuUpdateListener extends VmListener
+{
+
+    use VmNetwork;
+    use SnippetTrait;
+
+    public function handle(QemuUpdateEvent $event)
+    {
+        $this->event    = $event;
+        $this->initVmAnVmModel();
+        $configToDelete = $this->deleteCloudInit();
+        if (!empty($configToDelete))
+        {
+            sl('Vm')->getVm()->deleteConfig(implode(",", $configToDelete));
+        }
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            if (sl('Vm')->getVm()->config(true)['template'] == "1")
+            {
+                throw new \Exception("Can not update a vm if it is a template");
+            }
+            //Disk size
+            $diskSize = $this->event->getConfig()['disk'];
+            $masterHardDisk = sl('Vm')->getVm()->getMasterHardDisk();
+            if ($diskSize && $masterHardDisk->getGb() != $diskSize)
+            {
+                $size = "+" . abs((int)$diskSize - $masterHardDisk->getGb()) . "G";
+                $masterHardDisk->resize($size);
+            }
+
+            //init container
+            $container = [];
+            if ($hostname = $this->event->getVmModel()->name)
+            {
+                $container['name'] = $hostname;
+            }
+            //vcpus
+            if ($this->event->getVmModel()->vcpus)
+            {
+                $container['vcpus'] = $this->event->getVmModel()->vcpus;
+            }
+            //cpulimit
+            if ($this->event->getVmModel()->cpulimit)
+            {
+                $container['cpulimit'] = $this->event->getVmModel()->cpulimit;
+            }
+            // Boot order device
+            if ($this->configuration()->getBootOrder())
+            {
+                $container['boot'] = $this->configuration()->getBootOrder();
+            }
+            //numa
+            if ($this->configuration()->isNuma())
+            {
+                $container['numa'] = 1;
+            }
+            //Memory
+            $container['memory'] = $this->event->getVmModel()->memory;
+            //cpuunits
+            if ($this->event->getVmModel()->cpuunits)
+            {
+                $container['cpuunits'] = $this->event->getVmModel()->cpuunits;
+            }
+            //Name servers
+            $ns = [];
+            for ($i = 0; $i <= 1; $i++)
+            {
+                $n = $this->event->getConfig()['nameserver'][$i];
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            //sockets
+            if ($this->event->getVmModel()->sockets)
+            {
+                $container['sockets'] = $this->event->getVmModel()->sockets;
+            }
+            //cores
+            if ($this->event->getVmModel()->cores)
+            {
+                $container['cores'] = $this->event->getVmModel()->cores;
+            }
+            //description
+            $container['description'] =  htmlspecialchars($this->event->getConfig()['description'], ENT_QUOTES);
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //CD-ROM
+            $isoFile = $this->event->getConfig()['isoImage'];
+            $cdrom   = sl('Vm')->getVm()->getCdrom()->first();
+            if ($isoFile && $cdrom && $cdrom->getLocation() != $isoFile)
+            {
+                $cdrom->setLocation($isoFile);
+                $container[$cdrom->getId()] = $cdrom->asConfig();
+            }
+            //acpi
+            if ($this->configuration()->isAcpi())
+            {
+                $container['acpi'] = 1;
+            }
+            //agent
+            if ($this->configuration()->isAgent())
+            {
+                $container['agent'] = 1;
+            }
+            //autostart
+            if ($this->configuration()->isAutostart())
+            {
+                $container['autostart'] = 1;
+            }
+            //balloon
+            if ($this->configuration()->getBalloon())
+            {
+                $container['balloon'] = $this->configuration()->getBalloon();
+            }
+            //shares
+            if ($this->configuration()->getShares())
+            {
+                $container['shares'] = $this->configuration()->getShares();
+            }
+            //cdrom
+            if ($this->configuration()->getCdrom())
+            {
+                $container['cdrom'] = $this->configuration()->getCdrom();
+            }
+            //cpu
+            if ($this->configuration()->getCpu())
+            {
+                $container['cpu'] = $this->configuration()->getCpu();
+            }
+            //freeze
+            if ($this->configuration()->isFreeze())
+            {
+                $container['freeze'] = 1;
+            }
+            //hotplug
+            if ($this->configuration()->getHotplug())
+            {
+                $container['hotplug'] = $this->configuration()->getHotplug();
+            }
+            //keyboard
+            if ($this->configuration()->getKeyboard())
+            {
+                $container['keyboard'] = $this->configuration()->getKeyboard();
+            }
+            //kvm
+            if ($this->configuration()->isKvm())
+            {
+                $container['kvm'] = 1;
+            }
+            //localtime
+            if ($this->configuration()->isLocaltime())
+            {
+                $container['localtime'] = 1;
+            }
+            //migrate_downtime
+            if ($this->configuration()->getMigrateDowntime())
+            {
+                $container['migrate_downtime'] = $this->configuration()->getMigrateDowntime();
+            }
+            //migrate_speed
+            if ($this->configuration()->getMigrateSpeed())
+            {
+                $container['migrate_speed'] = $this->configuration()->getMigrateSpeed();
+            }
+            //reboot
+            if ($this->configuration()->isReboot())
+            {
+                $container['reboot'] = 1;
+            }
+            //startdate
+            if ($this->configuration()->getStartdate())
+            {
+                $container['startdate'] = $this->configuration()->getStartdate();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tablet
+            if ($this->configuration()->isTablet())
+            {
+                $container['tablet'] = 1;
+            }
+            //tdf
+            if ($this->configuration()->isTdf())
+            {
+                $container['tdf'] = 1;
+            }
+            //watchdog
+            if ($this->configuration()->getWatchdog())
+            {
+                $container['watchdog'] = $this->configuration()->getWatchdog();
+            }
+            //bootdisk
+            if ($this->configuration()->getBootdisk())
+            {
+                $container['bootdisk'] = $this->configuration()->getBootdisk();
+            }
+            //scsihw
+            if ($this->configuration()->getScsihw())
+            {
+                $container['scsihw'] = $this->configuration()->getScsihw();
+            }
+            //args
+            if ($this->configuration()->getArgs())
+            {
+                $container['args'] = $this->configuration()->getArgs();
+            }
+            //vga
+            $vga=[];
+            if ($this->configuration()->getVga())
+            {
+                $vga[] = $this->configuration()->getVga();
+            }
+            if($container['vga']!="none" && $this->configuration()->getVgaMemory()){
+                $vga[]="memory=".$this->configuration()->getVgaMemory();
+            }
+            if(!empty($vga)){
+                $container['vga'] = implode(",",$vga);
+            }
+            //xterm.js Console
+            if ($this->configuration()->isPermissionXtermjs())
+            {
+                $container['serial0'] = 'socket';
+            }
+            //cpu flags
+            if ($this->configuration()->hasCpuFlags()  && version_compare($this->api()->getVersion(), "5.2", '>'))
+            {
+                $container['cpu'] .= ',flags=' . $this->configuration()->getCpuFlagsAsSource();
+            }
+            //bios
+            if ($this->configuration()->getBios())
+            {
+                $container['bios'] = $this->configuration()->getBios();
+            }
+            //machine
+            if ($this->configuration()->getMachine())
+            {
+                $container['machine'] = $this->configuration()->getMachine();
+            }
+            //Hard Disks
+            $container += $this->updateHardDisks();
+            $container += $this->createNetwork();
+            $container += $this->updateNetwork();
+            //Cloud-Init
+            $container += $this->updateCloudInit();
+            //Update container
+            sl('Vm')->getVm()->updateConfig($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+
+    }
+
+    private function updateHardDisks()
+    {
+        $container = [];
+        //IOPS & MB/s
+        foreach (sl('Vm')->getVm()->getHardDisks() as $hardDisk)
+        {
+            $hash = $hardDisk->getHashCode();
+            //Edit master hard disk
+            if ($hardDisk->isMaster())
+            {
+                $hardDisk->setCache($this->configuration()->getDiskCache())
+                    ->setFormat($this->configuration()->getDiskFormat())
+                    ->setMbps_rd($this->configuration()->getMbpsRd())
+                    ->setMbps_wr($this->configuration()->getMbpsWr())
+                    ->setDiscard($this->configuration()->isDiscard() ? "on" : null)
+                    ->setIops_rd($this->configuration()->getIopsRd())
+                    ->setIops_rd_max($this->configuration()->getIopsRdMax())
+                    ->setIops_wr($this->configuration()->getIopsWr())
+                    ->setIops_wr_max($this->configuration()->getIopsWrMax())
+                    ->setReplicate($this->configuration()->isReplicate() ? 0 : null);
+                if ($this->configuration()->isIoThread() && in_array($hardDisk->getType(), ['virtio', 'scsi']))
+                {
+                    $hardDisk->setIothread($this->configuration()->isIoThread());
+                }
+                $hardDisk->setSsd($this->configuration()->isSsd() ? 1 : null);
+            }
+            else
+            {//additional disk
+                $hardDisk->setMbps_rd($this->configuration()->getAdditionalDiskMbpsRd())
+                    ->setMbps_wr($this->configuration()->getAdditionalDiskMbpsWr())
+                    ->setIops_rd($this->configuration()->getAdditionalDiskIopsRd())
+                    ->setIops_rd_max($this->configuration()->getAdditionalDiskIopsRdMax())
+                    ->setIops_wr($this->configuration()->getAdditionalDiskIopsWr())
+                    ->setIops_wr_max($this->configuration()->getAdditionalDiskIopsWrMax());
+                $hardDisk->setSsd($this->configuration()->isAdditionalDiskSsd() ? 1 : null);
+            }
+            if ($hash != $hardDisk->getHashCode())
+            {
+                $container[$hardDisk->getId()] = $hardDisk->asConfig();
+            }
+        }
+        return $container;
+    }
+
+    private function updateNetwork()
+    {
+        $container         = [];
+        $updateNetworkRate = !$this->configuration()->getMinimumRate() || !$this->hosting()->isBandwidthOverageUsage();
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        $queues            = $this->configuration()->getQueues();
+        foreach (sl('Vm')->getVm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($updateNetworkRate && $entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            $entity->setQueues($queues);
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        return $container;
+    }
+
+    private function deleteCloudInit()
+    {
+        $delete = [];
+        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
+        {
+            return $delete;
+        }
+        $vmConfig = sl('Vm')->getVm()->config();
+        //ciuser
+        if ($this->configuration()->isPermissionUsername() &&F && $vmConfig['ciuser'])
+        {
+            $delete[] = 'ciuser';
+        }
+        //cipassword
+        if ($this->configuration()->isPermissionPassword() && (!$this->event->getVmModel()->getPassword()) && $vmConfig['cipassword'])
+        {
+            $delete[] = 'cipassword';
+        }
+        //sshkeys
+        if (!$this->event->getConfig()['sshkeys'])
+        {
+            $delete[] = 'sshkeys';
+        }
+        //searchdomain
+        if (!$this->event->getConfig()['searchdomain'] && !$this->event->getConfig()['searchdomain'] && $vmConfig['searchdomain'])
+        {
+            $delete[] = 'searchdomain';
+        }
+        //nameserver
+        if (!$this->event->getConfig()['nameserver']  && $vmConfig['nameserver'] )
+        {
+            $delete[] = 'nameserver';
+        }
+        return $delete;
+    }
+
+    private function updateCloudInit()
+    {
+        $container = [];
+        if (!$this->configuration()->isCloudInit())
+        {
+            return $container;
+        }
+        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
+        {
+            return $container;
+        }
+        //ciuser
+        if ($this->configuration()->isPermissionUsername() && $this->event->getConfig()['ciuser']) {
+            $container['ciuser'] = $this->event->getConfig()['ciuser'];
+        } elseif ($this->configuration()->getCiuser()) {
+            $container['ciuser'] = $this->configuration()->getCiuser();
+        }
+        //cipassword
+        if ($this->event->getVmModel()->getPassword() && $this->event->isChangeVmPassword()) {
+            $container['cipassword'] = $this->event->getVmModel()->getPassword();
+        }
+        //sshkeys
+        if ($this->configuration()->isPermissionSshkeys() && $this->event->getConfig()['sshkeys'])
+        {
+            $container['sshkeys'] = rawurlencode($this->event->getConfig()['sshkeys']);
+        }
+        //searchdomain
+        if ($this->configuration()->isPermissionSearchdomain() && $this->event->getConfig()['searchdomain'])
+        {
+            $container['searchdomain'] = $this->event->getConfig()['searchdomain'];
+        }
+        //cicustom
+        if($this->hasSnippet() &&  $file = $this->getSnippetFile($this->event->getVmModel())){
+            $container['cicustom'] = sprintf("user=%s", $file->getVolid());
+        }
+        elseif ($this->configuration()->getCicustom())
+        {
+            $container['cicustom'] = $this->configuration()->getCicustom();
+        }
+        return $container;
+    }
+
+}

+ 36 - 0
app/Listeners/Cloud/VmCreatedListener.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmCreatedEvent;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class VmCreatedListener extends VmListener
+{
+
+    public function handle(VmCreatedEvent $event)
+    {
+        $this->event    = $event;
+        $this->api();
+        $this->initVmAnVmModel();
+        //update user permission
+        $this->userUpdatePermission([$this->event->getVmModel()->vmid]);
+        //Update firewal Options
+        $this->firewallOptionService->update();
+        //IP Set
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetFilterService->create();
+        }
+        //HA
+        if ($this->highAvailabilityClusterService->isConfigured() && !$this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        //Start the VM
+        if (!sl('Vm')->getVm()->isRunning() && $this->configuration()->isStart())
+        {
+            sl('Vm')->getVm()->start();
+        }
+    }
+}

+ 64 - 0
app/Listeners/Cloud/VmListener.php

@@ -0,0 +1,64 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;
+
+
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmEvent;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\FirewallOptionService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\UserService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\VirtualInterfaceConverter;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+abstract  class VmListener
+{
+    use WhmcsParams;
+    use ApiService;
+    use UserService;
+    use ProductService;
+    use HostingService;
+
+    /**
+     * @var IpSetIpFilterService
+     */
+    protected $ipSetFilterService;
+    /**
+     * @var FirewallOptionService
+     */
+    protected $firewallOptionService;
+    protected $emailService;
+    protected $containerService;
+    protected $networkService;
+
+    /**
+     * @var VmEvent
+     */
+    protected $event;
+
+    public function __construct()
+    {
+        $this->ipSetFilterService = new IpSetIpFilterService();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->emailService       = new EmailService();
+        $this->containerService   = new ContainerService();
+        $this->networkService     = new NetworkService();
+        $this->firewallOptionService = new FirewallOptionService();
+    }
+
+    public function initVmAnVmModel(){
+        sl('Vm')->setVm((new VmFactory())->fromVmModel($this->event->getVmModel()));
+        sl('Vm')->setVmModel($this->event->getVmModel());
+        return $this;
+    }
+}

+ 35 - 0
app/Listeners/Cloud/VmReinstalledListener.php

@@ -0,0 +1,35 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Events\Cloud\VmReinstalledEvent;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class VmReinstalledListener extends VmListener
+{
+
+    public function handle(VmReinstalledEvent $event)
+    {
+        $this->event    = $event;
+        $this->api();
+        $this->initVmAnVmModel();
+        //IP Set
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetFilterService->create();
+        }
+        //User permission
+        $this->userUpdatePermission([$this->event->getVmModel()->vmid]);
+        //HA
+        if ($this->highAvailabilityClusterService->isConfigured() && !$this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        if (!sl('Vm')->getVm()->isRunning() && $this->configuration()->isStart())
+        {
+            sl('Vm')->getVm()->start();
+        }
+    }
+}

+ 245 - 0
app/Listeners/Vps/LxcUpdateListener.php

@@ -0,0 +1,245 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Features;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\LxcUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalMountPointService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class LxcUpdateListener
+{
+    use WhmcsParams;
+    use ApiService;
+    use UserService;
+    use ProductService;
+    use HostingService;
+
+    private $containerService;
+    private $networkService;
+    /**
+     * @var AdditionalMountPointService
+     */
+    private $additionalMountPointService;
+    /**
+     * @var QemuUpdateEvent
+     */
+    private $event;
+
+    /**
+     * VmCreatedListener constructor.
+     */
+    public function __construct()
+    {
+        $this->containerService = new ContainerService();
+        $this->networkService   = new NetworkService();
+        $this->additionalMountPointService = new AdditionalMountPointService();
+    }
+
+    public function handle(LxcUpdateEvent $event)
+    {
+        $this->event   = $event;
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            //Disk size
+            $diskSize = $this->configuration()->getDiskSize();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+            {
+                $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
+            }
+            $masterHardDiskSize = $this->vm()->getMasterHddSize();
+            if ($diskSize && $diskSize - $masterHardDiskSize > 0)
+            {
+                $disk = ['disk' => "rootfs", 'size' => sprintf('+%sG', $diskSize - $masterHardDiskSize)];
+                $this->vm()->resize($disk);
+            }
+            //init container
+            $container = [];
+            //hostname
+            if ($hostname = $this->containerService->hostname())
+            {
+                $container['hostname'] = $hostname;
+            }
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //Configurable Options
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() ) )
+            {
+                $container['cpulimit'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() );
+            }
+            //Memory
+            $container['memory'] = $this->configuration()->getMemory();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $container['memory'] = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($container['memory'], $this->configuration()->getMemoryUnit(), 'mb');
+            }
+            //SWAP
+            $container['swap'] = $this->configuration()->getSwap();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::SWAP))
+            {
+                $container['swap'] = $this->getWhmcsConfigOption(ConfigurableOption::SWAP);
+                Utility::unitFormat($container['swap'], $this->configuration()->getSwapUnit(), 'mb');
+            }
+            //cpuunits
+            $container['cpuunits'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits()) ;
+            //Name servers
+            $ns = [];
+            for ($i = 1; $i <= 2; $i++)
+            {
+                $n = trim($this->hosting()->{"ns{$i}"});
+                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $n = gethostbyname($n);
+                }
+                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                {
+                    $ns[] = $n;
+                }
+            }
+            if ($ns)
+            {
+                $container['nameserver'] = implode(" ", $ns);
+            }
+            if ($this->vm()->isRunning() && $this->vm()->config()['nameserver'] == $container['nameserver'])
+            {
+                unset($container['nameserver']);
+            }
+            //cores
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CORES,$this->configuration()->getCores() ))
+            {
+                $container['cores'] = $this->getWhmcsConfigOption(ConfigurableOption::CORES,$this->configuration()->getCores() );
+            }
+            //arch
+            if ($this->configuration()->getArch())
+            {
+                $container['arch'] = $this->configuration()->getArch();
+            }
+            //cmode
+            if ($this->configuration()->getCmode() && !$this->vm()->isRunning())
+            {
+                $container['cmode'] = $this->configuration()->getCmode();
+            }
+            //console
+            if ($this->configuration()->isConsole() && !$this->vm()->isRunning())
+            {
+                $container['console'] = 1;
+            }
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //protection
+            if ($this->configuration()->isProtection())
+            {
+                $container['protection'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tty
+            if ($this->configuration()->getTty() && !$this->vm()->isRunning())
+            {
+                $container['tty'] = $this->configuration()->getTty();
+            }
+            //Features
+            $features = new Features();
+            //Keyctl
+            if($this->configuration()->isFeatureKeyctl() && $this->vm()->config()['unprivileged']){
+                $features->setKeyctl(1);
+            }
+            //Nesting
+            if($this->configuration()->isFeatureNesting() ){
+                $features->setNesting(1);
+            }
+            //NFS
+            if($this->configuration()->isFeatureNfs() ){
+                $features->addNfs();
+            }
+            //CIFS
+            if($this->configuration()->isFeatureCifs() ){
+                $features->addCifs();
+            }
+            //Fuse
+            if($this->configuration()->isFeatureFuse() ){
+                $features->setFuse(1);
+            }
+            //Mknod
+            if($this->configuration()->isFeatureMknod() ){
+                $features->setMknod(1);
+            }
+            $container[Features::ID] = $features->asConfig();
+            //Network create & update
+            $container += $this->networkService->buildLxc();
+            $container += $this->updateNetwork();
+            //Update container
+            $this->vm()->updateConfig($container);
+            DB::commit();
+            if($this->configuration()->isMountPoint() && $this->additionalMountPointService->hasMountPoint()){
+                $this->additionalMountPointService->resize();
+            }elseif ($this->configuration()->isMountPoint() && !$this->additionalMountPointService->hasMountPoint() ){
+                $this->additionalMountPointService->create();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            echo $ex->getMessage();
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+
+    }
+
+    private function updateNetwork()
+    {
+        $container         = [];
+        $updateNetworkRate = !$this->configuration()->getMinimumRate() || !$this->hosting()->isBandwidthOverageUsage();
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        foreach ($this->vm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($updateNetworkRate && $entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        return $container;
+    }
+
+
+}

+ 517 - 0
app/Listeners/Vps/QemuUpdateListener.php

@@ -0,0 +1,517 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\File;
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Traits\Vps\SnippetTrait;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class QemuUpdateListener
+{
+    use WhmcsParams;
+    use ApiService;
+    use UserService;
+    use ProductService;
+    use HostingService;
+    use SnippetTrait;
+
+    /**
+     * @var IpSetIpFilterService
+     */
+    private $ipSetFilterService;
+    private $emailService;
+    private $containerService;
+    private $networkService;
+    /**
+     * @var AdditionalDiskService
+     */
+    protected $additionalDiskService;
+    /**
+     * @var QemuUpdateEvent
+     */
+    private $event;
+
+    /**
+     * VmCreatedListener constructor.
+     */
+    public function __construct()
+    {
+        $this->ipSetFilterService = new IpSetIpFilterService();
+        $this->emailService       = new EmailService();
+        $this->containerService   = new ContainerService();
+        $this->networkService     = new NetworkService();
+        $this->additionalDiskService = new AdditionalDiskService();
+    }
+
+    public function handle(QemuUpdateEvent $event)
+    {
+        $this->event    = $event;
+        $configToDelete = $this->deleteCloudInit();
+        if (!empty($configToDelete))
+        {
+            $this->vm()->deleteConfig(implode(",", $configToDelete));
+        }
+
+
+        try
+        {
+            Api::beginTransaction();
+            DB::beginTransaction();
+            if ($this->vm()->config(true)['template'] == "1")
+            {
+                throw new \Exception("Can not update a vm if it is a template");
+            }
+            //Disk size
+            $diskSize = $this->configuration()->getDiskSize();
+            if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+            {
+                $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
+            }
+            $masterHardDisk = $this->vm()->getMasterHardDisk();
+            if ($diskSize && $masterHardDisk->getGb() < $diskSize)
+            {
+                $size = "+" . abs((int)$diskSize - $masterHardDisk->getGb()) . "G";
+                $masterHardDisk->resize($size);
+            }
+            //init container
+            $container = [];
+            if ($hostname = $this->containerService->hostname())
+            {
+                $container['name'] = $hostname;
+            }
+            //vcpus
+            if ($this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus()))
+            {
+                $container['vcpus'] = $this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus()) ;
+            }
+            //cpulimit
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() ))
+            {
+                $container['cpulimit'] =$this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() );
+            }
+            // Boot order device
+            if ($this->configuration()->getBootOrder())
+            {
+                $container['boot'] = $this->configuration()->getBootOrder();
+            }
+            //numa
+            if ($this->configuration()->isNuma())
+            {
+                $container['numa'] = 1;
+            }
+            //Memory
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $container['memory'] = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($container['memory'], $this->configuration()->getMemoryUnit(), 'mb');
+            }
+            else if ($this->configuration()->getMemory()) {
+                $container['memory'] = $this->configuration()->getMemory();
+            }
+            //cpuunits
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits()))
+            {
+                $container['cpuunits'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits());
+            }
+            //Name servers
+            if ($this->configuration()->isCloudInitServiceNameservers())
+            {
+                $ns = [];
+                for ($i = 1; $i <= 2; $i++)
+                {
+                    $n = trim($this->hosting()->{"ns{$i}"});
+                    if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
+                    {
+                        $n = gethostbyname($n);
+                    }
+                    if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
+                    {
+                        $ns[] = $n;
+                    }
+                }
+                if ($ns)
+                {
+                    $container['nameserver'] = implode(" ", $ns);
+                }
+            }
+            //sockets
+            if ($this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets()))
+            {
+                $container['sockets'] = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets());
+            }
+            //cores
+            if ($this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores()) )
+            {
+                $container['cores'] = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores());
+            }
+            //description
+            if ($this->configuration()->getDescription())
+            {
+                $container['description'] = $this->containerService->description();
+            }
+            //onboot
+            if ($this->configuration()->isOnboot())
+            {
+                $container['onboot'] = 1;
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //CD-ROM
+            $isoFile = $this->getWhmcsConfigOption(ConfigurableOption::ISO_IMAGE, $this->configuration()->getIsoImage());
+            $cdrom   = $this->vm()->getCdrom()->first();
+            if ($isoFile && $cdrom && $cdrom->getLocation() != $isoFile)
+            {
+                $cdrom->setLocation($isoFile);
+                $container[$cdrom->getId()] = $cdrom->asConfig();
+            }
+            //acpi
+            if ($this->configuration()->isAcpi())
+            {
+                $container['acpi'] = 1;
+            }
+            //agent
+            if ($this->configuration()->isAgent())
+            {
+                $container['agent'] = 1;
+            }
+            //autostart
+            if ($this->configuration()->isAutostart())
+            {
+                $container['autostart'] = 1;
+            }
+            //balloon
+            if ($this->configuration()->getBalloon())
+            {
+                $container['balloon'] = $this->configuration()->getBalloon();
+            }
+            //shares
+            if ($this->configuration()->getShares())
+            {
+                $container['shares'] = $this->configuration()->getShares();
+            }
+            //cdrom
+            if ($this->configuration()->getCdrom())
+            {
+                $container['cdrom'] = $this->configuration()->getCdrom();
+            }
+            //cpu
+            if ($this->configuration()->getCpu())
+            {
+                $container['cpu'] = $this->configuration()->getCpu();
+            }
+            //freeze
+            if ($this->configuration()->isFreeze())
+            {
+                $container['freeze'] = 1;
+            }
+            //hotplug
+            if ($this->configuration()->getHotplug())
+            {
+                $container['hotplug'] = $this->configuration()->getHotplug();
+            }
+            //keyboard
+            if ($this->configuration()->getKeyboard())
+            {
+                $container['keyboard'] = $this->configuration()->getKeyboard();
+            }
+            //kvm
+            if ($this->configuration()->isKvm())
+            {
+                $container['kvm'] = 1;
+            }else{
+                $container['kvm'] = 0;
+            }
+            //localtime
+            if ($this->configuration()->isLocaltime())
+            {
+                $container['localtime'] = 1;
+            }
+            //migrate_downtime
+            if ($this->configuration()->getMigrateDowntime())
+            {
+                $container['migrate_downtime'] = $this->configuration()->getMigrateDowntime();
+            }
+            //migrate_speed
+            if ($this->configuration()->getMigrateSpeed())
+            {
+                $container['migrate_speed'] = $this->configuration()->getMigrateSpeed();
+            }
+            //reboot
+            if ($this->configuration()->isReboot())
+            {
+                $container['reboot'] = 1;
+            }
+            //startdate
+            if ($this->configuration()->getStartdate())
+            {
+                $container['startdate'] = $this->configuration()->getStartdate();
+            }
+            //startup
+            if ($this->configuration()->getStartup())
+            {
+                $container['startup'] = $this->configuration()->getStartup();
+            }
+            //tablet
+            if ($this->configuration()->isTablet())
+            {
+                $container['tablet'] = 1;
+            }
+            //tdf
+            if ($this->configuration()->isTdf())
+            {
+                $container['tdf'] = 1;
+            }
+            //watchdog
+            if ($this->configuration()->getWatchdog())
+            {
+                $container['watchdog'] = $this->configuration()->getWatchdog();
+            }
+            //bootdisk
+            if ($this->configuration()->getBootdisk())
+            {
+                $container['bootdisk'] = $this->configuration()->getBootdisk();
+            }
+            //scsihw
+            if ($this->configuration()->getScsihw())
+            {
+                $container['scsihw'] = $this->configuration()->getScsihw();
+            }
+            //args
+            if ($this->configuration()->getArgs())
+            {
+                $container['args'] = $this->configuration()->getArgs();
+            }
+            //vga
+            $vga=[];
+            if ($this->configuration()->getVga())
+            {
+                $vga[] = $this->configuration()->getVga();
+            }
+            if($container['vga']!="none" && $this->configuration()->getVgaMemory()){
+                $vga[]="memory=".$this->configuration()->getVgaMemory();
+            }
+            if(!empty($vga)){
+                $container['vga'] = implode(",",$vga);
+            }
+            //xterm.js Console
+            if ($this->configuration()->isPermissionXtermjs())
+            {
+                $container['serial0'] = 'socket';
+            }
+            //cpu flags
+            if ($this->configuration()->hasCpuFlags()  && version_compare($this->api()->getVersion(), "5.2", '>'))
+            {
+                $container['cpu'] .= ',flags=' . $this->configuration()->getCpuFlagsAsSource();
+            }
+            //bios
+            if ($this->configuration()->getBios())
+            {
+                $container['bios'] = $this->configuration()->getBios();
+            }
+            //machine
+            if ($this->configuration()->getMachine())
+            {
+                $container['machine'] = $this->configuration()->getMachine();
+            }
+            //Hard Disks
+            $container += $this->updateHardDisks();
+            //Network create & update
+            $container += $this->networkService->buildQemu();
+            $container += $this->updateNetwork();
+            //Cloud-Init
+            $container += $this->updateCloudInit();
+            //Update container
+            $this->vm()->updateConfig($container);
+            DB::commit();
+            if($this->configuration()->isAdditionalDisk() && $this->additionalDiskService->hasDisk()){
+                $this->additionalDiskService->resize();
+            }elseif ($this->configuration()->isAdditionalDisk() && !$this->additionalDiskService->hasDisk()){
+                $this->additionalDiskService->create();
+            }
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+
+    }
+
+    private function updateHardDisks()
+    {
+        $container = [];
+        //IOPS & MB/s
+        foreach ($this->vm()->getHardDisks() as $hardDisk)
+        {
+            $hash = $hardDisk->getHashCode();
+            //Edit master hard disk
+            if ($hardDisk->isMaster())
+            {
+                $hardDisk->setCache($this->configuration()->getDiskCache())
+                    ->setFormat($this->configuration()->getDiskFormat())
+                    ->setMbps_rd($this->configuration()->getMbpsRd())
+                    ->setMbps_wr($this->configuration()->getMbpsWr())
+                    ->setDiscard($this->configuration()->isDiscard() ? "on" : null)
+                    ->setIops_rd($this->configuration()->getIopsRd())
+                    ->setIops_rd_max($this->configuration()->getIopsRdMax())
+                    ->setIops_wr($this->configuration()->getIopsWr())
+                    ->setIops_wr_max($this->configuration()->getIopsWrMax())
+                    ->setReplicate($this->configuration()->isReplicate() ? 0 : null);
+                if ($this->configuration()->isIoThread() && in_array($hardDisk->getType(), ['virtio', 'scsi']))
+                {
+                    $hardDisk->setIothread($this->configuration()->isIoThread());
+                }
+                $hardDisk->setSsd($this->configuration()->isSsd() ? 1 : null);
+            }
+            else
+            {//additional disk
+                $hardDisk->setMbps_rd($this->configuration()->getAdditionalDiskMbpsRd())
+                    ->setMbps_wr($this->configuration()->getAdditionalDiskMbpsWr())
+                    ->setIops_rd($this->configuration()->getAdditionalDiskIopsRd())
+                    ->setIops_rd_max($this->configuration()->getAdditionalDiskIopsRdMax())
+                    ->setIops_wr($this->configuration()->getAdditionalDiskIopsWr())
+                    ->setIops_wr_max($this->configuration()->getAdditionalDiskIopsWrMax());
+                $hardDisk->setSsd($this->configuration()->isAdditionalDiskSsd() ? 1 : null);
+            }
+            if ($hash != $hardDisk->getHashCode())
+            {
+                $container[$hardDisk->getId()] = $hardDisk->asConfig();
+            }
+        }
+        return $container;
+    }
+
+    private function updateNetwork()
+    {
+        $container         = [];
+        $updateNetworkRate = !$this->configuration()->getMinimumRate() || !$this->hosting()->isBandwidthOverageUsage();
+        $rate      = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
+        }
+        $queues            = $this->configuration()->getQueues();
+        foreach ($this->vm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
+        {
+            $hash = $entity->getHashCode();
+            if ($updateNetworkRate && $entity->getRate() != $rate)
+            {
+                $entity->setRate($rate);
+            }
+            $entity->setQueues($queues);
+            if ($hash != $entity->getHashCode())
+            {
+                $container[$entity->getId()] = $entity->asConfig();
+            }
+        }
+        return $container;
+    }
+
+    private function deleteCloudInit()
+    {
+        $delete = [];
+        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
+        {
+            return $delete;
+        }
+        $config = $this->vm()->config();
+        //ciuser
+        if (!$this->getWhmcsCustomField(CustomField::CI_USER) && $this->configuration()->isCloudInitServiceUsername() && !$this->getWhmcsParamByKey('username') && $config['ciuser'])
+        {
+            $delete[] = 'ciuser';
+        }
+        //cipassword
+        if ($this->configuration()->isCloudInitServicePassword() && (!$this->getWhmcsParamByKey('password') && !$this->getWhmcsCustomField('cipassword')) && $config['cipassword'])
+        {
+            $delete[] = 'cipassword';
+        }
+        //sshkeys
+        if (!$this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY))
+        {
+            $delete[] = 'sshkeys';
+        }
+        //searchdomain
+        if (!$this->configuration()->getSearchdomain() && $config['searchdomain'])
+        {
+            $delete[] = 'searchdomain';
+        }
+        //nameserver
+        if ($this->configuration()->isCloudInitServiceNameservers() && !trim($this->hosting()->ns1) && !trim($this->hosting()->ns2))
+        {
+            $delete[] = 'nameserver';
+        }
+        return $delete;
+    }
+
+    private function updateCloudInit()
+    {
+        $container = [];
+        if (!$this->configuration()->isCloudInit())
+        {
+            return $container;
+        }
+        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
+        {
+            return $container;
+        }
+        //ciuser
+        if ($this->getWhmcsCustomField(CustomField::CI_USER)) {
+            $container['ciuser'] = $this->getWhmcsCustomField(CustomField::CI_USER);
+        } elseif ($this->configuration()->getCiuser()) {
+            $container['ciuser'] = $this->configuration()->getCiuser();
+        } elseif ($this->configuration()->isCloudInitServiceUsername()) {
+            $container['ciuser'] = $this->getWhmcsParamByKey('username');
+        }
+        //cipassword
+        if ($this->configuration()->isCloudInitServicePassword() && $this->event->isChangeVmPassword())
+        {
+            $container['cipassword'] = $this->getWhmcsCustomField('cipassword') ? $this->getWhmcsCustomField('cipassword') : $this->getWhmcsParamByKey('password');
+        }
+        //sshkeys
+        if ($this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY))
+        {
+            $container['sshkeys'] = rawurlencode($this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY));
+        }
+        //searchdomain
+        if ($this->configuration()->getSearchdomain())
+        {
+            $container['searchdomain'] = $this->configuration()->getSearchdomain();
+        }
+        //cicustom
+        if($this->hasSnippet() &&  $file = $this->getSnippetFile()){
+            /**
+             * @var File $file
+             */
+            $container['cicustom'] = sprintf("user=%s", $file->getVolid());
+        } elseif($this->getWhmcsConfigOption(ConfigurableOption::CICUSTOM) ){
+            $container['cicustom'] = $this->getWhmcsConfigOption(ConfigurableOption::CICUSTOM) ;
+        }
+        elseif ($this->configuration()->getCicustom())
+        {
+            $container['cicustom'] = $this->configuration()->getCicustom();
+        }
+        return $container;
+    }
+
+}

+ 87 - 0
app/Listeners/Vps/VmCreatedListener.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Vps;
+
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmCreatedEvent;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\FirewallOptionService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+
+class VmCreatedListener
+{
+    use WhmcsParams;
+    use ApiService;
+    use UserService;
+    use ProductService;
+
+    /**
+     * @var IpSetIpFilterService
+     */
+    private $ipSetFilterService;
+    private $emailService;
+    /**
+     * @var NetworkService
+     */
+    private $networkService;
+
+    /**
+     * @var FirewallOptionService
+     */
+    protected $firewallOptionService;
+    /**
+     * VmCreatedListener constructor.
+     */
+    public function __construct()
+    {
+        $this->ipSetFilterService = new IpSetIpFilterService();
+        $this->emailService       = new EmailService();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->networkService = new NetworkService();
+        $this->firewallOptionService = new FirewallOptionService();
+    }
+
+    public function handle(VmCreatedEvent $event)
+    {
+        //update user permission
+        $this->userUpdate();
+        //Private Network
+        if($this->configuration()->isPrivateNetwork()){
+            //Get Private IP Addresses
+            $ip = $this->networkService->getPrivateIpAddress();
+            //Create network device
+            $this->networkService->addPrivate([$ip]);
+        }
+        //Update firewal Options
+        $this->firewallOptionService->update();
+        //IP Set
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetFilterService->create();
+        }
+        //HA
+        if ($this->highAvailabilityClusterService->isConfigured() && !$this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        //Start the VM<
+        if (!$this->vm()->isRunning() && $this->configuration()->isStart())
+        {
+            $this->vm()->start();
+        }
+        //Welcome email notification
+        if ($this->configuration()->getWelcomeEmailTemplateId())
+        {
+            $this->emailService->template($this->configuration()->getWelcomeEmailTemplateId())
+                ->vars(["id" => $this->getWhmcsParamByKey("serviceid")])
+                ->send();
+        }
+
+    }
+}

+ 68 - 0
app/Listeners/Vps/VmReinstalledListener.php

@@ -0,0 +1,68 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Listeners\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\App\Events\Vps\VmReinstalledEvent;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\IpSetIpFilterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class VmReinstalledListener
+{
+    use WhmcsParams;
+    use ApiService;
+    use UserService;
+    use ProductService;
+
+    /**
+     * @var IpSetIpFilterService
+     */
+    private $ipSetFilterService;
+    private $emailService;
+    /**
+     * @var  HighAvailabilityClusterService
+     */
+    protected $highAvailabilityClusterService;
+
+    public function __construct()
+    {
+        $this->ipSetFilterService             = new IpSetIpFilterService();
+        $this->emailService                   = new EmailService();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+        $this->networkService = new NetworkService();
+    }
+
+    public function handle(VmReinstalledEvent $event)
+    {
+        //IP Set
+        if ($this->configuration()->isIpsetIpFilter())
+        {
+            $this->ipSetFilterService->create();
+        }
+        //User permission
+        $this->userUpdate();
+        //HA
+        if ($this->highAvailabilityClusterService->isConfigured() && !$this->highAvailabilityClusterService->exist())
+        {
+            $this->highAvailabilityClusterService->create();
+        }
+        if (!$this->vm()->isRunning() && $this->configuration()->isStart())
+        {
+            $this->vm()->start();
+        }
+        //Reinstall notification
+        if ($this->configuration()->getReinstallEmailTemplateId())
+        {
+            $this->emailService->template($this->configuration()->getReinstallEmailTemplateId())
+                ->vars(["id" => $this->getWhmcsParamByKey('serviceid')])
+                ->send();
+        }
+    }
+}

+ 28 - 0
app/Models/CloudInitScript.php

@@ -0,0 +1,28 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Class CloudInitScript
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @property string $id;
+ * @property string $name
+ * @property  string $script
+ * @method static CloudInitScript ofIds($ids)
+ */
+class CloudInitScript extends ExtendedEloquentModel
+{
+
+    protected $table = 'CloudInitScript';
+
+    /** @var array */
+    protected $fillable = [ 'name', 'script'];
+
+
+    public function scopeOfIds($query , array $ids){
+        return $query->whereIn('id',$ids);
+    }
+}

+ 88 - 0
app/Models/IpAddress.php

@@ -0,0 +1,88 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use Illuminate\Database\Eloquent\model as EloquentModel;
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of RangeVm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $id
+ * @property string $ip
+ * @property string $type
+ * @property string $mac_address
+ * @property string $subnet_mask
+ * @property string $gateway
+ * @property int $cidr
+ * @property int $sid
+ * @property string $visualization
+ * @property string $last_check
+ * @property int $private
+ * @property int $hosting_id
+ * @property string $trunks
+ * @property string $tag
+ * @property string $node
+ * @property main\Core\Models\Whmcs\Hosting $hosting related client hosting
+ * @method  static $this ofHostingId($hostingId)
+ * @method  $this ofTags($tags)
+ * @method  $this ofIp($ip)
+ */
+class IpAddress extends EloquentModel
+{
+    public $timestamps = false;
+
+    /** @var string */
+    protected $table = 'mg_proxmox_addon_ip';
+
+    /** @var array */
+    protected $fillable = ['id', 'ip', 'type', 'mac_address', 'subnet_mask', 'gateway', 'cidr', 'sid', 'visualization', 'last_check', 'private', 'hosting_id', 'trunks', 'tag', 'node'];
+
+    /** @var int */
+    protected $guarded = ['id'];
+    protected $softDelete = true;
+
+    public function hosting()
+    {
+        return $this->belongsTo(main\Core\Models\Whmcs\Hosting::class, 'hosting_id');
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfTags($query, $tags)
+    {
+        return $query->whereIn("tag", $tags);
+    }
+
+    public function scopeOfIp($query, $ip)
+    {
+        return $query->where("ip", $ip);
+    }
+
+    public function scopePrivate($query)
+    {
+        return $query->where("private", 1);
+    }
+}

+ 65 - 0
app/Models/Job.php

@@ -0,0 +1,65 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+/**
+ * Class Job
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @method static $this waiting()
+ * @method static $this finished()
+ * @method $this ofJob($job)
+ * @method $this ofHostingId($hostingId)
+ * @method static $this ofParentId($parentId)
+ * @method static $this ofId($parentId)
+ * @method $this ofJobs($jobs)
+ * @method $this ofCustomId($id)
+ */
+class Job extends \ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job
+{
+
+    public function scopeOfJob($query, $job)
+    {
+        return $query->where("job", $job . '@handle');
+    }
+
+    public function scopeOfJobs($query, $jobs)
+    {
+        foreach ($jobs as &$job)
+        {
+            $job .= '@handle';
+        }
+        return $query->whereIn("job", $jobs);
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("rel_id", $hostingId)
+            ->where("rel_type", "hosting");
+    }
+
+    public function scopeOfCustomId($query, $id)
+    {
+        return $query->where("custom_id", $id);
+    }
+
+    public function scopeWaiting($query)
+    {
+        return $query->whereIn("status", ['waiting', "running", ""]);
+    }
+
+    public function scopeFinished($query)
+    {
+        return $query->where("status", "finished");
+    }
+
+    public function scopeOfParentId($query, $parentId)
+    {
+        return $query->where("parent_id", $parentId);
+    }
+
+    public function scopeOfId($query, $id)
+    {
+        return $query->where("id", $id);
+    }
+}

+ 144 - 0
app/Models/KeyPair.php

@@ -0,0 +1,144 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2016-12-12)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Description of KeyPair
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.0
+ * @property int $hosting_id
+ * @property int $vm_id
+ * @method static $this ofHostingId($hostingId)
+ * @method $this ofVmId($id)
+ */
+class KeyPair extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'KeyPair';
+    protected $fillable = ['id', 'vm_id', 'hosting_id', 'public', 'private'];
+    public $timestamps = false;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getHostingId()
+    {
+        return $this->hosting_id;
+    }
+
+    public function getPublic()
+    {
+        return decrypt($this->public);
+    }
+
+    public function getPrivate()
+    {
+        return decrypt($this->private);
+    }
+
+    public function isPublic()
+    {
+        return decrypt($this->public) != null;
+    }
+
+    public function isPrivate()
+    {
+        return decrypt($this->private) != null;
+    }
+
+    public function setId($id)
+    {
+        $this->id = $id;
+        return $this;
+    }
+
+    public function setHostingId($hostingId)
+    {
+        $this->hosting_id = $hostingId;
+        return $this;
+    }
+
+    public function setPublic($public)
+    {
+        $this->public = encrypt($public);
+        return $this;
+    }
+
+    public function setPrivate($private)
+    {
+        $this->private = encrypt($private);
+        return $this;
+    }
+
+    /**
+     * @return KeyPair
+     */
+    public static function make()
+    {
+        $config = ['private_key_type' => OPENSSL_KEYTYPE_RSA];
+        $res    = openssl_pkey_new($config);
+        openssl_pkey_export($res, $privkey);
+        $pubkey = openssl_pkey_get_details($res);
+        $pubKey = self::encodePublicKey($res);
+        $obj    = new KeyPair();
+        $obj->setPublic($pubKey)
+            ->setPrivate($privkey);
+        return $obj;
+
+    }
+
+    private static function encodePublicKey($privKey)
+    {
+        $keyInfo = openssl_pkey_get_details($privKey);
+        $buffer  = pack("N", 7) . "ssh-rsa" .
+                   self::encodeBuffer($keyInfo['rsa']['e']) .
+                   self::encodeBuffer($keyInfo['rsa']['n']);
+        return "ssh-rsa " . base64_encode($buffer);
+    }
+
+    private static function encodeBuffer($buffer)
+    {
+        $len = strlen($buffer);
+        if (ord($buffer[0]) & 0x80)
+        {
+            $len++;
+            $buffer = "\x00" . $buffer;
+        }
+        return pack("Na*", $len, $buffer);
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfVmId($query, $vmId)
+    {
+        return $query->where("vm_id", $vmId);
+    }
+
+}

+ 44 - 0
app/Models/ModuleSetting.php

@@ -0,0 +1,44 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (Mar 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model;
+
+/**
+ * Description of User
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property string $setting;
+ * @property string $value;
+ * @method  static ModuleSetting bandwidthResetDate
+ */
+class ModuleSetting extends Model
+{
+    protected $primaryKey = 'setting';
+
+    public function scopeBandwidthResetDate($query)
+    {
+        return $query->where("setting", 'bandwidthResetDate');
+    }
+
+
+}

+ 18 - 0
app/Models/ModuleSettings.php

@@ -0,0 +1,18 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+/**
+ * Class ModuleSettings
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @method static isDebug()
+ */
+class ModuleSettings extends \ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model
+{
+
+    public function scopeIsDebug($query)
+    {
+        return $query->where('setting', 'debug')->value("value")=="on";
+    }
+}

+ 71 - 0
app/Models/NodeSetting.php

@@ -0,0 +1,71 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Jan 15, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Description of NodeSetting
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $value
+ * @property string $server_id
+ * @property string $node
+ * @property string $setting
+ * @property string $value
+ * @property \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server $server
+ * @method NodeSetting ofServer(int $serverId)
+ * @method NodeSetting ofNode(string $node)
+ * @method NodeSetting ofSetting(string $setting)
+ * @method NodeSetting ofValue(string $value)
+ */
+class NodeSetting extends ExtendedEloquentModel
+{
+    public $timestamps = false;
+    protected $table = 'NodeSetting';
+    protected $fillable = ['server_id', 'node', 'setting', 'value'];
+    protected $softDelete = false;
+
+    public function server()
+    {
+        return $this->belongsTo('ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server', 'server_id');
+    }
+
+    public function scopeOfServer($query, $serverId)
+    {
+        return $query->where('server_id', $serverId);
+    }
+
+    public function scopeOfNode($query, $node)
+    {
+        return $query->where('node', $node);
+    }
+
+    public function scopeOfSetting($query, $setting)
+    {
+        return $query->where('setting', $setting);
+    }
+
+    public function scopeOfValue($query, $value)
+    {
+        return $query->where('value', $value);
+    }
+}

+ 29 - 0
app/Models/PrivateInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @deprecated
+ */
+class PrivateInterface extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'PrivateInterfaces';
+    protected $fillable = [ 'hosting_id','network_id', 'vm_id','ip_id', 'ip', 'net',
+    ];
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfNetworkId($query, $networkId)
+    {
+        return $query->where("network_id", $networkId);
+    }
+}

+ 24 - 0
app/Models/PrivateNetwork.php

@@ -0,0 +1,24 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @deprecated
+ */
+class PrivateNetwork extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'PrivateNetworks';
+    protected $fillable = [ 'hosting_id','server_id', 'name','tag','created_at', 'updated_at'
+    ];
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+}

+ 68 - 0
app/Models/ProductConfiguration.php

@@ -0,0 +1,68 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (26.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @property string $setting
+ * @property int $product_id
+ * @property string $value
+ * @method static ProductConfiguration ofProductId($productId)
+ * @method static ProductConfiguration ofSetting($productId)
+ */
+class ProductConfiguration extends ExtendedEloquentModel
+{
+    protected $table = 'ProductConfiguration';
+
+    protected $primaryKey = 'setting';
+    public $incrementing = false;
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = [
+        'value' => 'array',
+    ];
+
+    /**
+     * Eloquent fillable parameters
+     * @var array
+     */
+    protected $fillable = ['product_id', 'setting', 'value'];
+
+    public $timestamps = false;
+
+    public function scopeOfProductId($query, $productId)
+    {
+        return $query->where("product_id", $productId);
+    }
+
+    public function scopeOfSetting($query, $setting)
+    {
+        return $query->where("setting", $setting);
+    }
+
+    public function scopeOfValue($query, $value)
+    {
+        return $query->where("value", $value);
+    }
+}

+ 64 - 0
app/Models/RangeVm.php

@@ -0,0 +1,64 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use Illuminate\Database\Eloquent\model as EloquentModel;
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of RangeVm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property main\Core\Models\Whmcs\Server $server
+ * @property int $vmid_from
+ * @property int $vmid_to
+ * @property \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server $server
+ * @method static this ofServerId($serverId)
+ */
+class RangeVm extends EloquentModel
+{
+    public $timestamps = false;
+    protected $primaryKey = 'server_id';
+    protected $table = 'mg_proxmox_vmranges';
+
+    /**
+     *
+     *
+     */
+    protected $guarded = ['server_id'];
+
+    /**
+     *
+     * @var array
+     */
+    protected $fillable = ['server_id', 'vmid_from', 'vmid_to'];
+    protected $softDelete = false;
+
+    public function server()
+    {
+        return $this->belongsTo(main\Core\Models\Whmcs\Server::class, 'server_id');
+    }
+
+    public function scopeOfServerId($query, $serverId)
+    {
+        return $query->where("server_id", $serverId);
+    }
+}

+ 85 - 0
app/Models/RecoveryVm.php

@@ -0,0 +1,85 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use Illuminate\Database\Eloquent\model as EloquentModel;
+
+/**
+ * Description of RecoveryVm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $id
+ * @property int $client_id
+ * @property int $service_id
+ * @property int $server_id
+ * @property int $vserver_id
+ * @property string $node
+ * @property int $vmid
+ * @property string $virtualization
+ * @property string $config
+ * @property string $status
+ * @property string $dns
+ * @property string $last_update
+ * @property \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting $hosting Related hosting
+ */
+class RecoveryVm extends EloquentModel
+{
+    public $timestamps = false;
+    protected $table = 'mg_proxmox_addon_recovery_vm_list';
+
+    /**
+     *
+     *
+     */
+    protected $guarded = ['id'];
+
+    /**
+     *
+     * @var array
+     */
+    protected $fillable = ['client_id', 'service_id', 'server_id', 'vserver_id', 'node', 'vmid', 'virtualization', 'config', 'status', 'dns', 'last_update'];
+    protected $softDelete = false;
+
+    public function getConfig()
+    {
+        return json_decode($this->config, true);
+    }
+
+    public function getStatus()
+    {
+        return json_decode($this->status, true);
+    }
+
+    public function getDns()
+    {
+        return json_decode($this->dns, true);
+    }
+
+    /**
+     * Get related hosting
+     *
+     * @return type
+     */
+    public function hosting()
+    {
+        return $this->belongsTo(\ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting::class, "service_id");
+    }
+}

+ 50 - 0
app/Models/RrdData.php

@@ -0,0 +1,50 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Class RrdData
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @method  static $this ofHostingId($hostingId)
+ * @method sinceDayAgo()
+ */
+class RrdData extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'RrdData';
+    protected $fillable = [ 'hosting_id', 'vm_id', 'diskread', 'diskwrite','cpu','maxcpu','mem','maxmem','netin','netout','time'
+    ];
+
+    public $timestamps = false;
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeSinceDayAgo($query)
+    {
+        $now = new \DateTime();
+        $now->modify('-24 hours');
+        return $query->where("time", ">=",$now->format("Y-m-d H:i:s"));
+    }
+
+    public function scopeSinceHourAgo($query)
+    {
+        $now = new \DateTime();
+        $now->modify('-1 hours');
+        return $query->where("time", ">=",$now->format("Y-m-d H:i:s"));
+    }
+
+    public function scopeOlderThanDayAgo($query)
+    {
+        $now = new \DateTime();
+        $now->modify('-24 hours');
+        return $query->where("time", "<=",$now->format("Y-m-d H:i:s"));
+    }
+
+}

+ 106 - 0
app/Models/ServerConfiguration.php

@@ -0,0 +1,106 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (26.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @property string $setting
+ * @property int $server_id
+ * @property string $value
+ * @method static ServerConfiguration ofServerId($id)
+ * @method static ServerConfiguration ofSetting($setting)
+ */
+class ServerConfiguration extends ExtendedEloquentModel
+{
+    protected $table = 'ServerConfiguration';
+
+    protected $primaryKey = ['server_id', 'setting'];
+    public $incrementing = false;
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = [
+        'value' => 'array',
+    ];
+
+    /**
+     * Eloquent fillable parameters
+     * @var array
+     */
+    protected $fillable = ['server_id', 'setting', 'value'];
+
+    public $timestamps = false;
+
+    public function scopeOfServerId($query, $id)
+    {
+        return $query->where("server_id", $id);
+    }
+
+    public function scopeOfSetting($query, $setting)
+    {
+        return $query->where("setting", $setting);
+    }
+
+    public function scopeOfValue($query, $value)
+    {
+        return $query->where("value", $value);
+    }
+
+    /**
+     * Set the keys for a save update query.
+     *
+     * @param  \Illuminate\Database\Eloquent\Builder  $query
+     * @return \Illuminate\Database\Eloquent\Builder
+     */
+    protected function setKeysForSaveQuery( $query)
+    {
+        $keys = $this->getKeyName();
+        if(!is_array($keys)){
+            return parent::setKeysForSaveQuery($query);
+        }
+
+        foreach($keys as $keyName){
+            $query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
+        }
+        return $query;
+    }
+
+    /**
+     * Get the primary key value for a save query.
+     *
+     * @param mixed $keyName
+     * @return mixed
+     */
+    protected function getKeyForSaveQuery($keyName = null)
+    {
+        if(is_null($keyName)){
+            $keyName = $this->getKeyName();
+        }
+
+        if (isset($this->original[$keyName])) {
+            return $this->original[$keyName];
+        }
+
+        return $this->getAttribute($keyName);
+    }
+}

+ 45 - 0
app/Models/SnapshotJob.php

@@ -0,0 +1,45 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @property int $id
+ * @property int $hosting_id
+ * @property int $vm_id
+ * @property string $name
+ * @property string $description
+ * @property int $vmstate
+ * @property string $period
+ * @property int $run_every
+ * @property string $days
+ * @property string $start_time
+ * @property string $updated_at
+ * @property string $created_at
+ * @method static $this ofHostingId($hostingId)
+ */
+class SnapshotJob extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'SnapshotJob';
+    protected $fillable   = ['id','hosting_id','vm_id','name','description','vmstate','period', 'run_every','days','start_time','updated_at','created_at'];
+
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = [
+        'days' => 'array',
+    ];
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+}

+ 83 - 0
app/Models/Snippet.php

@@ -0,0 +1,83 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+class Snippet
+{
+    protected $directory;
+    protected $filename;
+    protected $content;
+
+    /**
+     * Snippet constructor.
+     * @param $filename
+     * @param $content
+     */
+    public function __construct($directory, $filename, $content)
+    {
+        $this->directory = $directory;
+        $this->filename = $filename;
+        $this->content = $content;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getFilename()
+    {
+        return $this->filename;
+    }
+
+    /**
+     * @param mixed $filename
+     * @return Snippet
+     */
+    public function setFilename($filename)
+    {
+        $this->filename = $filename;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    /**
+     * @param mixed $content
+     * @return Snippet
+     */
+    public function setContent($content)
+    {
+        $this->content = $content;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getDirectory()
+    {
+        return $this->directory;
+    }
+
+    /**
+     * @param mixed $directory
+     * @return Snippet
+     */
+    public function setDirectory($directory)
+    {
+        $this->directory = $directory;
+        return $this;
+    }
+
+    public function getFileDirectory(){
+        return $this->getDirectory().$this->getFilename();
+    }
+
+}

+ 63 - 0
app/Models/TaskHistory.php

@@ -0,0 +1,63 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use Illuminate\Database\Eloquent\model as EloquentModel;
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of RangeVm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $id
+ * @property int $hosting_id
+ * @property string $upid
+ * @property string $name
+ * @property string $description
+ * @property string $node
+ * @property int $vmid
+ * @property int $status
+ * @property string $start_time
+ * @property main\Core\Models\Whmcs\Hosting $hosting related client hosting
+ */
+class TaskHistory extends EloquentModel
+{
+    public $timestamps = false;
+    protected $table = 'mg_proxmox_addon_tasks';
+
+    /**
+     *
+     *
+     */
+    protected $guarded = ['id'];
+
+    /**
+     *
+     * @var array
+     */
+    protected $fillable = ['id', 'hosting_id', 'upid', 'name', 'description', 'node', 'vmid', 'status', 'start_time'];
+    protected $softDelete = true;
+
+    public function hosting()
+    {
+        return $this->belongsTo(main\Core\Models\Whmcs\Hosting::class, 'hosting_id');
+    }
+}

+ 71 - 0
app/Models/User.php

@@ -0,0 +1,71 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (Mar 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Description of User
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $id
+ * @property int $user_id
+ * @property int $hosting_id
+ * @property string $username
+ * @property string $realm
+ * @method  static $this ofHostingId($hostingId)
+ * @method $this ofUserId($userId)
+ */
+class User extends ExtendedEloquentModel
+{
+    protected $table = 'User';
+
+    protected $guarded = ['id'];
+
+    /**
+     *
+     * @var array
+     */
+    protected $fillable = ['user_id', 'hosting_id', 'username', 'password', 'realm'];
+    protected $softDelete = false;
+    public $timestamps = false;
+
+    public function setPassword($password)
+    {
+        $this->password = encrypt($password);
+        return $this;
+    }
+
+    public function getPassword()
+    {
+        return decrypt($this->password);
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfUserId($query, $userId)
+    {
+        return $query->where("user_id", $userId);
+    }
+}

+ 86 - 0
app/Models/VirtualInterface.php

@@ -0,0 +1,86 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @property int $id
+ * @property int $hosting_id
+ * @property int $vn_id
+ * @property int $vm_id
+ * @property string $ip
+ * @property int $ip_long
+ * @property string $net
+ * @property VirtualNetwork $virtualNetwork
+ * @property VmModel $vmModel
+ * @method static $this ofHostingId($hostingId)
+ * @method $this ofVnId($hostingId)
+ * @method $this ofVmId($id)
+ * @method $this ofIp($ip)
+ * @method $this notId($ids)
+ * @method $this ofNetEmpty($ids)
+ * @method $this ofNet($net)
+ */
+class VirtualInterface extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'VirtualInterface';
+    protected $fillable = [ 'hosting_id','vn_id', 'vm_id', 'ip', 'ip_long','net',
+    ];
+
+    public $timestamps = false;
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfVirtualNetworkId($query, $networkId)
+    {
+        return $query->where("vn_id", $networkId);
+    }
+
+    public function scopeOfVmId($query, $id)
+    {
+        return $query->where("vm_id", $id);
+    }
+
+    public function scopeOfVnId($query, $id)
+    {
+        return $query->where("vn_id", $id);
+    }
+
+    public function scopeOfIp($query, $ip)
+    {
+        return $query->where("ip", $ip);
+    }
+
+    public function virtualNetwork()
+    {
+        return $this->belongsTo(VirtualNetwork::class, "vn_id");
+    }
+
+    public function vmModel()
+    {
+        return $this->belongsTo(VmModel::class, "vm_id");
+    }
+
+    public function scopeNotId($query, $ips)
+    {
+        return $query->whereNotIn("id", $ips);
+    }
+
+    public function scopeOfNetEmpty($query)
+    {
+        return $query->where("net", "");
+    }
+
+    public function scopeOfNet($query , $net)
+    {
+        return $query->where("net", $net);
+    }
+}

+ 58 - 0
app/Models/VirtualNetwork.php

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Class VirtualNetwork
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @property int $id
+ * @property int $hosting_id
+ * @property string $name
+ * @property int $tag
+ * @property string $pool
+ * @property int $cidr
+ * @property string $gateway
+ * @property string $updated_at
+ * @property string $created_at
+ * @method static $this ofHostingId($hostingId)
+ * @method $this ofId($id)
+ * @property VirtualInterface[] $virtualInterfaces
+ * @method $this ofTag($tag)
+ */
+class VirtualNetwork extends ExtendedEloquentModel
+{
+
+    protected $table = 'VirtualNetwork';
+
+    protected $guarded = ['id'];
+
+    /**
+     *
+     * @var array
+     */
+    protected $fillable = ['hosting_id', 'name', 'tag', 'pool','cidr', 'gateway'];
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfId($query, $id)
+    {
+        return $query->where("id", $id);
+    }
+
+    public function virtualInterfaces()
+    {
+        return $this->hasMany(VirtualInterface::class, "vn_id");
+    }
+
+    public function scopeOfTag($query, $tag)
+    {
+        return $query->where("tag", $tag);
+    }
+}

+ 131 - 0
app/Models/VmIpAddress.php

@@ -0,0 +1,131 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (26.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * @property int $id
+ * @property int $hosting_id
+ * @property int $server_id
+ * @property string $ip
+ * @property string $mac_address
+ * @property string $subnet_mask
+ * @property string $gateway
+ * @property int $cidr
+ * @property int $trunks
+ * @property int $tag
+ * @property string $net
+ * @property int $vm_id
+ * @method static $this ofHostingId($hostingId)
+ * @method $this ofIp($ip)
+ * @method $this ofIp4()
+ * @method $this ofIp6()
+ * @method $this ofNetNull()
+ * @method $this ofNetNotNull()
+ * @method $this ofNet($net)
+ * @method $this limit($limit)
+ * @method $this orderByIdDesc()
+ * @method $this orderByIdAsc()
+ * @method $this ofVmId($id)
+ * @method $this notIdIn($ids)
+ * @property Hosting $hosting
+ * @property VmModel $vmModel
+ */
+class VmIpAddress extends ExtendedEloquentModel
+{
+    public $timestamps = false;
+
+    /** @var string */
+    protected $table = 'VmIpAddress';
+    protected $fillable = [ 'hosting_id', "vm_id", 'server_id', 'ip', 'mac_address', 'subnet_mask', 'gateway', 'cidr', 'trunks', 'tag', 'net'];
+
+    public function hosting()
+    {
+        return $this->belongsTo(Hosting::class, 'hosting_id');
+    }
+
+    public function vmModel()
+    {
+        return $this->belongsTo(VmModel::class, 'vm_id');
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfIp($query, $ip)
+    {
+        return $query->where("ip", $ip);
+    }
+
+    public function scopeOfIp4($query)
+    {
+        return $query->where('ip', 'LIKE', '%.%');
+    }
+
+    public function scopeOfIp6($query)
+    {
+        return $query->where('ip', 'LIKE', '%:%');
+    }
+
+    public function scopeOfNetNull($query)
+    {
+        return $query->whereNull('net');
+    }
+
+    public function scopeOfNetNotNull($query)
+    {
+        return $query->whereNotNull('net');
+    }
+
+    public function scopeOfNet($query, $net)
+    {
+        return $query->where('net', $net);
+    }
+
+    public function scopeOrderByIdDesc($query)
+    {
+        return $query->orderBy('id', "DESC");
+    }
+
+    public function scopeOrderByIdAsc($query)
+    {
+        return $query->orderBy('id', "ASC");
+    }
+
+    public function scopeOfVmId($query, $id)
+    {
+        return $query->where('vm_id', $id);
+    }
+
+    public function scopeNotIdIn($query, $ids)
+    {
+        return $query->whereNotIn("vm_id", $ids);
+    }
+
+    public function scopeOfVmIdNull($query)
+    {
+        return $query->whereNull('vm_id');
+    }
+
+}

+ 134 - 0
app/Models/VmModel.php

@@ -0,0 +1,134 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Models\ExtendedEloquentModel;
+
+/**
+ * Class VmModel
+ * @package ModulesGarden\ProxmoxAddon\App\Models
+ * @property  int $id
+ * @property int $hosting_id
+ * @property string $node
+ * @property int $vmid
+ * @property string $virtualization
+ * @property string $name
+ * @property string $password
+ * @property int $cores
+ * @property int $sockets
+ * @property int $vcpus
+ * @property string $cpulimit
+ * @property int $cpuunits
+ * @property int $memory
+ * @property int $swap
+ * @property int $netin
+ * @property int $netout
+ * @property int $template
+ * @property int $disk
+ * @property int $disks
+ * @property string $updated_at
+ * @property string $created_at
+ * @method static $this ofHostingId($hostingId)
+ * @method $this ofVmid($vmid)
+ * @method $this ofNode($node)
+ * @method $this ofId($id)
+ * @method $this notVmid($vmid)
+ * @method $this notIdIn($ids)
+ * @property VirtualInterface[] $virtualInterfaces
+ * @property VmIpAddress[] $ipv4Addresses
+ * @property VmIpAddress[] $ipv6Addresses
+ * @property KeyPair $keyPair
+ * @method $this ofTemplate()
+ * @method $this notTemplate()
+ */
+class VmModel extends ExtendedEloquentModel
+{
+
+    /** @var string */
+    protected $table = 'Vm';
+    protected $fillable = [ 'hosting_id','node', 'vmid','virtualization', 'name', 'password', 'cores','sockets',
+        'vcpus','cpulimit','cpuunits','memory','disk','swap','netin','netout','template', 'data',
+        'disks','created_at',
+        'updated_at'
+    ];
+
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = [
+        'data' => 'array',
+    ];
+
+    public function setPassword($password)
+    {
+        $this->password = encrypt($password);
+        return $this;
+    }
+
+    public function getPassword()
+    {
+        return decrypt($this->password);
+    }
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where("hosting_id", $hostingId);
+    }
+
+    public function scopeOfVmid($query, $vmid)
+    {
+        return $query->where("vmid", $vmid);
+    }
+
+    public function scopeNotVmid($query, $vmid)
+    {
+        return $query->where("vmid", "!=", $vmid);
+    }
+
+    public function scopeOfNode($query, $node)
+    {
+        return $query->where("node", $node);
+    }
+
+    public function scopeOfId($query, $id)
+    {
+        return $query->where("id",  $id);
+    }
+
+    public function virtualInterfaces()
+    {
+        return $this->hasMany(VirtualInterface::class, "vm_id");
+    }
+
+    public function keyPair(){
+        return $this->hasOne(KeyPair::class, "vm_id");
+    }
+
+    public function ipv4Addresses(){
+        return $this->hasMany(VmIpAddress::class, "vm_id")->ofIp4();
+    }
+
+    public function ipv6Addresses(){
+        return $this->hasMany(VmIpAddress::class, "vm_id")->ofIp6();
+    }
+
+    public function scopeNotIdIn($query, $ids)
+    {
+        return $query->whereNotIn("id", $ids);
+    }
+
+    public function scopeOfTemplate($query)
+    {
+        return $query->where("template", 1);
+    }
+
+    public function scopeNotTemplate($query)
+    {
+        return $query->where("template", 0);
+
+    }
+}

+ 37 - 0
app/Models/Whmcs/ActivityLog.php

@@ -0,0 +1,37 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+use Illuminate\Database\Eloquent\Model as EloquentModel;
+
+
+/**
+ * @property int $id
+ * @property string $date
+ * @property string $description
+ * @property string $user
+ * @property int $userid
+ * @property string $ipaddr
+ * @method static $this ofDescription($description)
+ * @method  $this today()
+ */
+class ActivityLog extends EloquentModel
+{
+    /** @var string */
+    protected $table = 'tblactivitylog';
+    protected $fillable = ['id', 'date', 'description', 'user', 'userid', 'ipaddr'];
+
+
+    public function scopeOfDescription($query, $description)
+    {
+        return $query->where('description', $description);
+    }
+
+
+    public function scopeToday($query)
+    {
+        return $query->whereRaw(' TIMESTAMP(`date`) >= TIMESTAMP(NOW()- INTERVAL 1 DAY )');
+    }
+
+}

+ 46 - 0
app/Models/Whmcs/CustomField.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+/**
+ * Description of CustomField
+ *
+ * @property  int id
+ * @property string type
+ * @property string relid
+ * @property string fieldname
+ * @property string fieldtype
+ * @property string description
+ * @property string fieldoptions
+ * @property string regexpr
+ * @property string adminonly
+ * @property string required
+ * @property string showorder
+ * @property string showinvoice
+ * @property string sortorder
+ * @property string created_at
+ * @property string updated_at
+ * @method  static $this ofClient()
+ * @method  $this ofName()
+ * @property CustomFieldValue $customFieldValue
+ */
+class CustomField extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\CustomField
+{
+
+
+    public function scopeOfClient($query)
+    {
+        return $query->where('type', 'client');
+    }
+
+    public function scopeOfName($query, $name)
+    {
+        return $query->where('fieldname', 'LIKE', "{$name}|%");
+    }
+
+    public function customFieldValue()
+    {
+        return $this->hasMany(CustomFieldValue::class,"fieldid");
+    }
+
+}

+ 47 - 0
app/Models/Whmcs/CustomFieldValue.php

@@ -0,0 +1,47 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Jan 17, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+
+/**
+ * Description of Hosting
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $fieldid
+ * @property int $relid
+ * @property int $value
+ * @property CustomField $customField
+ * @method $this ofRelid($id)
+ */
+class CustomFieldValue extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\CustomFieldValue
+{
+
+
+    public function customField()
+    {
+        return $this->hasOne("ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomField", "id", "fieldid");
+    }
+
+    public function scopeOfRelid($query, $id){
+        return $query->where('relid', $id);
+    }
+
+}

+ 56 - 0
app/Models/Whmcs/EmailTemplate.php

@@ -0,0 +1,56 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+/**
+ * @property int $id
+ * @property string $type
+ * @property string $name
+ * @property string $subject
+ * @property string $message
+ * @property string $attachments
+ * @property string $fromname
+ * @property string $fromemail
+ * @property int $disabled
+ * @property int $custom
+ * @property string $language
+ * @property string $copyto
+ * @property string $blind_copy_to
+ * @property int $plaintext
+ * @property string $created_at
+ * @property string $updated_at
+ * @method static $this ofAdmin()
+ * @method static $this ofGeneral()
+ * @method static $this ofCustom()
+ * @method static $this ofProduct()
+ * @method $this ofName($name)
+ */
+class EmailTemplate extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\EmailTemplate
+{
+    public function scopeOfAdmin($query)
+    {
+        return $query->where("type", "admin");
+    }
+
+    public function scopeOfGeneral($query)
+    {
+        return $query->where("type", "general");
+    }
+
+    public function scopeOfProduct($query)
+    {
+        return $query->where("type", "product");
+    }
+
+    public function scopeOfCustom($query)
+    {
+        return $query->where("custom", 1);
+    }
+
+    public function scopeOfName($query, $name)
+    {
+        return $query->where("name", $name);
+    }
+
+}

+ 272 - 0
app/Models/Whmcs/Hosting.php

@@ -0,0 +1,272 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Jan 17, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+
+/**
+ * Description of Hosting
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ * @property int $id
+ * @property int $userid
+ * @property int $orderid
+ * @property int $packageid
+ * @property int $server
+ * @property string $regdate
+ * @property string $domain
+ * @property string $paymentmethod
+ * @property float $firstpaymentamount
+ * @property float $amount
+ * @property string $billingcycle
+ * @property string $nextduedate
+ * @property string $nextinvoicedate
+ * @property string $termination_date
+ * @property string $completed_date
+ * @property string $domainstatus
+ * @property string $username
+ * @property string $password
+ * @property string $notes
+ * @property string $subscriptionid
+ * @property int $promoid
+ * @property string $suspendreason
+ * @property int $overideautosuspend
+ * @property string $overidesuspenduntil
+ * @property string $dedicatedip
+ * @property string $assignedips
+ * @property string $ns1
+ * @property string $ns2
+ * @property int $diskusage
+ * @property int $disklimit
+ * @property int $bwusage
+ * @property int $bwlimit
+ * @property string $lastupdate
+ * @property string $created_at
+ * @property string $updated_at
+ * @method Hosting active()
+ * @method Hosting activeAndSuspended
+ * @method Hosting ofUserId($userId)
+ * @method Hosting ofServerId($serverId)
+ * @method Hosting  ofCustomFieldNode($node)
+ * @method Hosting  ofvServerNode($node)
+ * @method Hosting  ofCustomFielVmid($vmid)
+ * @method Hosting  ofvServerVmid($vmid)
+ * @method Hosting  ofStatus($status)
+ * @method static Hosting ofServerType($hostingId, $moduleName)
+ * @method static Hosting  ofId($id)
+ * @method Hosting[] get()
+ * @property CustomFieldValue[] $customFieldValues
+ * @method static Hosting ofProxmoxVps()
+ * @method static Hosting ofProxmoxCloud()
+ * @method static Hosting ofProxmoxCloudAndStatusActiveAndSuspended
+ * @method static Hosting ofProxmoxVpsAndProxmoxCloud()
+ * @method static Hosting ofProductId($id)
+ */
+class Hosting extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting
+{
+
+    public function scopeActive($query)
+    {
+        return $query->where('domainstatus', 'Active');
+    }
+
+    public function scopeActiveAndSuspended($query)
+    {
+        return $query->whereIn('domainstatus', ['Active', 'Suspended']);
+    }
+
+    public function scopeOfId($query, $id)
+    {
+        return $query->where('id', $id);
+    }
+
+    public function scopeOfServerId($query, $serverId)
+    {
+        return $query->where('server', $serverId);
+    }
+
+    public function scopeOfUserId($query, $userId)
+    {
+        return $query->where('userid', $userId);
+    }
+
+    public function scopeOfCustomFieldNode($query, $node)
+    {
+        $h   = 'tblhosting';
+        $cf  = 'tblcustomfields';
+        $cfv = 'tblcustomfieldsvalues';
+        $query->select("{$h}.*")
+            ->rightJoin($cfv, "{$cfv}.relid", '=', "{$h}.id")
+            ->rightJoin($cf, "{$cfv}.fieldid", '=', "{$cf}.id")
+            ->where("{$cf}.type", "product")
+            ->where("{$cf}.fieldname", "LIKE", "node%")
+            ->where("{$cfv}.value", $node);
+        return $query;
+    }
+
+    public function scopeOfCustomFielVmid($query, $vmid)
+    {
+        $h   = 'tblhosting';
+        $cf  = 'tblcustomfields';
+        $cfv = 'tblcustomfieldsvalues';
+        $query->select("{$h}.*")
+            ->rightJoin($cfv, "{$cfv}.relid", '=', "{$h}.id")
+            ->rightJoin($cf, "{$cfv}.fieldid", '=', "{$cf}.id")
+            ->where("{$cf}.type", "product")
+            ->where("{$cf}.fieldname", "LIKE", "vmid%")
+            ->where("{$cfv}.value", $vmid);
+        return $query;
+    }
+
+    public function scopeOfvServerNode($query, $node)
+    {
+        $h  = 'tblhosting';
+        $vs = 'ProxmoxAddon_Vm';
+        $query->select("{$h}.*")
+            ->rightJoin($vs, "{$vs}.hosting_id", '=', "{$h}.id")
+            ->where("{$vs}.node", $node);
+        return $query;
+    }
+
+    public function scopeOfvServerVmid($query, $vmid)
+    {
+        $h  = 'tblhosting';
+        $vs = 'ProxmoxAddon_Vm';
+        $query->select("{$h}.*")
+            ->rightJoin($vs, "{$vs}.hosting_id", '=', "{$h}.id")
+            ->where("{$vs}.vmid", $vmid);
+        return $query;
+    }
+
+    public function scopeOfStatus($query, $status)
+    {
+        return $query->whereIn('domainstatus', $status);
+    }
+
+    public function scopeOfServerType($query, $hostingId, $moduleName)
+    {
+        $h = "tblhosting";
+        $p = "tblproducts";
+        $query->select("{$h}.*");
+        return $query->rightJoin($p, function ($join) use ($p)
+        {
+            $join->on('packageid', '=', "{$p}.id");
+        })->where("{$h}.id", $hostingId)
+            ->where("{$p}.servertype", $moduleName);
+    }
+
+    public function ipAdd($ip)
+    {
+        if (!$this->dedicatedip)
+        {
+            $this->dedicatedip = $ip;
+            return $this;
+        }
+        $ips               = explode("\n", $this->assignedips);
+        $ips[]             = $ip;
+        $ips               = array_unique($ips);
+        $this->assignedips = implode("\n", $ips);
+        return $this;
+    }
+
+    public function ipDelete($ip)
+    {
+        if ($this->dedicatedip == $ip)
+        {
+            $this->dedicatedip = null;
+            return $this;
+        }
+        $ips = explode("\n", $this->assignedips);
+        foreach ($ips as $k => $v)
+        {
+            if (trim($v) == trim($ip))
+            {
+                unset($ips[$k]);
+            }
+        }
+        $this->assignedips = implode("\n", $ips);
+        return $this;
+    }
+
+    /**
+     * @return \DateTime
+     * @throws \Exception
+     */
+    public function getLastUpdate()
+    {
+        return new \DateTime($this->lastupdate);
+    }
+
+    public function customFieldValues()
+    {
+        return $this->hasMany("ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomFieldValue", "relid");
+    }
+
+    public function scopeOfProxmoxVps($query)
+    {
+        $h = 'tblhosting';
+        $s = 'tblservers';
+        $query->select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$s}.type", "proxmoxVPS");
+        return $query;
+    }
+
+    public function scopeOfProxmoxVpsAndProxmoxCloud($query)
+    {
+        $h = 'tblhosting';
+        $s = 'tblservers';
+        $query->select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps" ]);
+        return $query;
+    }
+
+    public function scopeOfProxmoxCloud($query)
+    {
+        $h = 'tblhosting';
+        $s = 'tblservers';
+        $query->select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$s}.type", "ProxmoxCloudVps");
+        return $query;
+    }
+
+    public function scopeOfProxmoxCloudAndStatusActiveAndSuspended($query)
+    {
+        $h = 'tblhosting';
+        $s = 'tblservers';
+        $query->select("{$h}.*")
+            ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->where("{$s}.type", "ProxmoxCloudVps")
+            ->whereIn("{$h}.domainstatus", ['Active', 'Suspended']);
+        return $query;
+    }
+
+    public function isBandwidthOverageUsage()
+    {
+        return $this->bwlimit && $this->bwlimit < $this->bwusage;
+    }
+
+    public function scopeOfProductId($query, $id){
+        return $query->where('packageid', $id);
+    }
+
+}

+ 150 - 0
app/Models/Whmcs/Product.php

@@ -0,0 +1,150 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 8, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+
+/**
+ * Description of Product
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @property int $id
+ * @property string $type
+ * @property int $gid
+ * @property string $name
+ * @property string $description
+ * @property int $hidden
+ * @property int $showdomainoptions
+ * @property int $welcomeemail
+ * @property int $stockcontrol
+ * @property int $qty
+ * @property int $proratabilling
+ * @property int $proratadate
+ * @property int $proratachargenextmonth
+ * @property string $paytype
+ * @property int $allowqty
+ * @property string $subdomain
+ * @property string $autosetup
+ * @property string $servertype
+ * @property int $servergroup
+ * @property string $configoption1
+ * @property string $configoption2
+ * @property string $configoption3
+ * @property string $configoption4
+ * @property string $configoption5
+ * @property string $configoption6
+ * @property string $configoption7
+ * @property string $configoption8
+ * @property string $configoption9
+ * @property string $configoption10
+ * @property string $configoption11
+ * @property string $configoption12
+ * @property string $configoption13
+ * @property string $configoption14
+ * @property string $configoption15
+ * @property string $configoption16
+ * @property string $configoption17
+ * @property string $configoption18
+ * @property string $configoption19
+ * @property string $configoption20
+ * @property string $configoption21
+ * @property string $configoption22
+ * @property string $configoption23
+ * @property string $configoption24
+ * @property string $freedomain
+ * @property string $freedomainpaymentterms
+ * @property string $freedomaintlds
+ * @property int $recurringcycles
+ * @property int $autoterminatedays
+ * @property int $autoterminateemail
+ * @property int $configoptionsupgrade
+ * @property string $billingcycleupgrade
+ * @property int $upgradeemail
+ * @property string $overagesenabled
+ * @property int $overagesdisklimit
+ * @property int $overagesbwlimit
+ * @property float $overagesdiskprice
+ * @property float $overagesbwprice
+ * @property int $tax
+ * @property int $affiliateonetime
+ * @property string $affiliatepaytype
+ * @property float $affiliatepayamount
+ * @property int $order
+ * @property int $retired
+ * @property int $is_featured
+ * @property string $created_at
+ * @property string $updated_at
+ * @property ProductConfiguration $configuration
+ * @method static $this ofProxmoxCloud()
+ */
+class Product extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Product
+{
+
+    /**
+     *
+     * @return ProductConfiguration
+     */
+    public function configuration()
+    {
+        return $this->hasOne('\ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration', 'product_id', 'id');
+    }
+
+    /**
+     * @return []
+     */
+    public function getParams()
+    {
+        $statement = DB::connection()
+            ->getPdo()
+            ->prepare("SELECT s.ipaddress AS serverip, s.hostname AS serverhostname, s.username AS serverusername, s.password AS serverpassword, s.secure AS serversecure,
+                                    s.accesshash AS serveraccesshash, s.id AS serverid, s.port AS serverport,
+                                    configoption1,configoption2,configoption3,configoption4,configoption5,configoption6,configoption7,configoption8,configoption9
+                                    FROM tblservers AS s
+                                     JOIN tblservergroupsrel AS sgr ON sgr.serverid = s.id
+                                     JOIN tblservergroups AS sg ON sgr.groupid = sg.id
+                                     JOIN tblproducts AS p ON p.servergroup = sg.id
+                                    WHERE p.id = :pid 
+                                   ORDER BY s.active DESC LIMIT 1");
+        $statement->execute(["pid" => $this->id]);
+        $row = $statement->fetch(\PDO::FETCH_ASSOC);
+        if (empty($row))
+        {
+            $s   = 'tblservers';
+            $p   = 'tblproducts';
+            $row = (array)DB::table($s)
+                ->select("{$s}.ipaddress AS serverip", "{$s}.hostname AS serverhostname", "{$s}.username AS serverusername",
+                    "{$s}.password AS serverpassword", "{$s}.secure AS serversecure", "{$s}.accesshash AS serveraccesshash", "{$s}.port AS serverport")
+                ->rightJoin($p, "{$p}.servertype", "=", "{$s}.type")
+                ->where("{$p}.id", $this->id)
+                ->orderBy("{$s}.active", 'desc')
+                ->first();
+        }
+        $row['serverpassword'] = html_entity_decode(decrypt($row['serverpassword']),ENT_QUOTES);;
+        $row['packageid']      = $this->id;
+        return $row;
+    }
+
+    public function scopeOfProxmoxCloud($query){
+        $query->where('servertype', 'ProxmoxCloudVps');
+        return $query;
+    }
+}

+ 37 - 0
app/Models/Whmcs/ToDoList.php

@@ -0,0 +1,37 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+use Illuminate\Database\Eloquent\Model as EloquentModel;
+
+/**
+ * @property int $id
+ * @property string $date
+ * @property string $title
+ * @property string $description
+ * @property int $admin
+ * @property string $status
+ * @property string $duedate
+ * @method static $this ofTitle($title)
+ * @method this pending()
+ */
+class ToDoList extends EloquentModel
+{
+    protected $table = 'tbltodolist';
+    protected $primaryKey = 'id';
+    protected $fillable = ['id', 'date', 'title', 'description', 'admin', 'status', 'duedate'];
+    public $timestamps = false;
+
+    public function scopeOfTitle($query, $title)
+    {
+        return $query->where('title', $title);
+    }
+
+    public function scopePending($query)
+    {
+        return $query->where('status', 'pending');
+    }
+
+
+}

+ 37 - 0
app/Models/Whmcs/Upgrade.php

@@ -0,0 +1,37 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Models\Whmcs;
+
+/**
+ * Class Upgrade
+ * @package ModulesGarden\ProxmoxAddon\App\Models\Whmcs
+ * @method static $this ofHostingId($hostingId)
+ * @method $this today()
+ * @method $this pending()
+ * @method $this ofOptionId($optionId)
+ */
+class Upgrade extends \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Upgrade
+{
+
+    public function scopeOfHostingId($query, $hostingId)
+    {
+        return $query->where('relid', $hostingId)
+            ->where('type', "configoptions");
+    }
+
+    public function scopeToday($query)
+    {
+        return $query->whereRaw("DATE(`date`) = DATE(NOW())");
+    }
+
+    public function scopePending($query)
+    {
+        return $query->where('status', "Pending");
+    }
+
+    public function scopeOfOptionId($query, $optionId)
+    {
+        return $query->where('originalvalue', "like", "{$optionId}=>%");
+    }
+}

+ 51 - 0
app/Providers/SnippetProvider.php

@@ -0,0 +1,51 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\Providers;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Snippet;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use phpseclib3\Net\SSH2;
+
+class SnippetProvider
+{
+    /**
+     * @var SSH2
+     */
+    protected $ssh;
+    protected $lastResponse;
+
+
+    /**
+     * SnippetProvider constructor.
+     * @param SSH2 $ssh
+     * @param ServerConfigurationRepository $cerverConfiguration
+     */
+    public function __construct(SSH2 $ssh)
+    {
+        $this->ssh = $ssh;
+    }
+
+    public function create(Snippet $snippet){
+        if($this->exists($snippet)){
+            throw new \Exception(sprintf("File already exist: %s",$snippet->getFileDirectory()));
+        }
+        $this->lastResponse = $this->ssh->exec(sprintf("echo '%s' > %s", $snippet->getContent(), $snippet->getFileDirectory()));
+    }
+
+    /**
+     * @param Snippet $snippet
+     * @return bool
+     * @example ls /var/lib/vz/snippets/userconfig.yaml
+     */
+    public function exists(Snippet $snippet){
+        $this->lastResponse = $this->ssh->exec(sprintf("ls %s", $snippet->getFileDirectory()) );
+        $response = str_replace(["\r", "\n"],["",""],$this->lastResponse );
+        return $response == $snippet->getFileDirectory() ;
+    }
+
+    public function delete(Snippet $snippet){
+        $this->lastResponse = $this->ssh->exec(sprintf("rm %s", $snippet->getFileDirectory()) );
+        if(preg_match("/cannot remove/",$this->lastResponse)){
+            throw new \Exception($this->lastResponse);
+        }
+    }
+}

+ 251 - 0
app/Repositories/AbstractProductConfigurationRepository.php

@@ -0,0 +1,251 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2019-09-06)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper\WhmcsVersionComparator;
+use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
+
+/**
+ * Description of ProductConfigurationRepository
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.1
+ */
+abstract class AbstractProductConfigurationRepository
+{
+    protected $productId;
+    /**
+     *
+     * @var ProductConfiguration[]
+     */
+    protected $entries;
+    protected $force = false;
+
+    public function __construct($productId)
+    {
+        if (!is_numeric($productId) || $productId <= 0)
+        {
+            throw new \InvalidArgumentException(sprintf("Product id: %s is invalid", $productId));
+        }
+        $this->productId = $productId;
+    }
+
+    /**
+     * @return int|string
+     */
+    public function getProductId()
+    {
+        return $this->productId;
+    }
+
+    /**
+     * @param int|string $productId
+     */
+    public function setProductId($productId)
+    {
+        $this->productId = $productId;
+    }
+
+    public function getForce()
+    {
+        return $this->force;
+    }
+
+    public function setForce($force)
+    {
+        $this->force = $force;
+        return $this;
+    }
+
+    public function isEmpty()
+    {
+        return empty($this->entries);
+    }
+
+    public function flush()
+    {
+        ProductConfiguration::ofProductId($this->productId)
+            ->whereNotIn('setting', ['version'])
+            ->delete();
+        return $this;
+    }
+
+    /**
+     * Delete keys
+     * @param string $keys
+     * @param array $keys
+     * @return $this
+     */
+    public function forget($keys)
+    {
+        if (is_array($keys))
+        {
+            ProductConfiguration::ofProductId($this->productId)
+                ->whereIn('setting', $keys)
+                ->delete();
+            foreach ($keys as $k)
+            {
+                unset($this->entries[$k]);
+            }
+        }
+        else
+        {
+            ProductConfiguration::where('product_id', $this->productId)
+                ->where('setting', $keys)
+                ->limit(1)
+                ->delete();
+            unset($this->entries[$keys]);
+        }
+        return $this;
+    }
+
+    public function all($default = [])
+    {
+        $this->force = true;
+        $decode = !WhmcsVersionComparator::isWhmcsVersionHigherOrEqual('8.0.0');
+        foreach (ProductConfiguration::ofProductId($this->productId)->pluck('value', 'setting')->all() as $key => $value)
+        {
+            if($decode){
+                $value                = json_decode($value, true);
+            }
+            $this->entries[$key] = $value;
+        }
+        if (!$this->isEmpty())
+        {
+            return $this->entries;
+        }
+        return $default;
+    }
+
+    public function store(array $values)
+    {
+        foreach ($values as $k => $v)
+        {
+            $this->set($k, $v);
+        }
+        return $this;
+    }
+
+    public function exist($key)
+    {
+        return ProductConfiguration::ofProductId($this->productId)
+                ->where('setting', $key)
+                ->count() > 0;
+    }
+
+    public function set($key, $value)
+    {
+        $this->enterie[$key] = $value;
+    }
+
+    public function __isset($key)
+    {
+        return isset($this->entries[$key]);
+    }
+
+    public function get($key, $default = null)
+    {
+        if (isset($this->entries[$key]))
+        {
+            return $this->entries[$key];
+        }
+        else
+        {
+            if (!$this->force)
+            {
+                $this->getEntery($key);
+                if (isset($this->entries[$key]))
+                {
+                    return $this->entries[$key];
+                }
+            }
+        }
+        return $default;
+    }
+
+    /**
+     *
+     * @param string $key
+     * @return ProductConfiguration
+     */
+    protected function getEntery($key)
+    {
+        if (isset($this->entries[$key]))
+        {
+            return $this->entries[$key];
+        }
+        else
+        {
+            if (!$this->force)
+            {
+                $this->entries[$key] = ProductConfiguration::ofProductId($this->productId)
+                    ->ofSetting($key)
+                    ->value("value");
+                return $this->entries[$key];
+            }
+        }
+    }
+
+    public function fill(array $setting)
+    {
+        $this->entries = $setting;
+        return $this;
+    }
+
+    public function fillAndSave(array $setting)
+    {
+        $this->entries = $setting;
+        foreach ($this->entries as $key => $value)
+        {
+            $setting =  ProductConfiguration::firstOrNew([
+                'product_id' => $this->productId,
+                'setting' => $key
+            ]);
+            $setting->value      = $value;
+            $setting->save();
+        }
+        return $this;
+    }
+
+    public function save()
+    {
+        foreach ($this->entries as $key => $value)
+        {
+            $setting             = new ProductConfiguration();
+            $setting->product_id = $this->productId;
+            $setting->setting    = $key;
+            $setting->value      = $value;
+            $setting->save();
+        }
+    }
+
+    public function getVersion()
+    {
+        return $this->get('version');
+    }
+
+    public function setVersion($version)
+    {
+        $this->set('version', $version);
+        return $this;
+    }
+
+}

+ 242 - 0
app/Repositories/AbstractServerConfigurationRepository.php

@@ -0,0 +1,242 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2019-09-06)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories;
+
+use ModulesGarden\ProxmoxAddon\App\Models\ServerConfiguration;
+use ModulesGarden\ProxmoxAddon\Core\Helper\WhmcsVersionComparator;
+
+/**
+ * Description of ServerConfigurationRepository
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.1
+ */
+abstract class AbstractServerConfigurationRepository
+{
+    protected $serverId;
+    /**
+     *
+     * @var ServerConfiguration[]
+     */
+    protected $entries;
+    protected $force = false;
+
+    public function __construct($serverId)
+    {
+        if (!is_numeric($serverId) || $serverId <= 0)
+        {
+            throw new \InvalidArgumentException(sprintf("Server id: %s is invalid", $serverId));
+        }
+        $this->serverId = $serverId;
+    }
+
+    /**
+     * @return int|string
+     */
+    public function getServerId()
+    {
+        return $this->serverId;
+    }
+
+    /**
+     * @param int|string $serverId
+     */
+    public function setServerId($serverId)
+    {
+        $this->serverId = $serverId;
+    }
+
+    public function getForce()
+    {
+        return $this->force;
+    }
+
+    public function setForce($force)
+    {
+        $this->force = $force;
+        return $this;
+    }
+
+    public function isEmpty()
+    {
+        return empty($this->entries);
+    }
+
+    public function flush()
+    {
+        ServerConfiguration::ofServerId($this->serverId)
+                          ->delete();
+        return $this;
+    }
+
+    /**
+     * Delete keys
+     * @param string $keys
+     * @param array $keys
+     * @return $this
+     */
+    public function forget($keys)
+    {
+        if (is_array($keys))
+        {
+            ServerConfiguration::ofServerId($this->serverId)
+                ->whereIn('setting', $keys)
+                ->delete();
+            foreach ($keys as $k)
+            {
+                unset($this->entries[$k]);
+            }
+        }
+        else
+        {
+            ServerConfiguration::where('server_id', $this->serverId)
+                ->where('setting', $keys)
+                ->limit(1)
+                ->delete();
+            unset($this->entries[$keys]);
+        }
+        return $this;
+    }
+
+    public function all($default = [])
+    {
+        $this->force = true;
+        $decode = !WhmcsVersionComparator::isWhmcsVersionHigherOrEqual('8.0.0');
+        foreach (ServerConfiguration::ofServerId($this->serverId)->pluck('value', 'setting')->all() as $key => $value)
+        {
+            if($decode){
+                $value                = json_decode($value, true);
+            }
+            $this->entries[$key] = $value;
+        }
+        if (!$this->isEmpty())
+        {
+            return $this->entries;
+        }
+        return $default;
+    }
+
+    public function store(array $values)
+    {
+        foreach ($values as $k => $v)
+        {
+            $this->set($k, $v);
+        }
+        return $this;
+    }
+
+    public function exist($key)
+    {
+        return ServerConfiguration::ofServerId($this->serverId)
+                ->where('setting', $key)
+                ->count() > 0;
+    }
+
+    public function set($key, $value)
+    {
+        $this->entries[$key] = $value;
+    }
+
+    public function __isset($key)
+    {
+        return isset($this->entries[$key]);
+    }
+
+    public function get($key, $default = null)
+    {
+        if (isset($this->entries[$key]))
+        {
+            return $this->entries[$key];
+        }
+        else
+        {
+            if (!$this->force)
+            {
+                $this->getEntery($key);
+                if (isset($this->entries[$key]))
+                {
+                    return $this->entries[$key];
+                }
+            }
+        }
+        return $default;
+    }
+
+    /**
+     *
+     * @param string $key
+     * @return ServerConfiguration
+     */
+    protected function getEntery($key)
+    {
+        if (isset($this->entries[$key]))
+        {
+            return $this->entries[$key];
+        }
+        else
+        {
+            if (!$this->force)
+            {
+                $this->entries[$key] = ServerConfiguration::ofServerId($this->serverId)
+                    ->ofSetting($key)
+                    ->value("value");
+                return $this->entries[$key];
+            }
+        }
+    }
+
+    public function fill(array $setting)
+    {
+        $this->entries = $setting;
+        return $this;
+    }
+
+    public function fillAndSave(array $setting)
+    {
+        $this->entries = $setting;
+        foreach ($this->entries as $key => $value)
+        {
+            $setting =  ServerConfiguration::firstOrNew([
+                'server_id' => $this->serverId,
+                'setting' => $key
+            ]);
+            $setting->value      = $value;
+            $setting->server_id = $this->serverId;
+            $setting->save();
+        }
+
+
+        return $this;
+    }
+
+    public function save()
+    {
+        foreach ($this->entries as $key => $value)
+        {
+            $setting             = new ServerConfiguration();
+            $setting->server_id = $this->serverId;
+            $setting->setting    = $key;
+            $setting->value      = $value;
+            $setting->save();
+        }
+    }
+
+}

+ 1696 - 0
app/Repositories/Cloud/ProductConfigurationRepository.php

@@ -0,0 +1,1696 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2019-09-06)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Repositories\AbstractProductConfigurationRepository;
+
+/**
+ * Description of ProductConfigurationRepository
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.0
+ * @property $serverSockets
+ * @property $serverCores
+ * @property $serverVcpus
+ * @property $serverCpulimit
+ * @property $serverCpuunit
+ * @property $serverMemory
+ * @property $serverDiskSize
+ * @property $serverIpv4
+ * @property $serverIpv6
+ * @property $serverSwap
+ * @property $cpuunitsPriority1
+ * @property $cpulimitPriority1
+ */
+class ProductConfigurationRepository extends AbstractProductConfigurationRepository
+{
+
+    public function isDebug()
+    {
+        return $this->get("debugMode") == "on";
+    }
+
+    public function getVirtualization()
+    {
+        return $this->get("virtualization", 'qemu');
+    }
+
+    public function isQemu()
+    {
+        return $this->getVirtualization() == "qemu";
+    }
+
+    public function isLxc()
+    {
+        return $this->getVirtualization() == "lxc";
+    }
+
+    public function getDefaultNode()
+    {
+        return $this->get("defaultNode");
+    }
+
+    public function isCheckResources()
+    {
+        return $this->get('checkResources') == "on";
+    }
+
+    public function isBackupVmBeforeReinstall()
+    {
+        return $this->get('backupVmBeforeReinstall') == "on";
+    }
+
+    public function isRebootVmAfterChangePackage()
+    {
+        return $this->get('rebootVmAfterChangePackage') == "on";
+    }
+
+    public function isDeleteBackups()
+    {
+        return $this->get('deleteBackups') == "on";
+    }
+
+    public function isServerNameservers()
+    {
+        return $this->get('serverNameservers') == "on";
+    }
+
+    public function getConsoleHost()
+    {
+        return $this->get("consoleHost");
+    }
+
+    public function getUserPrefix()
+    {
+        return $this->get("userPrefix");
+    }
+
+    public function getRealm()
+    {
+        return $this->get("realm", "pve");
+    }
+
+    public function getUserComment()
+    {
+        return $this->get("userComment");
+    }
+
+    public function getUserRole()
+    {
+        return $this->get("userRole");
+    }
+
+    public function getWelcomeEmailTemplateId()
+    {
+        return $this->get("welcomeEmailTemplateId");
+    }
+
+    public function getReinstallEmailTemplateId()
+    {
+        return $this->get("reinstallEmailTemplateId");
+    }
+
+    public function getServiceCreationFailedTemplateId()
+    {
+        return $this->get("serviceCreationFailedTemplateId");
+    }
+
+    public function getUpgradeNotificationTemplateId()
+    {
+        return $this->get("upgradeNotificationTemplateId");
+    }
+
+    public function isToDoList()
+    {
+        return $this->get("toDoList") == "on";
+    }
+
+    public function getCmode()
+    {
+        return $this->get("cmode") == "on";
+    }
+
+    public function getOsType()
+    {
+        return $this->get("ostype");
+    }
+
+    public function getPool()
+    {
+        return $this->get("pool");
+    }
+
+    public function getDescription()
+    {
+        return $this->get("description");
+    }
+
+    public function getTty()
+    {
+        return $this->get("tty");
+    }
+
+    public function isSshKeyPairs()
+    {
+        return $this->get("sshKeyPairs") == "on";
+    }
+
+    public function getArch()
+    {
+        return $this->get("arch");
+    }
+
+    public function isConsole()
+    {
+        return $this->get("console") == "on";
+    }
+
+    public function isOnboot()
+    {
+        return $this->get("onboot") == "on";
+    }
+
+    public function isProtection()
+    {
+        return $this->get("protection") == "on";
+    }
+
+    public function isStartup()
+    {
+        return $this->get("startup") == "on";
+    }
+
+    public function isSshDeletePrivateKey()
+    {
+        return $this->get("sshDeletePrivateKey") == "on";
+    }
+
+    public function getOsTemplate()
+    {
+        return $this->get("osTemplate");
+    }
+
+    public function getCpuunits()
+    {
+        return $this->get("cpuunits");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMemory()
+    {
+        return $this->get("memory");
+    }
+
+    /**
+     * @return int
+     */
+    public function getProductId()
+    {
+        return $this->productId;
+    }
+
+    /**
+     * @return int
+     */
+    public function getStorageSize()
+    {
+        return $this->get("storageSize");
+    }
+
+    /**
+     * @return int
+     * @deprecated
+     */
+    public function getAdditionalDiskSize()
+    {
+        return $this->get("additionalDiskSize");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMinimumRate()
+    {
+        return $this->get("minimumRate");
+    }
+
+    /**
+     * @return int
+     */
+    public function getIpv4()
+    {
+        return $this->get("ipv4");
+    }
+
+    /**
+     * @return int
+     */
+    public function getIpv6()
+    {
+        return $this->get("ipv6");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupMaxFiles()
+    {
+        return $this->get("backupMaxFiles");
+    }
+
+    /**
+     * @return int
+     */
+    public function getCpulimit()
+    {
+        return $this->get("cpulimit");
+    }
+
+    /**
+     * @return int
+     */
+    public function getCores()
+    {
+        return $this->get("cores");
+    }
+
+    /**
+     * @return int
+     */
+    public function getSwap()
+    {
+        return $this->get("swap");
+    }
+
+    /**
+     * @return int
+     */
+    public function getRate()
+    {
+        return $this->get("rate");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupMaxSize()
+    {
+        return $this->get("backupMaxSize");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBandwidth()
+    {
+        return $this->get("bandwidth");
+    }
+
+    /**
+     * @return int
+     */
+    public function getSnapshotMaxFiles()
+    {
+        return $this->get("snapshotMaxFiles");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMountPointStorage()
+    {
+        return $this->get("mountPointStorage");
+    }
+
+    /**
+     * @return int
+     */
+    public function isMountPointRo()
+    {
+        return $this->get("mountPointRo") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function isReplicate()
+    {
+        return $this->get("replicate") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getMountPointAcl()
+    {
+        return $this->get("mountPointAcl");
+    }
+
+    /**
+     * @return int
+     */
+    public function isMountPointQuota()
+    {
+        return $this->get("mountPointQuota") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getIpv4NetworkMode()
+    {
+        return $this->get("ipv4NetworkMode");
+    }
+
+    /**
+     * @return string
+     */
+    public function getIpv6NetworkMode()
+    {
+        return $this->get("ipv6NetworkMode");
+    }
+
+    /**
+     * @return string
+     */
+    public function getPrivateBridge()
+    {
+        return $this->get("privateBridge");
+    }
+
+    /**
+     * @return string
+     */
+    public function getBridge()
+    {
+        return $this->get("bridge");
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isNetworkFirewall()
+    {
+        return $this->get("networkFirewall") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getTagFrom()
+    {
+        return $this->get("tagFrom");
+    }
+
+    /**
+     * @return int
+     */
+    public function getTagTo()
+    {
+        return $this->get("tagTo");
+    }
+
+    /**
+     * @return string
+     */
+    public function getSwapUnit()
+    {
+        return $this->get("swapUnit", "mb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getStorageUnit()
+    {
+        return $this->get("storageUnit", "gb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getMemoryUnit()
+    {
+        return $this->get("memoryUnit", "mb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskUnit()
+    {
+        return $this->get("additionalDiskUnit", "gb");
+    }
+
+    /**
+     * @return array
+     */
+    public function getFirewallInterfaces()
+    {
+        return $this->get("firewallInterfaces");
+    }
+
+    /**
+     * @return int
+     */
+    public function getFirewallMaxRules()
+    {
+        return $this->get("firewallMaxRules");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancer()
+    {
+        return $this->get("loadBalancer") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancerShutdownOnUpgrade()
+    {
+        return $this->get("loadBalancerShutdownOnUpgrade") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getLoadBalancerOnUpgrade()
+    {
+        return $this->get("loadBalancerOnUpgrade");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancerStopOnUpgrade()
+    {
+        return $this->get("loadBalancerStopOnUpgrade") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getBackupStorage()
+    {
+        return $this->get("backupStorage");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isBackupRouting()
+    {
+        return $this->get("backupRouting") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupStoreDays()
+    {
+        return $this->get("backupStoreDays");
+    }
+
+    /**
+     * @return string
+     */
+    public function getClusterState()
+    {
+        return $this->get("clusterState");
+    }
+
+    /**
+     * @return int
+     */
+    public function getClusterMaxRestart()
+    {
+        return $this->get("clusterMaxRestart");
+    }
+
+    /**
+     * @return string
+     */
+    public function getClusterGroup()
+    {
+        return $this->get("clusterGroup");
+    }
+
+    /**
+     * @return int
+     */
+    public function getClusterMaxRelocate()
+    {
+        return $this->get("clusterMaxRelocate");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionStart()
+    {
+        return $this->get("permissionStart") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionStop()
+    {
+        return $this->get("permissionStop") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionNovnc()
+    {
+        return $this->get("permissionNovnc") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionXtermjs()
+    {
+        return $this->get("permissionXtermjs") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionOsTemplate()
+    {
+        return $this->get("permissionOsTemplate") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionIsoImage()
+    {
+        return $this->get("permissionIsoImage") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionGraph()
+    {
+        return $this->get("permissionGraph") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionBackupJob()
+    {
+        return $this->get("permissionBackupJob") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionNetwork()
+    {
+        return $this->get("permissionNetwork") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionFirewall()
+    {
+        return $this->get("permissionFirewall") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionDisk()
+    {
+        return $this->get("permissionDisk") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionReboot()
+    {
+        return $this->get("permissionReboot") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionShutdown()
+    {
+        return $this->get("permissionShutdown") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSpice()
+    {
+        return $this->get("permissionSpice") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionReinstall()
+    {
+        return $this->get("permissionReinstall") == "on";
+    }
+
+    /**
+     * @return array
+     */
+    public function getPermissionOsTemplates()
+    {
+        return $this->get("permissionOsTemplates");
+    }
+
+    public function isPermissionOsTemplates()
+    {
+        return !empty($this->get("permissionOsTemplates"));
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionBackup()
+    {
+        return $this->get("permissionBackup") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionTaskHistory()
+    {
+        return $this->get("permissionTaskHistory") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSnapshot()
+    {
+        return $this->get("permissionSnapshot") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionFirewallOption()
+    {
+        return $this->get("permissionFirewallOption") == "on";
+    }
+
+    public function getNetworkModel()
+    {
+        return $this->get("networkModel");
+    }
+
+    public function getNetworkPrivateModel()
+    {
+        return $this->get("networkPrivateModel");
+    }
+
+    public function isCloudInit()
+    {
+        return $this->get("cloudInit") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskMbpsRd()
+    {
+        return $this->get("additionalDiskMbps_rd");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsRd()
+    {
+        return $this->get("additionalDiskIops_rd");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsWr()
+    {
+        return $this->get("additionalDiskIops_wr");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskMbpsWr()
+    {
+        return $this->get("additionalDiskMbps_wr");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsRdMax()
+    {
+        return $this->get("additionalDiskIops_rd_max");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsWrMax()
+    {
+        return $this->get("additionalDiskIops_wr_max");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskStorage()
+    {
+        return $this->get("additionalDiskStorage");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskFormat()
+    {
+        return $this->get("additionalDiskFormat");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskCache()
+    {
+        return $this->get("additionalDiskCache");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskIoThread()
+    {
+        return $this->get("additionalDiskIoThread") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskType()
+    {
+        return $this->get("additionalDiskType");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskReplicate()
+    {
+        return $this->get("additionalDiskReplicate") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskDiscard()
+    {
+        return $this->get("additionalDiskDiscard") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionAdditionalDiskBackup()
+    {
+        return $this->get("permissionAdditionalDiskBackup") == "on";
+    }
+
+    public function isPermissionMountPointBackup()
+    {
+        return $this->get("permissionMountPointBackup") == "on";
+    }
+
+    public function isMountPointReplicate()
+    {
+        return $this->get("mountPointReplicate") == "on";
+    }
+
+    public function isOsTemplatesInAllNodes()
+    {
+        return $this->get("osTemplatesInAllNodes") == "on";
+    }
+
+    public function getPermissionIsoImages()
+    {
+        return $this->get("permissionIsoImages");
+    }
+
+    public function isPermissionIsoImages()
+    {
+        return !empty($this->get("permissionIsoImages"));
+    }
+
+    public function getPermissionSecondaryIsoImages()
+    {
+        return $this->get("permissionSecondaryIsoImages");
+    }
+
+    public function isPermissionSecondaryIsoImages()
+    {
+        return !empty($this->get("permissionSecondaryIsoImages"));
+    }
+
+
+    public function getStorage()
+    {
+        return $this->get("storage");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAgent()
+    {
+        return $this->get('agent') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getCdrom()
+    {
+        return $this->get('cdrom');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNuma()
+    {
+        return $this->get('numa') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isSpec()
+    {
+        return $this->get('spec') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isFreeze()
+    {
+        return $this->get('freeze') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getKeyboard()
+    {
+        return $this->get('keyboard');
+    }
+
+    /**
+     * @return string
+     */
+    public function getVga()
+    {
+        return $this->get('vga');
+    }
+
+    public function getVgaMemory()
+    {
+        return $this->get('vgaMemory');
+    }
+
+    /**
+     * @return string
+     */
+    public function getClientNameForContainer()
+    {
+        return $this->get('clientNameForContainer');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAcpi()
+    {
+        return $this->get('acpi') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAutostart()
+    {
+        return $this->get('autostart') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPcid()
+    {
+        return $this->get('pcid') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function getHotplug()
+    {
+        return implode(",",$this->get('hotplug')) ;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isKvm()
+    {
+        return $this->get('kvm') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isReboot()
+    {
+        return $this->get('reboot') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTablet()
+    {
+        return $this->get('tablet') == "on";
+    }
+
+
+    /**
+     * @return string
+     */
+    public function getCloneMode()
+    {
+        return $this->get('cloneMode');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBalloon()
+    {
+        return $this->get('balloon');
+    }
+
+    /**
+     * @return string
+     */
+    public function getArgs()
+    {
+        return $this->get('args');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMigrateSpeed()
+    {
+        return $this->get('migrate_speed');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMigrateDowntime()
+    {
+        return $this->get('migrate_downtime');
+    }
+
+    /**
+     * @return string
+     */
+    public function getStartdate()
+    {
+        return $this->get('startdate');
+    }
+
+    /**
+     * @return string
+     */
+    public function getShares()
+    {
+        return $this->get('shares');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLocaltime()
+    {
+        return $this->get('localtime') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getWatchdog()
+    {
+        return $this->get('watchdog');
+    }
+
+    /**
+     * @return string
+     */
+    public function getStartup()
+    {
+        return $this->get('startup');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTdf()
+    {
+        return $this->get('tdf') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getSockets()
+    {
+        return $this->get('sockets');
+    }
+
+    /**
+     * @return string
+     */
+    public function getVcpus()
+    {
+        return $this->get('vcpus');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIsoImage()
+    {
+        return $this->get('isoImage');
+    }
+
+    public function getCdromType()
+    {
+        return $this->get('cdromType');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskStorage()
+    {
+        return $this->get('diskStorage');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskFormat()
+    {
+        return $this->get('diskFormat');
+    }
+
+    /**
+     * @return string
+     */
+    public function getScsihw()
+    {
+        return $this->get('scsihw');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskType()
+    {
+        return $this->get('diskType');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskCache()
+    {
+        return $this->get('diskCache');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDiscard()
+    {
+        return $this->get('discard') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isIoThread()
+    {
+        return $this->get('ioThread') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getMbpsRd()
+    {
+        return $this->get('mbps_rd');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsRd()
+    {
+        return $this->get('iops_rd');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsWr()
+    {
+        return $this->get('iops_wr');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMbpsWr()
+    {
+        return $this->get('mbps_wr');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsRdMax()
+    {
+        return $this->get('iops_rd_max');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsWrMax()
+    {
+        return $this->get('iops_wr_max');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEtworkOneDevice()
+    {
+        return $this->get('etworkOneDevice') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getQueues()
+    {
+        return $this->get('queues');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice1()
+    {
+        return $this->get('bootDevice1');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice2()
+    {
+        return $this->get('bootDevice2');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice3()
+    {
+        return $this->get('bootDevice3');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootdisk()
+    {
+        return $this->get('bootdisk');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServicePassword()
+    {
+        return $this->get('cloudInitServicePassword') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServiceUsername()
+    {
+        return $this->get('cloudInitServiceUsername') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServiceNameservers()
+    {
+        return $this->get('cloudInitServiceNameservers') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getCiuser()
+    {
+        return $this->get('ciuser');
+    }
+
+    public function getSearchdomain()
+    {
+        return $this->get('searchdomain');
+    }
+
+    public function isRandomHostname()
+    {
+        return $this->get('randomHostname') == "on";
+    }
+
+    public function isUnprivileged()
+    {
+        return $this->get('unprivileged') == "on";
+    }
+
+    public function isIpsetIpFilter()
+    {
+        return $this->get("ipsetIpFilter") == "on";
+    }
+
+    public function getBootOrder()
+    {
+
+        $boot = [];
+        if ($this->getBootDevice1())
+        {
+            $boot[] = $this->getBootDevice1();
+        }
+        if ($this->getBootDevice2())
+        {
+            $boot[] = $this->getBootDevice2();
+        }
+        if ($this->getBootDevice3())
+        {
+            $boot[] = $this->getBootDevice3();
+        }
+        return implode("", $boot);
+    }
+
+    public function isOneNetworkDevice()
+    {
+        return $this->get("oneNetworkDevice") == "on";
+    }
+
+
+    public function getCpu()
+    {
+        return $this->get("cpu");
+    }
+
+    public function getSuspensionAction()
+    {
+        return $this->get("suspensionAction");
+    }
+
+    public function isSuspendOnBandwidthOverage()
+    {
+        return $this->get("suspendOnBandwidthOverage") == "on";
+    }
+
+    public function isStart()
+    {
+        return $this->get("start") == "on";
+    }
+
+    /**
+     * @return array
+     */
+    public function getTags()
+    {
+        return $this->get("tags");
+    }
+
+    public function getCicustom()
+    {
+        return $this->get('cicustom');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSshkeys()
+    {
+        return $this->get("permissionSshkeys") == "on";
+    }
+
+    public function isPermissionSnapshotJob()
+    {
+        return $this->get("permissionSnapshotJob") == "on";
+    }
+
+    public function getPermissionSnapshotJobPeriod()
+    {
+        return $this->get("permissionSnapshotJobPeriod");
+    }
+
+    public function getSnapshotJobs()
+    {
+        return $this->get("snapshotJobs");
+    }
+
+    public function isAgentTemplateUser()
+    {
+        return $this->get('agentTemplateUser') == "on";
+    }
+
+    public function isAgentPassword()
+    {
+        return $this->get('agentPassword') == "on";
+    }
+
+    public function isAgentConfigureNetwork()
+    {
+        return $this->get('agentConfigureNetwork') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloneOnTheSameStorage()
+    {
+        return $this->get('cloneOnTheSameStorage') == "on";
+    }
+
+    public function isFeatureKeyctl()
+    {
+        return $this->get('featureKeyctl') == "on";
+    }
+
+    public function isFeatureNesting()
+    {
+        return $this->get('featureNesting') == "on";
+    }
+
+    public function isFeatureNfs()
+    {
+        return $this->get('featureNfs') == "on";
+    }
+
+    public function isFeatureCifs()
+    {
+        return $this->get('featureCifs') == "on";
+    }
+
+    public function isFeatureFuse()
+    {
+        return $this->get('featureFuse') == "on";
+    }
+
+    public function isFeatureMknod()
+    {
+        return $this->get('featureMknod') == "on";
+    }
+
+    public function hasCpuFlags(){
+        $configFlags =['md-clear', "pcid", "spec-ctrl", "ssbd", "ibpb", "virt-ssbd",
+            "amd-ssbd", "amd-no-ssb", "pdpe1gb", "hv-tlbflush","hv-evmcs", "aes" ];
+        foreach ($configFlags as $configFlag) {
+            if($this->get($configFlag)=="on"){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public function getCpuFlagsAsSource(){
+        $configFlags =['md-clear', "pcid", "spec-ctrl", "ssbd", "ibpb", "virt-ssbd",
+          "amd-ssbd", "amd-no-ssb", "pdpe1gb", "hv-tlbflush","hv-evmcs", "aes" ];
+        $cpuFlags=[];
+        foreach ($configFlags as $configFlag) {
+            if($this->get($configFlag)=="on"){
+                $cpuFlags[] = "+". $configFlag;
+            }
+        }
+        return implode(";", $cpuFlags);
+    }
+
+    public function isSsd()
+    {
+        return $this->get('ssd') == "on";
+    }
+
+    public function isAdditionalDiskSsd()
+    {
+        return $this->get('additionalDiskSsd') == "on";
+    }
+
+    /**
+     * @return array
+     */
+    public function getPermissionFirewalOptions()
+    {
+        return (array) $this->get("permissionFirewalOptions");
+    }
+
+    public function isFirewalOptionEnable()
+    {
+        return $this->get("firewalOptionEnable")  == "on";
+    }
+
+    public function isFirewalOptionNdp()
+    {
+        return $this->get("firewalOptionNdp")  == "on";
+    }
+
+    public function isFirewalOptionMacfilter()
+    {
+        return $this->get("firewalOptionMacfilter")  == "on";
+    }
+
+    public function isFirewalOptionDhcp()
+    {
+        return $this->get("firewalOptionDhcp")  == "on";
+    }
+
+    public function isFirewalOptionRadv()
+    {
+        return $this->get("firewalOptionRadv")  == "on";
+    }
+
+    public function isFirewalOptionIpfilter()
+    {
+        return $this->get("firewalOptionIpfilter")  == "on";
+    }
+
+    public function getBwLimit(){
+        return $this->get('bwlimit');
+    }
+    public function getBios(){
+        return $this->get('bios');
+    }
+
+    public function isPermissionVirtualNetwork()
+    {
+        return $this->get("permissionVirtualNetwork")  == "on";
+    }
+
+    public function getLocations()
+    {
+        return $this->get("locations");
+    }
+
+    public function getPermissionOstype()
+    {
+        return $this->get("permissionOstype");
+    }
+
+    public function isPermissionUsername()
+    {
+        return $this->get("permissionUsername") == "on";
+    }
+
+    public function isPermissionPassword()
+    {
+        return $this->get("permissionPassword") == "on";
+    }
+
+    public function isPermissionNameservers()
+    {
+        return $this->get("permissionNameservers") == "on";
+    }
+
+    public function isPermissionSearchdomain()
+    {
+        return $this->get("permissionSearchdomain") == "on";
+    }
+
+    public function isPermissionIpv4()
+    {
+        return $this->get("permissionIpv4") == "on";
+    }
+
+    public function isPermissionIpv6()
+    {
+        return $this->get("permissionIpv6") == "on";
+    }
+
+    public function isPermissionSockets()
+    {
+        return $this->get("permissionSockets") == "on";
+    }
+
+    public function isPermissionCores()
+    {
+        return $this->get("permissionCores") == "on";
+    }
+
+    public function isPermissionVcpus()
+    {
+        return $this->get("permissionVcpus") == "on";
+    }
+
+    public function isPermissionCpuLimit()
+    {
+        return $this->get("permissionCpuLimit") == "on";
+    }
+
+    public function isPermissionCpuunits()
+    {
+        return $this->get("permissionCpuunits") == "on";
+    }
+
+    public function isPermissionSwap()
+    {
+        return $this->get("permissionSwap") == "on";
+    }
+
+
+    public function __get($name)
+    {
+        if(preg_match("/server/", $name)){
+            $data = new \stdClass();
+            $data->min = 0;
+            $data->max = 0;
+            $value = $this->get($name);
+            if(is_null($value)){
+                return $data;
+            }
+            list($data->min, $data->max)  = explode("-", $this->get($name));
+            return $data;
+        }
+        return  $this->get($name);
+    }
+
+    public function getVirtualNetworks(){
+        return $this->get('virtualNetworks');
+    }
+
+    public function getButtonSyle()
+    {
+        return $this->get("buttonSyle");
+    }
+
+    public function hasCpuPriority(){
+        for($i=1; $i<=5; $i++) {
+            if(empty($this->get('cpuunitsPriority'.$i))){
+                return false;
+            }
+            if(empty($this->get('cpulimitPriority'. $i))){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public function isPermissionCustomTemplates(){
+        return $this->get('permissionCustomTemplates')=='on';
+    }
+
+    public function isDetailsCombinedView()
+    {
+        return $this->get("detailsView")=='combined';
+    }
+
+    public function isCalculateSocketsAndCores()
+    {
+        return $this->get("calculateSocketsAndCores")=='on';
+    }
+
+    public function getCloudInitScript(){
+        return $this->get('cloudInitScript');
+    }
+
+    public function getMachine(){
+        return $this->get('machine');
+    }
+
+
+}

+ 83 - 0
app/Repositories/RangeVmRepository.php

@@ -0,0 +1,83 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 21, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories;
+
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of RangeVmRepository
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RangeVmRepository
+{
+    private $serverId;
+    private $min;
+    private $max;
+
+    /**
+     *
+     * @var main\App\Models\RangeVm
+     */
+    private $model;
+
+    public function __construct($serverId)
+    {
+        $this->serverId = $serverId;
+        $this->model    = main\App\Models\RangeVm::where('server_id', $this->serverId)->first();
+        if ($this->model)
+        {
+            $this->min = $this->model->vmid_from;
+            $this->max = $this->model->vmid_to;
+        }
+        if (!$this->min)
+        {
+            $this->min = main\Core\Models\Whmcs\Configuration::where("setting", "proxmoxVPSMinimumVMID")->value('value');
+        }
+    }
+
+    public function has()
+    {
+        return $this->min > 0 || $this->max > 1;
+    }
+
+    public function getMin()
+    {
+        return $this->min;
+    }
+
+    public function setMin($min)
+    {
+        $this->min = $min;
+        return $this;
+    }
+
+    public function getMax()
+    {
+        return $this->max;
+    }
+
+    public function setMax($max)
+    {
+        $this->max = $max;
+        return $this;
+    }
+}

+ 52 - 0
app/Repositories/ServerConfigurationRepository.php

@@ -0,0 +1,52 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories;
+
+/**
+ * Class ServerConfigurationRepository
+ * @package ModulesGarden\ProxmoxAddon\App\Repositories
+ * @property $sshHost
+ * @property $sshPort
+ * @property $sshUser
+ * @property $snippetStorage
+ * @property $snippetDirectory
+ */
+class ServerConfigurationRepository extends  AbstractServerConfigurationRepository
+{
+
+    public function __get($name)
+    {
+        return  $this->get($name);
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getSshPassword()
+    {
+        return  html_entity_decode(decrypt($this->sshPassword), ENT_QUOTES);
+    }
+
+    public function getSshKey()
+    {
+        return decrypt($this->sshKey);
+    }
+
+    public function hasSshKey()
+    {
+        return decrypt($this->sshKey) !="";
+    }
+
+    /**
+     * @param mixed $sshPassword
+     * @return ServerConfigurationRepository
+     */
+    public function setSshPassword($sshPassword)
+    {
+        $this->set('sshPassword',encrypt($sshPassword));
+        return $this;
+    }
+
+
+}

+ 1566 - 0
app/Repositories/Vps/ProductConfigurationRepository.php

@@ -0,0 +1,1566 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2019-09-06)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Repositories\Vps;
+
+use ModulesGarden\ProxmoxAddon\App\Repositories\AbstractProductConfigurationRepository;
+
+/**
+ * Description of ProductConfigurationRepository
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.0
+ */
+class ProductConfigurationRepository extends AbstractProductConfigurationRepository
+{
+
+    public function isDebug()
+    {
+        return $this->get("debugMode") == "on";
+    }
+
+    public function getVirtualization()
+    {
+        return $this->get("virtualization", 'qemu');
+    }
+
+    public function isQemu()
+    {
+        return $this->getVirtualization() == "qemu";
+    }
+
+    public function isLxc()
+    {
+        return $this->getVirtualization() == "lxc";
+    }
+
+    public function getDefaultNode()
+    {
+        return $this->get("defaultNode");
+    }
+
+    public function isCheckResources()
+    {
+        return $this->get('checkResources') == "on";
+    }
+
+    public function isBackupVmBeforeReinstall()
+    {
+        return $this->get('backupVmBeforeReinstall') == "on";
+    }
+
+    public function isRebootVmAfterChangePackage()
+    {
+        return $this->get('rebootVmAfterChangePackage') == "on";
+    }
+
+    public function isDeleteBackups()
+    {
+        return $this->get('deleteBackups') == "on";
+    }
+
+    public function isServerNameservers()
+    {
+        return $this->get('serverNameservers') == "on";
+    }
+
+    public function getConsoleHost()
+    {
+        return $this->get("consoleHost");
+    }
+
+    public function isOneUserPerVps()
+    {
+        return $this->get('oneUserPerVps') == "on";
+    }
+
+    public function getUserPrefix()
+    {
+        return $this->get("userPrefix");
+    }
+
+    public function getRealm()
+    {
+        return $this->get("realm", "pve");
+    }
+
+    public function getUserComment()
+    {
+        return $this->get("userComment");
+    }
+
+    public function getUserRole()
+    {
+        return $this->get("userRole");
+    }
+
+    public function getWelcomeEmailTemplateId()
+    {
+        return $this->get("welcomeEmailTemplateId");
+    }
+
+    public function getReinstallEmailTemplateId()
+    {
+        return $this->get("reinstallEmailTemplateId");
+    }
+
+    public function getServiceCreationFailedTemplateId()
+    {
+        return $this->get("serviceCreationFailedTemplateId");
+    }
+
+    public function getUpgradeNotificationTemplateId()
+    {
+        return $this->get("upgradeNotificationTemplateId");
+    }
+
+    public function isToDoList()
+    {
+        return $this->get("toDoList") == "on";
+    }
+
+    public function getCmode()
+    {
+        return $this->get("cmode") == "on";
+    }
+
+    public function getOsType()
+    {
+        return $this->get("ostype");
+    }
+
+    public function getPool()
+    {
+        return $this->get("pool");
+    }
+
+    public function getDescription()
+    {
+        return $this->get("description");
+    }
+
+    public function getTty()
+    {
+        return $this->get("tty");
+    }
+
+    public function isSshKeyPairs()
+    {
+        return $this->get("sshKeyPairs") == "on";
+    }
+
+    public function getArch()
+    {
+        return $this->get("arch");
+    }
+
+    public function isConsole()
+    {
+        return $this->get("console") == "on";
+    }
+
+    public function isOnboot()
+    {
+        return $this->get("onboot") == "on";
+    }
+
+    public function isProtection()
+    {
+        return $this->get("protection") == "on";
+    }
+
+    public function isStartup()
+    {
+        return $this->get("startup") == "on";
+    }
+
+    public function isSshDeletePrivateKey()
+    {
+        return $this->get("sshDeletePrivateKey") == "on";
+    }
+
+    public function getOsTemplate()
+    {
+        return $this->get("osTemplate");
+    }
+
+    public function getCpuunits()
+    {
+        return $this->get("cpuunits");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMemory()
+    {
+        return $this->get("memory");
+    }
+
+    /**
+     * @return int
+     */
+    public function getProductId()
+    {
+        return $this->productId;
+    }
+
+    /**
+     * @return int
+     */
+    public function getDiskSize()
+    {
+        return $this->get("diskSize");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskSize()
+    {
+        return $this->get("additionalDiskSize");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMinimumRate()
+    {
+        return $this->get("minimumRate");
+    }
+
+    /**
+     * @return int
+     */
+    public function getIpv4()
+    {
+        return $this->get("ipv4");
+    }
+
+    /**
+     * @return int
+     */
+    public function getIpv6()
+    {
+        return $this->get("ipv6");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupMaxFiles()
+    {
+        return $this->get("backupMaxFiles");
+    }
+
+    /**
+     * @return int
+     */
+    public function getCpulimit()
+    {
+        return $this->get("cpulimit");
+    }
+
+    /**
+     * @return int
+     */
+    public function getCores()
+    {
+        return $this->get("cores");
+    }
+
+    /**
+     * @return int
+     */
+    public function getSwap()
+    {
+        return $this->get("swap");
+    }
+
+    /**
+     * @return int
+     */
+    public function getRate()
+    {
+        return $this->get("rate");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupMaxSize()
+    {
+        return $this->get("backupMaxSize");
+    }
+
+    /**
+     * @return int
+     */
+    public function getBandwidth()
+    {
+        return $this->get("bandwidth");
+    }
+
+    /**
+     * @return int
+     */
+    public function getSnapshotMaxFiles()
+    {
+        return $this->get("snapshotMaxFiles");
+    }
+
+    /**
+     * @return int
+     */
+    public function getMountPointStorage()
+    {
+        return $this->get("mountPointStorage");
+    }
+
+    /**
+     * @return int
+     */
+    public function isMountPointRo()
+    {
+        return $this->get("mountPointRo") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function isReplicate()
+    {
+        return $this->get("replicate") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getMountPointAcl()
+    {
+        return $this->get("mountPointAcl");
+    }
+
+    /**
+     * @return int
+     */
+    public function isMountPointQuota()
+    {
+        return $this->get("mountPointQuota") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getIpv4NetworkMode()
+    {
+        return $this->get("ipv4NetworkMode");
+    }
+
+    /**
+     * @return string
+     */
+    public function getIpv6NetworkMode()
+    {
+        return $this->get("ipv6NetworkMode");
+    }
+
+    /**
+     * @return string
+     */
+    public function getPrivateBridge()
+    {
+        return $this->get("privateBridge");
+    }
+
+    /**
+     * @return string
+     */
+    public function getBridge()
+    {
+        return $this->get("bridge");
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isNetworkFirewall()
+    {
+        return $this->get("networkFirewall") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getTagFrom()
+    {
+        return $this->get("tagFrom");
+    }
+
+    /**
+     * @return int
+     */
+    public function getTagTo()
+    {
+        return $this->get("tagTo");
+    }
+
+    /**
+     * @return string
+     */
+    public function getSwapUnit()
+    {
+        return $this->get("swapUnit", "mb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskUnit()
+    {
+        return $this->get("diskUnit", "gb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getMemoryUnit()
+    {
+        return $this->get("memoryUnit", "mb");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskUnit()
+    {
+        return $this->get("additionalDiskUnit", "gb");
+    }
+
+    /**
+     * @return array
+     */
+    public function getFirewallInterfaces()
+    {
+        return $this->get("firewallInterfaces");
+    }
+
+    /**
+     * @return int
+     */
+    public function getFirewallMaxRules()
+    {
+        return $this->get("firewallMaxRules");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancer()
+    {
+        return $this->get("loadBalancer") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancerShutdownOnUpgrade()
+    {
+        return $this->get("loadBalancerShutdownOnUpgrade") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getLoadBalancerOnUpgrade()
+    {
+        return $this->get("loadBalancerOnUpgrade");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLoadBalancerStopOnUpgrade()
+    {
+        return $this->get("loadBalancerStopOnUpgrade") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getBackupStorage()
+    {
+        return $this->get("backupStorage");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isBackupRouting()
+    {
+        return $this->get("backupRouting") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getBackupStoreDays()
+    {
+        return $this->get("backupStoreDays");
+    }
+
+    /**
+     * @return string
+     */
+    public function getClusterState()
+    {
+        return $this->get("clusterState");
+    }
+
+    /**
+     * @return int
+     */
+    public function getClusterMaxRestart()
+    {
+        return $this->get("clusterMaxRestart");
+    }
+
+    /**
+     * @return string
+     */
+    public function getClusterGroup()
+    {
+        return $this->get("clusterGroup");
+    }
+
+    /**
+     * @return int
+     */
+    public function getClusterMaxRelocate()
+    {
+        return $this->get("clusterMaxRelocate");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionStart()
+    {
+        return $this->get("permissionStart") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionStop()
+    {
+        return $this->get("permissionStop") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionNovnc()
+    {
+        return $this->get("permissionNovnc") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionXtermjs()
+    {
+        return $this->get("permissionXtermjs") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionOsTemplate()
+    {
+        return $this->get("permissionOsTemplate") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionIsoImage()
+    {
+        return $this->get("permissionIsoImage") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionGraph()
+    {
+        return $this->get("permissionGraph") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionBackupJob()
+    {
+        return $this->get("permissionBackupJob") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionNetwork()
+    {
+        return $this->get("permissionNetwork") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionFirewall()
+    {
+        return $this->get("permissionFirewall") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionDisk()
+    {
+        return $this->get("permissionDisk") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionReboot()
+    {
+        return $this->get("permissionReboot") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionShutdown()
+    {
+        return $this->get("permissionShutdown") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSpice()
+    {
+        return $this->get("permissionSpice") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionReinstall()
+    {
+        return $this->get("permissionReinstall") == "on";
+    }
+
+    /**
+     * @return array
+     */
+    public function getPermissionOsTemplates()
+    {
+        return $this->get("permissionOsTemplates");
+    }
+
+    public function isPermissionOsTemplates()
+    {
+        return !empty($this->get("permissionOsTemplates"));
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionBackup()
+    {
+        return $this->get("permissionBackup") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionTaskHistory()
+    {
+        return $this->get("permissionTaskHistory") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSnapshot()
+    {
+        return $this->get("permissionSnapshot") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionFirewallOption()
+    {
+        return $this->get("permissionFirewallOption") == "on";
+    }
+
+    public function getNetworkModel()
+    {
+        return $this->get("networkModel");
+    }
+
+    public function getNetworkPrivateModel()
+    {
+        return $this->get("networkPrivateModel");
+    }
+
+    public function isCloudInit()
+    {
+        return $this->get("cloudInit") == "on";
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskMbpsRd()
+    {
+        return $this->get("additionalDiskMbps_rd");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsRd()
+    {
+        return $this->get("additionalDiskIops_rd");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsWr()
+    {
+        return $this->get("additionalDiskIops_wr");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskMbpsWr()
+    {
+        return $this->get("additionalDiskMbps_wr");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsRdMax()
+    {
+        return $this->get("additionalDiskIops_rd_max");
+    }
+
+    /**
+     * @return int
+     */
+    public function getAdditionalDiskIopsWrMax()
+    {
+        return $this->get("additionalDiskIops_wr_max");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskStorage()
+    {
+        return $this->get("additionalDiskStorage");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskFormat()
+    {
+        return $this->get("additionalDiskFormat");
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskCache()
+    {
+        return $this->get("additionalDiskCache");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskIoThread()
+    {
+        return $this->get("additionalDiskIoThread") == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getAdditionalDiskType()
+    {
+        return $this->get("additionalDiskType");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskReplicate()
+    {
+        return $this->get("additionalDiskReplicate") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAdditionalDiskDiscard()
+    {
+        return $this->get("additionalDiskDiscard") == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionAdditionalDiskBackup()
+    {
+        return $this->get("permissionAdditionalDiskBackup") == "on";
+    }
+
+    public function isPermissionMountPointBackup()
+    {
+        return $this->get("permissionMountPointBackup") == "on";
+    }
+
+    public function isMountPointReplicate()
+    {
+        return $this->get("mountPointReplicate") == "on";
+    }
+
+    public function isOsTemplatesInAllNodes()
+    {
+        return $this->get("osTemplatesInAllNodes") == "on";
+    }
+
+    public function getPermissionIsoImages()
+    {
+        return $this->get("permissionIsoImages");
+    }
+
+    public function isPermissionIsoImages()
+    {
+        return !empty($this->get("permissionIsoImages"));
+    }
+
+    public function getStorage()
+    {
+        return $this->get("storage");
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAgent()
+    {
+        return $this->get('agent') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getCdrom()
+    {
+        return $this->get('cdrom');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNuma()
+    {
+        return $this->get('numa') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isSpec()
+    {
+        return $this->get('spec') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isFreeze()
+    {
+        return $this->get('freeze') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getKeyboard()
+    {
+        return $this->get('keyboard');
+    }
+
+    /**
+     * @return string
+     */
+    public function getVga()
+    {
+        return $this->get('vga');
+    }
+    public function getVgaMemory()
+    {
+        return $this->get('vgaMemory');
+    }
+    /**
+     * @return string
+     */
+    public function getClientNameForContainer()
+    {
+        return $this->get('clientNameForContainer');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAcpi()
+    {
+        return $this->get('acpi') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isAutostart()
+    {
+        return $this->get('autostart') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPcid()
+    {
+        return $this->get('pcid') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function getHotplug()
+    {
+        return implode(",",$this->get('hotplug')) ;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isKvm()
+    {
+        return $this->get('kvm') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isReboot()
+    {
+        return $this->get('reboot') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTablet()
+    {
+        return $this->get('tablet') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getContainerPrefix()
+    {
+        return $this->get('containerPrefix');
+    }
+
+    /**
+     * @return string
+     */
+    public function getCloneMode()
+    {
+        return $this->get('cloneMode');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBalloon()
+    {
+        return $this->get('balloon');
+    }
+
+    /**
+     * @return string
+     */
+    public function getArgs()
+    {
+        return $this->get('args');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMigrateSpeed()
+    {
+        return $this->get('migrate_speed');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMigrateDowntime()
+    {
+        return $this->get('migrate_downtime');
+    }
+
+    /**
+     * @return string
+     */
+    public function getStartdate()
+    {
+        return $this->get('startdate');
+    }
+
+    /**
+     * @return string
+     */
+    public function getShares()
+    {
+        return $this->get('shares');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLocaltime()
+    {
+        return $this->get('localtime') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getWatchdog()
+    {
+        return $this->get('watchdog');
+    }
+
+    /**
+     * @return string
+     */
+    public function getStartup()
+    {
+        return $this->get('startup');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTdf()
+    {
+        return $this->get('tdf') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getSockets()
+    {
+        return $this->get('sockets');
+    }
+
+    /**
+     * @return string
+     */
+    public function getVcpus()
+    {
+        return $this->get('vcpus');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIsoImage()
+    {
+        return $this->get('isoImage');
+    }
+
+    public function getCdromType()
+    {
+        return $this->get('cdromType');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskStorage()
+    {
+        return $this->get('diskStorage');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskFormat()
+    {
+        return $this->get('diskFormat');
+    }
+
+    /**
+     * @return string
+     */
+    public function getScsihw()
+    {
+        return $this->get('scsihw');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskType()
+    {
+        return $this->get('diskType');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDiskCache()
+    {
+        return $this->get('diskCache');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDiscard()
+    {
+        return $this->get('discard') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isIoThread()
+    {
+        return $this->get('ioThread') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getMbpsRd()
+    {
+        return $this->get('mbps_rd');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsRd()
+    {
+        return $this->get('iops_rd');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsWr()
+    {
+        return $this->get('iops_wr');
+    }
+
+    /**
+     * @return string
+     */
+    public function getMbpsWr()
+    {
+        return $this->get('mbps_wr');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsRdMax()
+    {
+        return $this->get('iops_rd_max');
+    }
+
+    /**
+     * @return string
+     */
+    public function getIopsWrMax()
+    {
+        return $this->get('iops_wr_max');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEtworkOneDevice()
+    {
+        return $this->get('etworkOneDevice') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getQueues()
+    {
+        return $this->get('queues');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice1()
+    {
+        return $this->get('bootDevice1');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice2()
+    {
+        return $this->get('bootDevice2');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootDevice3()
+    {
+        return $this->get('bootDevice3');
+    }
+
+    /**
+     * @return string
+     */
+    public function getBootdisk()
+    {
+        return $this->get('bootdisk');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServicePassword()
+    {
+        return $this->get('cloudInitServicePassword') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServiceUsername()
+    {
+        return $this->get('cloudInitServiceUsername') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloudInitServiceNameservers()
+    {
+        return $this->get('cloudInitServiceNameservers') == "on";
+    }
+
+    /**
+     * @return string
+     */
+    public function getCiuser()
+    {
+        return $this->get('ciuser');
+    }
+
+    public function getSearchdomain()
+    {
+        return $this->get('searchdomain');
+    }
+
+    public function isRandomHostname()
+    {
+        return $this->get('randomHostname') == "on";
+    }
+
+    public function isUnprivileged()
+    {
+        return $this->get('unprivileged') == "on";
+    }
+
+    public function isIpsetIpFilter()
+    {
+        return $this->get("ipsetIpFilter") == "on";
+    }
+
+    public function getBootOrder()
+    {
+
+        $boot = [];
+        if ($this->getBootDevice1())
+        {
+            $boot[] = $this->getBootDevice1();
+        }
+        if ($this->getBootDevice2())
+        {
+            $boot[] = $this->getBootDevice2();
+        }
+        if ($this->getBootDevice3())
+        {
+            $boot[] = $this->getBootDevice3();
+        }
+        return implode("", $boot);
+    }
+
+    public function isOneNetworkDevice()
+    {
+        return $this->get("oneNetworkDevice") == "on";
+    }
+
+
+    public function getCpu()
+    {
+        return $this->get("cpu");
+    }
+
+    public function getSuspensionAction()
+    {
+        return $this->get("suspensionAction");
+    }
+
+    public function isSuspendOnBandwidthOverage()
+    {
+        return $this->get("suspendOnBandwidthOverage") == "on";
+    }
+
+    public function isStart()
+    {
+        return $this->get("start") == "on";
+    }
+
+    /**
+     * @return array
+     */
+    public function getTags()
+    {
+        return $this->get("tags");
+    }
+
+    public function getCicustom()
+    {
+        return $this->get('cicustom');
+    }
+
+    /**
+     * @return bool
+     */
+    public function isPermissionSshkeys()
+    {
+        return $this->get("permissionSshkeys") == "on";
+    }
+
+    public function isPermissionSnapshotJob()
+    {
+        return $this->get("permissionSnapshotJob") == "on";
+    }
+
+    public function getPermissionSnapshotJobPeriod()
+    {
+        return $this->get("permissionSnapshotJobPeriod");
+    }
+
+    public function getSnapshotJobs()
+    {
+        return $this->get("snapshotJobs");
+    }
+
+    public function isAgentTemplateUser()
+    {
+        return $this->get('agentTemplateUser') == "on";
+    }
+
+    public function isAgentServicePassword()
+    {
+        return $this->get('agentServicePassword') == "on";
+    }
+
+    public function isAgentConfigureNetwork()
+    {
+        return $this->get('agentConfigureNetwork') == "on";
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCloneOnTheSameStorage()
+    {
+        return $this->get('cloneOnTheSameStorage') == "on";
+    }
+
+    public function isFeatureKeyctl()
+    {
+        return $this->get('featureKeyctl') == "on";
+    }
+
+    public function isFeatureNesting()
+    {
+        return $this->get('featureNesting') == "on";
+    }
+
+    public function isFeatureNfs()
+    {
+        return $this->get('featureNfs') == "on";
+    }
+
+    public function isFeatureCifs()
+    {
+        return $this->get('featureCifs') == "on";
+    }
+
+    public function isFeatureFuse()
+    {
+        return $this->get('featureFuse') == "on";
+    }
+
+    public function isFeatureMknod()
+    {
+        return $this->get('featureMknod') == "on";
+    }
+
+    public function hasCpuFlags(){
+        $configFlags =['md-clear', "pcid", "spec-ctrl", "ssbd", "ibpb", "virt-ssbd",
+            "amd-ssbd", "amd-no-ssb", "pdpe1gb", "hv-tlbflush","hv-evmcs", "aes" ];
+        foreach ($configFlags as $configFlag) {
+            if($this->get($configFlag)=="on"){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public function getCpuFlagsAsSource(){
+        $configFlags =['md-clear', "pcid", "spec-ctrl", "ssbd", "ibpb", "virt-ssbd",
+          "amd-ssbd", "amd-no-ssb", "pdpe1gb", "hv-tlbflush","hv-evmcs", "aes" ];
+        $cpuFlags=[];
+        foreach ($configFlags as $configFlag) {
+            if($this->get($configFlag)=="on"){
+                $cpuFlags[] = "+". $configFlag;
+            }
+        }
+        return implode(";", $cpuFlags);
+    }
+
+    public function isSsd()
+    {
+        return $this->get('ssd') == "on";
+    }
+
+    public function isAdditionalDiskSsd()
+    {
+        return $this->get('additionalDiskSsd') == "on";
+    }
+
+    public function isPrivateNetwork()
+    {
+        return $this->get("privateNetwork")  == "on";;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPermissionFirewalOptions()
+    {
+        return (array) $this->get("permissionFirewalOptions");
+    }
+
+    public function isFirewalOptionEnable()
+    {
+        return $this->get("firewalOptionEnable")  == "on";
+    }
+
+    public function isFirewalOptionNdp()
+    {
+        return $this->get("firewalOptionNdp")  == "on";
+    }
+
+    public function isFirewalOptionMacfilter()
+    {
+        return $this->get("firewalOptionMacfilter")  == "on";
+    }
+
+    public function isFirewalOptionDhcp()
+    {
+        return $this->get("firewalOptionDhcp")  == "on";
+    }
+
+    public function isFirewalOptionRadv()
+    {
+        return $this->get("firewalOptionRadv")  == "on";
+    }
+
+    public function isFirewalOptionIpfilter()
+    {
+        return $this->get("firewalOptionIpfilter")  == "on";
+    }
+
+    public function getBwLimit(){
+        return $this->get('bwlimit');
+    }
+    public function getBios(){
+        return $this->get('bios');
+    }
+
+    public function isAdditionalDisk(){
+        return $this->get('additionalDisk')=='on';
+    }
+
+    public function isMountPoint(){
+        return $this->get('mountPoint')=='on';
+    }
+
+    public function getCloudInitScript(){
+        return $this->get('cloudInitScript');
+    }
+
+    public function getMachine(){
+        return $this->get('machine');
+    }
+}

+ 101 - 0
app/Services/ApiService.php

@@ -0,0 +1,101 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
+use ModulesGarden\ProxmoxAddon\App\Models\ModuleSettings;
+
+/**
+ * Trait ApiService
+ * @package ModulesGarden\ProxmoxAddon\App\Services
+ */
+trait ApiService
+{
+    /**
+     * @return Api
+     */
+    public function api()
+    {
+        if (!empty($this->api))
+        {
+            return $this->api;
+        }
+        $host      = $this->getWhmcsParamByKey('serverip') ? $this->getWhmcsParamByKey('serverip') : $this->getWhmcsParamByKey('serverhostname');
+        if(is_numeric($this->getWhmcsParamByKey('serverport'))){
+            $host  .=":".$this->getWhmcsParamByKey('serverport');
+        }
+        $username  = $this->getWhmcsParamByKey('serverusername');
+        $realm     = $this->getWhmcsParamByKey('serveraccesshash');
+        $password  = $this->getWhmcsParamByKey('serverpassword');
+        $this->api = new Api($host, $username, $realm, $password);
+        $this->api->setInstance();
+        $this->api->debug(ModuleSettings::isDebug());
+        return $this->api;
+    }
+
+    /**
+     * @return Kvm|Lxc
+     * @throws \Exception
+     */
+    public function vm()
+    {
+        if (!empty($this->vm))
+        {
+            return $this->vm;
+        }
+        $virtualization = $this->configuration()->getVirtualization();
+        $vmid           = $this->getWhmcsCustomField(Vps\CustomField::VMID);
+        $node           = $this->getWhmcsCustomField(Vps\CustomField::NODE);
+        switch (strtolower($virtualization))
+        {
+            case 'kvm':
+            case 'qemu':
+                $this->vm = new Kvm($node, $vmid);
+                break;
+            case 'lxc':
+                $this->vm = new Lxc($node, $vmid);
+                break;
+            default:
+                throw new \Exception(sprintf("Invalid virtualization type  '%s'", $virtualization));
+        }
+        $hostname = $this->getWhmcsParamByKey('domain') ? $this->getWhmcsParamByKey('domain') : $this->getWhmcsCustomField(Vps\CustomField::HOSTNAME);
+        $this->vm->setName($hostname);
+        $this->vm->setApi($this->api());
+        $this->vm->setApiParser();
+        return $this->vm;
+    }
+
+    public function hasVm()
+    {
+        return $this->getWhmcsCustomField(Vps\CustomField::VMID) && $this->getWhmcsCustomField(Vps\CustomField::NODE);
+    }
+
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+        return $this;
+    }
+
+
+}

+ 64 - 0
app/Services/BaseService.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Doe labels datatable controler
+ *
+ */
+trait BaseService
+{
+    private $serverId;
+
+    /**
+     *
+     * @var main\Core\Models\Whmcs\Server
+     */
+    private $server;
+    private $api;
+
+    /**
+     *
+     * @return proxmox\Api
+     */
+    public function getApi()
+    {
+        if ($this->api)
+        {
+            return $this->api;
+        }
+        $host      = $this->getServer()->ipaddress ? $this->getServer()->ipaddress : $this->getServer()->hostname;
+        if(is_numeric($this->getServer()->port)){
+            $host  .=":".$this->getServer()->port;
+        }
+        $password = html_entity_decode(decrypt($this->getServer()->password),ENT_QUOTES);
+        return $this->api = new proxmox\Api($host, $this->getServer()->username, $this->getServer()->accesshash,$password );
+    }
+
+    /**
+     *
+     * @return main\Core\Models\Whmcs\Server
+     */
+    public function getServer()
+    {
+        if ($this->server)
+        {
+            return $this->server;
+        }
+        return $this->server = main\Core\Models\Whmcs\Server::findOrFail($this->getServerId());
+    }
+
+    public function getServerId()
+    {
+        return $this->serverId;
+    }
+
+    public function setServerId($serverId)
+    {
+        $this->serverId = $serverId;
+        return $this;
+    }
+}

+ 138 - 0
app/Services/Cloud/AclService.php

@@ -0,0 +1,138 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl;
+
+class AclService
+{
+    use ProductService;
+    use WhmcsParams;
+
+    public function backup()
+    {
+        if (!$this->configuration()->isPermissionBackup() && !$this->configuration()->isPermissionBackupJob())
+        {
+            throw new \Exception(sl("lang")->tr("No access to backup"));
+        }
+    }
+
+    public function backupJob()
+    {
+        if (!$this->configuration()->isPermissionBackupJob())
+        {
+            throw new \Exception(sl("lang")->tr("No access to backup job"));
+        }
+    }
+
+    public function novnc()
+    {
+        if (!$this->configuration()->isPermissionNovnc())
+        {
+            throw new \Exception(sl("lang")->tr("No access to noVNC console"));
+        }
+    }
+
+    public function xtermjs()
+    {
+        if (!$this->configuration()->isPermissionXtermjs())
+        {
+            throw new \Exception(sl("lang")->tr("No access to Xterm.js console"));
+        }
+    }
+
+    public function spice()
+    {
+        if (!$this->configuration()->isPermissionSpice())
+        {
+            throw new \Exception(sl("lang")->tr("No access to spice console"));
+        }
+    }
+
+    public function disk()
+    {
+        if (!$this->configuration()->isPermissionDisk())
+        {
+            throw new \Exception(sl("lang")->tr("No access to disk"));
+        }
+    }
+
+    public function firewall()
+    {
+        if (!$this->configuration()->isPermissionFirewall())
+        {
+            throw new \Exception(sl("lang")->tr("No access to firewall"));
+        }
+    }
+
+    public function firewallOption()
+    {
+        if (!$this->configuration()->isPermissionFirewallOption())
+        {
+            throw new \Exception(sl("lang")->tr("No access to firewall option"));
+        }
+    }
+
+    public function graph()
+    {
+        if (!$this->configuration()->isPermissionGraph())
+        {
+            throw new \Exception(sl("lang")->tr("No access to graph"));
+        }
+    }
+
+    public function network()
+    {
+        if (!$this->configuration()->isPermissionNetwork())
+        {
+            throw new \Exception(sl("lang")->tr("No access to network"));
+        }
+    }
+
+    public function reinstall()
+    {
+        if (!$this->configuration()->isPermissionReinstall())
+        {
+            throw new \Exception(sl("lang")->tr("No access to reinstall"));
+        }
+    }
+
+    public function snapshot()
+    {
+        if (!$this->configuration()->isPermissionSnapshot())
+        {
+            throw new \Exception(sl("lang")->tr("No access to snapshot"));
+        }
+    }
+
+    public function taskHistory()
+    {
+        if (!$this->configuration()->isPermissionTaskHistory())
+        {
+            throw new \Exception(sl("lang")->tr("No access to task history"));
+        }
+    }
+
+    public function additionalDiskBackup()
+    {
+        if (!$this->configuration()->isPermissionAdditionalDiskBackup())
+        {
+            throw new \Exception(sl("lang")->tr("No access to additional disk backup"));
+        }
+    }
+
+    public function hasFirewallOption($option)
+    {
+        return in_array( $option, $this->configuration()->getPermissionFirewalOptions());
+    }
+
+    public function customTemplate()
+    {
+        if (!$this->configuration()->isPermissionCustomTemplates())
+        {
+            throw new \Exception(sl("lang")->tr("No access to Custom Templates"));
+        }
+    }
+}

+ 63 - 0
app/Services/Cloud/AdditionalDiskService.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use MGProvision\Proxmox\v2\models\HardDisk;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class AdditionalDiskService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function hasDisk(){
+
+        foreach (sl('Vm')->getVm()->getHardDisks() as $hardDisk)
+        {
+            if (!$hardDisk->isMaster())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public function create($config){
+        for ($i=1; $i<=10; $i++){
+            $diskSize = (int) $config['additionalDiskSize'.$i];
+            if(!$diskSize){
+                continue;
+            }
+            $type = $config['additionalDiskBus'.$i];
+            $bus = sl('Vm')->getVm()->findFreeDeviceId($type);
+            $harDisk = new HardDisk($type . $bus);
+            $harDisk->setSize($diskSize)
+                ->setApi($this->api())
+                ->setPath(sl('Vm')->getVm()->getPath() . "/config")
+                ->setMedia("disk")
+                ->setBackup( $config['additionalDiskBackup'.$i]== "on" ? null : 0)
+                ->setStorage($this->configuration()->getAdditionalDiskStorage())
+                ->setCache($this->configuration()->getAdditionalDiskCache())
+                ->setFormat($config['additionalDiskFormat'.$i])
+                ->setMbps_rd($this->configuration()->getAdditionalDiskMbpsRd())
+                ->setMbps_wr($this->configuration()->getAdditionalDiskMbpsRd())
+                ->setDiscard($this->configuration()->isAdditionalDiskDiscard() ? "on" : null)
+                ->setIops_rd($this->configuration()->getAdditionalDiskIopsRd())
+                ->setIops_rd_max($this->configuration()->getAdditionalDiskIopsRdMax())
+                ->setIops_wr($this->configuration()->getAdditionalDiskIopsWr())
+                ->setIops_wr_max($this->configuration()->getAdditionalDiskIopsWrMax())
+                ->setReplicate($this->configuration()->isAdditionalDiskReplicate() ? 0 : null);
+            if ($this->configuration()->isAdditionalDiskIoThread() && in_array($type, ['virtio', 'scsi']))
+            {
+                $harDisk->setIothread(1);
+            }
+            $harDisk->create();
+        }
+    }
+
+}

+ 51 - 0
app/Services/Cloud/AdditionalMountPointService.php

@@ -0,0 +1,51 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use MGProvision\Proxmox\v2\models\MountPoint;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class AdditionalMountPointService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function hasMountPoint(){
+        foreach (sl('Vm')->getVm()->getMounPoints()->fetch() as $mounPoint){
+            if($mounPoint->isMaster()){
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public function create($config){
+        for ($i=1; $i<=10; $i++){
+            $size = (int) $config['additionalDiskSize'.$i];
+            if(!$size){
+                continue;
+            }
+            $storage = $this->configuration()->getMountPointStorage();
+            $mountPointRepository = sl('Vm')->getVm()->getMounPoints();
+            $hdd = new MountPoint($mountPointRepository->nextId());
+            $hdd->setLocation($storage . ":" . $size )
+                ->setAcl($this->configuration()->getMountPointAcl() == "default" ? null : $this->configuration()->getMountPointAcl())
+                ->setRo($this->configuration()->isMountPointRo() ? 1 : null)
+                ->setQuota($this->configuration()->isMountPointQuota() ? 1 : null)
+                ->setBackup($config['additionalDiskBackup'.$i]== "on" ? 1 : null)
+                ->setReplicate($this->configuration()->isMountPointReplicate() ? '0' : null)
+                ->setMp($config['additionalDiskMp'.$i]? $config['additionalDiskMp'.$i] :'/additional')
+                ->setPath(sl('Vm')->getVm()->getPath() . '/config')
+                ->setApi($this->api())
+                ->create();
+        }
+
+    }
+
+}

+ 131 - 0
app/Services/Cloud/AgentService.php

@@ -0,0 +1,131 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class AgentService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+    use HostingService;
+
+    private $networkInterfaces=[];
+    private $assignedIpAddresses=[];
+
+    public function isEnabled()
+    {
+        $configurationIsOn =  $this->configuration()->isAgent() && ( $this->configuration()->isAgentConfigureNetwork() ||  $this->configuration()->isAgentPassword());
+        $ostype = sl('Vm')->getVm()->config()['ostype'];
+        return $configurationIsOn &&  preg_match('/w/', $ostype);
+    }
+
+
+    public function passwordUpdate()
+    {
+        if(!$this->configuration()->isAgentPassword()){
+            return;
+        }
+        $user =  sl('Vm')->getVm()->config()['ciuser'] ? sl('Vm')->getVm()->config()['ciuser'] : 'Administrator';
+        sl('Vm')->getVm()->agent()->setUserPassword($user, sl('Vm')->getVmModel()->getPassword());
+
+    }
+
+    public function configureNetwork()
+    {
+        if(!$this->configuration()->isAgentConfigureNetwork()){
+            return;
+        }
+        $this->networkInterfaces = sl('Vm')->getVm()->agent()->networkGetInterfaces();
+        //remove
+        $this->deleteIpAddresses();
+        $ips = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofVmId( sl('Vm')->getVmModel()->id)
+                           ->orderByIdAsc();
+
+        foreach ($this->networkInterfaces['result'] as $net){
+            $networkName = $net['name'];
+            if(!$net['hardware-address'] || preg_match("/loopback/", strtolower($networkName) ) ){ //Loopback
+                continue;
+            }
+            /**
+             * Add IP Addresses
+             * @var $ip VmIpAddress
+             */
+            foreach ($ips->get() as $ip){
+                //exist?
+                if(in_array($ip->ip, (array) $this->assignedIpAddresses)){
+                    continue;
+                }
+                //IPV6
+                if(preg_match("/\:/", $ip->ip )){
+                    $command = sprintf("netsh interface ipv6 add address \"%s\" %s", $networkName,  $ip->ip);
+                    //IPV4
+                }else {
+                    $subnet = $ip->subnet_mask ? $ip->subnet_mask : IpAddressProvider::translateBitmaskToNetmask($ip->cidr);
+                    if($ip->net == 'net0'){
+                        $command = sprintf("netsh interface ipv4 set address name=\"%s\" static %s %s %s",$networkName, $ip->ip, $subnet, $ip->gateway);
+                    }else{
+                        $command = sprintf("netsh interface ipv4 add address name=\"%s\" %s %s %s",$networkName, $ip->ip, $subnet, $ip->gateway);
+                    }
+                }
+                sl('Vm')->getVm()->agent()->exec($command);
+                $this->assignedIpAddresses[] =  $ip->ip;
+                sleep(1);
+                if(!$this->configuration()->isOneNetworkDevice()){
+                    break;
+                }
+            }
+            //configure DNS
+            $ns1 = trim($this->hosting()->ns1);
+            $ns2 = trim($this->hosting()->ns2);
+            if (!empty($ns1) && filter_var($ns1, FILTER_VALIDATE_IP))
+            {
+                $command = sprintf("netsh interface ip set dns name=\"%s\" static %s",$networkName, $ns1);
+                sl('Vm')->getVm()->agent()->exec($command);
+                sleep(1);
+            }
+            if (!empty($ns2) && filter_var($ns2, FILTER_VALIDATE_IP))
+            {
+                $command = sprintf("netsh interface ip add dns name=\"%s\" %s index=2",$networkName, $ns2);
+                sl('Vm')->getVm()->agent()->exec($command);
+                sleep(1);
+            }
+        }
+    }
+
+    private function deleteIpAddresses(){
+        foreach ($this->networkInterfaces['result'] as $net){
+            foreach ($net['ip-addresses'] as $ipAddress)
+            {
+                if(!$net['hardware-address'] || preg_match("/loopback/", strtolower($net['name']) ) ){ //Loopback
+                    continue;
+                }
+
+                $ipAddress = $ipAddress['ip-address'];
+                $this->assignedIpAddresses[] = $ipAddress;
+                if(!$ipAddress  || VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                        ->ofVmId(sl('Vm')->getVmModel()->id)
+                        ->ofIp($ipAddress)->count()){
+                    continue;
+                }
+                //IPV6
+                if(preg_match("/\:/", $ipAddress )){
+                    $command = sprintf("netsh interface ipv6 delete address %s",  $ipAddress);
+                    //IPV4
+                }else{
+                    $command = sprintf("netsh interface ipv4 delete address name=\"%s\" %s",  $net['name'],  $ipAddress);
+                }
+                sl('Vm')->getVm()->agent()->exec($command);
+                sleep(1);
+            }
+        }
+    }
+}

+ 170 - 0
app/Services/Cloud/ClientAreaSidebarService.php

@@ -0,0 +1,170 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\Sidebar;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarItem;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarService;
+use ModulesGarden\Servers\ProxmoxCloudVps\App\Http\Client\BaseClientController;
+use function ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl;
+
+class ClientAreaSidebarService
+{
+    use HostingService;
+    use WhmcsParams;
+    use ProductService;
+    use BaseClientController;
+
+    /**
+     * @var \WHMCS\View\Menu\Item
+     */
+    private $primarySidebar;
+
+    /**
+     * ClientAreaSidebarService constructor.
+     * @param $hostingId
+     */
+    public function __construct($hostingId, \WHMCS\View\Menu\Item $primarySidebar)
+    {
+        $this->setHostingId($hostingId);
+        $this->primarySidebar = $primarySidebar;
+    }
+
+    public function informationReplaceUri()
+    {
+        $overview = $this->primarySidebar->getChild('Service Details Overview');
+        if (!is_a($overview, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel = $overview->getChild('Information');
+        if (!is_a($panel, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel->setUri("clientarea.php?action=productdetails&id={$this->getHostingId()}");
+        $panel->setAttributes([]);
+        return $this;
+    }
+
+    public function serviceDetailsOverview(){
+
+        if(!$this->getWhmcsParamByKey("packageid") || !$this->configuration()->isPermissionCustomTemplates()){
+            return;
+        }
+        $overview = $this->primarySidebar->getChild('Service Details Overview');
+        if (!is_a($overview, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $order = 800;
+        foreach ( sl("sidebar")->getSidebar('Service Details Overview')->get() as $sidebarItem)
+        {
+            /**
+             * @var SidebarItem $sidebarItem
+             */
+            $newItem = [
+                'label'   => sl('lang')->abtr($sidebarItem->getTitle()),
+                'uri'     => str_replace('{$hostingId}', $this->getHostingId(), $sidebarItem->getHref()),
+                'order'   => $sidebarItem->getOrder(),
+                "current" => $sidebarItem->isActive()
+            ];
+            $childOrder =  $sidebarItem->getOrder();
+            $overview->addChild($sidebarItem->getName(), $newItem);
+        }
+//        /**
+//         * @var Sidebar $sidebar
+//         */
+//        $newPanel = [
+//            'label' => sl("lang")->abtr('customTemplates'),
+//            'order' => 6969
+//            'uri'     => str_replace('{$hostingId}', $this->getHostingId(), $sidebarItem->getHref()),
+//            "current" => $sidebarItem->isActive()
+//        ];
+//        $childPanel = $this->primarySidebar->addChild('customTemplates', $newPanel);
+    }
+
+    public function build()
+    {
+        /**
+         * @var $sidebarService SidebarService
+         */
+        $sidebarService = sl("sidebar");
+        $this->setProductId($this->hosting()->packageid);
+        /**
+         * @var $lang \ModulesGarden\Servers\ProxmoxVps\Core\Lang\Lang
+         */
+        $lang  = sl("lang");
+        $request = sl('request');
+        $order = 671;
+        $vmUrl = 'clientarea.php?action=productdetails&id={$hostingId}&modop=custom&a=management&mg-page=vm&vm={$vmId}';
+
+        /**
+         * virtualMachines
+         */
+        foreach ($sidebarService->get() as $sidebar)
+        {
+
+            if($sidebar->getName()=='Service Details Overview'){
+                continue;
+            }
+            /**
+             * @var Sidebar $sidebar
+             */
+            $newPanel = [
+                'label' => $lang->abtr($sidebar->getTitle()),
+                'order' => $order
+            ];
+            $order++;
+            $childPanel = $this->primarySidebar->addChild($sidebar->getName(), $newPanel);
+            foreach ($sidebar->getChildren() as $sidebarItem)
+            {
+                /**
+                 * @var SidebarItem $sidebarItem
+                 */
+                $newItem = [
+                    'label'   => $lang->abtr($sidebarItem->getTitle()),
+                    'uri'     => str_replace('{$hostingId}', $this->getHostingId(), $sidebarItem->getHref()),
+                    'order'   => $sidebarItem->getOrder(),
+                    "current" => $sidebarItem->isActive()
+                ];
+                $childOrder =  $sidebarItem->getOrder();
+                $childPanel->addChild($sidebarItem->getName(), $newItem);
+            }
+            /**
+             * @var VmModel $vmModel
+             */
+            foreach (VmModel::ofHostingId( $this->getHostingId())->notVmid(0)->notTemplate()->get() as $vmModel){
+                $childOrder++;
+                $newItem = [
+                    'label'   => $vmModel->name,
+                    'uri'     => str_replace(['{$hostingId}', '{$vmId}'], [$this->getHostingId(),$vmModel->id], $vmUrl),
+                    'order'   => $childOrder,
+                    "current" => $request->get('vm')==$vmModel->id
+                ];
+                $childPanel->addChild($vmModel->name, $newItem);
+            }
+
+
+        }
+    }
+}

+ 32 - 0
app/Services/Cloud/ContainerService.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Models\KeyPair;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+
+class ContainerService
+{
+    use ApiService;
+    use WhmcsParams;
+    use Smarty;
+    use ProductService;
+    use HostingService;
+
+    /**
+     * @return KeyPair
+     */
+    public function makeKeyPairs()
+    {
+        //delete
+        KeyPair::ofHostingId($this->getWhmcsParamByKey('serviceid'))->delete();
+        $keyPairs = KeyPair::make();
+        $keyPairs->setHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->save();
+        return $keyPairs;
+    }
+
+}

+ 29 - 0
app/Services/Cloud/FirewallOptionService.php

@@ -0,0 +1,29 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class FirewallOptionService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function update(){
+        $attributes = [
+            "enable" => $this->configuration()->isFirewalOptionEnable() ? 1 : 0,
+            "dhcp" => $this->configuration()->isFirewalOptionDhcp()? 1 : 0,
+            "ndp" => $this->configuration()->isFirewalOptionNdp()? 1 : 0,
+            "radv" => $this->configuration()->isFirewalOptionRadv() ? 1 : 0,
+            "macfilter" => $this->configuration()->isFirewalOptionMacfilter() ? 1 : 0,
+            "ipfilter" => $this->configuration()->isFirewalOptionIpfilter() ? 1 : 0,
+        ];
+        sl('Vm')->getVm()->firewallOptions()->setAttributes($attributes)->update();
+    }
+
+}

+ 58 - 0
app/Services/Cloud/HighAvailabilityClusterService.php

@@ -0,0 +1,58 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use MGProvision\Proxmox\v2\models\HaResource;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class HighAvailabilityClusterService
+{
+    use ProductService;
+    use ApiService;
+    use WhmcsParams;
+
+
+    public function delete()
+    {
+        $haResurce = new HaResource();
+
+        $haResurce->setSid(sl('Vm')->getVm()->getVmid())
+            ->setType(sl('Vm')->getVm()->getVirtualization() == "lxc" ? "ct" : "vm");
+        $haResurce->setApi($this->api());
+        if ($haResurce->exist())
+        {
+            $haResurce->delete();
+        }
+        return $this;
+    }
+
+    public function create()
+    {
+        $haResurce = new HaResource();
+        $haResurce->setSid(sl('Vm')->getVm()->getVmid())
+            ->setState($this->configuration()->getClusterState())
+            ->setType(sl('Vm')->getVm()->getVirtualization() == "lxc" ? "ct" : "vm")
+            ->setGroup($this->configuration()->getClusterGroup())
+            ->setMaxRelocate($this->configuration()->getClusterMaxRelocate())
+            ->setMaxRestart($this->configuration()->getClusterMaxRestart())
+            ->create();
+    }
+
+    public function exist(){
+        $haResurce = new HaResource();
+        $haResurce->setSid(sl('Vm')->getVm()->getVmid())
+            ->setType(sl('Vm')->getVm()->getVirtualization() == "lxc" ? "ct" : "vm");
+        $haResurce->setApi($this->api());
+        return $haResurce->exist();
+    }
+
+    public function isConfigured(){
+        return $this->configuration()->getClusterState() &&
+                is_numeric($this->configuration()->getClusterMaxRelocate()) &&
+                is_numeric($this->configuration()->getClusterMaxRestart());
+    }
+}

+ 156 - 0
app/Services/Cloud/HostingService.php

@@ -0,0 +1,156 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomFieldValue;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Repositories\Cloud\ProductConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+
+/**
+ * Trait HostingService
+ * @package ModulesGarden\ProxmoxAddon\App\Services\Cloud
+ * @method ProductConfigurationRepository configuration()
+ */
+trait HostingService
+{
+
+    /**
+     * @return mixed
+     */
+    public function getHostingId()
+    {
+        if (!$this->hostingId)
+        {
+            $this->hostingId = $this->getWhmcsParamByKey("serviceid");
+        }
+        return $this->hostingId;
+    }
+
+    /**
+     * @param mixed $hostingId
+     */
+    public function setHostingId($hostingId)
+    {
+        $this->hostingId = $hostingId;
+        return $this;
+    }
+
+    /**
+     * @return Hosting
+     */
+    public function hosting()
+    {
+        if ($this->hosting instanceof Hosting)
+        {
+            return $this->hosting;
+        }
+        return $this->hosting = Hosting::where("id", $this->getHostingId())->firstOrFail();
+    }
+
+    public function isActive()
+    {
+        return Hosting::ofId($this->getHostingId())->active()->count() == 1;
+    }
+
+    public function isSupportedModule()
+    {
+        return Hosting::ofServerType($this->getHostingId(), "ProxmoxCloudVps")->count() == 1;
+    }
+
+    private function getCustomFieldId($fieldName)
+    {
+        return CustomField::select("id")
+            ->where("type", "product")
+            ->where("relid", $this->hosting()->packageid)
+            ->where("fieldname", "like", $fieldName . "%")
+            ->value("id");
+    }
+
+    public function customFieldUpdate($name, $value = '')
+    {
+        $fieldId = $this->getCustomFieldId($name);
+        //Update
+        if (CustomFieldValue::where('fieldid', $fieldId)->where("relid", $this->getHostingId())->count())
+        {
+            return CustomFieldValue::where('fieldid', $fieldId)->where("relid", $this->getHostingId())->update(['value' => $value]);
+        }
+        //Create
+        $customFiledValue = new CustomFieldValue();
+        $customFiledValue->fill([
+            'fieldid' => $fieldId,
+            'relid'   => $this->getHostingId(),
+            'value'   => $value
+        ]);
+        return $customFiledValue->save();
+    }
+
+    public function saveUsageLimit()
+    {
+        //bandwidth
+        if ($this->getWhmcsConfigOption(ConfigurableOption::BANDWIDTH))
+        {
+            $bandwidthMb = $this->getWhmcsConfigOption(ConfigurableOption::BANDWIDTH);
+            Utility::unitFormat($bandwidthMb, "gb", 'mb');
+        }
+        else
+        {
+            if ($this->getWhmcsConfigOption('Bandwidth Limit'))
+            {
+                $bandwidthMb = $this->getWhmcsConfigOption('Bandwidth Limit');
+                Utility::unitFormat($bandwidthMb, "gb", 'mb');
+            }
+            else
+            {
+                if ($this->configuration()->getBandwidth())
+                {
+                    $bandwidthMb = $this->configuration()->getBandwidth();
+                    Utility::unitFormat($bandwidthMb, "gb", 'mb');
+                }
+            }
+        }
+        //disk
+        $diskMb = 0;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::STORAGE))
+        {
+            $diskMb = $this->getWhmcsConfigOption(ConfigurableOption::STORAGE);
+            Utility::unitFormat($diskMb, $this->configuration()->getStorageUnit(), 'mb');
+        }
+        else
+        {
+            if ($this->configuration()->getStorageSize())
+            {
+                $diskMb = $this->configuration()->getStorageSize();
+                Utility::unitFormat($diskMb, "gb", 'mb');
+            }
+        }
+        $this->hosting()->update(['disklimit' => $diskMb, "bwlimit" => $bandwidthMb, "lastupdate" => date("Y-m-d H:i:s")]);
+    }
+
+
+    public function isBandwidthOverageUsage()
+    {
+
+    }
+
+
+}

+ 93 - 0
app/Services/Cloud/IpSetIpFilterService.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use MGProvision\Proxmox\v2\models\IpCidr;
+use MGProvision\Proxmox\v2\models\IpSet;
+use MGProvision\Proxmox\v2\repository\IpCidrRepository;
+use MGProvision\Proxmox\v2\repository\IpSetRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class IpSetIpFilterService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function create()
+    {
+
+        $networkDevices = sl('Vm')->getVm()->getNetworkDevices();
+        if (count($networkDevices) <= 0)
+        {
+            return $this;
+        }
+        $ipCurrent = [];
+        foreach ($networkDevices as $nd)
+        {
+            //Read
+            $ipSetRep = new IpSetRepository();
+            $ipSetRep->setApi($this->api());
+            $path = sl('Vm')->getVm()->getPath() . "/" . IpSet::DEFAULT_PATH;
+            $ipSetRep->findByPath($path);
+            $ipSet = $ipSetRep->find((new IpSet)->setName('ipfilter-' . $nd->getId()));
+            if (!$ipSet)
+            {
+                $ipSet = (new IpSet(sl('Vm')->getVm()->getPath() . "/" . IpSet::DEFAULT_PATH))
+                    ->setApi($this->api())
+                    ->setName('ipfilter-' . $nd->getId())
+                    ->setComment('only allow specified IPs on ' . $nd->getId())
+                    ->create();
+            }
+            //Add
+            $query = VirtualInterface::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                                      ->ofVmId(sl('Vm')->getVmModel()->id)
+                                      ->ofNet($nd->getId());
+
+            foreach ($query->get() as $ip)
+            {
+                /* @var VirtualInterface $ip */
+                $ipCurrent[] = $ip->ip;
+                $ipCirRep    = new IpCidrRepository();
+                $ipCirRep->findByPath($ipSet->getPath());
+                if ($ipCirRep->find((new IpCidr)->setCidr($ip->ip)))
+                {
+                    continue;
+                }
+                $ipCird = new IpCidr($ipSet->getPath());
+                $ipCird->setApi($this->api());
+                $ipCird->setCidr($ip->ip)->setComment($nd->getId())->create();
+            }
+        }
+        unset($networkDevices, $ipSetRep, $ipSet, $ipCirRep);
+        //Delete
+        foreach (sl('Vm')->getVm()->getIpSet() as $ipSet)
+        {
+            $ipCidrs = $ipSet->getIpCidr();
+            foreach ($ipCidrs as $key => $ipCidr)
+            {
+                if (in_array($ipCidr->getCidr(), $ipCurrent))
+                {
+                    continue;
+                }
+                $query = VirtualInterface::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                    ->ofVmId(sl('Vm')->getVmModel()->id)
+                    ->ofIp($ipCidr->getCidr());
+                if ($query->count() < 1)
+                {
+                    $ipCidr->delete();
+                    unset($ipCidrs[$key]);
+                    if (count($ipCidrs) == 0)
+                    {
+                        $ipSet->delete();
+                    }
+                }
+            }
+        }
+        return $this;
+    }
+}

+ 814 - 0
app/Services/Cloud/NetworkService.php

@@ -0,0 +1,814 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Config;
+use MGProvision\Proxmox\v2\models\IpConfig;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceKvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceLxc;
+use ModulesGarden\ProxmoxAddon\App\Models\IpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualNetwork;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\CustomField;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class NetworkService
+{
+    use WhmcsParams;
+    use HostingService;
+    use ApiService;
+    use ProductService;
+
+    /**
+     * @param VmIpAddress[] $entries
+     */
+    public function create(array $entries)
+    {
+        //to do acl
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        DB::beginTransaction();
+
+        try
+        {
+            foreach ($entries as &$ip)
+            {
+                //insert to data base
+                $ip->save();
+                $this->hosting()->ipAdd($ip->ip);
+                //lock ip in proxmox addon
+                if (!Utility::isIpManagerProxmoxVPSIntegration())
+                {
+                    IpAddress::where('ip', $ip->ip)
+                        ->update(["hosting_id" => $ip->hosting_id]);
+                }
+            }
+            //update hosting
+            $this->hosting()->save();
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            throw $ex;
+        }
+    }
+
+    /**
+     * @deprecated
+     */
+    public function addPrivate( $entries)
+    {
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $vm =  \ModulesGarden\ProxmoxAddon\Core\Helper\sl('Vm')->getVm();
+        DB::beginTransaction();
+        Api::beginTransaction();
+        $container = [];
+        $rate      = null;
+        if ($this->isWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE);
+        }
+        else
+        {
+            if ($this->configuration()->getRate())
+            {
+                $rate = $this->configuration()->getRate();
+            }
+        }
+        //cloud init
+        $isCloudInit = $this->configuration()->isQemu() && $this->configuration()->isCloudInit();
+        try
+        {
+
+            foreach ($entries as &$ip)
+            {
+                //public network
+                if($ip->vn_id == 0){
+                    continue;
+                }
+                $tag       = $ip->virtualNetwork->tag;
+                //insert to data base
+                $ip->save();
+                $this->hosting()->ipAdd($ip->ip);
+                $networkId = $vm->findFreeNetworDeviceId();
+
+                if ($this->configuration()->isLxc())
+                {
+                    $bridge = $this->configuration()->getPrivateBridge() ? $this->configuration()->getPrivateBridge() :  $this->configuration()->getBridge();
+                    $networkDevice = new NetworkDeviceLxc('net' . $networkId);
+                    $networkDevice->setName('eth' . $networkId)
+                        ->setBridge($bridge)
+                        ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0);
+
+                    $networkDevice->setIp($ip->ip)
+                        ->setCidr($ip->virtualNetwork->cidr)
+                        ->setGw($ip->virtualNetwork->gateway)
+                        ->setTag($tag)
+                        ->setRate($rate);
+                    $container[$networkDevice->getId()] = $networkDevice->asConfig();
+                }
+                else
+                {
+                    if ($this->configuration()->isQemu())
+                    {
+                        $networkDevice = new NetworkDeviceKvm('net' . $networkId);
+                        $networkDevice->setBridge($this->configuration()->getPrivateBridge())
+                            ->setModel($this->configuration()->getNetworkPrivateModel())
+                            ->setRate($rate)
+                            ->setTag($tag);
+                        $container[$networkDevice->getId()] = $networkDevice->asConfig();
+                        if ($isCloudInit)
+                        {
+                            $ipConfig = new IpConfig('ipconfig' . $networkId);
+                            $ipConfig->setIp(trim($ip->ip))
+                                ->setCidr(trim($ip->virtualNetwork->cidr))
+                                ->setGw(trim($ip->virtualNetwork->gateway));
+                            $container[$ipConfig->getId()] = $ipConfig->asConfig();
+                        }
+                    }
+                }
+                $ip->net = $networkDevice->getId();
+                $ip->save();
+            }
+            //update hosting
+            $this->hosting()->save();
+            $vm->updateConfig($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+    }
+
+    protected function makeTag()
+    {
+        if (is_numeric($this->getWhmcsCustomField(CustomField::TAG)))
+        {
+            return $this->getWhmcsCustomField(CustomField::TAG);
+        }
+        if (!$this->configuration()->getTagFrom() && $this->configuration()->getTagTo())
+        {
+            return null;
+        }
+        $tag = $this->nextTag();
+        if (!$tag)
+        {
+            throw new \Exception("Max VLAN tag have been reached, Please Configure product  (Max VLAN tag)");
+        }
+        $this->customFieldUpdate("VLAN Tag", $tag);
+        return $tag;
+    }
+
+    public function nextTag()
+    {
+        if(!$this->configuration()->getTagFrom() || !$this->configuration()->getTagTo() && $this->configuration()->isPermissionVirtualNetwork()){
+            throw new \Exception("Product configuration validator error: VLAN TAG Range Number is empty.");
+        }
+        $h = 'tblhosting';
+        $vn = (new VirtualNetwork)->getTable();
+        for ($i = $this->configuration()->getTagFrom(); $i <= $this->configuration()->getTagTo(); $i++)
+        {
+            $query = VirtualNetwork::select("{$vn}.tag")
+                           ->leftJoin($h, "{$h}.id", "=", "{$vn}.hosting_id")
+                          ->where("{$h}.packageid", $this->getWhmcsParamByKey("packageid"))
+                          ->where("{$vn}.tag", $i);
+            if ($query->count())
+            {
+                continue;
+            }
+            return $i;
+        }
+        return false;
+    }
+
+    /**
+     * @return VmIpAddress
+     */
+    public function getPrivateIpAddress()
+    {
+        $ip    = IpAddress::where('private', 1)
+            ->where('type', 'IPv4')
+            ->whereIn("sid", [$this->getWhmcsParamByKey('serverid'), "0"])
+            ->where('hosting_id', '0')
+            ->firstOrFail();
+        $newIp = new VmIpAddress();
+        $newIp->fill($ip->toArray());
+        $newIp->hosting_id = $this->getWhmcsParamByKey("serviceid");
+        $newIp->server_id  = $this->getWhmcsParamByKey("serverid");
+        return $newIp;
+    }
+
+    public function hasPrivateIpAddress()
+    {
+        $query = IpAddress::where('private', 1)
+            ->where('type', 'IPv4')
+            ->whereIn("sid", [$this->getWhmcsParamByKey('serverid'), "0"])
+            ->where('hosting_id', '0');
+        return $query->count();
+    }
+
+    public function deleteByNetworkId(array $networkIds)
+    {
+        if (is_null($networkIds))
+        {
+            throw new \InvalidArgumentException('The network ids cannot be empty');
+        }
+        $ipAddresses = VmIpAddress::whereIn("net", $networkIds)
+            ->ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofVmId(\ModulesGarden\ProxmoxAddon\Core\Helper\sl('Vm')->getVmModel()->id)
+            ->get()->all();
+        return $this->deleteByIpAddresses($ipAddresses);
+    }
+
+    public function deleteByIpAddresses( $ipAddresses)
+    {
+        if (is_null($ipAddresses))
+        {
+            throw new \InvalidArgumentException('The IP Addresses cannot be empty');
+        }
+        $vm = \ModulesGarden\ProxmoxAddon\Core\Helper\sl('Vm')->getVm();
+        $devices      = $vm->getNetworkDevices();
+        $configDelete = [];
+
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        DB::beginTransaction();
+        Api::beginTransaction();
+        try
+        {
+            foreach ($ipAddresses as &$ip)
+            {
+                if (is_numeric($ip))
+                {
+                    $ip = VmIpAddress::where("id", $ip)->firstOrFail();
+                }
+
+                //vi
+                $ip->delete();
+                //Public IP
+                if($ip instanceof  VirtualInterface && $ip->vn_id == 0){
+                    VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                        ->ofVmId($ip->vm_id)
+                        ->ofIp($ip->ip)
+                        ->update([ 'vm_id'=> null,'net'=> null]);
+                }
+                /**
+                 * @deprecated
+                if ($ip instanceof VmIpAddress)
+                {
+                    //Unlock ip in Proxmox Addon
+                    IpAddress::where('ip', $ip->ip)
+                        ->where('hosting_id', $ip->hosting_id)
+                        ->update(["hosting_id" => "0", 'last_check' => Utility::timeStamp()]);
+                }
+                 */
+                foreach ($devices as $deviceIndex => $device)
+                {
+                    if ($this->configuration()->isOneNetworkDevice() && $device->getId() == "net0" && $ip instanceof VirtualInterface)
+                    {
+                        continue;
+                    }
+                    if($device instanceof NetworkDeviceLxc)
+                    {
+                        if ($ip->ip == $device->getIp())
+                        {
+                            $device->setIp(null);
+                        }
+
+                        if ($ip->ip == $device->getIp6())
+                        {
+                            $device->setIp6(null);
+                        }
+
+                        if(empty($device->getIp6()) && empty($device->getIp()))
+                        {
+                            unset($devices[$deviceIndex]);
+                            $configDelete[] = $device->getId();
+                        }
+                    }
+                    elseif($device instanceof  NetworkDeviceKvm && $ip->net && $ip->net == $device->getId())
+                    {
+                        $configDelete[] = $device->getId();
+                        unset($devices[$deviceIndex]);
+                    }
+                }
+
+                //cloud init
+                if ($vm instanceof Kvm)
+                {
+                    foreach ($vm->getIpConfig()->fetch() as $ipConfig)
+                    {
+                        if ($ipConfig->getIp() == $ip->ip || $ipConfig->getIp6() == $ip->ip)
+                        {
+                            $configDelete[] = $ipConfig->getId();
+                        }
+                    }
+                }
+            }
+
+            $this->hosting()->save();
+
+            /**
+             * Delete config. We need that when there is no more network interface
+             */
+            if (!empty($configDelete))
+            {
+                $vm->deleteConfig(implode(",", $configDelete));
+            }
+
+            /**
+             * Update current config. We need that only if we remove only one IP and other IP is still on the same interface
+
+            $config = new Config();
+            $config->setNet($devices);
+            $config = $config->toArray();
+            if($config)
+            {
+                $vm->updateConfig($config);
+            }
+             *
+             */
+
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+    }
+
+    public function hasIp($totalIpv4, $totalIpv6, $nodeName)
+    {
+        $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC";
+        if ((int)$totalIpv4 > 0)
+        {
+            /**
+             * @var $ipCollections IpAddress
+             */
+            $ipCollections = IpAddress::where('hosting_id', '0')
+                ->where('private', '0')
+                ->where('type', 'IPv4')
+                ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                ->whereIn('visualization', [$virtualization, 'Auto'])
+                ->whereIn('node', [$nodeName, '0', ""]);
+            if ($this->configuration()->getTags())
+            {
+                $ipCollections->ofTags($this->configuration()->getTags());
+            }
+            $count = $ipCollections->count();
+            if ((int)$count < $totalIpv4)
+            {
+                throw new \Exception(sprintf("Unable to get %s of IPv4. IP Addresses available: %s", $totalIpv4, $count));
+            }
+        }
+        if ((int)$totalIpv6 > 0)
+        {
+            $ipCollections = IpAddress::where('hosting_id', '0')
+                ->where('private', '0')
+                ->where('type', 'IPv6')
+                ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                ->whereIn('visualization', [$virtualization, 'Auto'])
+                ->whereIn('node', [$nodeName, '0', ""]);
+            if ($this->configuration()->getTags())
+            {
+                $ipCollections->ofTags($this->configuration()->getTags());
+            }
+            $count = $ipCollections->count();
+            if ((int)$count < $totalIpv6)
+            {
+                throw new \Exception(sprintf("Unable to get %s of IPv6. IP Addresses available: %s", $totalIpv6, $count));
+            }
+        }
+        return $this;
+    }
+
+    public function addIp($totalIpv4, $totalIpv6, $nodeName)
+    {
+        try {
+            DB::beginTransaction();
+            $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC";
+            $this->setHostingId($this->getWhmcsParamByKey('serviceid'));
+            if ((int)$totalIpv4 > 0) {
+                $query = IpAddress::where('hosting_id', '0')
+                    ->where('private', '0')
+                    ->where('type', 'IPv4')
+                    ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                    ->whereIn('visualization', [$virtualization, 'Auto'])
+                    ->whereIn('node', [$nodeName, '0', ""]);
+                if ($this->configuration()->getTags()) {
+                    $query->ofTags($this->configuration()->getTags());
+                }
+                $count = $query->limit($totalIpv4)
+                    ->orderBy('last_check', 'asc')
+                    ->update(["hosting_id" => $this->getWhmcsParamByKey('serviceid'), 'last_check' => Utility::timeStamp()]);
+                if ((int)$count < $totalIpv4) {
+                    throw new \Exception(sprintf("Unable to get %s of IPv4. IP Addresses available: %s", $totalIpv4, $count));
+                }
+
+            }
+            if ((int)$totalIpv6 > 0) {
+                $query = IpAddress::where('hosting_id', '0')
+                    ->where('private', '0')
+                    ->where('type', 'IPv6')
+                    ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                    ->whereIn('visualization', [$virtualization, 'Auto'])
+                    ->whereIn('node', [$nodeName, '0', ""]);
+                if ($this->configuration()->getTags()) {
+                    $query->ofTags($this->configuration()->getTags());
+                }
+                $count = $query->limit($totalIpv6)
+                    ->orderBy('last_check', 'asc')
+                    ->update(["hosting_id" => $this->getWhmcsParamByKey('serviceid'), 'last_check' => Utility::timeStamp()]);
+
+                if ((int)$count < $totalIpv6) {
+                    throw new \Exception(sprintf("Unable to get %s of IPv6. IP Addresses available: %s", $totalIpv6, $count));
+                }
+            }
+            $vmIp = (new VmIpAddress())->getTable();
+            $collection = IpAddress::where('hosting_id', $this->getWhmcsParamByKey('serviceid'))
+                ->orderBy("ip", "desc")
+                ->whereNotIn('ip', function ($query) use ($vmIp) {
+                    $query->select('ip')->from($vmIp);
+                });
+            foreach ($collection->get()->sortBy("type")->all() as $ip) {
+                /*@var $ip IpAddress */
+                $newIp = new VmIpAddress();
+                $ipData = $ip->toArray();
+                unset($ipData['id']);
+                $ipData['hosting_id'] = $this->getWhmcsParamByKey('serviceid');
+                $ipData['server_id'] = $this->getWhmcsParamByKey('serverid');
+                $newIp->vm_id = null;
+                if(sl('Vm')->hasVm()){
+                    $newIp->vm_id =     sl('Vm')->getVmModel()->id;
+                }
+                $newIp->fill($ipData)->save();
+                $this->hosting()->ipAdd($ip->ip);
+            }
+            $this->hosting()->save();
+            DB::commit();
+        } catch (\Exception $ex) {
+            DB::rollBack();
+            throw $ex;
+        }
+    }
+
+    public function deleteIpAddresses()
+    {
+        $query = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofVmId(sl('Vm')->getVmModel()->id)
+             ->update(['vm_id'=>null,'net' => null]);
+        /**
+         * @deprecated
+        foreach ($query->get() as $vmIp){
+            //Unlock ip in Proxmox Addon
+            IpAddress::where('hosting_id', $this->getWhmcsParamByKey('serviceid'))
+                ->ofIp($vmIp->ip)
+                ->update(["hosting_id" => "0", 'last_check' => Utility::timeStamp()]);
+            $this->hosting()->ipDelete($vmIp->ip);
+            $vmIp->delete();
+        }
+         */
+        return $this;
+    }
+
+    /**
+     * @deprecated
+     */
+    public function buildLxc()
+    {
+
+        $vmId = sl('Vm')->getVmModel()->id;
+        $container = [];
+        /**
+         * @var VmIpAddress[] $ipv4
+         */
+        $ipv4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp4()
+            ->ofNetNull()
+            ->ofVmId($vmId)
+            ->get();
+        //ip6
+        $ipv6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp6()
+            ->ofNetNull()
+            ->ofVmId($vmId)
+            ->get();
+
+        //Network
+        $bridge   = $this->configuration()->getBridge();
+        $firewall = $this->configuration()->isNetworkFirewall() ? 1 : 0;
+        $ip4Mode  = $this->configuration()->getIpv4NetworkMode();
+        $ip6Mode  = $this->configuration()->getIpv6NetworkMode();
+        $rate     = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+        }
+        $networkId = 0;
+        if (sl('Vm')->hasVm())
+        {
+            $networkId = sl('Vm')->getVm()->findFreeNetworDeviceId();
+        }
+        for ($i = 0; $i <= 9; $i++)
+        {
+            //Empty IP Addresses
+            if (!$ipv4->get($i) && !$ipv6->get($i))
+            {
+                break;
+            }
+            if ($networkId > 9)
+            {
+                break;
+            }
+            $mac = null;
+            //Name
+            $interface = new NetworkDeviceLxc('net' . $networkId);
+            $interface->setName('eth' . $networkId)
+                ->setBridge($bridge)
+                ->setFirewall($firewall);
+            //IP 4
+            if ($ipv4->get($i))
+            {
+                if ($ipv4->get($i)->mac_address)
+                {
+                    $mac = $ipv4->get($i)->mac_address;
+                }
+                if ($ipv4->get($i)->tag)
+                {
+                    $interface->setTag($ipv4->get($i)->tag);
+                }
+                $interface->setTrunks($ipv4->get($i)->trunks);
+                if ($ip4Mode == 'static')
+                { //IP & CIRD
+                    $interface->setIp($ipv4->get($i)->ip);
+                    $interface->setCidr($ipv4->get($i)->cidr);
+                    //Gateway
+                    $interface->setGw($ipv4->get($i)->gateway);
+                }
+                $ip = trim($ipv4->get($i)->ip);
+                $ipv4->get($i)->net = $interface->getId();
+                $ipv4->get($i)->save();
+            }
+            if ($ip4Mode == "dhcp")
+            {
+                $interface->setIp("dhcp");
+            }
+            //IP 6
+            if ($ip6Mode == "dhcp")
+            {
+                $interface->setIp6("dhcp");
+            }
+            else
+            {
+                if ($ip6Mode == "slaac")
+                {
+                    $interface->setIp6("auto");
+                }
+            }
+            if ($ipv6->get($i))
+            {  //IP & CIRD
+                $ip = trim($ipv6->get($i)->ip);
+                if ($ip6Mode == "static")
+                {
+                    $interface->setIp6($ipv6->get($i)->ip);
+                    $interface->setCidr6($ipv6->get($i)->cidr);
+                    //Gateway
+                    $interface->setGw6($ipv6->get($i)->gateway);
+                }
+                if ($ipv6->get($i)->tag)
+                {
+                    $interface->setTag($ipv6->get($i)->tag);
+                }
+                if ($ipv6->get($i)->trunks)
+                {
+                    $interface->setTrunks($ipv6->get($i)->trunks);
+                }
+                if ($mac == null && $ipv6->get($i)->mac_address)
+                {
+                    $mac = $ipv6->get($i)->mac_address;
+                }
+                $ipv6->get($i)->net = $interface->getId();
+                $ipv6->get($i)->save();
+            }
+            if ($rate)
+            {
+                $interface->setRate($rate);
+            }
+            $interface->setHwaddr($mac);
+            $container['net' . $networkId] = $interface->asConfig();
+            $networkId++;
+            VirtualInterface::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                ->ofVmId($vmId )
+                ->ofIp($ip)
+                ->update(['net' =>$interface->getId()]);
+        }
+        return $container;
+
+    }
+
+    /**
+     * @deprecated
+     */
+    public function buildQemu()
+    {
+        $container = [];
+        $vmId = sl('Vm')->getVmModel()->id;
+        /**
+         * @var VmIpAddress[] $ipv4
+         */
+        $ipv4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp4()
+            ->ofNetNull()
+            ->ofVmId($vmId)
+            ->get();
+        //ip6
+        $ipv6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp6()
+            ->ofNetNull()
+            ->ofVmId($vmId)
+            ->get();
+        //Network
+        $model    = $this->configuration()->getNetworkModel();
+        $bridge   = $this->configuration()->getBridge();
+        $firewall = $this->configuration()->isNetworkFirewall() ? 1 : 0;
+        $rate     = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+        }
+        $networkId          = 0;
+        $vmNetworkDevices   = [];
+
+        if (sl('Vm')->hasVm())
+        {
+            $networkId =  sl('Vm')->getVm()->findFreeNetworDeviceId();
+            $vmNetworkDevices   =  sl('Vm')->getVm()->getNetworkDevices();
+        }
+        $i4      = 0;
+        $i6      = 0;
+        $isApi52 = version_compare($this->api()->getVersion(), "5.2", '>=');
+        for ($i = 0; $i <= 31; $i++)
+        {
+            //Empty IP Addresses
+            if (!$ipv4->get($i4) && !$ipv6->get($i6))
+            {
+                break;
+            }
+            if ($networkId > 31)
+            {
+                break;
+            }
+            if ($this->configuration()->isOneNetworkDevice() && ($i > 0 || count($vmNetworkDevices)))
+            {
+                VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                    ->ofNetNull()
+                    ->ofVmId($vmId )
+                    ->update(["net" => 'net0']);
+                break;
+            }
+
+            $mac   = null;
+            $isIp4 = false;
+            //Name
+            $interface = new NetworkDeviceKvm('net' . $networkId);
+            $interface->setModel($model)
+                ->setBridge($bridge)
+                ->setFirewall($firewall);
+            $ipConfig = new IpConfig('ipconfig' . $networkId);
+            //IP 4
+
+            if ($ipv4->get($i4))
+            {
+                $isIp4 = true;
+                $ip = trim($ipv4->get($i4)->ip);
+                if (trim($ipv4->get($i4)->mac_address))
+                {
+                    $mac = $ipv4->get($i4)->mac_address;
+                }
+                if ($ipv4->get($i4)->tag)
+                {
+                    $interface->setTag(trim($ipv4->get($i4)->tag));
+                }
+                $interface->setTrunks(trim($ipv4->get($i4)->trunks));
+
+                $ipConfig->setIp(trim($ipv4->get($i4)->ip));
+                $ipConfig->setCidr(trim($ipv4->get($i4)->cidr));
+                //Gateway
+                $ipConfig->setGw(trim($ipv4->get($i4)->gateway));
+                $ipv4->get($i4)->net = $interface->getId();
+                $ipv4->get($i4)->save();
+                $i4++;
+            }
+            //ipv6
+            if ($ipv6->get($i6) && (!$isIp4 || $interface->getTag() == $ipv6->get($i6)->tag))
+            {  //IP & CIRD
+                $ip = ttrim($ipv6->get($i6)->ip);
+                $ipConfig->setIp6(trim($ipv6->get($i6)->ip));
+                $ipConfig->setCidr6(trim($ipv6->get($i6)->cidr));
+                //Gateway
+                $ipConfig->setGw6(trim($ipv6->get($i6)->gateway));
+                if ($ipv6->get($i6)->tag)
+                {
+                    $tag = $ipv6->get($i6)->tag;
+                    $interface->setTag(trim($ipv6->get($i6)->tag));
+                }
+                if ($ipv6->get($i6)->trunks)
+                {
+                    $interface->setTrunks(trim($ipv6->get($i6)->trunks));
+                }
+                if ($mac == null && $ipv6->get($i6)->mac_address)
+                {
+                    $mac = $ipv6->get($i6)->mac_address;
+                }
+                $ipv6->get($i6)->net = $interface->getId();
+                $ipv6->get($i6)->save();
+                $i6++;
+            }
+            $interface->setRate(trim($rate));
+            $interface->setMacAddress(trim($mac));
+            $interface->setQueues(trim($this->configuration()->getQueues()));
+            $container[$interface->getId()] = $interface->asConfig();
+            if ($isApi52 && ($ipConfig->getIp() || $ipConfig->getIp6()))
+            {
+                $container[$ipConfig->getId()] = $ipConfig->asConfig();
+            }
+            VirtualInterface::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                ->ofVmId($vmId )
+                ->ofIp($ip)
+                ->update(['net' =>$interface->getId()]);
+            $networkId++;
+        }
+        return $container;
+    }
+
+    /**
+     * @throws \Exception
+     */
+    public function unassignIpAddressesAndDeleteNetwork($requestIPv4, $requestIPv6)
+    {
+        $vmId = sl('Vm')->getVmModel()->id;
+        $ip4 = [];
+        $ip6 = [];
+        if ($requestIPv4 < 0)
+        {
+            $ip4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                ->ofIp4()
+                ->ofNetNotNull()
+                ->ofVmId($vmId)
+                ->limit(abs($requestIPv4))
+                ->orderByIdDesc()
+                ->get()
+                ->all();
+        }
+        if ($requestIPv6 < 0)
+        {
+            $ip6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                ->ofIp6()
+                ->ofNetNotNull()
+                ->ofVmId($vmId)
+                ->limit(abs($requestIPv6))
+                ->orderByIdDesc()
+                ->get()
+                ->all();
+        }
+        $ipAddresses = array_merge($ip4, $ip6);
+        if (!empty($ipAddresses))
+        {
+            $this->deleteByIpAddresses($ipAddresses);
+        }
+    }
+
+    public function getIpAddressesRequest()
+    {
+        $ipv4Used    = (int)VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofIp4()
+            ->count();
+        $ipv6Used    = (int)VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofIp6()
+            ->count();
+        $requestIPv4 = (int)$this->configuration()->getIpv4();
+        $requestIPv6 = (int)$this->configuration()->getIpv6();
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV4))
+        {
+            $requestIPv4 = (int)$this->getWhmcsConfigOption(ConfigurableOption::IPV4);
+        }
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV6))
+        {
+            $requestIPv6 = (int)$this->getWhmcsConfigOption(ConfigurableOption::IPV6);
+        }
+        return [(int)$requestIPv4 -= $ipv4Used, (int)$requestIPv6 -= $ipv6Used];
+    }
+}

+ 245 - 0
app/Services/Cloud/ProductService.php

@@ -0,0 +1,245 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use MGProvision\Proxmox\v2\repository\NodeRepository;
+use MGProvision\Proxmox\v2\repository\StorageRepository;
+use ModulesGarden\ProxmoxAddon\App\Repositories\Cloud\ProductConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\LoadBalancerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+
+trait ProductService
+{
+
+
+    /**
+     * @return FileRepository
+     */
+    public function isoRepository()
+    {
+        if (!empty($this->isoRepository))
+        {
+            return $this->isoRepository;
+        }
+        $storageRepository = new StorageRepository();
+        $vm = \ModulesGarden\ProxmoxAddon\Core\Helper\sl('Vm')->getVm();
+        $storageRepository->findByNodes([$vm->getNode()])
+            ->findEnabed();
+        $storages            = $storageRepository->fetchAsArray();
+        $this->isoRepository = new FileRepository();
+        $this->isoRepository->findByNodes([$vm->getNode()])
+            ->findByStorages($storages);
+        $this->isoRepository->findIso();
+        return $this->isoRepository;
+    }
+
+    /**
+     * @return ProductConfigurationRepository
+     */
+    public function configuration()
+    {
+        if (!empty($this->configuration))
+        {
+            return $this->configuration;
+        }
+        if (!$this->productId)
+        {
+            $this->setProductId($this->getWhmcsParamByKey("packageid"));
+        }
+        return $this->configuration = new ProductConfigurationRepository($this->productId);
+    }
+
+    /**
+     * @return int
+     */
+    public function getProductId()
+    {
+        return $this->productId;
+    }
+
+    /**
+     * @param int $productId
+     */
+    public function setProductId($productId)
+    {
+        $this->productId = $productId;
+    }
+
+    public function acl()
+    {
+        if (!empty($this->acl))
+        {
+            return $this->acl;
+        }
+        return $this->acl = new AclService();
+    }
+
+    /**
+     * @return ResourceGuard
+     */
+    public function resourceGuard()
+    {
+        if (!empty($this->resourceGuard))
+        {
+            return $this->resourceGuard;
+        }
+        return $this->resourceGuard = new ResourceGuard();
+    }
+
+
+    /**
+     * @return Node
+     * @throws \MGProvision\Proxmox\v2\ProxmoxApiException
+     */
+    public function getNode()
+    {
+        if ($this->node)
+        {
+            return $this->node;
+        }
+        if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+        {
+            $diskBytes = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+            Utility::unitFormat($diskBytes, $this->configuration()->getStorageUnit(), 'bytes');
+        }
+        else
+        {
+            $diskBytes = $this->configuration()->getStorageSize() ? $this->configuration()->getStorageSize() : 1;
+            Utility::unitFormat($diskBytes, "gb", 'bytes');
+        }
+        if ($this->configuration()->isLoadBalancer())
+        {
+            $loadBalancer = new LoadBalancerService($this->getWhmcsParamByKey('serverid'));
+            $loadBalancer->setApi($this->api());
+            $loadBalancerNodes = $loadBalancer->findByVmCreate();
+            $nodesForUser      = $loadBalancer->findNotUser($this->getWhmcsParamByKey('userid'));
+            if (!$nodesForUser->isEmpty())
+            {
+                $loadBalancerNodes = $nodesForUser;
+            }
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $ram = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($ram, $this->configuration()->getMemoryUnit(), 'bytes');
+            }
+            else
+            {
+                $ram = $this->configuration()->getMemory();
+                Utility::unitFormat( $ram, "mb", 'bytes');
+            }
+            if ($this->configuration()->isQemu())
+            {
+                $socket = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets());
+                $cores  = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores());
+                $cpu    = $socket * $cores;
+            } else if ($this->configuration()->isLxc()) {
+                $cpu = $cores = $this->getWhmcsConfigOption(ConfigurableOption::CORES, $this->configuration()->getCores());
+            }
+
+            $node = $loadBalancerNodes->findByRam($ram)
+                ->findByCpu($cpu)
+                ->findByDisk($diskBytes)
+                ->findByVms(1)
+                ->nodeLowLoad();
+            return $this->node = new Node($node);
+        }
+        $defaultNode    = $this->configuration()->getDefaultNode();
+        $nodeRepository = new NodeRepository();
+        $nodeRepository->setApi($this->api());
+        switch ($defaultNode)
+        {
+            case "autoNode":
+                if (empty($diskBytes))
+                {
+                    throw new \Exception("Provide disk size.");
+                }
+                return $this->node = $nodeRepository->findByDiskSize($diskBytes);
+                break;
+            case "serverNode":
+                $servePrivateIP =  $this->getServerPrivateIpAddress();
+                $serverIp = $servePrivateIP? $servePrivateIP : $this->getWhmcsParamByKey('serverip');
+                return $this->node = $nodeRepository->findWithHostOrIp($this->getWhmcsParamByKey('serverhostname'), $serverIp);
+                break;
+            default:
+                return $this->getDefaultNode();
+        }
+    }
+
+
+    protected function getDefaultNode()
+    {
+        $defaultNode    = $this->configuration()->getDefaultNode();
+        $nodeRepository = new NodeRepository();
+        $nodeRepository->setApi($this->api());
+        switch ($defaultNode)
+        {
+            case "autoNode":
+                $diskBytes =1;
+                if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+                {
+                    $diskBytes = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                    Utility::unitFormat($diskBytes, $this->configuration()->getStorageUnit(), 'bytes');
+                }
+                else
+                {
+                    $diskBytes = $this->configuration()->getStorageSize();
+                    Utility::unitFormat($diskBytes, "gb", 'bytes');
+                }
+                if (empty($diskBytes))
+                {
+                    throw new \Exception("Provide disk size.");
+                }
+                return $nodeRepository->findByDiskSize($diskBytes);
+                break;
+            case "serverNode":
+                $servePrivateIP =  $this->getServerPrivateIpAddress();
+                $serverIp = $servePrivateIP? $servePrivateIP : $this->getWhmcsParamByKey('serverip');
+                return $nodeRepository->findWithHostOrIp($this->getWhmcsParamByKey('serverhostname'), $serverIp);
+                break;
+            default:
+                return new Node($defaultNode);
+        }
+    }
+
+    public function getServerPrivateIpAddress(){
+
+        $serverId = $this->getWhmcsParamByKey('serverid');
+        if( $serverId  &&  $serverId >0){
+            $serverAssignedIps = DB::table('tblservers')->where("id", $serverId)->value("assignedips");
+            $serverAssignedIps = preg_replace('/\s+/', '', $serverAssignedIps);
+            $serverAssignedIps = explode(PHP_EOL, $serverAssignedIps);
+            return current($serverAssignedIps);
+        }
+    }
+
+    protected function getDefaultNodeIfSet(){
+        $defaultNode    = $this->configuration()->getDefaultNode();
+        if(!in_array($defaultNode,['autoNode','serverNode'])){
+            return $defaultNode;
+        }
+        return  null;
+    }
+
+
+}

+ 141 - 0
app/Services/Cloud/Resource.php

@@ -0,0 +1,141 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+class Resource
+{
+
+    protected $name;
+    protected $unit;
+    protected $min;
+    protected $used;
+    protected $max;
+    protected $total;
+    protected $free;
+
+    /**
+     * Resource constructor.
+     * @param $name
+     */
+    public function __construct($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getUnit()
+    {
+        return $this->unit;
+    }
+
+    /**
+     * @param mixed $unit
+     * @return Resource
+     */
+    public function setUnit($unit)
+    {
+        $this->unit = $unit;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getMin()
+    {
+        return $this->min;
+    }
+
+    /**
+     * @param mixed $min
+     * @return Resource
+     */
+    public function setMin($min)
+    {
+        $this->min = $min;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getMax()
+    {
+        return $this->max;
+    }
+
+    /**
+     * @param mixed $max
+     * @return Resource
+     */
+    public function setMax($max)
+    {
+        $this->max = $max;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getUsed()
+    {
+        return $this->used;
+    }
+
+    /**
+     * @param mixed $used
+     * @return Resource
+     */
+    public function setUsed($used)
+    {
+        $this->used = $used;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getTotal()
+    {
+        return $this->total;
+    }
+
+    /**
+     * @param mixed $total
+     * @return Resource
+     */
+    public function setTotal($total)
+    {
+        $this->total = $total;
+        return $this;
+    }
+
+    public function free(){
+        $totalUsed = $this->freeTotal();
+        if($this->max && $this->max < $totalUsed){
+            return  $this->max;
+        }
+        return $totalUsed;
+    }
+
+    public function freeTotal(){
+        return $this->total - $this->used;
+    }
+
+    public function hasFreeTotal(){
+        return $this->freeTotal() > 0;
+    }
+
+    public function getPercent(){
+        if(!$this->total){
+            return 0;
+        }
+        return round($this->used / $this->total * 100);
+    }
+
+
+}

+ 185 - 0
app/Services/Cloud/ResourceGuard.php

@@ -0,0 +1,185 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use MGProvision\Proxmox\v2\repository\BackupScheduleRepository;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use MGProvision\Proxmox\v2\repository\FirewallRulesRepository;
+use MGProvision\Proxmox\v2\repository\HardDiskRepostiory;
+use MGProvision\Proxmox\v2\repository\MountPointRepostiory;
+use MGProvision\Proxmox\v2\repository\SnapshotRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\SnapshotJob;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\CloudService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use function ModulesGarden\Servers\ProxmoxCloudVps\Core\Helper\sl;
+
+/**
+ * Class ResourceGuard
+ * @package ModulesGarden\ProxmoxAddon\App\Services\Cloud
+ * @todo
+ */
+class ResourceGuard
+{
+    use WhmcsParams;
+    use ProductService;
+    use ApiService;
+
+
+    public function backupLimit()
+    {
+        $cloudSerice  = new CloudService();
+        //Backup repository
+        $backupRepository = new FileRepository();
+        $backupRepository->setApi($this->api());
+        $backupRepository->findBackupByVmModel($cloudSerice->getVmModelWithVmid())
+                        ->findByStorages([$this->configuration()->getBackupStorage()]);
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles()) ;
+        if ($maxFiles != "-1"  && $this->configuration()->isBackupRouting())
+        {
+            $maxFiles++;
+        }
+        if ($maxFiles != "-1" && $backupRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->abtr("The maximum number of backup files has been exceeded. Please remove the old backup files."));
+        }
+        //Size limit
+        $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_SIZE,  $this->configuration()->getBackupMaxSize()) ;
+        $used    = $backupRepository->size();
+        Utility::unitFormat($used, "bytes", "gb");
+        if ($maxSize != "-1" && $used >= $maxSize)
+        {
+            throw new \Exception(sl("lang")->abtr("The maximum size set for a backup has been exceeded. Please remove the old backup files."));
+        }
+    }
+
+    /**
+     * @return bool
+     * @todo
+     */
+    public function hasBackupLimit()
+    {
+        try
+        {
+            $this->backupLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function backupJobLimit()
+    {
+        $this->backupLimit();
+        $cloudSerice  = new CloudService();
+        //Backup repository
+        $backupRepository = new BackupScheduleRepository();
+        $backupRepository->setApi($this->api());
+        $backupRepository->findByVmModel($cloudSerice->getVmModelWithVmid());
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles()) ;
+        if ($maxFiles != "-1" && $backupRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of backup files has been exceeded. Please remove the old backup files."));
+        }
+    }
+
+    public function hasBackupJobLimit()
+    {
+        try
+        {
+            $this->backupJobLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function snapshotLimit()
+    {
+
+        $cloudSerice  = new CloudService();
+        $snapshotRepository = new SnapshotRepository();
+        $snapshotRepository->setApi($this->api());
+        $snapshotRepository->findByVmModel($cloudSerice->getVmModels());
+        $snapshotRepository->ignoreCurrent(true);
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::SNAPSHOTS, $this->configuration()->getSnapshotMaxFiles());
+        if ($maxFiles != "-1" && $snapshotRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of snapshots has been exceeded. Please remove the old snapshots."));
+        }
+    }
+
+    public function hasSnapshotLimit()
+    {
+        try
+        {
+            $this->snapshotLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    /**
+     * @return bool
+     * @to do implement snapshot jobs
+     */
+    public function snapshotJobLimit()
+    {
+        return true;
+    }
+
+    public function hasSnapshotJobLimit()
+    {
+        try
+        {
+            $this->snapshotJobLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function firewallLimit()
+    {
+        $cloudSerice  = new CloudService();
+        $repository    = new FirewallRulesRepository();
+        $repository->setApi($this->api());
+        $paths=[];
+        $repository->findByVmModel($cloudSerice->getVmModelWithVmid());
+        //Files limit
+        $max = (int)$this->configuration()->getFirewallMaxRules();
+        if ($max != "-1" && $repository->count() >= $max)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of firewall rules has been exceeded. Please remove the old firewall rules."));
+        }
+    }
+
+    public function hasfirewallLimit()
+    {
+        try
+        {
+            $this->firewallLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+}

+ 387 - 0
app/Services/Cloud/ResourceManager.php

@@ -0,0 +1,387 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualNetwork;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+
+class ResourceManager
+{
+    use WhmcsParams;
+    use ProductService;
+
+    protected $notVmIds = [];
+
+    /**
+     * @var Resource[]
+     */
+    protected $items = [];
+
+
+    public function notVmIds(array $ids)
+    {
+        $this->notVmIds = $ids;
+        return $this;
+    }
+
+    /**
+     * @return Resource
+     */
+    public function sockets()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit('int');
+        if ($this->isWhmcsConfigOption(ConfigurableOption::SOCKETS))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::SOCKETS));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getSockets());
+        }
+        $resurce->setMin($this->configuration()->serverSockets->min);
+        $resurce->setMax($this->configuration()->serverSockets->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function cores()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit('int');
+        if ($this->isWhmcsConfigOption(ConfigurableOption::CORES))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::CORES));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getCores());
+        }
+        $resurce->setMin($this->configuration()->serverCores->min);
+        $resurce->setMax($this->configuration()->serverCores->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function vcpus()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit('int');
+        if ($this->isWhmcsConfigOption(ConfigurableOption::VCPUS))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::VCPUS));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getVcpus());
+        }
+        $resurce->setMin($this->configuration()->serverVcpus->min);
+        $resurce->setMax($this->configuration()->serverVcpus->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function cpulimit()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit('int');
+        if ($this->isWhmcsConfigOption(ConfigurableOption::CPU_LIMIT))
+        {
+            $resurce->setTotal(round($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT),2));
+        }
+        else
+        {
+            $resurce->setTotal(round($this->configuration()->getCpulimit(),2));
+        }
+        $resurce->setMin($this->configuration()->serverCpulimit->min);
+        $resurce->setMax($this->configuration()->serverCpulimit->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function virtualNetworks()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit('int');
+        if ($this->isWhmcsConfigOption(ConfigurableOption::VIRTUAL_NETWORKS))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::VIRTUAL_NETWORKS));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getVirtualNetworks());
+        }
+        $resurce->setMin(0);
+        $resurce->setMax($resurce->getTotal());
+        $query = VirtualNetwork::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        $resurce->setUsed($query->count());
+
+
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+
+    /**
+     * @return Resource|Resource
+     * @todo  singel query
+     */
+    public function disk()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("gb");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::STORAGE))
+        {
+            $resurce->setUnit($this->configuration()->getStorageUnit());
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::STORAGE));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getStorageSize());
+        }
+        $resurce->setMin((int)$this->configuration()->serverDiskSize->min);
+        $resurce->setMax((int)$this->configuration()->serverDiskSize->max);
+        $query  = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        $query2 = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+            $query2->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum('disk') + $query2->sum('disks'));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    /**
+     * @return Resource|Resource
+     */
+    public function memory()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("mb");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+        {
+            $resurce->setUnit($this->configuration()->getMemoryUnit());
+            $total = (int)$this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+            Utility::unitFormat($total,$this->configuration()->getMemoryUnit(),"mb");
+            $resurce->setTotal($total);
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getMemory());
+        }
+        $resurce->setMin($this->configuration()->serverMemory->min);
+        $resurce->setMax($this->configuration()->serverMemory->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    /**
+     * @return Resource|Resource
+     */
+    public function swap()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("mb");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::SWAP))
+        {
+            $resurce->setUnit($this->configuration()->getSwapUnit());
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::SWAP));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getSwap());
+        }
+        $resurce->setMin($this->configuration()->serverSwap->min);
+        $resurce->setMax($this->configuration()->serverSwap->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function cpuunits()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("int");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::CPU_UNITS))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getCpuunits());
+        }
+        $resurce->setMin($this->configuration()->serverCpuunit->min);
+        $resurce->setMax($this->configuration()->serverCpuunit->max);
+        $query = VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->sum(__FUNCTION__));
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function ipv4()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("int");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV4))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::IPV4));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getIpv4());
+        }
+        $resurce->setMin($this->configuration()->serverIpv4->min);
+        $resurce->setMax($this->configuration()->serverIpv4->max);
+        $query = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        $query->ofIp4();
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->count());
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function ipv6()
+    {
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("int");
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV6))
+        {
+            $resurce->setTotal((int)$this->getWhmcsConfigOption(ConfigurableOption::IPV6));
+        }
+        else
+        {
+            $resurce->setTotal((int)$this->configuration()->getIpv6());
+        }
+        $resurce->setMin($this->configuration()->serverIpv6->min);
+        $resurce->setMax($this->configuration()->serverIpv6->max);
+        $query = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'));
+        $query->ofIp6();
+        if ($this->notVmIds)
+        {
+            $query->notIdIn($this->notVmIds);
+        }
+        $resurce->setUsed($query->count());
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+    public function cpuPriority(){
+        //cache
+        if ($this->items[__FUNCTION__])
+        {
+            return $this->items[__FUNCTION__];
+        }
+        $resurce = new Resource(__FUNCTION__);
+        $resurce->setUnit("int");
+        $resurce->setMin(1);
+        $resurce->setMax(5);
+        $resurce->setTotal(5);
+
+        for($i=1; $i<=5; $i++){
+            $cpuunits = $this->configuration()->get('cpuunitsPriority'.$i);
+            $cpulimit = round( $this->configuration()->get('cpulimitPriority'.$i),2);
+            if($this->cpulimit()->freeTotal() < $cpulimit){
+                $resurce->setUsed($i);
+            }
+            if($this->cpuunits()->freeTotal() < $cpuunits){
+                $resurce->setUsed($i);
+            }
+        }
+        return $this->items[__FUNCTION__] = $resurce;
+    }
+
+}

+ 154 - 0
app/Services/Cloud/UserService.php

@@ -0,0 +1,154 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon\App\Models\User;
+use ModulesGarden\ProxmoxAddon\App\Repositories\Cloud\ProductConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Trait UserService
+ * @method ProductConfigurationRepository configuration()
+ */
+trait UserService
+{
+    use Smarty;
+
+    /**
+     * @return User
+     */
+    protected function getUser()
+    {
+        $userId    = $this->getWhmcsParamByKey('userid');
+        $serviceId = $this->getWhmcsParamByKey('serviceid');
+        return User::ofUserId($userId)->ofHostingId($serviceId)->orderBy("id", "desc")->firstOrFail();
+    }
+
+    protected function isUser()
+    {
+        $userId    = $this->getWhmcsParamByKey('userid');
+        $serviceId = $this->getWhmcsParamByKey('serviceid');
+        return  User::ofUserId($userId)->ofHostingId($serviceId)->count()== 1;
+    }
+
+    /**
+     * @return User
+     * @throws proxmox\v2\ProxmoxApiException
+     */
+    public function userCreate(User $user = null)
+    {
+        if (is_null($user))
+        {
+            $user          = new User();
+            $user->user_id = $this->getWhmcsParamByKey('userid');
+        }
+        $userPrefix = $this->configuration()->getUserPrefix();
+        if ($userPrefix)
+        {
+            $userPrefix = $this->getSmarty()->fetchString($userPrefix, [
+                "serviceid"  => $this->getWhmcsParamByKey('serviceid'),
+                "service_id" => $this->getWhmcsParamByKey('serviceid')
+            ]);
+        }
+        $user->hosting_id = $this->getWhmcsParamByKey('serviceid');
+        $username =  $this->getWhmcsParamByKey('username');
+        $user->username = $userPrefix .  $username;
+        $password = $this->getWhmcsParamByKey('password', Utility::generatePassword(12));
+        $user->setPassword($password);
+        $user->realm = $this->configuration()->getRealm();
+        $userService = new proxmox\models\User();
+        $userService->setApi($this->api());
+        $data = [
+            "userid"    => "{$user->username}@{$user->realm}",
+            "comment"   => $this->configuration()->getUserComment(),
+            "email"     => $this->getWhmcsParamByKey('clientsdetails')['email'],
+            "enable"    => 1,
+            "firstname" => $this->getWhmcsParamByKey('clientsdetails')['firstname'],
+            "lastname"  => $this->getWhmcsParamByKey('clientsdetails')['lastname'],
+            "password" => $user->getPassword()
+        ];
+        $userService->create($data);
+        $userService->changePassword($user->getPassword());
+        $user->save();
+        return $user;
+    }
+
+    protected function userUpdate()
+    {
+        $user        = $this->getUser();
+        $userService = new proxmox\models\User("{$user->username}@{$user->realm}");
+        $userService->setApi($this->api());
+        try
+        {
+            $userService->changePassword($user->getPassword());
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match('/No such user/', $ex->getMessage()))
+            {
+                throw $ex;
+            }
+            $this->userCreate($user);
+        }
+
+        if(!sl('Vm')->hasVm()){
+            return ;
+        }
+
+        $vmid = sl('Vm')->getVm()->getVmid();
+        $this->userUpdatePermission([$vmid]);
+        return $this;
+    }
+
+    public function userUpdatePermission($vmIds){
+        $user        = $this->getUser();
+        $userService = new proxmox\models\User("{$user->username}@{$user->realm}");
+        $userService->setApi($this->api());
+        $permissions = $userService->permissions();
+        foreach ($vmIds as $vmid){
+            foreach ($permissions as $p)
+            {
+                if ($p['path'] == "/vms/" . $vmid && $p['ugid'] == $userService->getUserid())
+                {
+                    $userService->updatePermission($vmid, $p['roleid'], 1);
+                }
+            }
+            $role = $this->configuration()->getUserRole();
+            $userService->updatePermission($vmid, $role);
+        }
+
+        return $this;
+    }
+
+    protected function deleteUser()
+    {
+        $user        = $this->getUser();
+        $userService = new proxmox\models\User("{$user->username}@{$user->realm}");
+        $userService->setApi($this->api());
+        $userService->delete();
+        $user->delete();
+        return $this;
+    }
+
+
+}

+ 202 - 0
app/Services/Cloud/VirtualInterfaceConverter.php

@@ -0,0 +1,202 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Cloud;
+
+
+use MGProvision\Proxmox\v2\models\IpConfig;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use MGProvision\Proxmox\v2\models\NetworkDeviceKvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceLxc;
+use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use Illuminate\Database\Capsule\Manager as DB;
+
+class VirtualInterfaceConverter
+{
+    use WhmcsParams;
+    use ProductService;
+
+    /**
+     * @var Kvm|Lxc|null
+     */
+    protected $vm;
+
+
+    /**
+     * @var VirtualInterface[]
+     */
+    protected $virtualInterfaces;
+
+    /**
+     * @var NetworkDeviceKvm[]
+     */
+    protected $networkDevices;
+
+    /**
+     * @var IpConfig[]
+     */
+    protected $ipConfigs=[];
+
+    /**
+     * VirtualInterfaceConverter constructor.
+     * @param Kvm|Lxc $vm
+     * @param VirtualInterface[] $virtualInterfaces
+     */
+    public function __construct( $virtualInterfaces, $vm=null)
+    {
+        $this->vm = $vm;
+        $this->virtualInterfaces = $virtualInterfaces;
+    }
+
+    public function convert(){
+        $container=[];
+        $rate      = $this->getRate();
+        $networkId = $this->freeNetworDeviceId();
+        foreach ($this->virtualInterfaces as $virtualInterface){
+            //public network
+            if($virtualInterface->vn_id == 0){
+                /**
+                 * @var $ip VmIpAddress
+                 */
+                $ip = VmIpAddress::ofHostingId($virtualInterface->hosting_id)->ofIp($virtualInterface->ip)->first();
+            }
+            $bridge = $this->getBridge($virtualInterface);
+            if ($this->configuration()->isQemu())
+            {
+                $networkDevice = new NetworkDeviceKvm('net' . $networkId);
+                $networkDevice->setBridge($bridge)
+                    ->setRate($rate)
+                    ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0);
+                //ip config
+                $ipConfig = new IpConfig('ipconfig' . $networkId);
+                //Public
+                if($ip){
+                    $networkDevice->setModel($this->configuration()->getNetworkModel())
+                        ->setTag($ip->tag)
+                        ->setMacAddress($ip->mac_address)
+                        ->setTrunks($ip->trunks)
+                        ->setQueues(trim($this->configuration()->getQueues()));
+                    //ipv4
+                    if(!preg_match("/\:/",$virtualInterface->ip)){
+                        $ipConfig->setIp(trim($virtualInterface->ip))
+                            ->setCidr(trim($ip->cidr))
+                            ->setGw(trim($ip->gateway));
+                    }else{
+                        //ipv6
+                        $ipConfig->setIp6(trim($virtualInterface->ip))
+                            ->setCidr6(trim($ip->cidr))
+                            ->setGw6(trim($ip->gateway));
+                    }
+                }else{
+                    $networkDevice->setModel($this->configuration()->getNetworkPrivateModel())
+                        ->setTag($virtualInterface->virtualNetwork->tag);
+                    $ipConfig->setIp(trim($virtualInterface->ip))
+                        ->setCidr(trim($virtualInterface->virtualNetwork->cidr))
+                        ->setGw(trim($virtualInterface->virtualNetwork->gateway));
+                }
+                if(!$this->configuration()->isOneNetworkDevice() || ( $this->configuration()->isOneNetworkDevice() && $networkDevice->getId() =="net0")){
+                    $this->networkDevices[] = $networkDevice;
+                    $this->ipConfigs[] = $ipConfig;
+                }
+            }
+            //lxc
+            if ($this->configuration()->isLxc())
+            {
+                $networkDevice = new NetworkDeviceLxc('net' . $networkId);
+                $networkDevice->setName('eth' . $networkId)
+                    ->setBridge($bridge)
+                    ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0)
+                    ->setIp($virtualInterface->ip)
+                    ->setRate($rate);
+                //Public
+                if($ip){
+                    $networkDevice->setCidr($ip->cidr)
+                        ->setGw($ip->gateway)
+                        ->setTag($ip->tag)
+                        ->setHwaddr($ip->mac_address)
+                        ->setTrunks($ip->trunks);
+                }else{
+                    $networkDevice->setCidr($virtualInterface->virtualNetwork->cidr)
+                        ->setGw($virtualInterface->virtualNetwork->gateway)
+                        ->setTag($virtualInterface->virtualNetwork->tag);
+                }
+                if(!$this->configuration()->isOneNetworkDevice() || ( $this->configuration()->isOneNetworkDevice() && $networkDevice->getId() =="net0")){
+                    $this->networkDevices[] = $networkDevice;
+                }
+
+            }
+            //one network device for public ip only
+            if($this->configuration()->isOneNetworkDevice() && $networkDevice->getId() !="net0" && $ip instanceof VmIpAddress ){
+                $virtualInterface->net = "net0";
+                $virtualInterface->save();
+                $ip->net = "net0";
+                $ip->save();
+            }else{
+                $virtualInterface->net = $networkDevice->getId();
+                $virtualInterface->save();
+                //public ip
+                if($ip instanceof VmIpAddress){
+                    $ip->net = $networkDevice->getId();
+                    $ip->save();
+                }
+            }
+            $networkId++;
+            unset($ip);
+        }
+    }
+
+    public function asConfig(){
+        $this->convert();
+        return array_merge($this->getNetworkDevicesAsConfig(), $this->getIpConfigsAsConfig());
+    }
+
+    public function getNetworkDevicesAsConfig(){
+        $container=[];
+        foreach ($this->networkDevices as $networkDevice){
+            $container[$networkDevice->getId()] = $networkDevice->asConfig();
+        }
+        return $container;
+    }
+
+    public function getIpConfigsAsConfig(){
+        $container=[];
+        foreach ($this->ipConfigs as $ipConfig){
+            $container[$ipConfig->getId()] = $ipConfig->asConfig();
+        }
+        return $container;
+    }
+
+    private function getRate(){
+        $rate      = null;
+        if ($this->isWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE);
+        }
+        else
+        {
+            if ($this->configuration()->getRate())
+            {
+                $rate = $this->configuration()->getRate();
+            }
+        }
+        return $rate;
+    }
+
+    private function getBridge(VirtualInterface $virtualInterface){
+        if($virtualInterface->vn_id == 0){
+            return $this->configuration()->getBridge();
+        }
+        return $this->configuration()->getPrivateBridge() ? $this->configuration()->getPrivateBridge() :  $this->configuration()->getBridge();
+    }
+
+    private function freeNetworDeviceId(){
+        if($this->vm){
+            return $this->vm->findFreeNetworDeviceId();
+        }
+        return 0;
+    }
+}

+ 71 - 0
app/Services/CloudInitScriptConveter.php

@@ -0,0 +1,71 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Models\Snippet;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CloudInitScriptConveter
+{
+    use Smarty;
+    use WhmcsParams;
+    use ApiService;
+
+    /**
+     * @var CloudInitScript
+     */
+    protected $cloudInitScript;
+
+    /**
+     * @var ServerConfigurationRepository
+     */
+    protected $serverConfiguration;
+    /**
+     * @var VmModel|null
+     */
+    protected $vmModel;
+
+    /**
+     * CloudInitScriptConveter constructor.
+     * @param CloudInitScript $cloudInitScript
+     */
+    public function __construct(CloudInitScript $cloudInitScript, ServerConfigurationRepository $serverConfiguration, $vmModel =null)
+    {
+        $this->cloudInitScript = $cloudInitScript;
+        $this->serverConfiguration = $serverConfiguration;
+        $this->vmModel = $vmModel;
+    }
+
+    /**
+     * @return Snippet
+     */
+    public function convert(){
+        if(!$this->serverConfiguration->snippetDirectory){
+            throw new \InvalidArgumentException("Snippet Directory is empty");
+        }
+        $script = html_entity_decode($this->cloudInitScript->script, ENT_QUOTES);
+        $params = sl('whmcsParams')->getWhmcsParams();
+        $params['passwordHash'] = Utility::passwordHash($params['password']);
+        if($this->vmModel instanceof VmModel){
+            //load ip addresses
+            $this->vmModel->ipv4Addresses;
+            $this->vmModel->ipv6Addresses;
+            $params['vm'] = $this->vmModel->toArray();
+            //password decode
+            $params['vm']['password'] = $this->vmModel->getPassword();
+            $params['vm']['passwordHash'] = Utility::passwordHash($params['vm']['password']);
+            $filename = sprintf("userconfig-%s-%s.yaml",$this->getWhmcsParamByKey('serviceid'), $this->vmModel->id);
+        }else{//vps
+            $filename = sprintf("userconfig-%s.yaml",$this->getWhmcsParamByKey('serviceid'));
+        }
+        $content = $this->getSmarty()->fetchString($script, $params);
+        return new Snippet($this->serverConfiguration->snippetDirectory,$filename, $content);
+    }
+}

+ 177 - 0
app/Services/CloudService.php

@@ -0,0 +1,177 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\BackupScheduleRepository;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use MGProvision\Proxmox\v2\repository\FirewallRulesRepository;
+use MGProvision\Proxmox\v2\VmFactory;
+use ModulesGarden\ProxmoxAddon\App\Models\IpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\KeyPair;
+use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\HighAvailabilityClusterService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\NetworkService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ProductService;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\ResourceManager;
+use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\SnippetTrait;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CloudService
+{
+    use ProductService;
+    use WhmcsParams;
+    use ApiService;
+    use SnippetTrait;
+
+    protected $networkService;
+    protected $highAvailabilityClusterService;
+    /**
+     * @var ResourceManager
+     */
+    protected $resourceManager;
+
+    /**
+     * CloudService constructor.
+     */
+    public function __construct()
+    {
+        $this->networkService = new NetworkService();
+        $this->highAvailabilityClusterService = new HighAvailabilityClusterService();
+    }
+
+
+
+    /**
+     * @return VmModel[]
+     */
+    public function getVmModels(){
+        return VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'))->get();
+    }
+
+    public function getVmModelWithVmid(){
+        return VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'))->notVmid(0)->get();
+    }
+    /**
+     * @return array
+     */
+    public function getVmids(){
+        return VmModel::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->notVmid('0')
+            ->pluck('vmid')
+            ->toArray();
+    }
+
+    public function destroy(){
+        foreach ($this->getVmModels() as $vmModel){
+            $this->delete($vmModel);
+        }
+        VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))->delete();
+        IpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->update(["hosting_id" => 0]);
+    }
+
+    public function delete(VmModel $vmModel){
+        //Cron delete active jobs
+        $this->cancellJobs($vmModel->id);
+        sl('Vm')->setVmModel($vmModel);
+        //Unassing IP
+        $this->networkService->deleteIpAddresses();
+        if($vmModel->vmid == 0){
+            $vmModel->delete();
+            return;
+        }
+        //init api
+        $this->api();
+        //init vm
+        sl('Vm')->setVm((new VmFactory())->fromVmModel($vmModel));
+        //Delete HA
+        if ($this->highAvailabilityClusterService->exist())
+        {
+                $this->highAvailabilityClusterService->delete();
+        }
+        try{
+            //Stop VM
+            if (sl('Vm')->getVm()->isRunning())
+            {
+                sl('Vm')->getVm()->stop();
+                sleep(4);
+            }
+            //VM Protection LXC
+            if (sl('Vm')->getVm() instanceof Lxc && sl('Vm')->getVm()->config()['protection'] == "1")
+            {
+                sl('Vm')->getVm()->protectionOff();
+
+            }
+        } catch (ProxmoxApiException $ex) {
+            //Configuration file 'nodes/proxmox4/qemu-server/105.conf' does not exist
+            if (!preg_match("/Configuration file/", $ex->getMessage()) && !preg_match("/does not exist/", $ex->getMessage())) {
+                throw $ex;
+            }
+        }
+        //virtual interfaces delete
+        VirtualInterface::ofHostingId($this->getWhmcsParamByKey("serviceid"))->ofVmId($vmModel->id)->delete();
+        //key pair delete
+        KeyPair::ofHostingId($this->getWhmcsParamByKey("serviceid"))->ofVmId($vmModel->id)->delete();
+
+        //Destroy firewall rules
+        if (version_compare($this->api()->getVersion(), "3.2", '>'))
+        {
+            $rules = new FirewallRulesRepository();
+            $rules->findByVm(sl('Vm')->getVm())
+                ->delete();
+        }
+        //Destroy Backups
+        $storage        = $this->configuration()->getBackupStorage() ? $this->configuration()->getBackupStorage() : 'local';
+        $fileRepository = new FileRepository();
+        $fileRepository->findBackup(sl('Vm')->getVm())
+            ->findByStorages([$storage])
+            ->delete();
+        //Destroy Backup Jobs
+        $schedule = new BackupScheduleRepository();
+        $schedule->findByVm(sl('Vm')->getVm());
+        $schedule->delete();
+
+        //Destroy VM
+        try {
+            $upid = sl('Vm')->getVm()->delete();
+            //Create Task History
+            $type = str_replace(["qemu", "lxc"], ["VM", "CT"], sl('Vm')->getVm()->getVirtualization());
+            $task = new TaskHistory();
+            $task->fill([
+                'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
+                'upid'       => $upid,
+                'name'       => sprintf("%s %s - %s", $type, sl('Vm')->getVm()->getVmid(), "Delete"),
+                'vmid'       => sl('Vm')->getVm()->getVmid(),
+                'node'       => sl('Vm')->getVm()->getNode(),
+                'status'     => 0
+            ])->save();
+        } catch (ProxmoxApiException $ex) {
+            //Configuration file 'nodes/proxmox4/qemu-server/105.conf' does not exist
+            if (!preg_match("/Configuration file/", $ex->getMessage()) && !preg_match("/does not exist/", $ex->getMessage())) {
+                throw $ex;
+            }
+        }
+        //delete vmModel
+        $vmModel->delete();
+        if($this->hasSnippet() &&  $file = $this->getSnippetFile($vmModel)){
+            $file->delete();
+        }
+
+    }
+
+    public function cancellJobs($customId){
+        Job::whereIn("status", ['waiting', "running", "", "error"])
+            ->where("rel_id", $this->getWhmcsParamByKey("serviceid"))
+            ->where("rel_type", "hosting")
+            ->where("custom_id", $customId)
+            ->update(["status" => 'cancelled']);
+    }
+}

+ 82 - 0
app/Services/EmailService.php

@@ -0,0 +1,82 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+
+use ModulesGarden\ProxmoxAddon\Core\Api\Whmcs;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Admins;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\EmailTemplate;
+
+class EmailService
+{
+    private $api;
+    private $templateId;
+    private $vars = [];
+    private $lastResponse;
+
+    /**
+     * EmailService constructor.
+     * @param $api
+     */
+    public function __construct()
+    {
+        $this->api = new Whmcs(new Admins());
+    }
+
+    public function template($templateId)
+    {
+        if (!$templateId)
+        {
+            throw new \InvalidArgumentException("Email Template Id cannot be empty");
+        }
+        $this->templateId          = $templateId;
+        $this->vars                = EmailTemplate::select("name", "type")->where("id", $this->templateId)->first()->toArray();
+        $this->vars['messagename'] = $this->vars['name'];
+        unset($this->vars['name']);
+        return $this;
+    }
+
+    public function vars(array $vars)
+    {
+        $this->vars = array_merge($this->vars, $vars);
+        return $this;
+    }
+
+    public function getVars()
+    {
+        return $this->vars;
+    }
+
+    public function send()
+    {
+        $this->lastResponse = $this->api->call("sendemail", $this->vars);
+        $this->vars         = [];
+        $this->templateId   = null;
+        return $this;
+    }
+
+    public function sendToAdmin()
+    {
+        if (function_exists('sendAdminMessage'))
+        {
+            /**
+             * sendAdminMessage ($templateName, $adminId = "", $templateVars = array(), $to = "system", $deptid = "", $ticketNotify = "")
+             */
+            sendAdminMessage($this->vars['messagename'], $this->vars);
+            $this->vars       = [];
+            $this->templateId = null;
+        }
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getLastResponse()
+    {
+        return $this->lastResponse;
+    }
+
+
+}

+ 143 - 0
app/Services/Ip/Ipv4Range.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Ip;
+
+class Ipv4Range
+{
+
+    protected  $pool;
+    protected $cidr;
+    protected $limit;
+    protected $disableIpAddresses=[];
+    const NET_MASK = [
+            0  => "0.0.0.0",
+            1  => "128.0.0.0",
+            2  => "192.0.0.0",
+            3  => "224.0.0.0",
+            4  => "240.0.0.0",
+            5  => "248.0.0.0",
+            6  => "252.0.0.0",
+            7  => "254.0.0.0",
+            8  => "255.0.0.0",
+            9  => "255.128.0.0",
+            10 => "255.192.0.0",
+            11 => "255.224.0.0",
+            12 => "255.240.0.0",
+            13 => "255.248.0.0",
+            14 => "255.252.0.0",
+            15 => "255.254.0.0",
+            16 => "255.255.0.0",
+            17 => "255.255.128.0",
+            18 => "255.255.192.0",
+            19 => "255.255.224.0",
+            20 => "255.255.240.0",
+            21 => "255.255.248.0",
+            22 => "255.255.252.0",
+            23 => "255.255.254.0",
+            24 => "255.255.255.0",
+            25 => "255.255.255.128",
+            26 => "255.255.255.192",
+            27 => "255.255.255.224",
+            28 => "255.255.255.240",
+            29 => "255.255.255.248",
+            30 => "255.255.255.252",
+            31 => "255.255.255.254",
+            32 => "255.255.255.255"
+    ];
+    protected $firstIp = false;
+
+    /**
+     * Ipv4Range constructor.
+     * @param $pool
+     * @param $cidr
+     */
+    public function __construct($pool, $cidr)
+    {
+        $this->pool = $pool;
+        $this->cidr = $cidr;
+    }
+
+
+    public function get(){
+
+        $pool   = [];
+        $ip_enc = ip2long($this->pool);
+        //convert last (32-$mask) bits to zeroes
+        $curr_ip         = $ip_enc | pow(2, (32 - $this->cidr)) - pow(2, (32 - $this->cidr));
+        $ips             = [];
+        $ip_nmask        = self::NET_MASK[$this->cidr];
+        $ip_address_long = $ip_enc;
+        $ip_nmask_long   = ip2long($ip_nmask);
+        //caculate network address
+        $ip_net = $ip_address_long & $ip_nmask_long;
+        //caculate first usable address
+        $ip_host_first = ((~$ip_nmask_long) & $ip_address_long);
+        $ip_first      = ($ip_address_long ^ $ip_host_first) + 1;
+        //caculate last usable address
+        $ip_broadcast_invert = ~$ip_nmask_long;
+        $ip_last             = ($ip_address_long | $ip_broadcast_invert) - 1;
+        //caculate broadcast address
+        $ip_last       = ($ip_address_long | $ip_broadcast_invert) - 1;
+        $ip_last_short = long2ip($ip_last);
+        $totalHost     = (float)pow(2, (32 - $this->cidr));
+        if ($totalHost > 10000)
+        {
+            throw new \Exception(sprintf("Subnet %s/%s is too large. You are tring to add %s addresses.", $this->pool, $this->cidr, $totalHost));
+        }
+        for ($pos = 0; $pos < pow(2, (32 - $this->cidr)); ++$pos)
+        {
+            $ip     = long2ip((float)$curr_ip + $pos);
+            if(!$this->firstIp && preg_match("/\.0$/", $ip)){
+                continue;
+            }
+            if(in_array($ip, $this->disableIpAddresses)){
+                continue;
+            }
+            $pool[] = $ip;
+            if ($ip == $ip_last_short)
+            {
+                break;
+            }
+            if($this->limit && count($pool) == $this->limit){
+                return  $pool;
+            }
+        }
+        return $pool;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getLimit()
+    {
+        return $this->limit;
+    }
+
+    /**
+     * @param mixed $limit
+     * @return Ipv4Range
+     */
+    public function setLimit($limit)
+    {
+        $this->limit = $limit;
+        return $this;
+    }
+
+    /**
+     * @param array $disableIpAddresses
+     * @return Ipv4Range
+     */
+    public function disableIpAddresses($disableIpAddresses)
+    {
+        $this->disableIpAddresses = $disableIpAddresses;
+        return $this;
+    }
+
+    public function firstIp($enable=false){
+
+        $this->firstIp = $enable;
+        return $this;
+    }
+
+
+}

+ 461 - 0
app/Services/LoadBalancerService.php

@@ -0,0 +1,461 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (Jan 16, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model as Settings;
+
+/**
+ * Description of LoadBalancerService
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class LoadBalancerService
+{
+    protected $nodesResurces = [];
+    private $serverId;
+
+    /**
+     *
+     * @var \MGProvision\Proxmox\v2\Api
+     */
+    private $api;
+    private $usage = false;
+    private $filter = [];
+    private $excludeVmid;
+    private $vmsWeight;
+    private $ramWeight;
+    private $diskWeight;
+    private $cpuWeight;
+
+    function __construct($serverId)
+    {
+        $this->serverId = $serverId;
+    }
+
+    public function getExcludeVmid()
+    {
+        return $this->excludeVmid;
+    }
+
+    public function setExcludeVmid($excludeVmid)
+    {
+        $this->excludeVmid = $excludeVmid;
+        return $this;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByVmCreate()
+    {
+        //Node settings
+        $this->nodesResurces = [];
+        $query               = NodeSetting::ofServer($this->serverId)->ofSetting('vmCreate')->ofValue(1);
+        foreach ($query->get() as $entity)
+        {
+            $this->nodesResurces[] = $this->format($entity);
+        }
+        return clone $this;
+    }
+
+    private function format(NodeSetting $entity)
+    {
+        $nodesResurce = [
+            'node'       => $entity->node,
+            'vmCreate'   => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmCreate')->value('value'),
+            'vmsMax'     => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsMax')->value('value'),
+            'vmsWeight'  => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsWeight')->value('value'),
+            'vmsUsed'    => 0,
+            'vmsFree'    => null,
+            'cpuMax'     => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuMax')->value('value'),
+            'cpuWeight'  => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuWeight')->value('value'),
+            'cpuUsed'    => 0,
+            'cpuFree'    => null,
+            'diskUsed'   => 0,
+            'diskMax'    => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskMax')->value('value'),
+            'diskWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskWeight')->value('value'),
+            'diskFree'   => null,
+            'ramMax'     => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramMax')->value('value'),
+            'ramWeight'  => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramWeight')->value('value'),
+            'ramUsed'    => 0,
+            'ramFree'    => 0,
+        ];
+        if ($nodesResurce['diskMax'])
+        {
+            $nodesResurce['diskMax'] = $nodesResurce['diskMax'] * pow(1024, 3);
+        }
+        if ($nodesResurce['ramMax'])
+        {
+            $nodesResurce['ramMax'] = $nodesResurce['ramMax'] * pow(1024, 3);
+        }
+        return $nodesResurce;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByNode($node, $vmCreate = true)
+    {
+        $this->filter['node'] = $node;
+        $this->usage = false;
+        //Node settings
+        $this->nodesResurces = [];
+        $query               = NodeSetting::ofServer($this->serverId)
+            ->ofNode($node)
+            ->ofSetting('vmCreate');
+        if ($vmCreate)
+        {
+            $query->ofValue(1);
+        }
+        if(!$query->count()){
+            $entity = new NodeSetting();
+            $entity->node = $node;
+            $entity->server_id = $this->serverId;
+            $entity->setting =  'vmsMax';
+            $entity->value = "";
+            $this->nodesResurces[] = $this->format($entity);
+        }
+        foreach ($query->get() as $entity)
+        {
+            $this->nodesResurces[] = $this->format($entity);
+        }
+
+        return clone $this;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findNotUser($userId)
+    {
+        $this->filter['notUserId'] = $userId;
+        foreach ($this->nodesResurces as $k => $nodeResurce)
+        {
+            if ($this->hasNode($userId, $nodeResurce['node']))
+            {
+                unset($this->nodesResurces[$k]);
+            }
+        }
+        return clone $this;
+    }
+
+    private function hasNode($userId, $node)
+    {
+        $total = Hosting::ofUserId($userId)
+            ->ofServerId($this->serverId)
+            ->active()
+            ->ofCustomFieldNode($node)
+            ->count();
+        try
+        {
+            $total += Hosting::ofUserId($userId)
+                ->ofServerId($this->serverId)
+                ->active()
+                ->ofvServerNode($node)
+                ->count();
+        }
+        catch (\Exception $ex)
+        {//Table does not exist
+        }
+        return $total > 0;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByRam($bytes)
+    {
+        $this->filter['ram'] = $bytes;
+        $this->usage();
+        foreach ($this->nodesResurces as $k => $nodeResurce)
+        {
+            if (!is_null($nodeResurce['ramFree']) && $nodeResurce['ramFree'] < $bytes)
+            {
+                unset($this->nodesResurces[$k]);
+            }
+        }
+        return clone $this;
+    }
+
+    private function usage()
+    {
+        if ($this->usage)
+        {
+            return true;
+        }
+        if (!$this->api instanceof \MGProvision\Proxmox\v2\Api)
+        {
+            throw new \Exception("API instance is not initialized");
+        }
+        $nodes = [];
+        foreach ($this->api->get("/nodes") as $node)
+        {
+            if ($node['status'] != "online")
+            {
+                continue;
+            }
+            $nodes[] = $node['node'];
+        }
+        foreach ($this->nodesResurces as $k => &$nodeResurce)
+        {
+            if (!in_array($nodeResurce['node'], $nodes))
+            {
+                continue;
+            }
+            //kvm
+            $qemu = $this->api->get("/nodes/{$nodeResurce['node']}/qemu");
+            foreach ($qemu as $record)
+            {
+                if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid']))
+                {
+                    continue;
+                }
+                $vm = new Kvm($nodeResurce['node'], $record['vmid']);
+                $vm->setApi($this->api);
+                foreach ($vm->getHardDisks() as $hardDisk)
+                {
+                    $nodeResurce['diskUsed'] += $hardDisk->getBytes();
+                }
+                $nodeResurce['ramUsed'] += $record['maxmem'];
+                $nodeResurce['vmsUsed']++;
+                $nodeResurce['cpuUsed'] += $record['cpus'];
+            }
+            unset($qemu, $record, $vm);
+            //lxc
+            $lxc = $this->api->get("/nodes/{$nodeResurce['node']}/lxc");
+            foreach ($lxc as $record)
+            {
+                if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid']))
+                {
+                    continue;
+                }
+                $vm = new Lxc($nodeResurce['node'], $record['vmid']);
+                $vm->setApi($this->api);
+                foreach ($vm->getMounPoints()->fetch() as $mounPoint)
+                {
+                    /*@var $mounPoint MountPoint */
+                    $nodeResurce['diskUsed'] += $mounPoint->getBytes();
+                }
+                $nodeResurce['ramUsed'] += $record['maxmem'];
+                $nodeResurce['vmsUsed']++;
+                $nodeResurce['cpuUsed'] += $record['cpus'];
+            }
+            unset($lxc, $record, $vm);
+        }
+        //calculate free resurces
+        foreach ($this->nodesResurces as $k => &$nodeResurce)
+        {
+            $nodeResurce['ramFree']  = $nodeResurce['ramMax'] - $nodeResurce['ramUsed'];
+            $nodeResurce['vmsFree']  = $nodeResurce['vmsMax'] - $nodeResurce['vmsUsed'];
+            $nodeResurce['cpuFree']  = $nodeResurce['cpuMax'] - $nodeResurce['cpuUsed'];
+            $nodeResurce['diskFree'] = $nodeResurce['diskMax'] - $nodeResurce['diskUsed'];
+        }
+        $this->usage = true;
+    }
+
+    private function hasVmid($vmid)
+    {
+        $total = Hosting::ofServerId($this->serverId)
+            ->ofStatus(['Active', 'Suspended'])
+            ->ofCustomFielVmid($vmid)
+            ->count();
+        try
+        {
+            $total += Hosting::ofServerId($this->serverId)
+                ->active()
+                ->ofvServerVmid($vmid)
+                ->count();
+        }
+        catch (\Exception $ex)
+        {//Table does not exist
+        }
+        return $total > 0;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByDisk($bytes)
+    {
+        $this->filter['disk'] = $bytes;
+        $this->usage();
+        foreach ($this->nodesResurces as $k => $nodeResurce)
+        {
+            if (!is_null($nodeResurce['diskFree']) && $nodeResurce['diskFree'] < $bytes)
+            {
+                unset($this->nodesResurces[$k]);
+            }
+        }
+        return clone $this;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByCpu($cpu)
+    {
+        $this->filter['cpu'] = $cpu;
+        $this->usage();
+        foreach ($this->nodesResurces as $k => $nodeResurce)
+        {
+            if (!is_null($nodeResurce['cpuFree']) && $nodeResurce['cpuFree'] < $cpu)
+            {
+                unset($this->nodesResurces[$k]);
+            }
+        }
+        return clone $this;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function findByVms($number)
+    {
+        $this->filter['vms'] = $number;
+        $this->usage();
+        foreach ($this->nodesResurces as $k => $nodeResurce)
+        {
+            if (!is_null($nodeResurce['vmsFree']) && $nodeResurce['vmsFree'] < $number)
+            {
+                unset($this->nodesResurces[$k]);
+            }
+        }
+        return clone $this;
+    }
+
+    public function getApi()
+    {
+        return $this->api;
+    }
+
+    public function setApi(\MGProvision\Proxmox\v2\Api $api)
+    {
+        $this->api = $api;
+        return $this;
+    }
+
+    public function getNodesResurces()
+    {
+        $this->usage();
+        return $this->nodesResurces;
+    }
+
+    public function count()
+    {
+        return count($this->nodesResurces);
+    }
+
+    public function isEmpty()
+    {
+        return empty($this->nodesResurces);
+    }
+
+    public function nodeLowLoad()
+    {
+        $this->usage();
+        $weight           = Settings::where("setting", 'vmsWeight')->value("value");
+        $this->vmsWeight  = $weight ? $weight : 1;
+        $weight           = Settings::where("setting", 'cpuWeight')->value("value");
+        $this->cpuWeight  = $weight ? $weight : 1;
+        $weight           = Settings::where("setting", 'ramWeight')->value("value");
+        $this->ramWeight  = $weight ? $weight : 1;
+        $weight           = Settings::where("setting", 'diskWeight')->value("value");
+        $this->diskWeight = $weight ? $weight : 1;
+
+        $nodes = [];
+        //By ram
+        $nodes = $this->nodesUsage($nodes, $this->orderByRamUsage(), $this->ramWeight);
+        //By cpu
+        $nodes = $this->nodesUsage($nodes, $this->orderByCpuUsage(), $this->cpuWeight);
+        //By Disk
+        $nodes = $this->nodesUsage($nodes, $this->orderByDiskUsage(), $this->diskWeight);
+        //By Vms
+        $nodes = $this->nodesUsage($nodes, $this->orderByVmsUsage(), $this->vmsWeight);
+
+        if (empty($nodes))
+        {
+            throw new \Exception("Load Balancer: Cannot find node with free resources");
+        }
+
+        asort($nodes);
+        $nodes = array_reverse($nodes);
+
+        return key($nodes);
+    }
+
+    private function nodesUsage($nodes, $values, $weight = 1)
+    {
+        foreach (array_reverse($values) as $index => $nodeName)
+        {
+            if (!array_key_exists($nodeName, $nodes))
+            {
+                $nodes[$nodeName] = 0;
+            }
+
+            $nodes[$nodeName] += ($index + 1) * $weight; // we don't want zero value so we add "1"
+        }
+
+
+        return $nodes;
+    }
+
+    /**
+     * @return LoadBalancerService
+     */
+    public function orderByRamUsage()
+    {
+        return $this->orderBy('ramUsed');
+    }
+
+    private function orderBy($column)
+    {
+        $this->usage();
+        $column = array_column($this->nodesResurces, $column);
+        array_multisort($column, SORT_ASC, $this->nodesResurces);
+        $nodes = [];
+        foreach ($column as $k => $v)
+        {
+            $nodes[] = $this->nodesResurces[$k]['node'];
+        }
+        return $nodes;
+    }
+
+    public function orderByCpuUsage()
+    {
+        return $this->orderBy('ramUsed');
+    }
+
+    public function orderByDiskUsage()
+    {
+        return $this->orderBy('vmsUsed');
+    }
+
+    public function orderByVmsUsage()
+    {
+        return $this->orderBy('vmsUsed');
+    }
+
+
+}

+ 70 - 0
app/Services/RecoveryVmDumpService.php

@@ -0,0 +1,70 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Jan 23, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+use ModulesGarden\ProxmoxAddon\App\Models\RecoveryVm;
+
+/**
+ * Description of RecoveryVmDumpService
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RecoveryVmDumpService
+{
+
+    public function generate()
+    {
+        $data   = [];
+        $date   = date("Y-m-d H:i:s");
+        $data[] = 'Recovery VM Dump: ' . $date;
+        $data[] = "";
+        foreach (RecoveryVm::get() as $recoveryVm)
+        {
+            /* @var $recoveryVm RecoveryVm */
+            try
+            {
+                $data[] = "[ID] #{$recoveryVm->id}";
+                $data[] = "[LAST UPDATE] {$recoveryVm->last_update}";
+                $data[] = "[HOSTING ID] #{$recoveryVm->service_id}";
+                $data[] = "[CLIENT] #{$recoveryVm->client_id} {$recoveryVm->hosting->client->firstname} {$recoveryVm->hosting->client->lastname}";
+                $data[] = "[NODE] {$recoveryVm->node}";
+                $data[] = "[VMID] {$recoveryVm->vmid}";
+                $data[] = "[VIRTUALIZATION] {$recoveryVm->virtualization}";
+                $data[] = "[CONFIG]: ";
+                $data[] = $recoveryVm->config;
+                $data[] = "";
+                $data[] = "[STATUS]: ";
+                $data[] = $recoveryVm->status;
+                $data[] = "";
+                $data[] = "[DNS]: ";
+                $data[] = $recoveryVm->dns;
+                $data[] = "";
+                $data[] = "-------------------------------------------------------------------";
+                $data[] = "";
+            }
+            catch (\Exception $ex)
+            {
+            }
+        }
+        return implode("\r\n", $data);
+    }
+
+}

+ 55 - 0
app/Services/UrlService.php

@@ -0,0 +1,55 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\isAdmin;
+
+class UrlService
+{
+    use WhmcsParams;
+
+    public function getUrl($controller = null, $action = null, array $params = [])
+    {
+        $url = 'clientarea.php?action=productdetails&id=' . $this->getWhmcsParamByKey('serviceid');
+        if ($controller)
+        {
+            $params['modop']   = 'custom';
+            $params['a']       = 'management';
+            $params['mg-page'] = $controller;
+            if ($action)
+            {
+                $params['mg-action'] = $action;
+            }
+            if ($params)
+            {
+                $url .= '&' . http_build_query($params);
+            }
+        }
+        if (isAdmin())
+        {
+            return '../dologin.php?username=' . urlencode($this->getWhmcsParamByKey('clientsdetails')['email']) . '&goto=' . urlencode($url);
+        }
+        return $url;
+    }
+
+}

+ 225 - 0
app/Services/Utility.php

@@ -0,0 +1,225 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxVPS product developed. (2016-12-15)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+use Illuminate\Database\Capsule\Manager as DB;
+
+/**
+ * Description of Utility
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ * @version 1.0.0
+ */
+class Utility
+{
+
+    public static function unitFormat(&$value, $inUnit, $outUnit)
+    {
+        if (!in_array($inUnit, ['bytes', 'mb', 'gb', 'tb'])) {
+            $debug = debug_backtrace();
+            throw new \Exception(sprintf("Unit value ('%s') is invalid. File: %s:%s", $inUnit, $debug[0]['file'], $debug[0]['line']));
+        }
+
+        if (!in_array($outUnit, ['mb', 'gb', 'bytes'])) {
+            $debug = debug_backtrace();
+            throw new \Exception(sprintf("Unit value ('%s') is invalid. File: %s:%s", $outUnit, $debug[0]['file'], $debug[0]['line']));
+        }
+
+        if ($value == 0) {
+            return;
+        }
+        if (empty($value) || !is_numeric($value)) {
+            $debug = debug_backtrace();
+            throw new \Exception(sprintf("Unit value ('%s') is invalid. File: %s:%s", $value, $debug[0]['file'], $debug[0]['line']));
+        }
+
+        if ($inUnit == 'mb' && $outUnit == 'gb' && $value < 1024) {
+            $debug = debug_backtrace();
+            throw new \Exception(sprintf("Unit value %sMB is smaller than 1 GB. File: %s:%s", $value, $debug[0]['file'], $debug[0]['line']));
+        }
+
+        if ($inUnit == $outUnit) {
+            return;
+        } else if ($inUnit == 'bytes' && $outUnit == 'mb') {
+            $value /= pow(1024, 2);
+            $value = round($value);
+        } else if ($inUnit == 'bytes' && $outUnit == 'gb') {
+            $value /= pow(1024, 3);
+            $value = round($value);
+        } else if ($inUnit == 'mb' && $outUnit == 'gb') {
+            $value = ceil($value / 1024);
+        } else if ($inUnit == 'gb' && $outUnit == 'mb') {
+            $value *= 1024;
+        } else if ($inUnit == 'gb' && $outUnit == 'bytes') {
+            $value *= pow(1024, 3);
+        } else if ($inUnit == 'mb' && $outUnit == 'bytes') {
+            $value *= pow(1024, 2);
+        }else if ($inUnit == 'tb' && $outUnit == 'gb') {
+            $value *= 1024;
+        }
+
+    }
+
+    static function timeStamp($strTime = 'now')
+    {
+        return date('Y-m-d H:i:s', strtotime($strTime));
+    }
+
+    static function obClean()
+    {
+        $outputBuffering = ob_get_contents();
+        if ($outputBuffering !== false) {
+            if (!empty($outputBuffering)) {
+                ob_clean();
+            } else {
+                ob_start();
+            }
+        }
+        http_response_code(200);
+    }
+
+    /**
+     * FUNCTION MG_uptime
+     * Calculate uptime
+     * @param int $uptime
+     * @return boolean
+     */
+    public static function uptime($uptime)
+    {
+        if (!$uptime) {
+            return false;
+        }
+        $days = floor($uptime / 60 / 60 / 24);
+        $hours = $uptime / 60 / 60 % 24;
+        $mins = $uptime / 60 % 60;
+        $secs = $uptime % 60;
+        $hours = ($hours < 10) ? "0" . $hours : $hours;
+        $mins = ($mins < 10) ? "0" . $mins : $mins;
+        $secs = ($secs < 10) ? "0" . $secs : $secs;
+        if ($days) {
+            return "{$days} days $hours:$mins:$secs";
+        } else {
+            return "$hours:$mins:$secs";
+        }
+    }
+
+    public static function generatePassword($length = 8, $chars = "")
+    {
+        if (!$chars) {
+            $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+        }
+        $count = mb_strlen($chars);
+        for ($i = 0, $result = ''; $i < $length; $i++) {
+            $index = rand(0, $count - 1);
+            $result .= mb_substr($chars, $index, 1);
+        }
+        return $result;
+    }
+
+    public static function passwordHash($password){
+        $salt = self::generatePassword();
+        return crypt($password, "\$5\$$salt\$");
+    }
+
+
+    public static function isAddon($name)
+    {
+        if (DB::table('tbladdonmodules')->where("module", $name)->count()) {
+            $file = ROOTDIR . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'addons' . DIRECTORY_SEPARATOR . $name . DIRECTORY_SEPARATOR . $name . '.php';
+            return file_exists($file);
+        }
+        return false;
+    }
+
+    static function isIpManagerProxmoxVPSIntegration()
+    {
+        if (!self::isAddon('ipmanager2')) {
+            return false;
+        }
+        return DB::table('ip_manager_modules')->where("modulename", "ProxmoxVPSIntegration")->where("enabled", "1")->count();
+    }
+
+    static function isIpManagerProxmoxCloudIntegration()
+    {
+        if (!self::isAddon('ipmanager2')) {
+            return false;
+        }
+        return DB::table('ip_manager_modules')->where("modulename", "ProxmoxCloudIntegration")->where("enabled", "1")->count();
+    }
+
+    static function replaceSpecialChars($string)
+    {
+        $replace = [
+            '&lt;' => '', '&gt;' => '', '&#039;' => '', '&amp;' => '',
+            '&quot;' => '', 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'Ae',
+            '&Auml;' => 'A', 'Å' => 'A', 'Ā' => 'A', 'Ą' => 'A', 'Ă' => 'A', 'Æ' => 'Ae',
+            'Ç' => 'C', 'Ć' => 'C', 'Č' => 'C', 'Ĉ' => 'C', 'Ċ' => 'C', 'Ď' => 'D', 'Đ' => 'D',
+            'Ð' => 'D', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ē' => 'E',
+            'Ę' => 'E', 'Ě' => 'E', 'Ĕ' => 'E', 'Ė' => 'E', 'Ĝ' => 'G', 'Ğ' => 'G',
+            'Ġ' => 'G', 'Ģ' => 'G', 'Ĥ' => 'H', 'Ħ' => 'H', 'Ì' => 'I', 'Í' => 'I',
+            'Î' => 'I', 'Ï' => 'I', 'Ī' => 'I', 'Ĩ' => 'I', 'Ĭ' => 'I', 'Į' => 'I',
+            'İ' => 'I', 'IJ' => 'IJ', 'Ĵ' => 'J', 'Ķ' => 'K', 'Ł' => 'K', 'Ľ' => 'K',
+            'Ĺ' => 'K', 'Ļ' => 'K', 'Ŀ' => 'K', 'Ñ' => 'N', 'Ń' => 'N', 'Ň' => 'N',
+            'Ņ' => 'N', 'Ŋ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O',
+            'Ö' => 'Oe', '&Ouml;' => 'Oe', 'Ø' => 'O', 'Ō' => 'O', 'Ő' => 'O', 'Ŏ' => 'O',
+            'Œ' => 'OE', 'Ŕ' => 'R', 'Ř' => 'R', 'Ŗ' => 'R', 'Ś' => 'S', 'Š' => 'S',
+            'Ş' => 'S', 'Ŝ' => 'S', 'Ș' => 'S', 'Ť' => 'T', 'Ţ' => 'T', 'Ŧ' => 'T',
+            'Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'Ue', 'Ū' => 'U',
+            '&Uuml;' => 'Ue', 'Ů' => 'U', 'Ű' => 'U', 'Ŭ' => 'U', 'Ũ' => 'U', 'Ų' => 'U',
+            'Ŵ' => 'W', 'Ý' => 'Y', 'Ŷ' => 'Y', 'Ÿ' => 'Y', 'Ź' => 'Z', 'Ž' => 'Z',
+            'Ż' => 'Z', 'Þ' => 'T', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a',
+            'ä' => 'ae', '&auml;' => 'ae', 'å' => 'a', 'ā' => 'a', 'ą' => 'a', 'ă' => 'a',
+            'æ' => 'ae', 'ç' => 'c', 'ć' => 'c', 'č' => 'c', 'ĉ' => 'c', 'ċ' => 'c',
+            'ď' => 'd', 'đ' => 'd', 'ð' => 'd', 'è' => 'e', 'é' => 'e', 'ê' => 'e',
+            'ë' => 'e', 'ē' => 'e', 'ę' => 'e', 'ě' => 'e', 'ĕ' => 'e', 'ė' => 'e',
+            'ƒ' => 'f', 'ĝ' => 'g', 'ğ' => 'g', 'ġ' => 'g', 'ģ' => 'g', 'ĥ' => 'h',
+            'ħ' => 'h', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ī' => 'i',
+            'ĩ' => 'i', 'ĭ' => 'i', 'į' => 'i', 'ı' => 'i', 'ij' => 'ij', 'ĵ' => 'j',
+            'ķ' => 'k', 'ĸ' => 'k', 'ł' => 'l', 'ľ' => 'l', 'ĺ' => 'l', 'ļ' => 'l',
+            'ŀ' => 'l', 'ñ' => 'n', 'ń' => 'n', 'ň' => 'n', 'ņ' => 'n', 'ʼn' => 'n',
+            'ŋ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'oe',
+            '&ouml;' => 'oe', 'ø' => 'o', 'ō' => 'o', 'ő' => 'o', 'ŏ' => 'o', 'œ' => 'oe',
+            'ŕ' => 'r', 'ř' => 'r', 'ŗ' => 'r', 'š' => 's', 'ù' => 'u', 'ú' => 'u',
+            'û' => 'u', 'ü' => 'ue', 'ū' => 'u', '&uuml;' => 'ue', 'ů' => 'u', 'ű' => 'u',
+            'ŭ' => 'u', 'ũ' => 'u', 'ų' => 'u', 'ŵ' => 'w', 'ý' => 'y', 'ÿ' => 'y',
+            'ŷ' => 'y', 'ž' => 'z', 'ż' => 'z', 'ź' => 'z', 'þ' => 't', 'ß' => 'ss',
+            'ſ' => 'ss', 'ый' => 'iy', 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G',
+            'Д' => 'D', 'Е' => 'E', 'Ё' => 'YO', 'Ж' => 'ZH', 'З' => 'Z', 'И' => 'I',
+            'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O',
+            'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F',
+            'Х' => 'H', 'Ц' => 'C', 'Ч' => 'CH', 'Ш' => 'SH', 'Щ' => 'SCH', 'Ъ' => '',
+            'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'YU', 'Я' => 'YA', 'а' => 'a',
+            'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo',
+            'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'y', 'к' => 'k', 'л' => 'l',
+            'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's',
+            'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'ch',
+            'ш' => 'sh', 'щ' => 'sch', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e',
+            'ю' => 'yu', 'я' => 'ya'
+        ];
+        return str_replace(array_keys($replace), $replace, $string);
+    }
+
+    public static function cpuUsage($usege)
+    {
+        $usege *= 100;
+        return number_format($usege, 2, '.', '');
+    }
+}

+ 68 - 0
app/Services/Vm.php

@@ -0,0 +1,68 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services;
+
+
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+
+class Vm
+{
+    /**
+     * @var Kvm|Lxc
+     */
+    protected $vm;
+    /**
+     * @var VmModel
+     */
+    protected $vmModel;
+
+    /**
+     * @return Kvm|Lxc
+     */
+    public function getVm()
+    {
+        return $this->vm;
+    }
+
+    /**
+     * @param Kvm|Lxc $vm
+     * @return Vm
+     */
+    public function setVm($vm)
+    {
+        $this->vm = $vm;
+        return $this;
+    }
+
+    public function hasVm(){
+        return $this->vm != null;
+    }
+
+    /**
+     * @return VmModel
+     */
+    public function getVmModel()
+    {
+
+        if(is_null( $this->vmModel)){
+            throw new \InvalidArgumentException('VmModel not set');
+        }
+        return $this->vmModel;
+    }
+
+    /**
+     * @param VmModel $vmModel
+     * @return $this
+     */
+    public function setVmModel(VmModel $vmModel)
+    {
+        $this->vmModel = $vmModel;
+        return $this;
+    }
+
+
+
+}

+ 130 - 0
app/Services/Vps/AclService.php

@@ -0,0 +1,130 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class AclService
+{
+    use ProductService;
+    use WhmcsParams;
+
+    public function backup()
+    {
+        if (!$this->configuration()->isPermissionBackup() && !$this->configuration()->isPermissionBackupJob())
+        {
+            throw new \Exception(sl("lang")->tr("No access to backup"));
+        }
+    }
+
+    public function backupJob()
+    {
+        if (!$this->configuration()->isPermissionBackupJob())
+        {
+            throw new \Exception(sl("lang")->tr("No access to backup job"));
+        }
+    }
+
+    public function novnc()
+    {
+        if (!$this->configuration()->isPermissionNovnc())
+        {
+            throw new \Exception(sl("lang")->tr("No access to noVNC console"));
+        }
+    }
+
+    public function xtermjs()
+    {
+        if (!$this->configuration()->isPermissionXtermjs())
+        {
+            throw new \Exception(sl("lang")->tr("No access to Xterm.js console"));
+        }
+    }
+
+    public function spice()
+    {
+        if (!$this->configuration()->isPermissionSpice())
+        {
+            throw new \Exception(sl("lang")->tr("No access to spice console"));
+        }
+    }
+
+    public function disk()
+    {
+        if (!$this->configuration()->isPermissionDisk())
+        {
+            throw new \Exception(sl("lang")->tr("No access to disk"));
+        }
+    }
+
+    public function firewall()
+    {
+        if (!$this->configuration()->isPermissionFirewall())
+        {
+            throw new \Exception(sl("lang")->tr("No access to firewall"));
+        }
+    }
+
+    public function firewallOption()
+    {
+        if (!$this->configuration()->isPermissionFirewallOption())
+        {
+            throw new \Exception(sl("lang")->tr("No access to firewall option"));
+        }
+    }
+
+    public function graph()
+    {
+        if (!$this->configuration()->isPermissionGraph())
+        {
+            throw new \Exception(sl("lang")->tr("No access to graph"));
+        }
+    }
+
+    public function network()
+    {
+        if (!$this->configuration()->isPermissionNetwork())
+        {
+            throw new \Exception(sl("lang")->tr("No access to network"));
+        }
+    }
+
+    public function reinstall()
+    {
+        if (!$this->configuration()->isPermissionReinstall())
+        {
+            throw new \Exception(sl("lang")->tr("No access to reinstall"));
+        }
+    }
+
+    public function snapshot()
+    {
+        if (!$this->configuration()->isPermissionSnapshot())
+        {
+            throw new \Exception(sl("lang")->tr("No access to snapshot"));
+        }
+    }
+
+    public function taskHistory()
+    {
+        if (!$this->configuration()->isPermissionTaskHistory())
+        {
+            throw new \Exception(sl("lang")->tr("No access to task history"));
+        }
+    }
+
+    public function additionalDiskBackup()
+    {
+        if (!$this->configuration()->isPermissionAdditionalDiskBackup())
+        {
+            throw new \Exception(sl("lang")->tr("No access to additional disk backup"));
+        }
+    }
+
+    public function hasFirewallOption($option)
+    {
+        return in_array( $option, $this->configuration()->getPermissionFirewalOptions());
+    }
+}

+ 102 - 0
app/Services/Vps/AdditionalDiskService.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use MGProvision\Proxmox\v2\models\HardDisk;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class AdditionalDiskService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function hasDisk(){
+        foreach ($this->vm()->getHardDisks() as $hardDisk)
+        {
+            if (!$hardDisk->isMaster())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public function create(){
+        $diskSize = $this->getSize();
+        if(!$diskSize || $diskSize == "-1" ){
+            return;
+        }
+        $formats = $this->configuration()->getAdditionalDiskFormat();
+        $typeArray = $this->configuration()->getAdditionalDiskType();
+        $type = $typeArray[0];
+        foreach($typeArray as $t){
+            $bus = $this->vm()->findFreeDeviceId($t);
+            if($bus===null){
+                $type = $t;
+                break;
+            }
+        }
+        $harDisk = new HardDisk($type . $bus);
+        $harDisk->setSize($diskSize)
+            ->setApi($this->api())
+            ->setPath($this->vm()->getPath() . "/config")
+            ->setMedia("disk")
+            ->setBackup( 0)//off
+            ->setStorage($this->configuration()->getAdditionalDiskStorage())
+            ->setCache($this->configuration()->getAdditionalDiskCache())
+            ->setFormat($formats[0])
+            ->setMbps_rd($this->configuration()->getAdditionalDiskMbpsRd())
+            ->setMbps_wr($this->configuration()->getAdditionalDiskMbpsRd())
+            ->setDiscard($this->configuration()->isAdditionalDiskDiscard() ? "on" : null)
+            ->setIops_rd($this->configuration()->getAdditionalDiskIopsRd())
+            ->setIops_rd_max($this->configuration()->getAdditionalDiskIopsRdMax())
+            ->setIops_wr($this->configuration()->getAdditionalDiskIopsWr())
+            ->setIops_wr_max($this->configuration()->getAdditionalDiskIopsWrMax())
+            ->setReplicate($this->configuration()->isAdditionalDiskReplicate() ? 0 : null);
+        if ($this->configuration()->isAdditionalDiskIoThread() && in_array($type, ['virtio', 'scsi']))
+        {
+            $harDisk->setIothread(1);
+        }
+        if($this->configuration()->isPermissionAdditionalDiskBackup()){
+            $harDisk->setBackup(null);//on
+        }
+        $harDisk->create();
+    }
+
+    public function resize(){
+        $diskSize = $this->getSize();
+        if(!$diskSize || $diskSize == "-1" ){
+            return;
+        }
+        foreach ($this->vm()->getHardDisks() as $hardDisk)
+        {
+            if ($hardDisk->isMaster())
+            {
+                continue;
+            }
+            if($hardDisk->getGb() == $diskSize){
+                return true;
+            }
+            if ( (int) $hardDisk->getGb() > (int) $diskSize){
+                throw new \InvalidArgumentException("Downgrading the additional disk size is restricted");
+            }
+            $size = "+" . abs((int)$diskSize - $hardDisk->getGb()) . "G";
+            $hardDisk->resize($size);
+            return true;
+        }
+    }
+    private function getSize(){
+        $diskSize = $this->configuration()->getAdditionalDiskSize();
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE))
+        {
+            $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($diskSize, $this->configuration()->getAdditionalDiskUnit(), 'gb');
+        }
+        return $diskSize;
+    }
+}

+ 81 - 0
app/Services/Vps/AdditionalMountPointService.php

@@ -0,0 +1,81 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use MGProvision\Proxmox\v2\models\MountPoint;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class AdditionalMountPointService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function hasMountPoint(){
+        foreach ($this->vm()->getMounPoints()->fetch() as $mounPoint){
+            if($mounPoint->isMaster()){
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public function create(){
+        $size = $this->getSize();
+        if(!$size ||   $size == "-1"){
+            return;
+        }
+        $storage = $this->configuration()->getMountPointStorage();
+        $mountPointRepository = $this->vm()->getMounPoints();
+        $hdd = new MountPoint($mountPointRepository->nextId());
+        $hdd->setLocation($storage . ":" . $size )
+            ->setAcl($this->configuration()->getMountPointAcl() == "default" ? null : $this->configuration()->getMountPointAcl())
+            ->setRo($this->configuration()->isMountPointRo() ? 1 : null)
+            ->setQuota($this->configuration()->isMountPointQuota() ? 1 : null)
+            ->setBackup(null)
+            ->setReplicate($this->configuration()->isMountPointReplicate() ? '0' : null)
+            ->setMp('/additional')
+            ->setPath($this->vm()->getPath() . '/config')
+            ->setApi($this->api())
+            ->create();
+        if($this->configuration()->isPermissionMountPointBackup()){
+            $hdd->setBackup(1);
+        }
+    }
+
+    private function getSize(){
+        $diskSize = $this->configuration()->getAdditionalDiskSize();
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE))
+        {
+            $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($diskSize, $this->configuration()->getAdditionalDiskUnit(), 'gb');
+        }
+        return $diskSize;
+    }
+
+    public function resize(){
+        $diskSize = $this->getSize();
+        if(!$diskSize || $diskSize == "-1" ){
+            return;
+        }
+        foreach ($this->vm()->getMounPoints()->fetch() as $mounPoint){
+            if($mounPoint->isMaster()){
+                continue;
+            }
+            if((int)$mounPoint->getGb() == (int) $diskSize ){
+                return true;
+            }
+            if((int)$mounPoint->getGb() > (int) $diskSize){
+                throw new \InvalidArgumentException("Downgrading the mount point size is restricted");
+            }
+            $size = "+" . abs((int)$diskSize - (int)$mounPoint->getGb()) . "G";
+            $mounPoint->resize($size);
+            return true;
+        }
+    }
+}

+ 141 - 0
app/Services/Vps/AgentService.php

@@ -0,0 +1,141 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class AgentService
+{
+    use WhmcsParams;
+    use HostingService;
+    use ApiService;
+    use ProductService;
+
+    private $networkInterfaces=[];
+    private $assignedIpAddresses=[];
+
+    public function isEnabled()
+    {
+        $configurationIsOn =  $this->configuration()->isAgent() && ( $this->configuration()->isAgentConfigureNetwork() || $this->configuration()->isAgentTemplateUser() || $this->configuration()->isAgentServicePassword());
+        $ostype = $this->vm()->config()['ostype'];
+        return $configurationIsOn &&  preg_match('/w/', $ostype);
+    }
+
+    public function getUserAndUpdate()
+    {
+        if(!$this->configuration()->isAgentTemplateUser()){
+            return;
+        }
+        $response = $this->vm()->agent()->getUsers();
+        if(!$response['result']['0']['user']){
+            throw  new ProxmoxApiException("OS user not found");
+        }
+        $user = $response['result']['0']['user'];
+        $this->hosting()->username = $user;
+        $this->hosting()->save();
+
+    }
+
+    public function passwordUpdate()
+    {
+        if(!$this->configuration()->isAgentServicePassword()){
+            return;
+        }
+        $this->vm()->agent()->setUserPassword($this->hosting()->username, $this->getWhmcsParamByKey("password"));
+
+    }
+
+    public function configureNetwork()
+    {
+        if(!$this->configuration()->isAgentConfigureNetwork()){
+            return;
+        }
+        $this->networkInterfaces = $this->vm()->agent()->networkGetInterfaces();
+        //remove
+        $this->deleteIpAddresses();
+        $ips = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                           ->orderByIdAsc();
+
+        foreach ($this->networkInterfaces['result'] as $net){
+            $networkName = $net['name'];
+            if(!$net['hardware-address'] || preg_match("/loopback/", strtolower($networkName) ) ){ //Loopback
+                continue;
+            }
+            /**
+             * Add IP Addresses
+             * @var $ip VmIpAddress
+             */
+            foreach ($ips->get() as $ip){
+                //exist?
+                if(in_array($ip->ip, (array) $this->assignedIpAddresses)){
+                    continue;
+                }
+                //IPV6
+                if(preg_match("/\:/", $ip->ip )){
+                    $command = sprintf("netsh interface ipv6 add address \"%s\" %s", $networkName,  $ip->ip);
+                    //IPV4
+                }else {
+                    $subnet = $ip->subnet_mask ? $ip->subnet_mask : IpAddressProvider::translateBitmaskToNetmask($ip->cidr);
+                    if($ip->ip == $this->hosting()->dedicatedip){
+                        $command = sprintf("netsh interface ipv4 set address name=\"%s\" static %s %s %s",$networkName, $ip->ip, $subnet, $ip->gateway);
+                    }else{
+                        $command = sprintf("netsh interface ipv4 add address name=\"%s\" %s %s %s",$networkName, $ip->ip, $subnet, $ip->gateway);
+                    }
+                }
+                $this->vm()->agent()->exec($command);
+                $this->assignedIpAddresses[] =  $ip->ip;
+                sleep(1);
+                if(!$this->configuration()->isOneNetworkDevice()){
+                    break;
+                }
+            }
+            //configure DNS
+            $ns1 = trim($this->hosting()->ns1);
+            $ns2 = trim($this->hosting()->ns2);
+            if (!empty($ns1) && filter_var($ns1, FILTER_VALIDATE_IP))
+            {
+                $command = sprintf("netsh interface ip set dns name=\"%s\" static %s",$networkName, $ns1);
+                $this->vm()->agent()->exec($command);
+                sleep(1);
+            }
+            if (!empty($ns2) && filter_var($ns2, FILTER_VALIDATE_IP))
+            {
+                $command = sprintf("netsh interface ip add dns name=\"%s\" %s index=2",$networkName, $ns2);
+                $this->vm()->agent()->exec($command);
+                sleep(1);
+            }
+        }
+    }
+
+    private function deleteIpAddresses(){
+        foreach ($this->networkInterfaces['result'] as $net){
+            foreach ($net['ip-addresses'] as $ipAddress)
+            {
+                if(!$net['hardware-address'] || preg_match("/loopback/", strtolower($net['name']) ) ){ //Loopback
+                    continue;
+                }
+
+                $ipAddress = $ipAddress['ip-address'];
+                $this->assignedIpAddresses[] = $ipAddress;
+                if(!$ipAddress  || VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))->ofIp($ipAddress)->count()){
+                    continue;
+                }
+                //IPV6
+                if(preg_match("/\:/", $ipAddress )){
+                    $command = sprintf("netsh interface ipv6 delete address %s",  $ipAddress);
+                    //IPV4
+                }else{
+                    $command = sprintf("netsh interface ipv4 delete address name=\"%s\" %s",  $net['name'],  $ipAddress);
+                }
+                $this->vm()->agent()->exec($command);
+                sleep(1);
+            }
+        }
+    }
+}

+ 135 - 0
app/Services/Vps/ClientAreaSidebarService.php

@@ -0,0 +1,135 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\Sidebar;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarItem;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarService;
+use ModulesGarden\Servers\ProxmoxVps\App\Http\Client\BaseClientController;
+use ModulesGarden\Servers\ProxmoxVps\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl;
+
+class ClientAreaSidebarService
+{
+    use HostingService;
+    use WhmcsParams;
+    use ProductService;
+    use BaseClientController;
+
+    /**
+     * @var \WHMCS\View\Menu\Item
+     */
+    private $primarySidebar;
+
+    /**
+     * ClientAreaSidebarService constructor.
+     * @param $hostingId
+     */
+    public function __construct($hostingId, \WHMCS\View\Menu\Item $primarySidebar)
+    {
+        $this->setHostingId($hostingId);
+        $this->primarySidebar = $primarySidebar;
+    }
+
+    public function informationReplaceUri()
+    {
+        $overview = $this->primarySidebar->getChild('Service Details Overview');
+        if (!is_a($overview, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel = $overview->getChild('Information');
+        if (!is_a($panel, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel->setUri("clientarea.php?action=productdetails&id={$this->getHostingId()}");
+        $panel->setAttributes([]);
+        return $this;
+    }
+
+    public function changePasswordReplaceUri()
+    {
+        $actions = $this->primarySidebar->getChild('Service Details Overview');
+        if (!is_a($actions, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel = $actions->getChild('Change Password');
+        if (!is_a($panel, '\WHMCS\View\Menu\Item'))
+        {
+            return;
+        }
+        $panel->setUri("clientarea.php?action=productdetails&id={$this->getHostingId()}#tabChangepw");
+        $panel->setAttributes([]);
+        return $this;
+    }
+
+    public function build()
+    {
+        /**
+         * @var $sidebarService SidebarService
+         */
+        $sidebarService = sl("sidebar");
+        $this->setProductId($this->hosting()->packageid);
+        /**
+         * @var $lang \ModulesGarden\Servers\ProxmoxVps\Core\Lang\Lang
+         */
+        $lang  = sl("lang");
+        $order = 671;
+        foreach ($sidebarService->get() as $sidebar)
+        {
+            /**
+             * @var Sidebar $sidebar
+             */
+            $newPanel = [
+                'label' => $lang->abtr($sidebar->getTitle()),
+                'order' => $order
+            ];
+            $order++;
+            $childPanel = $this->primarySidebar->addChild($sidebar->getName(), $newPanel);
+            foreach ($sidebar->getChildren() as $sidebarItem)
+            {
+                //acl
+                if ($sidebarItem->getName() == "backup" && !$this->configuration()->isPermissionBackup() && !$this->configuration()->isPermissionBackupJob())
+                {
+                    continue;
+                }
+                else
+                {
+                    if ($sidebarItem->getName() != "backup" && $this->configuration()->get("permission" . ucfirst($sidebarItem->getName())) != "on")
+                    {
+                        continue;
+                    }
+                }
+                /**
+                 * @var SidebarItem $sidebarItem
+                 */
+                $newItem = [
+                    'label'   => $lang->abtr($sidebarItem->getTitle()),
+                    'uri'     => str_replace('{$hostingId}', $this->getHostingId(), $sidebarItem->getHref()),
+                    'order'   => $sidebarItem->getOrder(),
+                    "current" => $sidebarItem->isActive()
+                ];
+                $childPanel->addChild($sidebarItem->getName(), $newItem);
+            }
+        }
+    }
+}

+ 106 - 0
app/Services/Vps/ContainerService.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use ModulesGarden\ProxmoxAddon\App\Models\KeyPair;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+
+
+class ContainerService
+{
+    use ApiService;
+    use WhmcsParams;
+    use HostingService;
+    use Smarty;
+    use ProductService;
+
+    public function hostname()
+    {
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        if ($this->configuration()->isQemu())
+        {
+            $domain    = $this->getWhmcsCustomField(CustomField::HOSTNAME,  $this->getWhmcsParamByKey('domain')) ;
+            $nameForVm = $this->configuration()->getClientNameForContainer();
+            if ($nameForVm == "emptyHostnameOnly" && empty($domain))
+            { // Yes  [only when hostname is empty]
+                $domain = $this->getWhmcsParamByKey('clientsdetails')['companyname'] ? $this->getWhmcsParamByKey('clientsdetails')['companyname'] : $this->getWhmcsParamByKey('clientsdetails')['lastname'] . $this->getWhmcsParamByKey('clientsdetails')['firstname'];
+                $domain = trim(preg_replace('/[^A-Za-z0-9\.]/', '', Utility::replaceSpecialChars($domain)), '.');
+                $this->hosting()->update(["domain" => $domain]);
+            }
+            else
+            {
+                if ($nameForVm == "overwriteHostname")
+                { //Yes [overwrite hostname]
+                    $domain = $this->getWhmcsParamByKey('clientsdetails')['companyname'] ? $this->getWhmcsParamByKey('clientsdetails')['companyname'] : $this->getWhmcsParamByKey('clientsdetails')['lastname'] . $this->getWhmcsParamByKey('clientsdetails')['firstname'];
+                    $domain = trim(preg_replace('/[^A-Za-z0-9\.]/', '', Utility::replaceSpecialChars($domain)), '.');
+                    $this->hosting()->update(["domain" => $domain]);
+                }
+                else
+                {
+                    if ($nameForVm == "overwriteHostnameWithPrefix" || $nameForVm == "0")
+                    { //NO
+                        if (empty($domain) || $nameForVm == "overwriteHostnameWithPrefix")
+                        {
+                            $domain = $this->configuration()->getContainerPrefix() . $this->getWhmcsParamByKey('serviceid');
+                            $this->hosting()->update(["domain" => $domain]);
+                        }
+                    }
+                }
+            }
+            return $domain;
+        }
+        else
+        {
+            if ($this->configuration()->isLxc())
+            {
+                if ($this->configuration()->isRandomHostname())
+                {
+                    $domain = parse_url($GLOBALS['CONFIG']['Domain']);
+                    return $this->getWhmcsParamByKey('serviceid') . '.' . $domain['host'];
+                }
+                else
+                {
+                    return  $this->getWhmcsCustomField(CustomField::HOSTNAME,  $this->getWhmcsParamByKey('domain'));
+                }
+            }
+        }
+    }
+
+    public function description()
+    {
+        if (!$this->params['model'])
+        {
+            $this->params['model'] = \WHMCS\Service\Service::where("id", $this->params['serviceid'])->first();
+        }
+        $vars = [
+            'client_name'  => $this->getWhmcsParamByKey('clientsdetails')['firstname'] . ' ' . $this->getWhmcsParamByKey('clientsdetails')['lastname'] . (($this->getWhmcsParamByKey('clientsdetails')['companyname']) ? (' (' . $this->getWhmcsParamByKey('clientsdetails')['companyname'] . ')') : ''),
+            'client_id'    => $this->getWhmcsParamByKey('userid'),
+            'client_email' => $this->getWhmcsParamByKey('clientsdetails')['email'],
+            'product_name' => $this->hosting()->product->name,
+            'product_id'   => $this->getWhmcsParamByKey('pid'),
+            'service_id'   => $this->getWhmcsParamByKey('serviceid'),
+            'service_domain'  => $this->hosting()->domain,
+            'service_dedicated_ip'  => $this->hosting()->dedicatedip,
+            'service_assigned_ips'  => $this->hosting()->assignedips
+        ];
+        return $this->getSmarty()->fetchString($this->configuration()->getDescription(), $vars);
+    }
+
+    /**
+     * @return KeyPair
+     */
+    public function makeKeyPairs()
+    {
+        //delete
+        KeyPair::ofHostingId($this->getWhmcsParamByKey('serviceid'))->delete();
+        $keyPairs = KeyPair::make();
+        $keyPairs->setHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->save();
+        return $keyPairs;
+    }
+
+}

+ 28 - 0
app/Services/Vps/FirewallOptionService.php

@@ -0,0 +1,28 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class FirewallOptionService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function update(){
+        $attributes = [
+            "enable" => $this->configuration()->isFirewalOptionEnable() ? 1 : 0,
+            "dhcp" => $this->configuration()->isFirewalOptionDhcp()? 1 : 0,
+            "ndp" => $this->configuration()->isFirewalOptionNdp()? 1 : 0,
+            "radv" => $this->configuration()->isFirewalOptionRadv() ? 1 : 0,
+            "macfilter" => $this->configuration()->isFirewalOptionMacfilter() ? 1 : 0,
+            "ipfilter" => $this->configuration()->isFirewalOptionIpfilter() ? 1 : 0,
+        ];
+        $this->vm()->firewallOptions()->setAttributes($attributes)->update();
+    }
+
+}

+ 57 - 0
app/Services/Vps/HighAvailabilityClusterService.php

@@ -0,0 +1,57 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use MGProvision\Proxmox\v2\models\HaResource;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+
+class HighAvailabilityClusterService
+{
+    use ProductService;
+    use ApiService;
+    use WhmcsParams;
+
+
+    public function delete()
+    {
+        $haResurce = new HaResource();
+        $haResurce->setSid($this->vm()->getVmid())
+            ->setType($this->vm()->getVirtualization() == "lxc" ? "ct" : "vm");
+        $haResurce->setApi($this->api());
+        if ($haResurce->exist())
+        {
+            $haResurce->delete();
+        }
+        return $this;
+    }
+
+    public function create()
+    {
+        $haResurce = new HaResource();
+        $haResurce->setSid($this->vm()->getVmid())
+            ->setState($this->configuration()->getClusterState())
+            ->setType($this->vm()->getVirtualization() == "lxc" ? "ct" : "vm")
+            ->setGroup($this->configuration()->getClusterGroup())
+            ->setMaxRelocate($this->configuration()->getClusterMaxRelocate())
+            ->setMaxRestart($this->configuration()->getClusterMaxRestart())
+            ->create();
+    }
+
+    public function exist(){
+        $haResurce = new HaResource();
+        $haResurce->setSid($this->vm()->getVmid())
+            ->setType($this->vm()->getVirtualization() == "lxc" ? "ct" : "vm");
+        $haResurce->setApi($this->api());
+        return $haResurce->exist();
+    }
+
+    public function isConfigured(){
+        return $this->configuration()->getClusterState() &&
+                is_numeric($this->configuration()->getClusterMaxRelocate()) &&
+                is_numeric($this->configuration()->getClusterMaxRestart());
+    }
+}

+ 148 - 0
app/Services/Vps/HostingService.php

@@ -0,0 +1,148 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomFieldValue;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+
+trait HostingService
+{
+
+    /**
+     * @return mixed
+     */
+    public function getHostingId()
+    {
+        if (!$this->hostingId)
+        {
+            $this->hostingId = $this->getWhmcsParamByKey("serviceid");
+        }
+        return $this->hostingId;
+    }
+
+    /**
+     * @param mixed $hostingId
+     */
+    public function setHostingId($hostingId)
+    {
+        $this->hostingId = $hostingId;
+        return $this;
+    }
+
+    /**
+     * @return Hosting
+     */
+    public function hosting()
+    {
+        if ($this->hosting instanceof Hosting)
+        {
+            return $this->hosting;
+        }
+        return $this->hosting = Hosting::where("id", $this->getHostingId())->firstOrFail();
+    }
+
+    public function isActive()
+    {
+        return Hosting::ofId($this->getHostingId())->active()->count() == 1;
+    }
+
+    public function isSupportedModule()
+    {
+        return Hosting::ofServerType($this->getHostingId(), "proxmoxVPS")->count() == 1;
+    }
+
+    private function getCustomFieldId($fieldName)
+    {
+        return CustomField::select("id")
+            ->where("type", "product")
+            ->where("relid", $this->hosting()->packageid)
+            ->where("fieldname", "like", $fieldName . "%")
+            ->value("id");
+    }
+
+    public function customFieldUpdate($name, $value = '')
+    {
+        $fieldId = $this->getCustomFieldId($name);
+        //Update
+        if (CustomFieldValue::where('fieldid', $fieldId)->where("relid", $this->getHostingId())->count())
+        {
+            return CustomFieldValue::where('fieldid', $fieldId)->where("relid", $this->getHostingId())->update(['value' => $value]);
+        }
+        //Create
+        $customFiledValue = new CustomFieldValue();
+        $customFiledValue->fill([
+            'fieldid' => $fieldId,
+            'relid'   => $this->getHostingId(),
+            'value'   => $value
+        ]);
+        return $customFiledValue->save();
+    }
+
+    public function saveUsageLimit()
+    {
+        //bandwidth
+        if ($this->getWhmcsConfigOption(ConfigurableOption::BANDWIDTH))
+        {
+            $bandwidthMb = $this->getWhmcsConfigOption(ConfigurableOption::BANDWIDTH);
+            Utility::unitFormat($bandwidthMb, "gb", 'mb');
+        }
+        else
+        {
+            if ($this->getWhmcsConfigOption('Bandwidth Limit'))
+            {
+                $bandwidthMb = $this->getWhmcsConfigOption('Bandwidth Limit');
+                Utility::unitFormat($bandwidthMb, "gb", 'mb');
+            }
+            else
+            {
+                if ($this->configuration()->getBandwidth())
+                {
+                    $bandwidthMb = $this->configuration()->getBandwidth();
+                    Utility::unitFormat($bandwidthMb, "gb", 'mb');
+                }
+            }
+        }
+        //disk
+        $diskMb = 0;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+        {
+            $diskMb = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+            Utility::unitFormat($diskMb, $this->configuration()->getDiskUnit(), 'mb');
+        }
+        else
+        {
+            if ($this->configuration()->getDiskSize())
+            {
+                $diskMb = $this->configuration()->getDiskSize();
+                Utility::unitFormat($diskMb, "gb", 'mb');
+            }
+        }
+        $this->hosting()->update(['disklimit' => $diskMb, "bwlimit" => $bandwidthMb, "lastupdate" => date("Y-m-d H:i:s")]);
+    }
+
+
+    public function isBandwidthOverageUsage()
+    {
+
+    }
+}

+ 89 - 0
app/Services/Vps/IpSetIpFilterService.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use MGProvision\Proxmox\v2\models\IpCidr;
+use MGProvision\Proxmox\v2\models\IpSet;
+use MGProvision\Proxmox\v2\repository\IpCidrRepository;
+use MGProvision\Proxmox\v2\repository\IpSetRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class IpSetIpFilterService
+{
+    use WhmcsParams;
+    use ApiService;
+    use ProductService;
+
+    public function create()
+    {
+        $networkDevices = $this->vm()->getNetworkDevices();
+        if (count($networkDevices) <= 0)
+        {
+            return $this;
+        }
+        $ipCurrent = [];
+        foreach ($networkDevices as $nd)
+        {
+            //Read
+            $ipSetRep = new IpSetRepository();
+            $ipSetRep->setApi($this->api());
+            $path = $this->vm()->getPath() . "/" . IpSet::DEFAULT_PATH;
+            $ipSetRep->findByPath($path);
+            $ipSet = $ipSetRep->find((new IpSet)->setName('ipfilter-' . $nd->getId()));
+            if (!$ipSet)
+            {
+                $ipSet = (new IpSet($this->vm()->getPath() . "/" . IpSet::DEFAULT_PATH))
+                    ->setApi($this->api())
+                    ->setName('ipfilter-' . $nd->getId())
+                    ->setComment('only allow specified IPs on ' . $nd->getId())
+                    ->create();
+            }
+            //Add
+            $query = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                ->ofNet($nd->getId());
+
+            foreach ($query->get() as $ip)
+            {
+                /* @var VmIpAddress $ip */
+                $ipCurrent[] = $ip->ip;
+                $ipCirRep    = new IpCidrRepository();
+                $ipCirRep->findByPath($ipSet->getPath());
+                if ($ipCirRep->find((new IpCidr)->setCidr($ip->ip)))
+                {
+                    continue;
+                }
+                /* @var $ip  main\models\IpAddress */
+                $ipCird = new IpCidr($ipSet->getPath());
+                $ipCird->setApi($this->api());
+                $ipCird->setCidr($ip->ip)->setComment($nd->getId())->create();
+            }
+        }
+        unset($networkDevices, $ipSetRep, $ipSet, $ipCirRep);
+        //Delete
+        foreach ($this->vm()->getIpSet() as $ipSet)
+        {
+            $ipCidrs = $ipSet->getIpCidr();
+            foreach ($ipCidrs as $key => $ipCidr)
+            {
+                if (in_array($ipCidr->getCidr(), $ipCurrent))
+                {
+                    continue;
+                }
+                $query = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                    ->ofIp($ipCidr->getCidr());
+                if ($query->count() < 1)
+                {
+                    $ipCidr->delete();
+                    unset($ipCidrs[$key]);
+                    if (count($ipCidrs) == 0)
+                    {
+                        $ipSet->delete();
+                    }
+                }
+            }
+        }
+        return $this;
+    }
+}

+ 794 - 0
app/Services/Vps/NetworkService.php

@@ -0,0 +1,794 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\Api;
+use MGProvision\Proxmox\v2\models\Config;
+use MGProvision\Proxmox\v2\models\IpConfig;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceKvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceLxc;
+use ModulesGarden\ProxmoxAddon\App\Models\IpAddress;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class NetworkService
+{
+    use WhmcsParams;
+    use HostingService;
+    use ApiService;
+    use ProductService;
+    protected $deletePrivateNetwork = false;
+
+    /**
+     * @param VmIpAddress[] $enteries
+     */
+    public function create(array $enteries)
+    {
+        //to do acl
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        DB::beginTransaction();
+
+        try
+        {
+            foreach ($enteries as &$ip)
+            {
+                //insert to data base
+                $ip->save();
+                $this->hosting()->ipAdd($ip->ip);
+                //lock ip in proxmox addon
+                if (!Utility::isIpManagerProxmoxVPSIntegration())
+                {
+                    IpAddress::where('ip', $ip->ip)
+                        ->update(["hosting_id" => $ip->hosting_id]);
+                }
+            }
+            //update hosting
+            $this->hosting()->save();
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            throw $ex;
+        }
+    }
+
+    /**
+     * @param VmIpAddress[] $enteries
+     */
+    public function addPrivate(array $enteries)
+    {
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        $container = [];
+        $rate      = null;
+        $tag       = $this->makeTag();
+        if ($this->isWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE);
+        }
+        else
+        {
+            if ($this->configuration()->getRate())
+            {
+                $rate = $this->configuration()->getRate();
+            }
+        }
+        //cloud init
+        $isCloudInit = $this->configuration()->isQemu() && $this->configuration()->isCloudInit();
+        try
+        {
+            DB::beginTransaction();
+            Api::beginTransaction();
+            foreach ($enteries as &$ip)
+            {
+                //insert to data base
+                $ip->save();
+                $this->hosting()->ipAdd($ip->ip);
+                //lock ip in proxmox addon
+                IpAddress::where('ip', $ip->ip)
+                    ->update(["hosting_id" => $ip->hosting_id]);
+                $networkId = $this->vm()->findFreeNetworDeviceId();
+                if ($this->configuration()->isLxc())
+                {
+                    $networkDevice = new NetworkDeviceLxc('net' . $networkId);
+                    $networkDevice->setName('eth' . $networkId)
+                        ->setBridge($this->configuration()->getPrivateBridge())
+                        ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0);
+                    if ($ip->mac_address && $ip->mac_address != "auto")
+                    {
+                        $networkDevice->setHwaddr($ip->mac_address);
+                    }
+                    $networkDevice->setIp($ip->ip)
+                        ->setCidr($ip->cidr)
+                        ->setGw($ip->gateway)
+                        ->setTag($tag)
+                        ->setRate($rate)
+                        ->setTrunks($ip->trunks);
+                    $container[$networkDevice->getId()] = $networkDevice->asConfig();
+                }
+                else
+                {
+                    if ($this->configuration()->isQemu())
+                    {
+                        $networkDevice = new NetworkDeviceKvm('net' . $networkId);
+                        $networkDevice->setBridge($this->configuration()->getPrivateBridge())
+                            ->setModel($this->configuration()->getNetworkPrivateModel())
+                            ->setRate($rate)
+                            ->setTag($tag)
+                            ->setTrunks($ip->trunks)
+                            ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0);
+
+                        if ($ip->mac_address && $ip->mac_address != "auto")
+                        {
+                            $networkDevice->setMacAddress($ip->mac_address);
+                        }
+                        $container[$networkDevice->getId()] = $networkDevice->asConfig();
+                        if ($isCloudInit)
+                        {
+                            $ipConfig = new IpConfig('ipconfig' . $networkId);
+                            $ipConfig->setIp(trim($ip->ip))
+                                ->setCidr(trim($ip->cidr))
+                                ->setGw(trim($ip->gateway));
+                            $container[$ipConfig->getId()] = $ipConfig->asConfig();
+                        }
+                    }
+                }
+                $ip->net = $networkDevice->getId();
+                $ip->tag = $tag;
+                $ip->save();
+            }
+            //update hosting
+            $this->hosting()->save();
+            $this->vm()->updateConfig($container);
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+    }
+
+    protected function makeTag()
+    {
+        if (is_numeric($this->getWhmcsCustomField(CustomField::TAG)))
+        {
+            return $this->getWhmcsCustomField(CustomField::TAG);
+        }
+        if (!$this->configuration()->getTagFrom() && !$this->configuration()->getTagTo())
+        {
+            return null;
+        }
+        $tag = $this->nextTag();
+        if (!$tag)
+        {
+            throw new \Exception("Max VLAN tag have been reached, Please Configure product  (Max VLAN tag)");
+        }
+        $this->customFieldUpdate("VLAN Tag", $tag);
+        return $tag;
+    }
+
+    public function nextTag()
+    {
+        $cf = "tblcustomfields";
+        $cv = "tblcustomfieldsvalues";
+        for ($i = $this->configuration()->getTagFrom(); $i <= $this->configuration()->getTagTo(); $i++)
+        {
+            $query = DB::table($cf)
+                ->select("{$cv}.value")
+                ->leftJoin($cv, "{$cv}.fieldid", "=", "{$cf}.id")
+                ->where("{$cf}.type", "product")
+                ->where("{$cf}.relid", $this->getWhmcsParamByKey("packageid"))
+                ->where("{$cf}.fieldname", 'like', "VLAN Tag%")
+                ->where("{$cv}.relid", '!=', $this->getWhmcsParamByKey("serviceid"))
+                ->where("{$cv}.value", $i);
+            if ($query->count())
+            {
+                continue;
+            }
+            return $i;
+        }
+        return false;
+    }
+
+    /**
+     * @return VmIpAddress
+     */
+    public function getPrivateIpAddress()
+    {
+        $ip    = IpAddress::where('private', 1)
+            ->where('type', 'IPv4')
+            ->whereIn("sid", [$this->getWhmcsParamByKey('serverid'), "0"])
+            ->where('hosting_id', '0')
+            ->firstOrFail();
+        $newIp = new VmIpAddress();
+        $newIp->fill($ip->toArray());
+        $newIp->hosting_id = $this->getWhmcsParamByKey("serviceid");
+        $newIp->server_id  = $this->getWhmcsParamByKey("serverid");
+        return $newIp;
+    }
+
+    public function hasPrivateIpAddress()
+    {
+        $query = IpAddress::where('private', 1)
+            ->where('type', 'IPv4')
+            ->whereIn("sid", [$this->getWhmcsParamByKey('serverid'), "0"])
+            ->where('hosting_id', '0');
+        return $query->count();
+    }
+
+    public function deleteByNetworkId(array $networkIds)
+    {
+        if (is_null($networkIds))
+        {
+            throw new \InvalidArgumentException('The network ids cannot be empty');
+        }
+        $ipAddresses = VmIpAddress::whereIn("net", $networkIds)->get()->all();
+        return $this->deleteByIpAddresses($ipAddresses);
+    }
+
+    public function deleteByIpAddresses(array $ipAddresses)
+    {
+        if (is_null($ipAddresses))
+        {
+            throw new \InvalidArgumentException('The IP Addresses cannot be empty');
+        }
+        $devices      = $this->vm()->getNetworkDevices();
+        if($this->configuration()->getPrivateBridge()){
+            $privateDevices = $this->vm()->getNetworkDevices($this->configuration()->getPrivateBridge());
+        }
+        $configDelete = [];
+
+        $this->setHostingId($this->getWhmcsParamByKey("serviceid"));
+        DB::beginTransaction();
+        Api::beginTransaction();
+        try
+        {
+            foreach ($ipAddresses as &$ip)
+            {
+                if (is_numeric($ip))
+                {
+                    $ip = VmIpAddress::where("id", $ip)->firstOrFail();
+                }
+                if (!$ip instanceof VmIpAddress)
+                {
+                    throw new \InvalidArgumentException(sprintf('The following class are not supported "%s"', get_class($ip)));
+                };
+                //private ip
+                if(!$this->deletePrivateNetwork && isset($privateDevices[0]) && $privateDevices[0] && $privateDevices[0]->getId() == $ip->net){
+                    continue;
+                }
+                $ip->delete();
+
+                $this->hosting()->ipDelete($ip->ip);
+                //Unlock ip in Proxmox Addon
+                IpAddress::where('ip', $ip->ip)
+                    ->where('hosting_id', $ip->hosting_id)
+                    ->update(["hosting_id" => "0", 'last_check' => Utility::timeStamp()]);
+
+                foreach ($devices as $deviceIndex => $device)
+                {
+                    if ($this->configuration()->isOneNetworkDevice() && $device->getId() == "net0")
+                    {
+                        continue;
+                    }
+                    if($device instanceof NetworkDeviceLxc)
+                    {
+                        if ($ip->ip == $device->getIp())
+                        {
+                            $device->setIp(null);
+                        }
+
+                        if ($ip->ip == $device->getIp6())
+                        {
+                            $device->setIp6(null);
+                        }
+
+                        if(empty($device->getIp6()) && empty($device->getIp()))
+                        {
+                            unset($devices[$deviceIndex]);
+                            $configDelete[] = $device->getId();
+                        }
+                    }
+                    elseif($device instanceof  NetworkDeviceKvm && $ip->net && $ip->net == $device->getId())
+                    {
+                        $configDelete[] = $device->getId();
+                        unset($devices[$deviceIndex]);
+                    }
+                }
+                
+                //cloud init
+                if ($this->vm() instanceof Kvm)
+                {
+                    foreach ($this->vm()->getIpConfig()->fetch() as $ipConfig)
+                    {
+                        if ($ipConfig->getIp() == $ip->ip || $ipConfig->getIp6() == $ip->ip)
+                        {
+                            $configDelete[] = $ipConfig->getId();
+                        }
+                    }
+                }
+            }
+
+            $this->hosting()->save();
+            /**
+             * Update current config. We need that only if we remove only one IP and other IP is still on the same interface
+             */
+            $config = new Config();
+            $config->setNet($devices);
+            $config = $config->toArray();
+            if($config)
+            {
+                $this->vm()->updateConfig($config);
+            }
+
+            /**
+             * Delete config. We need that when there is no more network interface
+             */
+            if (!empty($configDelete))
+            {
+                $this->vm()->deleteConfig(implode(",", $configDelete));
+            }
+            DB::commit();
+        }
+        catch (\Exception $ex)
+        {
+            DB::rollBack();
+            Api::commit();
+            throw $ex;
+        }
+    }
+
+    public function hasIp($totalIpv4, $totalIpv6, $nodeName)
+    {
+        $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC";
+        if ((int)$totalIpv4 > 0)
+        {
+            /**
+             * @var $ipCollections IpAddress
+             */
+            $ipCollections = IpAddress::where('hosting_id', '0')
+                ->where('private', '0')
+                ->where('type', 'IPv4')
+                ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                ->whereIn('visualization', [$virtualization, 'Auto'])
+                ->whereIn('node', [$nodeName, '0', ""]);
+            if ($this->configuration()->getTags())
+            {
+                $ipCollections->ofTags($this->configuration()->getTags());
+            }
+            $count = $ipCollections->count();
+            if ((int)$count < $totalIpv4)
+            {
+                $errorMessage =  sprintf("Unable to get %s of IPv4. IP Addresses available: %s", $totalIpv4, $count);
+                if($nodeName){
+                    $errorMessage =  sprintf("Unable to get %s of IPv4, on node: %s. IP Addresses available: %s", $totalIpv4, $nodeName, $count);
+                }
+                throw new \Exception($errorMessage);
+            }
+        }
+        if ((int)$totalIpv6 > 0)
+        {
+            $ipCollections = IpAddress::where('hosting_id', '0')
+                ->where('private', '0')
+                ->where('type', 'IPv6')
+                ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                ->whereIn('visualization', [$virtualization, 'Auto'])
+                ->whereIn('node', [$nodeName, '0', ""]);
+            if ($this->configuration()->getTags())
+            {
+                $ipCollections->ofTags($this->configuration()->getTags());
+            }
+            $count = $ipCollections->count();
+            if ((int)$count < $totalIpv6)
+            {
+                $errorMessage =  sprintf("Unable to get %s of IPv6. IP Addresses available: %s", $totalIpv6, $count);
+                if($nodeName){
+                    $errorMessage =  sprintf("Unable to get %s of IPv6, on node: %s. IP Addresses available: %s", $totalIpv6, $nodeName, $count);
+                }
+                throw new \Exception($errorMessage);
+            }
+        }
+        return $this;
+    }
+
+    public function addIp($totalIpv4, $totalIpv6, $nodeName)
+    {
+        try {
+            DB::beginTransaction();
+            $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC";
+            $this->setHostingId($this->getWhmcsParamByKey('serviceid'));
+            if ((int)$totalIpv4 > 0) {
+                $query = IpAddress::where('hosting_id', '0')
+                    ->where('private', '0')
+                    ->where('type', 'IPv4')
+                    ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                    ->whereIn('visualization', [$virtualization, 'Auto'])
+                    ->whereIn('node', [$nodeName, '0', ""]);
+                if ($this->configuration()->getTags()) {
+                    $query->ofTags($this->configuration()->getTags());
+                }
+                $count = $query->limit($totalIpv4)
+                    ->orderBy('last_check', 'asc')
+                    ->update(["hosting_id" => $this->getWhmcsParamByKey('serviceid'), 'last_check' => Utility::timeStamp()]);
+                if ((int)$count < $totalIpv4) {
+                    throw new \Exception(sprintf("Unable to get %s of IPv4. IP Addresses available: %s", $totalIpv4, $count));
+                }
+
+            }
+            if ((int)$totalIpv6 > 0) {
+                $query = IpAddress::where('hosting_id', '0')
+                    ->where('private', '0')
+                    ->where('type', 'IPv6')
+                    ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0'])
+                    ->whereIn('visualization', [$virtualization, 'Auto'])
+                    ->whereIn('node', [$nodeName, '0', ""]);
+                if ($this->configuration()->getTags()) {
+                    $query->ofTags($this->configuration()->getTags());
+                }
+                $count = $query->limit($totalIpv6)
+                    ->orderBy('last_check', 'asc')
+                    ->update(["hosting_id" => $this->getWhmcsParamByKey('serviceid'), 'last_check' => Utility::timeStamp()]);
+
+                if ((int)$count < $totalIpv6) {
+                    throw new \Exception(sprintf("Unable to get %s of IPv6. IP Addresses available: %s", $totalIpv6, $count));
+                }
+            }
+            $vmIp = (new VmIpAddress())->getTable();
+            $collection = IpAddress::where('hosting_id', $this->getWhmcsParamByKey('serviceid'))
+                ->orderBy("ip", "desc")
+                ->whereNotIn('ip', function ($query) use ($vmIp) {
+                    $query->select('ip')->from($vmIp);
+                });
+            foreach ($collection->get()->sortBy("type")->all() as $ip) {
+                /*@var $ip IpAddress */
+                $newIp = new VmIpAddress();
+                $ipData = $ip->toArray();
+                unset($ipData['id']);
+                $ipData['hosting_id'] = $this->getWhmcsParamByKey('serviceid');
+                $ipData['server_id'] = $this->getWhmcsParamByKey('serverid');
+                $newIp->fill($ipData)->save();
+                $this->hosting()->ipAdd($ip->ip);
+            }
+            $this->hosting()->save();
+            DB::commit();
+        } catch (\Exception $ex) {
+            DB::rollBack();
+            throw $ex;
+        }
+    }
+
+    public function deleteIpAddresses()
+    {
+        VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))->delete();
+        //Unlock ip in Proxmox Addon
+        IpAddress::where('hosting_id', $this->getWhmcsParamByKey('serviceid'))
+            ->update(["hosting_id" => "0", 'last_check' => Utility::timeStamp()]);
+        $this->setHostingId($this->getWhmcsParamByKey('serviceid'));
+        $this->hosting()->dedicatedip = "";
+        $this->hosting()->assignedips = "";
+        $this->hosting()->save();
+        return $this;
+    }
+
+    public function buildLxc()
+    {
+        $container = [];
+        /**
+         * @var VmIpAddress[] $ipv4
+         */
+        $ipv4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp4()
+            ->ofNetNull()
+            ->get();
+        //ip6
+        $ipv6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp6()
+            ->ofNetNull()
+            ->get();
+
+        //Network
+        $bridge   = $this->configuration()->getBridge();
+        $firewall = $this->configuration()->isNetworkFirewall() ? 1 : 0;
+        $ip4Mode  = $this->configuration()->getIpv4NetworkMode();
+        $ip6Mode  = $this->configuration()->getIpv6NetworkMode();
+        $rate     = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+        }
+        $networkId = 0;
+        if ($this->hasVm())
+        {
+            $networkId = $this->vm()->findFreeNetworDeviceId();
+        }
+        for ($i = 0; $i <= 9; $i++)
+        {
+            //Empty IP Addresses
+            if (!$ipv4->get($i) && !$ipv6->get($i))
+            {
+                break;
+            }
+            if ($networkId > 9)
+            {
+                break;
+            }
+            $mac = null;
+            //Name
+            $interface = new NetworkDeviceLxc('net' . $networkId);
+            $interface->setName('eth' . $networkId)
+                ->setBridge($bridge)
+                ->setFirewall($firewall);
+            //IP 4
+            if ($ipv4->get($i))
+            {
+                if ($ipv4->get($i)->mac_address)
+                {
+                    $mac = $ipv4->get($i)->mac_address;
+                }
+                if ($ipv4->get($i)->tag)
+                {
+                    $interface->setTag($ipv4->get($i)->tag);
+                }
+                $interface->setTrunks($ipv4->get($i)->trunks);
+                if ($ip4Mode == 'static')
+                { //IP & CIRD
+                    $interface->setIp($ipv4->get($i)->ip);
+                    $interface->setCidr($ipv4->get($i)->cidr);
+                    //Gateway
+                    $interface->setGw($ipv4->get($i)->gateway);
+                }
+                $ipv4->get($i)->net = $interface->getId();
+                $ipv4->get($i)->save();
+            }
+            if ($ip4Mode == "dhcp")
+            {
+                $interface->setIp("dhcp");
+            }
+            //IP 6
+            if ($ip6Mode == "dhcp")
+            {
+                $interface->setIp6("dhcp");
+            }
+            else
+            {
+                if ($ip6Mode == "slaac")
+                {
+                    $interface->setIp6("auto");
+                }
+            }
+            if ($ipv6->get($i))
+            {  //IP & CIRD
+                if ($ip6Mode == "static")
+                {
+                    $interface->setIp6($ipv6->get($i)->ip);
+                    $interface->setCidr6($ipv6->get($i)->cidr);
+                    //Gateway
+                    $interface->setGw6($ipv6->get($i)->gateway);
+                }
+                if ($ipv6->get($i)->tag)
+                {
+                    $interface->setTag($ipv6->get($i)->tag);
+                }
+                if ($ipv6->get($i)->trunks)
+                {
+                    $interface->setTrunks($ipv6->get($i)->trunks);
+                }
+                if ($mac == null && $ipv6->get($i)->mac_address)
+                {
+                    $mac = $ipv6->get($i)->mac_address;
+                }
+                $ipv6->get($i)->net = $interface->getId();
+                $ipv6->get($i)->save();
+            }
+            if ($rate)
+            {
+                $interface->setRate($rate);
+            }
+            $interface->setHwaddr($mac);
+            $container['net' . $networkId] = $interface->asConfig();
+            $networkId++;
+        }
+        return $container;
+
+    }
+
+    public function buildQemu()
+    {
+        $container = [];
+        /**
+         * @var VmIpAddress[] $ipv4
+         */
+        $ipv4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp4()
+            ->ofNetNull()
+            ->get();
+        //ip6
+        $ipv6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+            ->ofIp6()
+            ->ofNetNull()
+            ->get();
+        //Network
+        $model    = $this->configuration()->getNetworkModel();
+        $bridge   = $this->configuration()->getBridge();
+        $firewall = $this->configuration()->isNetworkFirewall() ? 1 : 0;
+        $rate     = null;
+        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) != "-1")
+        {
+            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
+        }
+        $networkId          = 0;
+        $vmNetworkDevices   = [];
+
+        if ($this->hasVm())
+        {
+            $networkId = $this->vm()->findFreeNetworDeviceId();
+            $vmNetworkDevices   = $this->vm()->getNetworkDevices();
+        }
+
+        $i4      = 0;
+        $i6      = 0;
+        $isApi52 = version_compare($this->api()->getVersion(), "5.2", '>=');
+        for ($i = 0; $i <= 31; $i++)
+        {
+            //Empty IP Addresses
+            if (!$ipv4->get($i4) && !$ipv6->get($i6))
+            {
+                break;
+            }
+            if ($networkId > 31)
+            {
+                break;
+            }
+            if ($this->configuration()->isOneNetworkDevice() && ($i > 0 || count($vmNetworkDevices)))
+            {
+                VmIpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))
+                    ->ofNetNull()
+                    ->update(["net" => 'net0']);
+                break;
+            }
+
+            $mac   = null;
+            $isIp4 = false;
+            //Name
+            $interface = new NetworkDeviceKvm('net' . $networkId);
+            $interface->setModel($model)
+                ->setBridge($bridge)
+                ->setFirewall($firewall);
+            $ipConfig = new IpConfig('ipconfig' . $networkId);
+            //IP 4
+            if ($ipv4->get($i4))
+            {
+                $isIp4 = true;
+                if (trim($ipv4->get($i4)->mac_address))
+                {
+                    $mac = $ipv4->get($i4)->mac_address;
+                }
+                if ($ipv4->get($i4)->tag)
+                {
+                    $interface->setTag(trim($ipv4->get($i4)->tag));
+                }
+                $interface->setTrunks(trim($ipv4->get($i4)->trunks));
+                //private ip
+                if($this->configuration()->getPrivateBridge() && IpAddress::ofHostingId($this->getWhmcsParamByKey("serviceid"))->ofIp($ipv4->get($i4)->ip)->private()->count()){
+                    $interface->setBridge($this->configuration()->getPrivateBridge());
+                }
+                $ipConfig->setIp(trim($ipv4->get($i4)->ip));
+                $ipConfig->setCidr(trim($ipv4->get($i4)->cidr));
+                //Gateway
+                $ipConfig->setGw(trim($ipv4->get($i4)->gateway));
+                $ipv4->get($i4)->net = $interface->getId();
+                $ipv4->get($i4)->save();
+                $i4++;
+            }
+            //ipv6
+            if ($ipv6->get($i6) && (!$isIp4 || $interface->getTag() == $ipv6->get($i6)->tag))
+            {  //IP & CIRD
+                $ipConfig->setIp6(trim($ipv6->get($i6)->ip));
+                $ipConfig->setCidr6(trim($ipv6->get($i6)->cidr));
+                //Gateway
+                $ipConfig->setGw6(trim($ipv6->get($i6)->gateway));
+                if ($ipv6->get($i6)->tag)
+                {
+                    $tag = $ipv6->get($i6)->tag;
+                    $interface->setTag(trim($ipv6->get($i6)->tag));
+                }
+                if ($ipv6->get($i6)->trunks)
+                {
+                    $interface->setTrunks(trim($ipv6->get($i6)->trunks));
+                }
+                if ($mac == null && $ipv6->get($i6)->mac_address)
+                {
+                    $mac = $ipv6->get($i6)->mac_address;
+                }
+                $ipv6->get($i6)->net = $interface->getId();
+                $ipv6->get($i6)->save();
+                $i6++;
+            }
+            $interface->setRate(trim($rate));
+            $interface->setMacAddress(trim($mac));
+            $interface->setQueues(trim($this->configuration()->getQueues()));
+            $container[$interface->getId()] = $interface->asConfig();
+            if ($isApi52 && ($ipConfig->getIp() || $ipConfig->getIp6()))
+            {
+                $container[$ipConfig->getId()] = $ipConfig->asConfig();
+            }
+            $networkId++;
+        }
+
+        return $container;
+    }
+
+    public function unassignIpAddressesAndDeleteNetwork()
+    {
+        list($requestIPv4, $requestIPv6) = $this->getIpAddressesRequest();
+        $ip4 = [];
+        $ip6 = [];
+        if ($requestIPv4 < 0)
+        {
+            $ip4 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                ->ofIp4()
+                ->ofNetNotNull()
+                ->limit(abs($requestIPv4))
+                ->orderByIdDesc()
+                ->get()
+                ->all();
+        }
+        if ($requestIPv6 < 0)
+        {
+            $ip6 = VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+                ->ofIp6()
+                ->ofNetNotNull()
+                ->limit(abs($requestIPv6))
+                ->orderByIdDesc()
+                ->get()
+                ->all();
+        }
+        $ipAddresses = array_merge($ip4, $ip6);
+        if (!empty($ipAddresses))
+        {
+            $this->deleteByIpAddresses($ipAddresses);
+        }
+    }
+
+    public function getIpAddressesRequest()
+    {
+        $ipv4Used    = (int)VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofIp4()
+            ->count();
+        $ipv6Used    = (int)VmIpAddress::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofIp6()
+            ->count();
+        $requestIPv4 = (int)$this->configuration()->getIpv4();
+        $requestIPv6 = (int)$this->configuration()->getIpv6();
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV4))
+        {
+            $requestIPv4 = (int)$this->getWhmcsConfigOption(ConfigurableOption::IPV4);
+        }
+        if ($this->isWhmcsConfigOption(ConfigurableOption::IPV6))
+        {
+            $requestIPv6 = (int)$this->getWhmcsConfigOption(ConfigurableOption::IPV6);
+        }
+        return [(int)$requestIPv4 -= $ipv4Used, (int)$requestIPv6 -= $ipv6Used];
+    }
+
+    /**
+     * @param bool $deletePrivateNetwork
+     * @return NetworkService
+     */
+    public function setDeletePrivateNetwork(bool $deletePrivateNetwork): NetworkService
+    {
+        $this->deletePrivateNetwork = $deletePrivateNetwork;
+        return $this;
+    }
+
+
+}

+ 235 - 0
app/Services/Vps/ProductService.php

@@ -0,0 +1,235 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2\models\Node;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use MGProvision\Proxmox\v2\repository\NodeRepository;
+use MGProvision\Proxmox\v2\repository\StorageRepository;
+use ModulesGarden\ProxmoxAddon\App\Repositories\Vps\ProductConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\LoadBalancerService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+
+trait ProductService
+{
+
+
+    /**
+     * @return FileRepository
+     */
+    public function isoRepository()
+    {
+        if (!empty($this->isoRepository))
+        {
+            return $this->isoRepository;
+        }
+        $storageRepository = new StorageRepository();
+        $storageRepository->findByNodes([$this->vm()->getNode()])
+            ->findEnabed();
+        $storages            = $storageRepository->fetchAsArray();
+        $this->isoRepository = new FileRepository();
+        $this->isoRepository->findByNodes([$this->vm()->getNode()])
+            ->findByStorages($storages);
+        $this->isoRepository->findIso();
+        return $this->isoRepository;
+    }
+
+    /**
+     * @return ProductConfigurationRepository
+     */
+    public function configuration()
+    {
+        if (!empty($this->configuration))
+        {
+            return $this->configuration;
+        }
+        if (!$this->productId)
+        {
+            $this->setProductId($this->getWhmcsParamByKey("packageid"));
+        }
+        return $this->configuration = new ProductConfigurationRepository($this->productId);
+    }
+
+    /**
+     * @return int
+     */
+    public function getProductId()
+    {
+        return $this->productId;
+    }
+
+    /**
+     * @param int $productId
+     */
+    public function setProductId($productId)
+    {
+        $this->productId = $productId;
+    }
+
+    public function acl()
+    {
+        if (!empty($this->acl))
+        {
+            return $this->acl;
+        }
+        return $this->acl = new AclService();
+    }
+
+    /**
+     * @return ResourceGuard
+     */
+    public function resourceGuard()
+    {
+        if (!empty($this->resourceGuard))
+        {
+            return $this->resourceGuard;
+        }
+        return $this->resourceGuard = new ResourceGuard();
+    }
+
+
+    /**
+     * @return Node
+     * @throws \MGProvision\Proxmox\v2\ProxmoxApiException
+     */
+    public function getNode()
+    {
+        if ($this->node)
+        {
+            return $this->node;
+        }
+        if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+        {
+            $diskBytes = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+            Utility::unitFormat($diskBytes, $this->configuration()->getDiskUnit(), 'bytes');
+        }
+        else
+        {
+            $diskBytes = $this->configuration()->getDiskSize();
+            Utility::unitFormat($diskBytes, "gb", 'bytes');
+        }
+        if ($this->configuration()->isLoadBalancer())
+        {
+            $loadBalancer = new LoadBalancerService($this->getWhmcsParamByKey('serverid'));
+            $loadBalancer->setApi($this->api());
+            $loadBalancerNodes = $loadBalancer->findByVmCreate();
+            $nodesForUser      = $loadBalancer->findNotUser($this->getWhmcsParamByKey('userid'));
+            if (!$nodesForUser->isEmpty())
+            {
+                $loadBalancerNodes = $nodesForUser;
+            }
+            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
+            {
+                $ram = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
+                Utility::unitFormat($ram, $this->configuration()->getMemoryUnit(), 'bytes');
+            }
+            else
+            {
+                $ram = $this->configuration()->getMemory();
+                Utility::unitFormat( $ram, "mb", 'bytes');
+            }
+            if ($this->configuration()->isQemu())
+            {
+                $socket = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets());
+                $cores  = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores());
+                $cpu    = $socket * $cores;
+            } else if ($this->configuration()->isLxc()) {
+                $cpu = $cores = $this->getWhmcsConfigOption(ConfigurableOption::CORES, $this->configuration()->getCores());
+            }
+
+            $node = $loadBalancerNodes->findByRam($ram)
+                ->findByCpu($cpu)
+                ->findByDisk($diskBytes)
+                ->findByVms(1)
+                ->nodeLowLoad();
+            return $this->node = new Node($node);
+        }
+        $defaultNode    = $this->configuration()->getDefaultNode();
+        $nodeRepository = new NodeRepository();
+        $nodeRepository->setApi($this->api());
+        switch ($defaultNode)
+        {
+            case "autoNode":
+                if (empty($diskBytes))
+                {
+                    throw new \Exception("Provide disk size.");
+                }
+                return $this->node = $nodeRepository->findByDiskSize($diskBytes);
+                break;
+            case "serverNode":
+                $servePrivateIP =  $this->getServerPrivateIpAddress();
+                $serverIp = $servePrivateIP? $servePrivateIP : $this->getWhmcsParamByKey('serverip');
+                return $this->node = $nodeRepository->findWithHostOrIp($this->getWhmcsParamByKey('serverhostname'), $serverIp);
+                break;
+            default:
+                return $this->getDefaultNode();
+        }
+    }
+
+
+    protected function getDefaultNode()
+    {
+        $defaultNode    = $this->configuration()->getDefaultNode();
+        $nodeRepository = new NodeRepository();
+        $nodeRepository->setApi($this->api());
+        switch ($defaultNode)
+        {
+            case "autoNode":
+                $diskBytes =1;
+                if ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
+                {
+                    $diskBytes = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
+                    Utility::unitFormat($diskBytes, $this->configuration()->getDiskUnit(), 'bytes');
+                }
+                else
+                {
+                    $diskBytes = $this->configuration()->getDiskSize();
+                    Utility::unitFormat($diskBytes, "gb", 'bytes');
+                }
+                if (empty($diskBytes))
+                {
+                    throw new \Exception("Provide disk size.");
+                }
+                return $nodeRepository->findByDiskSize($diskBytes);
+                break;
+            case "serverNode":
+                $servePrivateIP =  $this->getServerPrivateIpAddress();
+                $serverIp = $servePrivateIP? $servePrivateIP : $this->getWhmcsParamByKey('serverip');
+                return $nodeRepository->findWithHostOrIp($this->getWhmcsParamByKey('serverhostname'), $serverIp);
+                break;
+            default:
+                return new Node($defaultNode);
+        }
+    }
+
+    public function getServerPrivateIpAddress(){
+
+        $serverId = $this->getWhmcsParamByKey('serverid');
+        if( $serverId  &&  $serverId >0){
+            $serverAssignedIps = DB::table('tblservers')->where("id", $serverId)->value("assignedips");
+            $serverAssignedIps = preg_replace('/\s+/', '', $serverAssignedIps);
+            $serverAssignedIps = explode(PHP_EOL, $serverAssignedIps);
+            return current($serverAssignedIps);
+        }
+    }
+
+}

+ 252 - 0
app/Services/Vps/ResourceGuard.php

@@ -0,0 +1,252 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use MGProvision\Proxmox\v2\repository\BackupScheduleRepository;
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use MGProvision\Proxmox\v2\repository\FirewallRulesRepository;
+use MGProvision\Proxmox\v2\repository\HardDiskRepostiory;
+use MGProvision\Proxmox\v2\repository\MountPointRepostiory;
+use MGProvision\Proxmox\v2\repository\SnapshotRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\SnapshotJob;
+use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl;
+
+class ResourceGuard
+{
+    use WhmcsParams;
+    use ProductService;
+    use ApiService;
+
+    public function backupLimit()
+    {
+        //Backup repository
+        $backupRepository = new FileRepository();
+        $backupRepository->setApi($this->api());
+        $backupRepository->findBackup($this->vm())
+            ->findByStorages([$this->configuration()->getBackupStorage()]);
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles()) ;
+        if ($maxFiles != "-1"  && $this->configuration()->isBackupRouting())
+        {
+            $maxFiles++;
+        }
+        if ($maxFiles != "-1" && $backupRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->abtr("The maximum number of backup files has been exceeded. Please remove the old backup files."));
+        }
+        //Size limit
+        $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_SIZE,  $this->configuration()->getBackupMaxSize()) ;
+        $used    = $backupRepository->size();
+        Utility::unitFormat($used, "bytes", "gb");
+        if ($maxSize != "-1" && $used >= $maxSize)
+        {
+            throw new \Exception(sl("lang")->abtr("The maximum size set for a backup has been exceeded. Please remove the old backup files."));
+        }
+    }
+
+    public function hasBackupLimit()
+    {
+        try
+        {
+            $this->backupLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function backupJobLimit()
+    {
+        $this->backupLimit();
+        //Backup repository
+        $backupRepository = new BackupScheduleRepository();
+        $backupRepository->setApi($this->api());
+        $backupRepository->findByVm($this->vm());
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::BACKUPS_FILES,  $this->configuration()->getBackupMaxFiles()) ;
+        if ($maxFiles != "-1" && $backupRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of backup files has been exceeded. Please remove the old backup files."));
+        }
+    }
+
+    public function hasBackupJobLimit()
+    {
+        try
+        {
+            $this->backupJobLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function snapshotLimit()
+    {
+        $snapshotRepository = new SnapshotRepository();
+        $snapshotRepository->setApi($this->api());
+        $snapshotRepository->findByVm($this->vm());
+        $snapshotRepository->ignoreCurrent(true);
+        //Files limit
+        $maxFiles = $this->getWhmcsConfigOption(ConfigurableOption::SNAPSHOTS, $this->configuration()->getSnapshotMaxFiles());
+        if ($maxFiles != "-1" && $snapshotRepository->count() >= $maxFiles)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of snapshots has been exceeded. Please remove the old snapshots."));
+        }
+    }
+
+    public function hasSnapshotLimit()
+    {
+        try
+        {
+            $this->snapshotLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+
+    public function snapshotJobLimit()
+    {
+
+        $max = $this->getWhmcsConfigOption(ConfigurableOption::SNAPSHOT_JOBS, $this->configuration()->getSnapshotJobs());
+        if($max == "-1"){
+            return;
+        }
+        $query = SnapshotJob::ofHostingId($this->getWhmcsParamByKey("serviceid"));
+        if ($max != "-1" && $query->count() >= $max)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of snapshot jobs has been exceeded. Please remove the old snapshot jobs."));
+        }
+    }
+
+    public function hasSnapshotJobLimit()
+    {
+        try
+        {
+            $this->snapshotJobLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function firewallLimit()
+    {
+        $repository    = new FirewallRulesRepository();
+        $repository->setApi($this->api());
+        $repository->findByVm($this->vm());
+        //Files limit
+        $max = (int)$this->configuration()->getFirewallMaxRules();
+        if ($max != "-1" && $repository->count() >= $max)
+        {
+            throw new \Exception(sl("lang")->tr("The maximum number of firewall rules has been exceeded. Please remove the old firewall rules."));
+        }
+    }
+
+    public function hasfirewallLimit()
+    {
+        try
+        {
+            $this->firewallLimit();
+            return true;
+        }
+        catch (\Exception $ex)
+        {
+            return false;
+        }
+    }
+
+    public function mountPointLimit($newSize = 0, $excludeId = null)
+    {
+        //Max size
+        $maxSize = $this->configuration()->getAdditionalDiskSize();//GB
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE) )
+        {
+            $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($maxSize, $this->configuration()->getAdditionalDiskUnit(), "gb");
+        }
+        $repository = new MountPointRepostiory();
+        $repository->setApi($this->api());
+        $repository->findByPath($this->vm()->getPath() . '/config');
+        if (!is_null($excludeId))
+        {
+            $repository->whereNotIn([$excludeId]);
+        }
+        $used = $repository->additionalSize();
+        $free = $maxSize - $used;
+        if ($maxSize != "-1"  && $newSize > $free)
+        {
+            throw new \Exception(sprintf(sl("lang")->tr("You are not able to set %s GB of disk size. Available disk size: %s GB"), $newSize, $free));
+        }
+
+    }
+
+    public function hasMountPointLimit()
+    {
+        //Max size
+        $maxSize = $this->configuration()->getAdditionalDiskSize();//GB
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE) )
+        {
+            $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($maxSize, $this->configuration()->getAdditionalDiskUnit(), "gb");
+        }
+        return $this->vm()->getMounPoints()->additionalSize() < $maxSize;
+    }
+
+
+    public function diskLimit($newSize = 0, $excludeId = null)
+    {
+        //Max size
+        $maxSize = $this->configuration()->getAdditionalDiskSize();//GB
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE) )
+        {
+            $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($maxSize, $this->configuration()->getAdditionalDiskUnit(), "gb");
+        }
+        $repository = new HardDiskRepostiory();
+        $repository->setApi($this->api());
+        $repository->findByPath($this->vm()->getPath() . '/config');
+        if (!is_null($excludeId))
+        {
+            $repository->whereNotIn([$excludeId]);
+        }
+        $used = $repository->additionalSize();
+        $free = $maxSize - $used;
+        if ($maxSize != "-1" &&  $newSize > $free)
+        {
+            throw new \Exception(sprintf(sl("lang")->tr("You are not able to set %s GB of disk size. Available disk size: %s GB"), $newSize, $free));
+        }
+
+    }
+
+    public function hasDiskLimit()
+    {
+        //Max size
+        $maxSize = $this->configuration()->getAdditionalDiskSize();//GB
+        if ($this->isWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE) )
+        {
+            $maxSize = $this->getWhmcsConfigOption(ConfigurableOption::ADDITIONAL_DISKS_SIZE);
+            Utility::unitFormat($maxSize, $this->configuration()->getAdditionalDiskUnit(), "gb");
+        }
+        $repository = new HardDiskRepostiory();
+        $repository->setApi($this->api());
+        $repository->findByPath($this->vm()->getPath() . '/config');
+        return $repository->additionalSize() < $maxSize;
+    }
+
+}

+ 55 - 0
app/Services/Vps/UrlService.php

@@ -0,0 +1,55 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\isAdmin;
+
+class UrlService
+{
+    use WhmcsParams;
+
+    public function getUrl($controller = null, $action = null, array $params = [])
+    {
+        $url = 'clientarea.php?action=productdetails&id=' . $this->getWhmcsParamByKey('serviceid');
+        if ($controller)
+        {
+            $params['modop']   = 'custom';
+            $params['a']       = 'management';
+            $params['mg-page'] = $controller;
+            if ($action)
+            {
+                $params['mg-action'] = $action;
+            }
+            if ($params)
+            {
+                $url .= '&' . http_build_query($params);
+            }
+        }
+        if (isAdmin())
+        {
+            return '../dologin.php?username=' . urlencode($this->getWhmcsParamByKey('clientsdetails')['email']) . '&goto=' . urlencode($url);
+        }
+        return $url;
+    }
+
+}

+ 219 - 0
app/Services/Vps/UserService.php

@@ -0,0 +1,219 @@
+<?php
+/* * ********************************************************************
+*  ProxmoxVPS Product developed. (27.03.19)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+* ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+use Illuminate\Database\Capsule\Manager as DB;
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon\App\Models\User;
+use ModulesGarden\ProxmoxAddon\App\Repositories\Vps\ProductConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\Utility;
+use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+
+/**
+ * Trait UserService
+ * @package ModulesGarden\Servers\ProxmoxVps\App\Services
+ * @method ProductConfigurationRepository configuration()
+ */
+trait UserService
+{
+    use Smarty;
+
+    /**
+     * @return User
+     */
+    protected function getUser()
+    {
+        $userId    = $this->getWhmcsParamByKey('userid');
+        $serviceId = $this->getWhmcsParamByKey('serviceid');
+        if (User::ofUserId($userId)->ofHostingId($serviceId)->count())
+        {
+            return User::ofUserId($userId)->ofHostingId($serviceId)->orderBy("id", "desc")->firstOrFail();
+        } else if(User::ofUserId($userId)->ofHostingId('0')->count()){
+            return User::ofUserId($userId)->ofHostingId('0')->orderBy("id", "desc")->firstOrFail();
+        }
+        $hostingId = $this->configuration()->isOneUserPerVps() ? $serviceId : '0';
+        return User::ofUserId($userId)->ofHostingId($hostingId)->orderBy("id", "desc")->firstOrFail();
+    }
+
+    protected function isUser()
+    {
+        $userId    = $this->getWhmcsParamByKey('userid');
+        $serviceId = $this->getWhmcsParamByKey('serviceid');
+        if (User::ofUserId($userId)->ofHostingId($serviceId)->count())
+        {
+            return true;
+        }
+        else if(User::ofUserId($userId)->ofHostingId('0')->count()){
+            return true;
+        }
+        $hostingId = $this->configuration()->isOneUserPerVps() ? $serviceId : '0';
+        return User::ofUserId($userId)->ofHostingId($hostingId)->count() == 1;
+    }
+
+    /**
+     * @return User
+     * @throws proxmox\v2\ProxmoxApiException
+     */
+    public function userCreate(User $user = null)
+    {
+
+        if (is_null($user))
+        {
+            $user          = new User();
+            $user->user_id = $this->getWhmcsParamByKey('userid');
+        }
+        $userPrefix = $this->configuration()->getUserPrefix();
+        if ($userPrefix)
+        {
+            $userPrefix = $this->getSmarty()->fetchString($userPrefix, [
+                "serviceid"  => $this->getWhmcsParamByKey('serviceid'),
+                "service_id" => $this->getWhmcsParamByKey('serviceid')
+            ]);
+        }
+        if ($this->configuration()->isOneUserPerVps())
+        {
+            $user->hosting_id = $this->getWhmcsParamByKey('serviceid');
+        }
+        else
+        {
+            $user->hosting_id = '0';
+        }
+        $username = $this->getWhmcsCustomField(CustomField::USERNAME) ? $this->getWhmcsCustomField(CustomField::USERNAME) : $this->getWhmcsParamByKey('username');
+        $user->username = $userPrefix .  $username;
+        $user->setPassword(Utility::generatePassword(12));
+        $user->realm = $this->configuration()->getRealm();
+        $userService = new proxmox\models\User();
+        $userService->setApi($this->api());
+        $data = [
+            "userid"    => "{$user->username}@{$user->realm}",
+            "comment"   => $this->configuration()->getUserComment(),
+            "email"     => $this->getWhmcsParamByKey('clientsdetails')['email'],
+            "enable"    => 1,
+            "firstname" => $this->getWhmcsParamByKey('clientsdetails')['firstname'],
+            "lastname"  => $this->getWhmcsParamByKey('clientsdetails')['lastname'],
+            "password" => $user->getPassword()
+        ];
+        $userService->create($data);
+        $userService->changePassword($user->getPassword());
+        $user->save();
+        return $user;
+    }
+
+    protected function userUpdate()
+    {
+        $user        = $this->getUser();
+        $userService = new proxmox\models\User("{$user->username}@{$user->realm}");
+        $userService->setApi($this->api());
+        try
+        {
+            $userService->changePassword($user->getPassword());
+        }
+        catch (\Exception $ex)
+        {
+            if (!preg_match('/No such user/', $ex->getMessage()))
+            {
+                throw $ex;
+            }
+            $this->userCreate($user);
+        }
+        $vmid = $this->getWhmcsCustomField(CustomField::VMID);
+        if (!$vmid)
+        {
+            return;
+        }
+        $permissions = $userService->permissions();
+        foreach ($permissions as $p)
+        {
+            if ($p['path'] == "/vms/" . $vmid && $p['ugid'] == $userService->getUserid())
+            {
+                $userService->updatePermission($vmid, $p['roleid'], 1);
+            }
+        }
+        $role = $this->configuration()->getUserRole();
+        $userService->updatePermission($vmid, $role);
+        return $this;
+    }
+
+    protected function deleteUser()
+    {
+        $user        = $this->getUser();
+        $userService = new proxmox\models\User("{$user->username}@{$user->realm}");
+        $userService->setApi($this->api());
+        if (!$this->configuration()->isOneUserPerVps())
+        {
+            $h     = 'tblhosting';
+            $p     = 'tblproducts';
+            $query = DB::table($h)
+                ->rightJoin($p, "{$p}.id", "=", "{$h}.packageid")
+                ->where("{$h}.userid", $this->getWhmcsParamByKey('userid'))
+                ->where("{$h}.id", "!=", $this->getWhmcsParamByKey('serviceid'))
+                ->whereIn("{$h}.domainstatus", ['Active', 'Suspended'])
+                ->where("{$p}.servertype", 'proxmoxVPS');
+            if ($query->count() == "0")
+            {
+                $userService->delete();
+                $user->delete();
+                return $this;
+            }
+            $vmid = $this->getWhmcsParamByKey('customfields')['vmid'];
+            if (!$vmid)
+            {
+                return $this;
+            }
+            //Delete permissions
+            $permissions = $userService->permissions();
+            foreach ($permissions as $p)
+            {
+                if ($p['path'] == "/vms/" . $vmid && $p['ugid'] == $userService->getUserid())
+                {
+                    $userService->updatePermission($vmid, $p['roleid'], 1);
+                }
+            }
+        }
+        else
+        {
+            $userService->delete();
+            $user->delete();
+            return $this;
+        }
+    }
+
+    public function createUserIfNotExist(User $user){
+        $userService = new proxmox\models\User();
+        $userService->setApi($this->api());
+        $userService->setUserid("{$user->username}@{$user->realm}");
+        if(!$userService->exist()){
+            $data = [
+                "userid"    => "{$user->username}@{$user->realm}",
+                "comment"   => $this->configuration()->getUserComment(),
+                "email"     => $this->getWhmcsParamByKey('clientsdetails')['email'],
+                "enable"    => 1,
+                "firstname" => $this->getWhmcsParamByKey('clientsdetails')['firstname'],
+                "lastname"  => $this->getWhmcsParamByKey('clientsdetails')['lastname'],
+                "password" => $user->getPassword()
+            ];
+            $userService->create($data);
+        }
+        return $this;
+
+    }
+
+}

+ 158 - 0
app/Services/Vps/VmIpAddressConveter.php

@@ -0,0 +1,158 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\Services\Vps;
+
+
+use MGProvision\Proxmox\v2\models\IpConfig;
+use MGProvision\Proxmox\v2\models\Kvm;
+use MGProvision\Proxmox\v2\models\Lxc;
+use MGProvision\Proxmox\v2\models\NetworkDeviceKvm;
+use MGProvision\Proxmox\v2\models\NetworkDeviceLxc;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress;
+use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
+
+class VmIpAddressConveter
+{
+    use WhmcsParams;
+    use ProductService;
+
+    /**
+     * @var Kvm|Lxc|null
+     */
+    protected $vm;
+
+    /**
+     * @var NetworkDeviceKvm[]
+     */
+    protected $networkDevices;
+
+    /**
+     * @var IpConfig[]
+     */
+    protected $ipConfigs=[];
+
+    /**
+     * @var VmIpAddress[]
+     */
+    protected $vmIpAddress;
+
+    /**
+     * VmIpAddressConveter constructor.
+     * @param Kvm|Lxc|null $vm
+     * @param VmIpAddress[] $vmIpAddress
+     */
+    public function __construct( array $vmIpAddress, $vm=null)
+    {
+        $this->vmIpAddress = $vmIpAddress;
+        $this->vm = $vm;
+    }
+
+    public function convert(){
+        $rate      = $this->getRate();
+        $networkId = $this->freeNetworDeviceId();
+        foreach ($this->vmIpAddress as $vmIpAddress){
+            $bridge = $this->getBridge($vmIpAddress);
+            if ($this->configuration()->isQemu())
+            {
+                $networkDevice = new NetworkDeviceKvm('net' . $networkId);
+                $networkDevice->setBridge($bridge)
+                    ->setRate($rate)
+                    ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0);
+                //ip config
+                $ipConfig = new IpConfig('ipconfig' . $networkId);
+                //ipv4
+                if(!preg_match("/\:/",$vmIpAddress->ip)){
+                    $ipConfig->setIp(trim($vmIpAddress->ip))
+                        ->setCidr(trim($vmIpAddress->cidr))
+                        ->setGw(trim($vmIpAddress->gateway));
+                }else{
+                    //ipv6
+                    $ipConfig->setIp6(trim($vmIpAddress->ip))
+                        ->setCidr6(trim($vmIpAddress->cidr))
+                        ->setGw6(trim($vmIpAddress->gateway));
+                }
+                $networkDevice->setTag($vmIpAddress->tag)
+                    ->setMacAddress($vmIpAddress->mac_address)
+                    ->setTrunks($vmIpAddress->trunks)
+                    ->setQueues(trim($this->configuration()->getQueues()));
+                if(filter_var($vmIpAddress->ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)){
+                    $networkDevice->setModel($this->configuration()->getNetworkModel());
+                }else{
+                    //private
+                    $networkDevice->setModel($this->configuration()->getNetworkPrivateModel());
+                }
+                $this->networkDevices[] = $networkDevice;
+                $this->ipConfigs[] = $ipConfig;
+            }
+            //lxc
+            if ($this->configuration()->isLxc())
+            {
+                $networkDevice = new NetworkDeviceLxc('net' . $networkId);
+                $networkDevice->setName('eth' . $networkId)
+                    ->setBridge($bridge)
+                    ->setFirewall($this->configuration()->isNetworkFirewall() ? 1 : 0)
+                    ->setIp($vmIpAddress->ip)
+                    ->setRate($rate)
+                    ->setCidr($vmIpAddress->cidr)
+                    ->setGw($vmIpAddress->gateway)
+                    ->setTag($vmIpAddress->tag)
+                    ->setHwaddr($vmIpAddress->mac_address)
+                    ->setTrunks($vmIpAddress->trunks);
+                $this->networkDevices[] = $networkDevice;
+            }
+            $vmIpAddress->net = $networkDevice->getId();
+            $vmIpAddress->save();
+            $networkId++;
+            unset($ip);
+        }
+    }
+
+    public function asConfig(){
+        $this->convert();
+        return array_merge($this->getNetworkDevicesAsConfig(), $this->getIpConfigsAsConfig());
+    }
+
+    public function getNetworkDevicesAsConfig(){
+        $container=[];
+        foreach ($this->networkDevices as $networkDevice){
+            $container[$networkDevice->getId()] = $networkDevice->asConfig();
+        }
+        return $container;
+    }
+
+    public function getIpConfigsAsConfig(){
+        $container=[];
+        foreach ($this->ipConfigs as $ipConfig){
+            $container[$ipConfig->getId()] = $ipConfig->asConfig();
+        }
+        return $container;
+    }
+
+    private function getRate(){
+        if ($this->isWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) && $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE) != "-1")
+        {
+            return $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE);
+        }
+        return  $this->configuration()->getRate();
+    }
+
+    private function getBridge(VmIpAddress $ipAddress){
+        //public
+        if(filter_var($ipAddress->ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)){
+            return $this->configuration()->getBridge();
+        }
+        //private
+        return $this->configuration()->getPrivateBridge() ? $this->configuration()->getPrivateBridge() :  $this->configuration()->getBridge();
+    }
+
+    private function freeNetworDeviceId(){
+        if($this->vm){
+            return $this->vm->findFreeNetworDeviceId();
+        }
+        return 0;
+    }
+
+
+}

+ 37 - 0
app/Traits/Cloud/SnippetTrait.php

@@ -0,0 +1,37 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\Traits\Cloud;
+
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+
+
+trait SnippetTrait
+{
+
+    public function hasSnippet(){
+        $ids = $this->configuration()->getCloudInitScript();
+        if(!$ids || !CloudInitScript::ofIds($ids)->count()){
+            return false;
+        }
+        return $ids && !empty($ids);
+    }
+
+    /**
+     * @return \MGProvision\Proxmox\v2\models\File|null
+     */
+    public function getSnippetFile(VmModel $vmModel){
+
+        $serverConfiguration = new ServerConfigurationRepository($this->getWhmcsParamByKey('serverid'));
+        if(!$serverConfiguration->snippetDirectory || !$serverConfiguration->snippetStorage){
+            return;
+        }
+        $fileRepository = new FileRepository();
+        $fileRepository->findByNodes([$vmModel->node])
+            ->findSnippetsByVmModel($vmModel)
+            ->findByStorages([$serverConfiguration->snippetStorage]);
+        return $fileRepository->first();
+
+    }
+}

+ 23 - 0
app/Traits/Cloud/VmNetwork.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\Traits\Cloud;
+
+use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
+use ModulesGarden\ProxmoxAddon\App\Services\Cloud\VirtualInterfaceConverter;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+trait VmNetwork
+{
+    protected function createNetwork(){
+        //Network create & update
+        $virtualInterfaces = VirtualInterface::ofHostingId($this->getWhmcsParamByKey('serviceid'))
+            ->ofVmId(sl('Vm')->getVmModel()->id)
+            ->ofNetEmpty()
+            ->get();
+        $vm =  null;
+        if( sl('Vm')->hasVm()){
+            $vm = sl('Vm')->getVm();
+        }
+        return (new VirtualInterfaceConverter( $virtualInterfaces, $vm))->asConfig();
+    }
+}

+ 42 - 0
app/Traits/Vps/SnippetTrait.php

@@ -0,0 +1,42 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\Traits\Vps;
+
+use MGProvision\Proxmox\v2\repository\FileRepository;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
+use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+
+trait SnippetTrait
+{
+
+    public function hasSnippet(){
+        if($this->isWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT) &&
+            $this->getWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT) !=0 ){
+            $id =  $this->getWhmcsConfigOption(ConfigurableOption::CLOUD_INIT_SCRIPT);
+        }else{
+            $id = $this->configuration()->getCloudInitScript();
+        }
+        if(!$id || !CloudInitScript::find($id)->count()){
+            return false;
+        }
+        return $id && $id > 0;
+    }
+
+    /**
+     * @return \MGProvision\Proxmox\v2\models\File|null
+     */
+    public function getSnippetFile(){
+
+        $serverConfiguration = new ServerConfigurationRepository($this->getWhmcsParamByKey('serverid'));
+        if(!$serverConfiguration->snippetDirectory || !$serverConfiguration->snippetStorage){
+            return;
+        }
+        $fileRepository = new FileRepository();
+        $fileRepository->findByNodes([$this->getWhmcsCustomField(CustomField::NODE)])
+            ->findSnippetsByServiceId($this->getWhmcsParamByKey('serviceid'))
+            ->findByStorages([$serverConfiguration->snippetStorage]);
+        return $fileRepository->first();
+
+    }
+}

+ 45 - 0
app/UI/Admin/Templates/assets/js/hooks/server.js

@@ -0,0 +1,45 @@
+var proxmoxVPS_td1;
+var proxmoxVPS_td2;
+var proxmoxVPS_accesshash;
+var proxmoxVPS_selectAdded = false;
+
+function proxmoxVPS_addSelect(){
+
+    var accessTd = $(  "#inputServerType").closest("tbody").find(" tr:eq(3)").find(" td:eq(0)");
+    proxmoxVPS_td1 = accessTd.html();
+    accessTd.html("Authentication");
+
+    var accessInput = $(  "#inputServerType").closest("tbody").find(" tr:eq(3)").find(" td:eq(1)");
+    proxmoxVPS_td2 = accessInput.html();
+    accessInput.html("<select name=\"accesshash\"><option value=\"pam\">Linux PAM standard authentication</option><option value=\"pve\">Proxmox VE authentication server</option><option value=\"PVEAPIToken\" hidden>API Tokens</option></select>");
+    proxmoxVPS_accesshash ? $("select[name='accesshash']").val(proxmoxVPS_accesshash): $("select[name='accesshash']").val("pam")
+    proxmoxVPS_selectAdded = true;
+
+}
+
+function proxmoxVPS_removeSelect(){
+    $( "#inputServerType").closest("tbody").find(" tr:eq(3)").find(" td:eq(0)").html(proxmoxVPS_td1);
+    $(  "#inputServerType").closest("tbody").find(" tr:eq(3)").find(" td:eq(1)").html(proxmoxVPS_td2);
+    proxmoxVPS_selectAdded = false
+}
+
+$(document).ready(function(){
+
+    var pc_ServerType =  $( "#inputServerType").val();
+
+    proxmoxVPS_accesshash = $( "textarea[name='accesshash']").val();
+    if(pc_ServerType=="proxmoxVPS" || pc_ServerType=="ProxmoxCloudVps"){
+        proxmoxVPS_addSelect();
+    }
+
+    $( "#inputServerType").change(function(){
+        pc_ServerType = $( this).val();
+
+        if(proxmoxVPS_selectAdded==true &&  pc_ServerType!="proxmoxVPS" && pc_ServerType!="ProxmoxCloudVps"   ){
+            proxmoxVPS_removeSelect();
+        }else if(!proxmoxVPS_selectAdded && pc_ServerType=="proxmoxVPS" || pc_ServerType=="ProxmoxCloudVps"  ){
+            proxmoxVPS_addSelect();
+        }
+
+    });
+});

+ 4 - 0
app/UI/Admin/Templates/cloudInitScript/update.js

@@ -0,0 +1,4 @@
+function pkOnCloudInitScriptCreatedAjaxDone(data) {
+    var id = data['htmlData']['id'];
+    $("#cloudInitScriptContainter input[name=id]").val(id);
+}

+ 30 - 0
app/UI/Admin/Templates/home/nodeDetail.js

@@ -0,0 +1,30 @@
+function mgBytesToSize(bytes) {
+    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+    if (bytes <= 1) {
+        if (bytes !== 0) {
+            var bytes = Number(bytes).toFixed(1);
+        }
+        return bytes + ' Byte';
+    }
+    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)));
+    return Math.round(bytes / Math.pow(1000, i), 2) + ' ' + sizes[i];
+}
+
+function mgTooltipCpu(tooltipItem, data) {
+    var used = Number(tooltipItem.yLabel).toFixed(2);
+    return used + "%";
+}
+
+function mgTooltipServerLoad(tooltipItem, data) {
+    var used = Number(tooltipItem.yLabel).toFixed(2);
+    return used;
+}
+
+function mgTooltipCallbackForMemory(tooltipItem, data) {
+    return mgBytesToSize(tooltipItem.yLabel) + "/s";
+}
+
+function mgTooltipCallbackForNet(tooltipItem, data) {
+    return mgBytesToSize(tooltipItem.yLabel) + "/s";
+}
+

+ 43 - 0
app/UI/CloudInitScript/Buttons/DeleteButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('deleteButton');
+        $this->initLoadModalAction(new DeleteModal());
+
+        $this->switchToRemoveBtn();
+    }
+}

+ 43 - 0
app/UI/CloudInitScript/Buttons/DeleteMassButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Modals\DeleteMassModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of DeleteMassButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassButton extends ButtonMassAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+
+        $this->initIds('deleteMassButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteMassModal());
+    }
+}

+ 18 - 0
app/UI/CloudInitScript/Buttons/UpdateButton.php

@@ -0,0 +1,18 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper\BuildUrl;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect;
+
+class UpdateButton extends ButtonRedirect
+{
+
+    public function initContent()
+    {
+        $this->initIds('update');
+        $this->setRawUrl(BuildUrl::getUrl('CloudInitScript', 'update'));
+        $this->setRedirectParams(['id'=> ":id"]);
+        $this->setIcon('lu-zmdi lu-zmdi-edit');
+        parent::initContent();
+    }
+}

+ 62 - 0
app/UI/CloudInitScript/Forms/DeleteForm.php

@@ -0,0 +1,62 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Providers\CloudInitScriptDeleteProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Hidden;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('deleteForm');
+        $this->setFormType('delete');
+        $this->setProvider(new CloudInitScriptDeleteProvider());
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $field = new Hidden();
+        $field->setName('id');
+        $field->setId('id');
+        $this->addField($field);
+        $field = new Hidden();
+        $field->setName('name');
+        $field->setId('name');
+        $this->addField($field);
+        $this->setConfirmMessage('confirmDelete', ['name' => null]);
+        $this->loadDataToForm();
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['delete'];
+    }
+}

+ 52 - 0
app/UI/CloudInitScript/Forms/DeleteMassForm.php

@@ -0,0 +1,52 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Providers\CloudInitScriptDeleteProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('deleteMassForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new CloudInitScriptDeleteProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setConfirmMessage('confirmDeleteMass', ['title' => null]);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['deleteMass'];
+    }
+}

+ 40 - 0
app/UI/CloudInitScript/Modals/DeleteMassModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Forms\DeleteMassForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('deleteMassModal');
+        $this->addForm(new DeleteMassForm());
+    }
+}

+ 40 - 0
app/UI/CloudInitScript/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('deleteModal');
+        $this->addForm(new DeleteForm());
+    }
+}

+ 55 - 0
app/UI/CloudInitScript/Pages/CloudInitScriptDataTable.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Pages;
+
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons\DeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons\DeleteMassButton;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Buttons\UpdateButton;
+use ModulesGarden\ProxmoxAddon\Core\Helper\BuildUrl;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+
+class CloudInitScriptDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        //create
+        $buttonRedirect = new ButtonRedirect('create');
+        $buttonRedirect->setShowTitle();
+        $buttonRedirect->setRawUrl(BuildUrl::getUrl('CloudInitScript', 'create'));
+        $buttonRedirect->setIcon('lu-icon-in-button lu-zmdi lu-zmdi-plus');
+        $buttonRedirect->replaceClasses(['lu-btn lu-btn--primary']);
+        $buttonRedirect->deleteHtmlAttribute('data-toggle');
+        $this->addButton($buttonRedirect);
+        //update
+        $this->addActionButton(new UpdateButton());
+        //delete
+        $this->addActionButton(new DeleteButton());
+        //mass delete
+        $this->addMassActionButton(new DeleteMassButton());
+
+    }
+
+    protected function loadHtml()
+    {
+        $this->addColumn((new Column('id'))->setSearchable(true)->setOrderable('DESC'))
+             ->addColumn((new Column('name'))->setSearchable(true, Column::TYPE_STRING));
+    }
+
+    protected function loadData()
+    {
+        $query    = (new CloudInitScript())
+            ->query()
+            ->getQuery()
+            ->select("id", "name");
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'DESC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 41 - 0
app/UI/CloudInitScript/Providers/CloudInitScriptDeleteProvider.php

@@ -0,0 +1,41 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScript\Providers;
+
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CloudInitScriptDeleteProvider extends BaseModelDataProvider
+{
+
+
+    /**
+     * CloudInitScriptProvider constructor.
+     * @param string $model
+     */
+    public function __construct()
+    {
+        parent::__construct(CloudInitScript::class);
+    }
+
+    public function delete()
+    {
+        parent::delete();
+        sl('lang')->addReplacementConstant('name', $this->formData['name']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Cloud-Init Script :name: has been deleted successfully');
+    }
+
+    public function deleteMass()
+    {
+
+        if (!$this->getRequestValue('massActions'))
+        {
+            return;
+        }
+        CloudInitScript::destroy($this->getRequestValue('massActions'));
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The selected Cloud-Init script have been deleted successfully')
+            ->setStatusSuccess()
+            ->setCallBackFunction($this->callBackFunction);
+    }
+}

+ 40 - 0
app/UI/CloudInitScriptCreate/Pages/CloudInitScriptContainter.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Pages;
+
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Providers\CloudInitScriptProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Sections\GeneralSection;
+use ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Sections\VariableSection;
+use ModulesGarden\ProxmoxAddon\Core\Helper\BuildUrl;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseStandaloneFormExtSections;
+
+class CloudInitScriptContainter extends BaseStandaloneFormExtSections implements AdminArea
+{
+
+    protected $id = 'cloudInitScriptContainter';
+    protected $name = 'cloudInitScriptContainter';
+    protected $title = 'cloudInitScriptContainter';
+
+    public function initContent()
+    {
+        $this->setProvider(new CloudInitScriptProvider());
+        $this->setFormType('update');
+        //General
+        $section = new GeneralSection('general');
+        $section->initContent();
+        $this->addSection($section);
+        //variable
+        $section = new VariableSection('variable');
+        $section->initContent();
+        $this->addSection($section);
+        $this->loadDataToForm();
+
+    }
+
+}

+ 49 - 0
app/UI/CloudInitScriptCreate/Providers/CloudInitScriptProvider.php

@@ -0,0 +1,49 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Providers;
+
+use ModulesGarden\ProxmoxAddon\App\Models\CloudInitScript;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class CloudInitScriptProvider extends BaseModelDataProvider
+{
+
+
+    /**
+     * CloudInitScriptProvider constructor.
+     * @param string $model
+     */
+    public function __construct()
+    {
+        parent::__construct(CloudInitScript::class);
+    }
+
+    public function read()
+    {
+        $this->actionElementId = $this->getRequestValue('id');
+        return parent::read(); // TODO: Change the autogenerated stub
+    }
+
+    public function create()
+    {
+        parent::create();
+        sl('lang')->addReplacementConstant('name', $this->formData['name']);
+        return (new HtmlDataJsonResponse(['id' => $this->model->id]))
+            ->setMessageAndTranslate('Cloud-Init Script :name: has been added successfully')
+            ->setCallBackFunction('pkOnCloudInitScriptCreatedAjaxDone');
+    }
+
+    public function update()
+    {
+        $enable                   = $this->formData['enable'] == 'on' ? '1' : '0';
+        $this->formData['enable'] = $enable;
+        if (!$this->formData['id'])
+        {
+            return $this->create();
+        }
+        parent::update();
+        sl('lang')->addReplacementConstant('name', $this->formData['name']);
+        return (new HtmlDataJsonResponse(['id' => $this->model->id]))->setMessageAndTranslate('Cloud-Init Script :name: has been updated successfully');
+    }
+}

+ 28 - 0
app/UI/CloudInitScriptCreate/Sections/GeneralSection.php

@@ -0,0 +1,28 @@
+<?php
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Sections;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Hidden;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Text;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Textarea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\BoxSection;
+
+class GeneralSection extends BoxSection
+{
+
+    public function initContent()
+    {
+        //id
+        $field = new Hidden("id");
+        $this->addField($field);
+        //name
+        $field = new Text("name");
+        $field->notEmpty();
+        $field->setLabelWidth(12);
+        $this->addField($field);
+        //script
+        $field = new Textarea("script");
+        $field->notEmpty();
+        $field->setLabelWidth(12);
+        $this->addField($field);
+    }
+}

+ 12 - 0
app/UI/CloudInitScriptCreate/Sections/VariableSection.php

@@ -0,0 +1,12 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\CloudInitScriptCreate\Sections;
+
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\BoxSection;
+
+class VariableSection extends BoxSection
+{
+
+}

+ 60 - 0
app/UI/CloudInitScriptCreate/Templates/sections/generalSection.tpl

@@ -0,0 +1,60 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-06)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+
+<div class="lu-widget">
+    {if ($rawObject->getRawTitle() || $rawObject->getTitle()) && $rawObject->isViewHeader()}
+        <div class="widget__header">
+            <div class="lu-widget__top lu-top">
+                <div class="lu-top__title">
+                    {if $rawObject->getIcon()}<i class="{$rawObject->getIcon()}"></i>{/if}
+                    {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T($rawObject->getTitle())}{/if}
+                </div>
+            </div>
+        </div>
+    {/if}
+    <div class="alert alert--sm alert-warning alert--faded datatable-alert-top">
+        <div class="alert__body">
+            {$MGLANG->tr('alertWarning')}
+        </div>
+    </div>
+    <div class="lu-widget__body">
+        <div class="lu-widget__content">
+  {literal}
+            <pre>#cloud-config
+hostname: {$domain}
+manage_etc_hosts: true
+fqdn: {$domain}
+user: {$username}
+password: {$passwordHash}
+chpasswd:
+  expire: False
+users:
+  - default
+package_upgrade: true</pre>
+            {/literal}
+
+            {foreach from=$rawObject->getFields() item=field }
+                {$field->getHtml()}
+            {/foreach}
+        </div>
+    </div>
+</div>

+ 55 - 0
app/UI/CloudInitScriptCreate/Templates/sections/variableSection.tpl

@@ -0,0 +1,55 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-06)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+<div class="lu-widget">
+    {if ($rawObject->getRawTitle() || $rawObject->getTitle()) && $rawObject->isViewHeader()}
+        <div class="lu-widget__header">
+            <div class="lu-widget__top top">
+                <div class="lu-top__title">
+                    {if $rawObject->getIcon()}<i class="{$rawObject->getIcon()}"></i>{/if}
+                    {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T($rawObject->getTitle())}{/if}
+                </div>
+            </div>
+        </div>
+    {/if}
+    {if $rawObject->haveInternalAlertMessage()}
+        <div class="lu-alert {if $rawObject->getInternalAlertSize() !== ''}lu-alert--{$rawObject->getInternalAlertSize()}{/if} lu-alert--{$rawObject->getInternalAlertMessageType()} lu-alert--faded modal-alert-top">
+            <div class="lu-alert__body">
+                {if $rawObject->isInternalAlertMessageRaw()|unescape:'html'}{$rawObject->getInternalAlertMessage()}{else}{$MGLANG->T($rawObject->getInternalAlertMessage())|unescape:'html'}{/if}
+            </div>
+        </div>
+    {/if}
+    <div class="lu-widget__body">
+        <div class="lu-widget__content">
+            <p>{$MGLANG->T('Product/Service Related:')}
+                {literal}
+            <pre>{$userid} {$serviceid} {$domain} {$username} {$password} {$passwordHash} {$model->dedicatedip} {$model->assignedips} {$customfields.sshkeys}</pre>
+            {/literal}
+            </p>
+            <p>{$MGLANG->T('Proxmox Cloud VPS Related:')}
+                {literal}
+            <pre>{$vm.id} {$vm.name} {$vm.password} {$vm.passwordHash} {$vm.ipv4Addresses} {$vm.ipv6Addresses} {$vm.ciuser} {$vm.sshkeys}
+        </pre>
+            {/literal}
+            </p>
+        </div>
+    </div>
+</div>

+ 41 - 0
app/UI/Cluster/Buttons/UpdateButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Cluster\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Cluster\Modals\UpdateModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class UpdateButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-zmdi lu-zmdi-edit';
+
+    public function initContent()
+    {
+        $this->initIds('clusterUpdateButton');
+        $this->initLoadModalAction(new UpdateModal());
+    }
+}

+ 76 - 0
app/UI/Cluster/Forms/UpdateForm.php

@@ -0,0 +1,76 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Cluster\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Cluster\Providers\NodeSettingProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\Validators\NumberValidator;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('nodeUpdateForm');
+        $this->setFormType('update');
+        $this->setProvider(new NodeSettingProvider);
+        $this->initFields();
+        $this->loadDataToForm();
+    }
+
+    protected function initFields()
+    {
+        //Server ID
+        $this->addField(new Fields\Hidden('server_id'));
+        //Node
+        $this->addField(new Fields\Hidden('node'));
+        //VM create
+        $field = (new Fields\Switcher('vmCreate'))->setDefaultValue('on')->setDescription('description');
+        $this->addField($field);
+        //VMs max
+        $field = ((new Fields\Text('vmsMax'))->addValidator(new NumberValidator(1, null))->setDescription('description'));
+        $this->addField($field);
+        //CPU max
+        $field = ((new Fields\Text('cpuMax'))->addValidator(new NumberValidator(1, null))->setDescription('description'));
+        $this->addField($field);
+        //Disk max
+        $field = ((new Fields\Text('diskMax'))->addValidator(new NumberValidator(1, null))->setDescription('description'));
+        $this->addField($field);
+        //RAM max
+        $field = ((new Fields\Text('ramMax'))->addValidator(new NumberValidator(1, null))->setDescription('description'));
+        $this->addField($field);
+        //Default Storage
+        $field = ((new Fields\Select('defaultStorage'))->setDescription('description'));
+        $this->addField($field);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['update'];
+    }
+}

+ 40 - 0
app/UI/Cluster/Modals/UpdateModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Cluster\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Cluster\Forms\UpdateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('clusterUpdateModal');
+        $this->addForm(new UpdateForm());
+    }
+}

+ 136 - 0
app/UI/Cluster/Pages/ClusterDataTable.php

@@ -0,0 +1,136 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Feb 5, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Cluster\Pages;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\RawDataTable\RawDataTable;
+use \ModulesGarden\ProxmoxAddon\App\UI\Cluster\Buttons\UpdateButton;
+
+/**
+ * Description of PluginInstalled
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class ClusterDataTable extends RawDataTable implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+    protected $id    = 'cluster';
+    protected $name  = 'clusterName';
+    protected $title = 'clusterTitle';
+
+    public function isRawTitle()
+    {
+        return false;
+    }
+
+    public function initContent()
+    {
+        //Update
+        $this->addActionButton(new UpdateButton);
+        $rd = new main\Core\UI\Widget\Buttons\ButtonRedirect();
+        $rd->setRawUrl('addonmodules.php?module=proxmoxAddon&mg-page=home&mg-action=nodeDetail&serverId=' . $this->getRequestValue('id'))
+            ->setIcon('lu-btn__icon lu-zmdi lu-zmdi-info-outline')
+            ->setRedirectParams(['id' => ':id']);
+        $this->addActionButton($rd);
+        try
+        {
+            $this->setServerId($this->getRequestValue('id'))->getApi();
+        }
+        catch (\Exception $ex)
+        {
+            $this->setInternalAlertMessage($ex->getMessage())
+                ->setInternalAlertMessageType('danger');
+        }
+    }
+
+    public function replaceFieldUptime($key, $row)
+    {
+        return main\App\Libs\Format::uptime($row['status']['uptime']);
+    }
+
+    public function replaceFieldCpuUsage($key, $row)
+    {
+        $cpu = $row['status']['cpu'] ? round($row['status']['cpu'] * 100, 2) . " %" : "0 %";
+        return $cpu . ' of  ' . $row['status']['cpuinfo']['cpus'] . ' CPU(s)';
+    }
+
+    public function replaceFieldSwap($key, $row)
+    {
+        return main\App\Libs\Format::convertBytes($row['status']['swap']['used']) . ' / ' . main\App\Libs\Format::convertBytes($row['status']['swap']['total']);
+    }
+
+    public function replaceFieldMemory($key, $row)
+    {
+        return main\App\Libs\Format::convertBytes($row['status']['memory']['used']) . ' / ' . main\App\Libs\Format::convertBytes($row['status']['memory']['total']);
+    }
+
+    public function replaceFieldRootfs($key, $row)
+    {
+        return main\App\Libs\Format::convertBytes($row['status']['rootfs']['used']) . ' / ' . main\App\Libs\Format::convertBytes($row['status']['rootfs']['total']);
+    }
+
+    protected function loadHtml()
+    {
+        $this->addColumn((new Column('node'))->setSearchable(true, Column::TYPE_STRING)->setOrderable('ASC'))
+            ->addColumn((new Column('uptime'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('vmsLimit'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('cpuLimit'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('diskLimit'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('ramLimit'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('cpuUsage'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('swap'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('memory'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('rootfs'))->setSearchable(true, Column::TYPE_STRING)->setOrderable());
+    }
+
+    protected function loadData()
+    {
+        $this->loadRequestObj()->setServerId($this->getRequestValue('id'))->getApi()->setInstance();
+        $dataProv       = new main\Core\UI\Widget\DataTable\DataProviders\Providers\ArrayDataProvider();
+        $nodeRepository = new proxmox\repository\NodeRepository();
+        $data           = [];
+        $loadBalancer = new main\App\Services\LoadBalancerService($this->getRequestValue('id'));
+        $loadBalancer->setApi($this->getApi());
+        foreach ($nodeRepository->fetch() as $node)
+        {
+            $loadBalancer->findByNode($node->getNode(),false);
+            $resurces = $loadBalancer->getNodesResurces()[0];
+            /* @var $node proxmox\models\Node */
+            $data[] = [
+                "id" => $node->getNode(),
+                "node" => $node->getNode(),
+                "status" => $node->getStatus(),
+                "vmsLimit" => sprintf("%s / %s" ,(int)$resurces['vmsUsed'] , $resurces['vmsMax'] ? $resurces['vmsMax']  : '-'  ) ,
+                "cpuLimit" => sprintf("%s / %s" ,(int)$resurces['cpuUsed'] , $resurces['cpuMax'] ? $resurces['cpuMax']  : '-'  ) ,
+                "diskLimit" => sprintf("%s / %s" ,main\App\Libs\Format::convertBytes((int)$resurces['diskUsed'] ), main\App\Libs\Format::convertBytes($resurces['diskMax'] ) ) ,
+                "ramLimit" => sprintf("%s / %s" ,main\App\Libs\Format::convertBytes((int)$resurces['ramUsed'] ), main\App\Libs\Format::convertBytes($resurces['ramMax']  )) ,
+
+            ];
+        }
+        $dataProv->setDefaultSorting("node", 'ASC');
+        $dataProv->setData((array) $data);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 129 - 0
app/UI/Cluster/Providers/NodeSettingProvider.php

@@ -0,0 +1,129 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Cluster\Providers;
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\StorageRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
+use ModulesGarden\ProxmoxAddon\App\Services\BaseService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class NodeSettingProvider extends BaseModelDataProvider implements AdminArea
+{
+    use BaseService;
+
+    public function __construct()
+    {
+        parent::__construct(NodeSetting::class);
+    }
+
+    public function read()
+    {
+        if (!$this->actionElementId)
+        {
+            return false;
+        }
+        $this->data['node']      = $this->actionElementId;
+        $this->data['server_id'] = $this->getRequestValue('id');
+        $query                   = NodeSetting::ofServer($this->getRequestValue('id'))
+            ->ofNode($this->data['node']);
+        foreach ($query->get() as $nodeSetting)
+        {
+
+            $this->data[$nodeSetting->setting] = $nodeSetting->value;
+        }
+        if (isset($this->data['vmCreate']))
+        {
+            $vmCreate               = $this->data['vmCreate'];
+            $this->data['vmCreate'] = $vmCreate == 1 ? "on" : "off";
+        }
+        $this->loadDefaultStorages();
+
+        sl('lang')->addReplacementConstant('node', $this->data['node']);
+    }
+
+    private function loadDefaultStorages(){
+        //defaultStorage
+        $this->availableValues['defaultStorage'][0] = sl("lang")->tr("None");
+        if(!$this->data['node']){
+            return;
+        }
+        try{
+            $this->loadRequestObj()->setServerId($this->getRequestValue('id'))->getApi()->setInstance();
+
+            $storageRepository = new StorageRepository();
+            $storageRepository->findByNodes([$this->data['node']])
+                              ->findEnabed();
+            foreach ($storageRepository->fetch() as $entity)
+            {
+                if (!in_array("images", $entity->getContentAsArray()))
+                {
+                    continue;
+                }
+                $this->availableValues['defaultStorage'][$entity->getStorage()] = $entity->getStorage();
+            }
+        }catch (ProxmoxApiException $ex){
+        }
+    }
+    public function update()
+    {
+        $fillable                   = ['vmCreate', 'vmsMax', 'cpuMax', 'diskMax', 'ramMax', 'defaultStorage'];
+        $vmCreate                   = $this->formData['vmCreate'];
+        $this->formData['vmCreate'] = $vmCreate == "on" ? 1 : 0;
+        foreach ($this->formData as $key => $value)
+        {
+            if (!in_array($key, $fillable))
+            {
+                continue;
+            }
+            //Create & update
+            if (isset($value))
+            {
+                $entity = NodeSetting::OfServer($this->formData['server_id'])->ofNode($this->formData['node'])->ofSetting($key)->first();
+                if (!$entity)
+                {
+                    $entity = new NodeSetting();
+                }
+                $entity->fill(['node' => $this->formData['node'], 'server_id' => $this->formData['server_id'], 'setting' => $key, 'value' => $value])
+                    ->save();
+                //Delete
+            }
+            else
+            {
+                if (empty($value))
+                {
+                    NodeSetting::OfServer($this->formData['server_id'])->ofNode($$this->formData['node'])->ofSetting($key)->delete();
+                }
+            }
+        }
+        sl('lang')->addReplacementConstant('node', $this->formData['node']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The settings for the node :node: has been edited successfully');
+    }
+}

+ 40 - 0
app/UI/IpManagement/Buttons/CreateButton.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals\CreateModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonCreate;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class CreateButton extends ButtonCreate implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementCreateButton');
+        $this->initLoadModalAction(new CreateModal());
+    }
+}

+ 43 - 0
app/UI/IpManagement/Buttons/DeleteButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteButton');
+        $this->initLoadModalAction(new DeleteModal());
+
+        $this->switchToRemoveBtn();
+    }
+}

+ 43 - 0
app/UI/IpManagement/Buttons/DeleteMassButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals\DeleteMassModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteMassButton extends ButtonMassAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteMassButton');
+        $this->initLoadModalAction(new DeleteMassModal());
+
+        $this->switchToRemoveBtn();
+    }
+}

+ 40 - 0
app/UI/IpManagement/Buttons/UpdateButton.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals\UpdateModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class UpdateButton extends ButtonDataTableModalAction implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementUpdateButton');
+        $this->initLoadModalAction(new UpdateModal());
+    }
+}

+ 85 - 0
app/UI/IpManagement/Fields/NodeSelect.php

@@ -0,0 +1,85 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 10, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Fields;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\AjaxFields\Select;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of NodeSelect
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class NodeSelect extends Select implements AdminArea
+{
+    private $serverId;
+
+    public function prepareAjaxData()
+    {
+        $this->serverId          = $this->getRequestValue('sid', null);
+        $this->availableValues[] = ['key' => 0, 'value' => sl('lang')->absoluteT('Any')];
+        if ($this->serverId > 0)
+        {
+            $servers = main\Core\Models\Whmcs\Server::where('id', $this->serverId);
+        }
+        else
+        {
+            $servers = main\Core\Models\Whmcs\Server::whereIn("type", ["proxmoxVPS", "ProxmoxCloudVps"])->where('disabled', '0');
+        }
+        $nodeRepository = new proxmox\repository\NodeRepository();
+        $this->value    = [0];
+        if ($this->getRequestValue('id'))
+        {
+            $this->value = [main\App\Models\IpAddress::where('id', $this->getRequestValue('id'))->first()->node];
+        }
+        foreach ($servers->get() as $server)
+        {
+            /* @var $server main\Core\Models\Whmcs\Server */
+            try
+            {
+                $host = $server->ipaddress ? $server->ipaddress : $server->hostname;
+                if(is_numeric($server->port)){
+                    $host .=":".$server->port;
+                }
+                $api  = new proxmox\Api($host, $server->username, $server->accesshash, decrypt($server->password));
+                $nodeRepository->setApi($api);
+                foreach ($nodeRepository->fetch() as $node)
+                {
+                    $this->availableValues[] = [
+                        'key'   => $node->getNode(),
+                        'value' => $node->getNode()
+                    ];
+
+                }
+            }
+            catch (proxmox\ProxmoxApiException $ex)
+            {//login to proxmox host failed
+                if ($ex->getCode() != 401)
+                {
+                    throw $ex;
+                }
+            }
+        }
+    }
+}

+ 132 - 0
app/UI/IpManagement/Forms/CreateForm.php

@@ -0,0 +1,132 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Fields\NodeSelect;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\Validators\NumberValidator;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\HalfPageSection;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\RawSection;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class CreateForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementCreateForm');
+        $this->setFormType('create');
+        $this->addClass('lu-row');
+        $this->setProvider(new IpAddressProvider);
+        $this->initFields();
+        $this->loadDataToForm();
+    }
+
+    protected function initFields()
+    {
+        //Sections
+        $mainSection = new RawSection('mainSection');
+        $mainSection->setMainContainer($this->mainContainer);
+        $leftSection = new HalfPageSection('leftSection');
+        $leftSection->setMainContainer($this->mainContainer);
+        $rightSection = new HalfPageSection('rightSection');
+        $rightSection->setMainContainer($this->mainContainer);
+        //IP Pool
+        $field = new Fields\Text('ipPool');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(true));
+        $field->setPlaceholder('192.168.0.100');
+        $mainSection->addField($field);
+        //Mask -> cidr
+        $field = new Fields\Text('cidr');
+        $field->addValidator(new main\App\UI\Validators\CidrValidator(true));
+        $field->setPlaceholder('24');
+        $mainSection->addField($field);
+
+        $row1 = new main\Core\UI\Widget\Forms\Sections\SectionLuRow('row1');
+        //VLAN Trunks
+        $field = new Fields\Text('trunks');
+        $field->addValidator(new NumberValidator());
+        $leftSection->addField($field);
+        //Tag
+        $field = new Fields\Text('tag');
+        $field->addValidator(new NumberValidator(1, 4094));
+        $rightSection->addField($field);
+
+        $row1->addSection($leftSection);
+        $row1->addSection($rightSection);
+
+        $leftSection = new HalfPageSection('leftSectionr2');
+        $leftSection->setMainContainer($this->mainContainer);
+        $rightSection = new HalfPageSection('rightSectionr2');
+        $rightSection->setMainContainer($this->mainContainer);
+        $row2 = new main\Core\UI\Widget\Forms\Sections\SectionLuRow('row2');
+
+        //Subnet Mask
+        $field = new Fields\Text('subnet_mask');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(false));
+        $field->setPlaceholder('255.255.255.0');
+        $mainSection->addField($field);
+        //Gateway
+        $field = new Fields\Text('gateway');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(false, false));
+        $field->setPlaceholder('192.168.0.1');
+        $mainSection->addField($field);
+        //Server
+        $field  = new Fields\Select('sid');
+        $values = ["0" => main\Core\ServiceLocator::call('lang')->absoluteT('Any')];
+        $values += (array)main\Core\Models\Whmcs\Server::whereIn("type", ["proxmoxVPS", "ProxmoxCloudVps"])->pluck("name", "id")->toArray();
+        $field->setAvailableValues($values);
+        $leftSection->addField($field);
+        //Node
+        $field = new NodeSelect('node');
+        $field->addReloadOnChangeField('sid');
+        $field->setMainContainer($this->mainContainer);
+        $rightSection->addField($field);
+
+        $row2->addSection($leftSection);
+        $row2->addSection($rightSection);
+
+        //Virtualization
+        $field = new Fields\Select('visualization');
+        $field->setAvailableValues(["Auto" => main\Core\ServiceLocator::call('lang')->absoluteT('Auto'), "KVM" => "KVM", "LXC" => "LXC"]);
+        $mainSection->addField($field);
+        //Private Address
+        $field = new Fields\Switcher('private');
+        $mainSection->addField($field);
+        //Add sections
+        $this->addSection($mainSection);
+        $this->addSection($row1);
+        $this->addSection($row2);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['create'];
+    }
+}

+ 62 - 0
app/UI/IpManagement/Forms/DeleteForm.php

@@ -0,0 +1,62 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteMassForm');
+        $this->setFormType('delete');
+        $this->setProvider(new IpAddressProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $field = new Fields\Hidden();
+        $field->setName('id');
+        $field->setId('id');
+        $this->addField($field);
+        $field = new Fields\Hidden();
+        $field->setName('ip');
+        $field->setId('ip');
+        $this->addField($field);
+        $this->setConfirmMessage('confirmIpAddressDelete', ['ip' => null]);
+        $this->loadDataToForm();
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['delete'];
+    }
+}

+ 52 - 0
app/UI/IpManagement/Forms/DeleteMassForm.php

@@ -0,0 +1,52 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteMassForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new IpAddressProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setConfirmMessage('confirmIpAddressDeleteMass');
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['deleteMass'];
+    }
+}

+ 122 - 0
app/UI/IpManagement/Forms/UpdateForm.php

@@ -0,0 +1,122 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Fields\NodeSelect;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers\IpAddressProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\Validators\NumberValidator;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\HalfPageSection;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\RawSection;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementUpdateForm');
+        $this->setFormType('update');
+        $this->addClass('lu-row');
+        $this->setProvider(new IpAddressProvider);
+        $this->initFields();
+        $this->loadDataToForm();
+    }
+
+    protected function initFields()
+    {
+        $mainSection = new RawSection('mainSection');
+        $mainSection->setMainContainer($this->mainContainer);
+        $leftSection = new HalfPageSection('leftSection');
+        $leftSection->setMainContainer($this->mainContainer);
+        $rightSection = new HalfPageSection('rightSection');
+        $rightSection->setMainContainer($this->mainContainer);
+        //id
+        $field = new Fields\Hidden('id');
+        $mainSection->addField($field);
+        //IP Address
+        $field = new Fields\Text('ip');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(true));
+        $field->setPlaceholder('192.168.0.100');
+        $mainSection->addField($field);
+        //MAC Address
+        $field = new Fields\Text('mac_address');
+        $field->addValidator(new main\App\UI\Validators\MacAddressValidator());
+        $field->setPlaceholder('E6:AA:5C:8B:DF:12');
+        $mainSection->addField($field);
+        //VLAN Trunks
+        $field = new Fields\Text('trunks');
+        $field->addValidator(new NumberValidator());
+        $leftSection->addField($field);
+        //Tag
+        $field = new Fields\Text('tag');
+        $field->addValidator(new NumberValidator(1, 4094));
+        $rightSection->addField($field);
+        //Subnet Mask
+        $field = new Fields\Text('subnet_mask');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(false));
+        $field->setPlaceholder('255.255.255.0');
+        $mainSection->addField($field);
+        //Gateway
+        $field = new Fields\Text('gateway');
+        $field->addValidator(new main\App\UI\Validators\IpAddressValidator(false, false));
+        $field->setPlaceholder('192.168.0.1');
+        $mainSection->addField($field);
+        //CIDR
+        $field = new Fields\Text('cidr');
+        $field->addValidator(new main\App\UI\Validators\CidrValidator(true));
+        $field->setPlaceholder('24');
+        $mainSection->addField($field);
+        //Server
+        $field  = new Fields\Select('sid');
+        $values = ["0" => main\Core\ServiceLocator::call('lang')->absoluteT('Any')];
+        $values += (array)main\Core\Models\Whmcs\Server::whereIn("type", ["proxmoxVPS", "ProxmoxCloudVps"])->pluck("name", "id")->toArray();
+        $field->setAvailableValues($values);
+        $leftSection->addField($field);
+        //Node
+        $field = new NodeSelect('node');
+        $field->addReloadOnChangeField('sid');
+        $field->setMainContainer($this->mainContainer);
+        $rightSection->addField($field);
+        //Virtualization
+        $field = new Fields\Select('visualization');
+        $field->setAvailableValues(["Auto" => main\Core\ServiceLocator::call('lang')->absoluteT('Auto'), "KVM" => "KVM", "LXC" => "LXC"]);
+        $mainSection->addField($field);
+        //Private Address
+        $field = new Fields\Switcher('private');
+        $mainSection->addField($field);
+        $this->addSection($mainSection);
+        $this->addSection($leftSection);
+        $this->addSection($rightSection);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['update'];
+    }
+}

+ 41 - 0
app/UI/IpManagement/Modals/CreateModal.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms\CreateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class CreateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementCreateModal');
+
+        $this->addForm(new CreateForm());
+    }
+}

+ 40 - 0
app/UI/IpManagement/Modals/DeleteMassModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms\DeleteMassForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteMassModal');
+        $this->addForm(new DeleteMassForm());
+    }
+}

+ 40 - 0
app/UI/IpManagement/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementDeleteModal');
+        $this->addForm(new DeleteForm());
+    }
+}

+ 40 - 0
app/UI/IpManagement/Modals/UpdateModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Forms\UpdateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagementUpdateModal');
+        $this->addForm(new UpdateForm());
+    }
+}

+ 153 - 0
app/UI/IpManagement/Pages/IpManagementDataTable.php

@@ -0,0 +1,153 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons\CreateButton;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons\DeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons\DeleteMassButton;
+use ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Buttons\UpdateButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class IpManagementDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('ipManagement');
+        $this->title = null;
+        $this->addButton(new CreateButton);
+        $this->addActionButton(new UpdateButton);
+        $this->addActionButton(new DeleteButton);
+        $this->addMassActionButton(new DeleteMassButton);
+    }
+
+    public function replaceFieldMac_address($key, $row)
+    {
+        if ($row->mac_address && $row->mac_address != 'auto')
+        {
+            return $row->mac_address;
+        }
+        return '-';
+    }
+
+    public function replaceFieldTrunks($key, $row)
+    {
+        return $row->trunks ? $row->trunks : '-';
+    }
+
+    public function replaceFieldTag($key, $row)
+    {
+        return $row->tag ? $row->tag : '-';
+    }
+
+    public function replaceFieldSubnet_mask($key, $row)
+    {
+        return $row->subnet_mask ? $row->subnet_mask : '-';
+    }
+
+    public function replaceFieldGateway($key, $row)
+    {
+        return $row->gateway ? $row->gateway : '-';
+    }
+
+    public function replaceFieldName($key, $row)
+    {
+        if ($row->sid == "0" && !$row->sid)
+        {
+            return '-';
+        }
+        return sprintf('<a href="configservers.php?action=manage&id=%s">%s</a>', $row->sid, $row->name);;
+    }
+
+    public function replaceFieldNode($key, $row)
+    {
+        if ($row->node == "0" && !$row->node)
+        {
+            return '-';
+        }
+        return $row->node;
+    }
+
+    public function replaceFieldVisualization($key, $row)
+    {
+        if ($row->visualization == "Auto")
+        {
+            return '-';
+        }
+        return $row->visualization;
+    }
+
+    public function replaceFieldPrivate($key, $row)
+    {
+        if ($row->private == '1')
+        {
+            return '<span class="lu-label lu-label--success lu-label--status">' . sl('lang')->tr('Yes') . '</span>';
+        }
+        return '<span class="lu-label lu-label--default lu-label--status">' . sl('lang')->tr('No') . '</span>';
+
+        return $row->private == '1' ? '<span class="glyphicon glyphicon-ok-sign"></span>' : '<span class="glyphicon glyphicon-remove-sign"></span>';
+    }
+
+    protected function loadHtml()
+    {
+        $i = (new main\App\Models\IpAddress())->getTable();
+        $s = (new Server)->getTable();
+        $this->addColumn((new Column('ip', $i))->setSearchable(true, Column::TYPE_STRING)->setOrderable('ASC'))
+            ->addColumn((new Column('mac_address', $i))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('trunks', $i))->setSearchable(true, 'int')->setOrderable())
+            ->addColumn((new Column('tag', $i))->setSearchable(true, 'int')->setOrderable())
+            ->addColumn((new Column('subnet_mask', $i))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('gateway', $i))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('cidr', $i))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('name', $s))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('node', $i))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('visualization', $i))->setSearchable(true, 'string')->setOrderable())
+            ->addColumn((new Column('private', $i))
+            );
+    }
+
+    protected function loadData()
+    {
+        $i        = (new main\App\Models\IpAddress())->getTable();
+        $s        = (new Server)->getTable();
+        $query    = (new main\App\Models\IpAddress)
+            ->query()
+            ->getQuery()
+            ->leftJoin($s, "{$s}.id", '=', "{$i}.sid")
+            ->select("{$i}.*", "{$s}.name")
+            ->where("{$i}.hosting_id", '0');
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("ip", 'ASC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 248 - 0
app/UI/IpManagement/Providers/IpAddressProvider.php

@@ -0,0 +1,248 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\Libs\BigInteger;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\App\Libs\inet_itop;
+use function ModulesGarden\ProxmoxAddon\App\Libs\inet_ptoi;
+use function ModulesGarden\ProxmoxAddon\App\Libs\ipv6_range;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class IpAddressProvider extends BaseModelDataProvider implements AdminArea
+{
+
+    public function __construct()
+    {
+        parent::__construct(main\App\Models\IpAddress::class);
+    }
+
+    public function read()
+    {
+        if (!$this->actionElementId)
+        {
+            return false;
+        }
+        $dbData = $this->model->where('id', $this->actionElementId)->first();
+        if ($dbData === null)
+        {
+            return;
+        }
+        $data = $dbData->toArray();
+        //Node
+        $this->data['node']          = [];
+        $this->data['node']['value'] = $data['node'];
+        unset($data['node']);
+        //sid
+        $this->data['sid']          = [];
+        $this->data['sid']['value'] = $data['sid'];
+        unset($data['sid']);
+        //visualization
+        $this->data['visualization']          = [];
+        $this->data['visualization']['value'] = $data['visualization'];
+        unset($data['visualization']);
+        //private
+        $this->data['private'] = $data['private'] == "1" ? "on" : "off";
+        unset($data['private']);
+        $this->data = array_merge($this->data, $data);
+        sl('lang')->addReplacementConstant('ip', $this->data['ip']);
+    }
+
+    public function create()
+    {
+
+        try
+        {
+            $form = $this->getFormDataValues();
+            if (preg_match('/\:/', $this->formData['ipPool']))
+            {
+                $pool = $this->createIpv6Pool($this->formData['ipPool'], $this->formData['cidr']);
+                $type = 'IPv6';
+            }
+            else
+            {
+                $pool = $this->createIpv4Pool($this->formData['ipPool'], $this->formData['cidr']);
+                $type = 'IPv4';
+            }
+            foreach ($pool as $ip)
+            {
+                if (main\App\Models\IpAddress::where("ip", $ip)->count())
+                {
+                    continue;
+                }
+                $attributes              = $this->formData;
+                $attributes['ip']        = $ip;
+                $attributes['private']   = $this->formData['private'] == "on" ? "1" : "0";
+                $attributes['trunks']    = (int)$this->formData['trunks'] > 0 ? $this->formData['trunks'] : null;
+                $attributes['tag']       = (int)$this->formData['tag'] > 0 ? $this->formData['tag'] : null;
+                $attributes['type']      = $type;
+                $attributes['hostingId'] = 0;
+                $ipAddress               = new main\App\Models\IpAddress();
+                $ipAddress->fill($attributes)->save();
+                unset($ipAddress);
+            }
+            main\Core\ServiceLocator::call('lang')->addReplacementConstant("ipPool", $this->formData['ipPool'])
+                ->addReplacementConstant("cidr", $this->formData['cidr']);
+            return (new HtmlDataJsonResponse())->setMessageAndTranslate('Pool address :ipPool:/:cidr: has been added');
+        }
+        catch (\Exception $ex)
+        {
+            return (new HtmlDataJsonResponse())->setStatusError()->setMessageAndTranslate($ex->getMessage());
+        }
+    }
+
+    private function createIpv6Pool($ipPool, $mask)
+    {
+        $prefix = implode('/', [$ipPool, $mask]);
+        $range  = ipv6_range($prefix);
+        $start  = inet_ptoi($range[1]);
+
+        $end  = inet_ptoi($range[2]);
+        $a    = new BigInteger($start);
+        $b    = new BigInteger($end);
+        $diff = $b->subtract($a)->toString();
+
+        $pool = [];
+        for ($i = 0; $i < ($diff + 1); $i++)
+        {
+            $c       = new BigInteger($i);
+            $iptoint = $a->add($c)->toString();
+            $pool[]  = inet_itop($iptoint);
+        }
+        return $pool;
+    }
+
+    private function createIpv4Pool($ipPool, $mask)
+    {
+        $pool   = [];
+        $ip_enc = ip2long($ipPool);
+        //convert last (32-$mask) bits to zeroes
+        $curr_ip         = $ip_enc | pow(2, (32 - $mask)) - pow(2, (32 - $mask));
+        $ips             = [];
+        $ip_nmask        = self::translateBitmaskToNetmask((int)$mask);
+        $ip_address_long = $ip_enc;
+        $ip_nmask_long   = ip2long($ip_nmask);
+        //caculate network address
+        $ip_net = $ip_address_long & $ip_nmask_long;
+        //caculate first usable address
+        $ip_host_first = ((~$ip_nmask_long) & $ip_address_long);
+        $ip_first      = ($ip_address_long ^ $ip_host_first) + 1;
+        //caculate last usable address
+        $ip_broadcast_invert = ~$ip_nmask_long;
+        $ip_last             = ($ip_address_long | $ip_broadcast_invert) - 1;
+        //caculate broadcast address
+        $ip_last       = ($ip_address_long | $ip_broadcast_invert) - 1;
+        $ip_last_short = long2ip($ip_last);
+        $totalHost     = (float)pow(2, (32 - $mask));
+        if ($totalHost > 10000)
+        {
+            throw new \Exception(sprintf("Subnet %s/%s is too large. You are tring to add %s addresses.", $ipPool, $mask, $totalHost));
+        }
+        for ($pos = 0; $pos < pow(2, (32 - $mask)); ++$pos)
+        {
+            $ip     = long2ip((float)$curr_ip + $pos);
+            $pool[] = $ip;
+            if ($ip == $ip_last_short)
+            {
+                break;
+            }
+        }
+        return $pool;
+    }
+
+    public static function translateBitmaskToNetmask($bitmask)
+    {
+        $maskMap = [
+            0  => "0.0.0.0",
+            1  => "128.0.0.0",
+            2  => "192.0.0.0",
+            3  => "224.0.0.0",
+            4  => "240.0.0.0",
+            5  => "248.0.0.0",
+            6  => "252.0.0.0",
+            7  => "254.0.0.0",
+            8  => "255.0.0.0",
+            9  => "255.128.0.0",
+            10 => "255.192.0.0",
+            11 => "255.224.0.0",
+            12 => "255.240.0.0",
+            13 => "255.248.0.0",
+            14 => "255.252.0.0",
+            15 => "255.254.0.0",
+            16 => "255.255.0.0",
+            17 => "255.255.128.0",
+            18 => "255.255.192.0",
+            19 => "255.255.224.0",
+            20 => "255.255.240.0",
+            21 => "255.255.248.0",
+            22 => "255.255.252.0",
+            23 => "255.255.254.0",
+            24 => "255.255.255.0",
+            25 => "255.255.255.128",
+            26 => "255.255.255.192",
+            27 => "255.255.255.224",
+            28 => "255.255.255.240",
+            29 => "255.255.255.248",
+            30 => "255.255.255.252",
+            31 => "255.255.255.254",
+            32 => "255.255.255.255"
+        ];
+
+        return isset($maskMap[$bitmask]) ? $maskMap[$bitmask] : $bitmask;
+    }
+
+    public function update()
+    {
+        $form                      = $this->getFormDataValues();
+        $this->formData['private'] = $form['private'] == "on" ? "1" : "0";
+        $this->formData['trunks']  = (int)$form['trunks'] > 0 ? $form['trunks'] : null;
+        $this->formData['tag']     = (int)$form['tag'] > 0 ? $form['tag'] : null;
+        parent::update();
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('ip', $this->formData['ip']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('IP Address :ip: has been updated successfully');
+    }
+
+    public function delete()
+    {
+        parent::delete();
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('ip', $this->formData['ip']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('IP Address :ip: has been deleted successfully');
+    }
+
+    public function deleteMass()
+    {
+        if (!$this->getRequestValue('massActions'))
+        {
+            return;
+        }
+        $this->model->destroy((array)$this->getRequestValue('massActions'));
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The selected IP Addresses have been deleted successfully');
+    }
+}

+ 41 - 0
app/UI/Jobs/Buttons/DeleteButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    public function initContent()
+    {
+        $this->initIds('deleteButton');
+        $this->initLoadModalAction(new DeleteModal());
+
+        $this->switchToRemoveBtn();
+    }
+}

+ 41 - 0
app/UI/Jobs/Buttons/InfoButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals\InfoModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class InfoButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi-info';
+
+    public function initContent()
+    {
+        $this->initIds('infoButton');
+        $this->initLoadModalAction(new InfoModal());
+    }
+}

+ 42 - 0
app/UI/Jobs/Buttons/MassDeleteButton.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals\MassDeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of MassDeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class MassDeleteButton extends ButtonMassAction implements AdminArea
+{
+    public function initContent()
+    {
+        $this->initIds('massDeleteButton');
+        $this->initLoadModalAction(new MassDeleteModal());
+
+        $this->switchToRemoveBtn();
+    }
+}
+

+ 43 - 0
app/UI/Jobs/Buttons/RunButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals\InfoModal;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals\RunModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RunButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi-replay';
+
+    public function initContent()
+    {
+        $this->initIds('runButton');
+        $this->setDisableByColumnValue('statusRaw','finished');
+        $this->initLoadModalAction(new RunModal());
+    }
+}

+ 56 - 0
app/UI/Jobs/Forms/DeleteForm.php

@@ -0,0 +1,56 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Providers\JobProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Hidden;
+
+/**
+ * Description of MassDeleteForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function getAllowedActions()
+    {
+        return ['delete'];
+    }
+
+    public function initContent()
+    {
+        $this->initIds('massDeleteForm');
+        $this->setFormType('delete');
+        $this->setProvider(new JobProvider());
+        $this->initFields();
+    }
+
+    public function initFields()
+    {
+        $this->addField(new Hidden('id'));
+        $this->addField(new Hidden('job'));
+        $this->setConfirmMessage('confirmDelete', ['job' => null]);
+        $this->loadDataToForm();
+    }
+}

+ 47 - 0
app/UI/Jobs/Forms/MassDeleteForm.php

@@ -0,0 +1,47 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Providers\JobProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of MassDeleteForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class MassDeleteForm extends BaseForm implements AdminArea
+{
+    public function getAllowedActions()
+    {
+        return ['deleteMass'];
+    }
+
+    public function initContent()
+    {
+        $this->initIds('massDeleteForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new JobProvider());
+        $this->setConfirmMessage('confirmMassDelete');
+    }
+
+}

+ 56 - 0
app/UI/Jobs/Forms/RunForm.php

@@ -0,0 +1,56 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Providers\JobProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Hidden;
+
+/**
+ * Description of MassDeleteForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RunForm extends BaseForm implements AdminArea
+{
+
+    public function getAllowedActions()
+    {
+        return ['run'];
+    }
+
+    public function initContent()
+    {
+        $this->initIds('runForm');
+        $this->setFormType('run');
+        $this->setProvider(new JobProvider());
+        $this->initFields();
+    }
+
+    public function initFields()
+    {
+        $this->addField(new Hidden('id'));
+        $this->addField(new Hidden('job'));
+        $this->setConfirmMessage('confirmRun', ['job' => null]);
+        $this->loadDataToForm();
+    }
+}

+ 40 - 0
app/UI/Jobs/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of MassDeleteModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+    public function initContent()
+    {
+        $this->initIds('deleteModal');
+        $this->replaceSubmitButtonClasses(['lu-btn lu-btn--danger submitForm']);
+        $this->addForm(new DeleteForm());
+    }
+}

+ 86 - 0
app/UI/Jobs/Modals/InfoModal.php

@@ -0,0 +1,86 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Others\StatusLabel;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job;
+use ModulesGarden\ProxmoxAddon\Core\Queue\Models\JobLog;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ModalActionButtons\BaseCancelButton;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class InfoModal extends BaseEditModal implements AdminArea
+{
+    /**
+     *
+     * @var Job
+     */
+    protected $job;
+
+    public function initContent()
+    {
+        $this->initIds('infoModal');
+        if (!$this->getRequestValue('actionElementId'))
+        {
+            return;
+        }
+        /**
+         * @var \ModulesGarden\ProxmoxAddon\App\Models\Job
+         */
+        $this->job                  = Job::findOrFail($this->getRequestValue('actionElementId'));
+        $this->customTplVars['job'] = $this->job->toArray();
+        //Job status
+        $status                        = $this->job->status ? $this->job->status : 'pending';
+        $this->customTplVars['status'] = (new StatusLabel())->setStatus($status)->getHtml();
+        $this->customTplVars['data']   = unserialize($this->job->data);
+        if ($this->job->rel_type == "hosting" && $this->job->rel_id)
+        {
+            /*@var $hosting Hosting*/
+            $hosting                            = Hosting::findOrFail($this->job->rel_id);
+            $this->customTplVars['productName'] = $hosting->product->name;
+
+        }
+        //JobLogs
+        foreach (JobLog::where('job_id', $this->job->id)->orderBy('id', 'desc')->get() as $jobLog)
+        {
+            $log                              = $jobLog->toArray();
+            $log['status']                    = (new StatusLabel())->setStatus($jobLog->type)->getHtml();
+            $log['created_at']                = fromMySQLDate($jobLog->created_at, true);
+            $this->customTplVars['jobLogs'][] = $log;
+        }
+    }
+
+    protected function initActionButtons()
+    {
+        if (!empty($this->actionButtons))
+        {
+            return $this;
+        }
+        $this->addActionButton(new BaseCancelButton);
+        return $this;
+    }
+}

+ 40 - 0
app/UI/Jobs/Modals/MassDeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms\MassDeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of MassDeleteModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class MassDeleteModal extends ModalConfirmDanger implements AdminArea
+{
+    public function initContent()
+    {
+        $this->initIds('massDeleteModal');
+        $this->replaceSubmitButtonClasses(['lu-btn lu-btn--danger submitForm']);
+        $this->addForm(new MassDeleteForm());
+    }
+}

+ 41 - 0
app/UI/Jobs/Modals/RunModal.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Forms\RunForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of MassDeleteModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RunModal extends ModalConfirmDanger implements AdminArea
+{
+    public function initContent()
+    {
+        $this->initIds('runModal');
+        $this->replaceSubmitButtonClasses(['lu-btn lu-btn--danger submitForm']);
+        $this->addForm(new RunForm());
+    }
+}

+ 74 - 0
app/UI/Jobs/Others/StatusLabel.php

@@ -0,0 +1,74 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Jan 9, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Others;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Others\Label;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of JobLabel
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class StatusLabel extends Label
+{
+    protected $name = 'labelStatus';
+    protected $id = 'labelStatus';
+    protected $title = 'labelStatus';
+    private $status;
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function setStatus($status)
+    {
+        $this->status = $status;
+        $this->setMessage(sl('lang')->tr($this->status))
+            ->setTitle(sl('lang')->tr($this->status));
+        switch ($this->status)
+        {
+            case "pending":
+                $this->setColor('e9fff7')->setBackgroundColor('737980');
+                break;
+            case "in_progress":
+            case "running":
+                $this->setColor('7b007b')->setBackgroundColor('e9ebf0');
+                break;
+            case "completed":
+            case "finished":
+                $this->setColor('e5fff4')->setBackgroundColor('5bc758');
+                break;
+            case "error":
+            case "critical":
+                $this->setColor('fcffff')->setBackgroundColor('ed4040');
+                break;
+            default:
+                //pending
+                $this->setColor('e9fff7')->setBackgroundColor('737980');
+                break;
+        }
+        return $this;
+    }
+
+
+}

+ 136 - 0
app/UI/Jobs/Pages/JobsDataTable.php

@@ -0,0 +1,136 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Pages;
+
+use http\Client;
+use ModulesGarden\ProxmoxAddon\App\Models\Job;
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Product;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons\DeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons\InfoButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons\MassDeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Buttons\RunButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Jobs\Others\StatusLabel;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of PluginPackageDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class JobsDataTable extends DataTable implements AdminArea
+{
+
+    protected function loadHtml()
+    {
+        $this->initIds('jobsDataTable');
+        $this->title = null;
+        $j           = (new Job())->getTable();
+        $h        = (new Hosting)->getTable();
+        $p        = (new Product())->getTable();
+        $this->addColumn((new Column('id'))->setOrderable('DESC'))
+            ->addColumn((new Column('name', $p))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('domain', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('job', $j))->setSearchable(true, 'string'))
+            ->addColumn((new Column('status', $j))->setSearchable(true, 'string'))
+            ->addColumn((new Column('updated_at', $j))->setSearchable(true, 'date')->setOrderable())
+            ->addColumn((new Column('created_at', $j))->setSearchable(true, 'date')->setOrderable());
+    }
+
+    public function initContent()
+    {
+        $this->addActionButton(new InfoButton());
+        $this->addActionButton(new RunButton());
+        //delete
+        $this->addActionButton(new DeleteButton());
+        //mass delete
+        $this->addMassActionButton(new MassDeleteButton());
+    }
+
+    public function replaceFieldName($key, $row)
+    {
+        return sprintf('<a href="configproducts.php?action=edit&id=%s">%s</a>', $row->packageid, $row->name);
+    }
+
+    public function replaceFieldDomain($key, $row)
+    {
+        if (!$row->domain)
+        {
+            return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->hostingId, '-');
+        }
+        return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->hostingId, $row->domain);
+    }
+
+    public function replaceFieldid_($key, $row)
+    {
+        return $row->id;
+    }
+
+    public function replaceFieldJob($key, $row)
+    {
+        return sl('lang')->tr($row->job);
+    }
+
+    public function replaceFieldStatus($key, $row)
+    {
+        if (!$row->status)
+        {
+            $row->status = 'pending';
+        }
+        $label = new StatusLabel();
+        $label->setStatus($row->status);
+        return $label->getHtml();
+    }
+
+    public function replaceFieldUpdated_at($key, $row)
+    {
+        return fromMySQLDate($row->$key, true);
+    }
+
+    public function replaceFieldCreated_at($key, $row)
+    {
+        return fromMySQLDate($row->$key, true);
+    }
+
+    protected function loadData()
+    {
+        $j        = (new Job)->getTable();
+        $h        = (new Hosting)->getTable();
+        $p        = (new Product())->getTable();
+        $query    = (new  Job)
+            ->query()
+            ->getQuery()
+            ->leftJoin($h, "{$j}.rel_id", '=', "{$h}.id")
+            ->leftJoin($p, "{$h}.packageid", '=', "{$p}.id")
+            ->select("{$j}.id", "{$j}.job", "{$j}.status", "{$j}.status AS statusRaw", "{$j}.created_at", "{$j}.updated_at",
+                     "{$h}.domain",  "{$h}.userid", "{$h}.server", "{$h}.packageid", "{$h}.id AS hostingId",
+                     "{$p}.name"
+            );
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'desc');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 82 - 0
app/UI/Jobs/Providers/JobProvider.php

@@ -0,0 +1,82 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Addon product developed. (Oct 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Jobs\Providers;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+
+/**
+ * Description of InstanceImageProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class JobProvider extends BaseModelDataProvider implements AdminArea
+{
+    public function __construct()
+    {
+        parent::__construct(main\Core\Queue\Models\Job::class);
+    }
+
+    public function read()
+    {
+        parent::read();
+        $job               = $this->data['job'];
+        $this->data['job'] = sl('lang')->tr($job);
+    }
+
+    public function create()
+    {
+
+    }
+
+    public function update()
+    {
+
+    }
+
+    public function deleteMass()
+    {
+        foreach ($this->getRequestValue('massActions') as $id)
+        {
+            $this->model->where('id', $id)->delete();
+        }
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The selected jobs have been deleted successfully');
+    }
+
+    public function delete()
+    {
+        parent::delete();
+        sl('lang')->addReplacementConstant('job', $this->formData['job']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Job :job: has been deleted successfully');
+    }
+
+    public function run(){
+        sl('lang')->addReplacementConstant('job', $this->formData['job']);
+        $task = main\App\Models\Job::findOrFail($this->formData['id']);
+        $manager = new main\Core\Queue\Manager($task);
+        $manager->fire();
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Job :job: has been ran successfully');
+    }
+}

+ 135 - 0
app/UI/Jobs/Templates/modals/infoModal.tpl

@@ -0,0 +1,135 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-11-16)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+<div class="lu-modal show lu-modal--{$rawObject->getModalSize()}" id="confirmationModal"
+     namespace="{$rawObject->getNamespace()}" index="{$rawObject->getId()}">
+    <div class="lu-modal__dialog">
+        <div class="lu-modal__content" id="mgModalContainer">
+            <div class="lu-modal__top lu-top">
+                <div class="lu-top__title lu-type-6">
+                    <span class="lu-text-faded lu-font-weight-normal">
+                {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T('modal', $rawObject->getTitle())}{/if}
+            </span>
+                </div>
+                <div class="lu-top__toolbar">
+                    <button class="lu-btn lu-btn--xs lu-btn--default lu-btn--icon lu-btn--link lu-btn--plain closeModal"
+                            data-dismiss="lu-modal" aria-label="Close" @click='closeModal($event)'>
+                        <i class="lu-btn__icon lu-zmdi lu-zmdi-close"></i>
+                    </button>
+                </div>
+            </div>
+            <div class="lu-modal__nav">
+                <ul class="lu-nav lu-nav--md lu-nav--h lu-nav--tabs">
+                    <li class="lu-nav__item is-active">
+                        <a class="lu-nav__link" data-toggle="lu-tab" href="#modalTabJob">
+                            <span class="lu-nav__link-text">{$MGLANG->T('Info')}</span>
+                        </a>
+                    </li>
+                    <li class="lu-nav__item">
+                        <a class="lu-nav__link" data-toggle="lu-tab" href="#modalTabLogs">
+                            <span class="lu-nav__link-text">{$MGLANG->T('Logs')}</span>
+                        </a>
+                    </li>
+                </ul>
+            </div>
+            <div class="lu-modal__body">
+                <div class="lu-tab-content">
+                    <div class="lu-tab-pane is-active" id="modalTabJob">
+                        <div class="lu-list-group lu-list-group--simple lu-list-group--p-h-0x list-group--collapse lu-m-b-0x">
+                            <div class="lu-row">
+                                <div class="lu-col-md-12">
+                                    <ul class="lu-list lu-list--info">
+                                        <li class="lu-list__item">
+                                            <span class="lu-list__item-title">{$MGLANG->T('Job')} </span>
+                                            <span class="lu-list__value">{$MGLANG->T($customTplVars.job.job)}</span>
+                                        </li>
+                                        <li class="lu-list__item">
+                                            <span class="lu-list__item-title">{$MGLANG->T('Status')}  </span>
+                                            <span class="lu-list__value">{$customTplVars.status}</span>
+                                        </li>
+                                        {if $customTplVars.job.rel_type == "hosting"  && $customTplVars.job.rel_id}
+                                            <li class="lu-list__item">
+                                                <span class="lu-list__item-title">{$MGLANG->T('Product/Service')}  </span>
+                                                <span class="lu-list__value"><a
+                                                            href="clientsservices.php?id={$customTplVars.job.rel_id}">#{$customTplVars.job.rel_id} {$customTplVars.productName}</a></span>
+                                            </li>
+                                        {/if}
+                                        {if $customTplVars.data.softpath}
+                                            <li class="lu-list__item">
+                                                <span class="lu-list__item-title">{$MGLANG->T('Installation Directory')}  </span>
+                                                <span class="lu-list__value">{$customTplVars.data.softpath}</span>
+                                            </li>
+                                        {/if}
+                                    </ul>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="lu-tab-pane" id="modalTabLogs">
+                        <div class="lu-list-group lu-list-group--simple lu-list-group--p-h-0x list-group--collapse lu-m-b-0x">
+                            <div class="lu-row">
+                                <div class="lu-col-md-12" style="overflow: auto; height: 430px;">
+                                    {if !$customTplVars.jobLogs}
+                                        <span>{$MGLANG->T('Job Logs not available.')}</span>
+                                    {else}
+                                        <table class="lu-table lu-table-hover">
+                                            <thead>
+                                            <tr>
+                                                <th> {$MGLANG->T('Message')}</th>
+                                                <th> {$MGLANG->T('Status')}</th>
+                                                <th> {$MGLANG->T('Created')}</th>
+                                            </tr>
+                                            </thead>
+                                            <tbody>
+                                            {foreach from=$customTplVars.jobLogs item=jobLog}
+                                                <tr>
+                                                    <td>{$jobLog.message}</td>
+                                                    <td>{$jobLog.status} </td>
+                                                    <td>{$jobLog.created_at} </td>
+                                                </tr>
+                                                {foreachelse}
+                                                <tr>
+                                                    <td colspan="2">{$MGLANG->T('Job Logs not available.')}</td>
+                                                </tr>
+                                            {/foreach}
+                                            </tbody>
+                                        </table>
+                                    {/if}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="lu-modal__actions">
+                {foreach from=$rawObject->getActionButtons() item=actionButton}
+                    {$actionButton->getHtml()}
+                {/foreach}
+            </div>
+            {if ($isDebug eq true AND (count($MGLANG->getMissingLangs()) != 0))}{literal}
+                <div class="lu-modal__actions">
+                <div class="lu-row">
+            {/literal}{foreach from=$MGLANG->getMissingLangs() key=varible item=value}{literal}
+                <div class="lu-col-md-12"><b>{/literal}{$varible}{literal}</b> = '{/literal}{$value}{literal}';</div>
+            {/literal}{/foreach}{literal}
+                </div>
+                </div>
+            {/literal}{/if}
+        </div>
+    </div>
+</div

+ 111 - 0
app/UI/NodeDetail/Pages/CpuGraph.php

@@ -0,0 +1,111 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 1, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Select;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Line;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Models\DataSet;
+
+class CpuGraph extends Line implements AdminArea
+{
+
+    use LoadAPIData;
+    protected $id = 'cpuGraph';
+    protected $name = 'cpuGraph';
+    protected $graphSettingsEnabled = true;
+    protected $graphSettingsKey = 'cpuGraphSettings';
+
+    public function initContent()
+    {
+        $selectScope = new Select('timeframe');
+        $selectScope->setAvailableValues([
+            'hour' => 'Hour',
+            'day'  => 'Day',
+            'week' => 'Week',
+            'year' => 'Year',
+        ]);
+        $selectScope->setDefaultValue('week');
+        $this->addSettingField($selectScope);
+        //yAxes
+        $this->updateChartScale('yAxes', [
+            [
+                'scaleLabel' => [
+                    'display'     => true,
+                    'labelString' => '%'
+                ],
+                'ticks'      => [
+                    'beginAtZero' => true
+                ],
+            ]]);
+        //Tooltip
+        $this->updateChartOption('tooltips', [
+            'callbacks' => [
+                'label' => 'mgTooltipCpu'
+            ]
+        ]);
+    }
+
+    public function prepareAjaxData()
+    {
+        if ($this->configChartsSettings->timeframe)
+        {
+            $this->timeframe = $this->configChartsSettings->timeframe;
+        }
+        $rrdata     = main\Core\Helper\DatabaseCache::loadData(
+            $this->graphSettingsKey .$this->timeframe. '_cacheData', [$this, 'loadApiData'], 30, true);
+        $labels     = [];
+        $dataSets   = [
+            'cpu'  => [],
+            'iowait' => [],
+        ];
+        $dateFormat = in_array($this->timeframe, ['hour', 'day']) ? "H:i:s" : "Y-m-d";
+        foreach ($rrdata as $rrd)
+        {
+            $labels[] = date($dateFormat, $rrd['time']);
+            $dataSets['cpu'][]    = (float)$rrd['cpu'] * 100;
+            $dataSets['iowait'][] = (float)$rrd['iowait'] * 100;
+        }
+
+        //Labels
+        $this->setLabels($labels);
+        //CPU Usage
+        $lang    = main\Core\ServiceLocator::call('lang');
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('CPU Usage'))
+            ->setData($dataSets['cpu'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => "rgba(174, 198, 57, 0.79)",
+                "borderColor"     => "rgba(174, 198, 57, 1)"
+            ]);
+        $this->addDataSet($dataSet);
+        //IO Delay
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('IO Delay'))
+            ->setData($dataSets['iowait'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(39, 133, 134, 0.91)',
+                "borderColor"     => 'rgba(39, 133, 134, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+    }
+}

+ 30 - 0
app/UI/NodeDetail/Pages/LoadAPIData.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+use MGProvision\Proxmox\v2 as proxmox;
+
+trait LoadAPIData
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    use \ModulesGarden\ProxmoxAddon\App\Services\BaseService;
+
+    protected $timeframe = "week";
+
+    public function loadApiData()
+    {
+        $this->setServerId($this->getRequestValue('serverId'))
+            ->getApi()
+            ->setInstance();
+        $data = [];
+
+        $request = [
+            "timeframe" => $this->timeframe,
+            "cf"        => "MAX",
+        ];
+        $node    = new proxmox\models\Node($this->getRequestValue('id'));
+        $rrdata  = $node->rrdData($request);
+        return $rrdata;
+    }
+}

+ 112 - 0
app/UI/NodeDetail/Pages/MemoryGraph.php

@@ -0,0 +1,112 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 1, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Select;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Line;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Models\DataSet;
+
+class MemoryGraph extends Line implements AdminArea
+{
+
+    use LoadAPIData;
+    protected $id = 'memoryGraph';
+    protected $name = 'memoryGraph';
+    protected $graphSettingsEnabled = true;
+    protected $graphSettingsKey = 'cpuGraphSettings';
+
+    public function initContent()
+    {
+        $selectScope = new Select('timeframe');
+        $selectScope->setAvailableValues([
+            'hour' => 'Hour',
+            'day'  => 'Day',
+            'week' => 'Week',
+            'year' => 'Year',
+        ]);
+        $selectScope->setDefaultValue('week');
+        $this->addSettingField($selectScope);
+
+        $this->updateChartScale('yAxes', [
+            [
+                'scaleLabel' => [
+                    'display'     => true,
+                    'labelString' => 'Bytes/s'
+                ],
+                'ticks'      => [
+                    'callback' => 'mgBytesToSize'
+                ],
+            ]]);
+
+        $this->updateChartOption('tooltips', [
+            'callbacks' => [
+                'label' => 'mgTooltipCallbackForMemory'
+            ]
+        ]);
+    }
+
+    public function prepareAjaxData()
+    {
+        if ($this->configChartsSettings->timeframe)
+        {
+            $this->timeframe = $this->configChartsSettings->timeframe;
+        }
+        $rrdata = main\Core\Helper\DatabaseCache::loadData(
+            $this->graphSettingsKey.$this->timeframe . '_cacheData', [$this, 'loadApiData'], 30, true);
+
+        $labels     = [];
+        $dataSets   = [
+            'memused' => [],
+            "memtotal"  => [],
+        ];
+        $dateFormat = in_array($this->timeframe, ['hour', 'day']) ? "H:i:s" : "Y-m-d";
+        foreach ($rrdata as $rrd)
+        {
+            $labels[] = date($dateFormat, $rrd['time']);
+            $dataSets['memused'][]    = (float)$rrd['memused'];
+            $dataSets['memtotal'][] = (float)$rrd['memtotal'];
+
+        }
+        //Labels
+        $this->setLabels($labels);
+        //Memory
+        $lang    = main\Core\ServiceLocator::call('lang');
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('Memory Usage'))
+            ->setData($dataSets['memused'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(39, 133, 134, 0.91)',
+                "borderColor"     => 'rgba(39, 133, 134, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+        //Total
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('Total'))
+            ->setData($dataSets['memtotal'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(174, 198, 57, 0.79)',
+                "borderColor"     => 'rgba(174, 198, 57, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+    }
+}

+ 110 - 0
app/UI/NodeDetail/Pages/NetworkGraph.php

@@ -0,0 +1,110 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 1, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Select;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Line;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Models\DataSet;
+
+class NetworkGraph extends Line implements AdminArea
+{
+
+    use LoadAPIData;
+    protected $id = 'networkGraph';
+    protected $name = 'networkGraph';
+    protected $graphSettingsEnabled = true;
+    protected $graphSettingsKey = 'cpuGraphSettings';
+
+    public function initContent()
+    {
+        $selectScope = new Select('timeframe');
+        $selectScope->setAvailableValues([
+            'hour' => 'Hour',
+            'day'  => 'Day',
+            'week' => 'Week',
+            'year' => 'Year',
+        ]);
+        $selectScope->setDefaultValue('week');
+        $this->addSettingField($selectScope);
+        $this->updateChartScale('yAxes', [
+            [
+                'scaleLabel' => [
+                    'display'     => true,
+                    'labelString' => 'Bytes/s'
+                ],
+                'ticks'      => [
+                    'callback' => 'mgBytesToSize'
+                ],
+            ]]);
+
+        $this->updateChartOption('tooltips', [
+            'callbacks' => [
+                'label' => 'mgTooltipCallbackForNet'
+            ]
+        ]);
+    }
+
+    public function prepareAjaxData()
+    {
+        if ($this->configChartsSettings->timeframe)
+        {
+            $this->timeframe = $this->configChartsSettings->timeframe;
+        }
+        $rrdata = main\Core\Helper\DatabaseCache::loadData(
+            $this->graphSettingsKey .$this->timeframe. '_cacheData', [$this, 'loadApiData'], 30, true);
+
+        $labels     = [];
+        $dataSets   = [
+            'netin' => [],
+            'netout' => [],
+        ];
+        $dateFormat = in_array($this->timeframe, ['hour', 'day']) ? "H:i:s" : "Y-m-d";
+        foreach ($rrdata as $rrd)
+        {
+            $labels[] = date($dateFormat, $rrd['time']);
+            $dataSets['netin'][]    = (float)$rrd['netin'] ;
+            $dataSets['netout'][] = (float)$rrd['netout'];
+        }
+        //Labels
+        $this->setLabels($labels);
+        //Net In
+        $lang    = main\Core\ServiceLocator::call('lang');
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('Net In'))
+            ->setData($dataSets['netin'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(174, 198, 57, 0.79)',
+                "borderColor"     => 'rgba(174, 198, 57, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+        //Net Out
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('Net Out'))
+            ->setData($dataSets['netout'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(39, 133, 134, 0.91)',
+                "borderColor"     => 'rgba(39, 133, 134, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+    }
+}

+ 90 - 0
app/UI/NodeDetail/Pages/ServerLoadGraph.php

@@ -0,0 +1,90 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 1, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Select;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Line;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Graphs\Models\DataSet;
+
+class ServerLoadGraph extends Line implements AdminArea
+{
+
+    use LoadAPIData;
+    protected $id = 'serverLoadGraph';
+    protected $name = 'serverLoadGraph';
+    protected $graphSettingsEnabled = true;
+    protected $graphSettingsKey = 'cpuGraphSettings';
+
+    public function initContent()
+    {
+
+        $selectScope = new Select('timeframe');
+        $selectScope->setAvailableValues([
+            'hour' => 'Hour',
+            'day'  => 'Day',
+            'week' => 'Week',
+            'year' => 'Year',
+        ]);
+        $selectScope->setDefaultValue('week');
+        $this->addSettingField($selectScope);
+        //Tooltip
+        $this->updateChartOption('tooltips', [
+            'callbacks' => [
+                'label' => 'mgTooltipServerLoad'
+            ]
+        ]);
+    }
+
+    public function prepareAjaxData()
+    {
+        if ($this->configChartsSettings->timeframe)
+        {
+            $this->timeframe = $this->configChartsSettings->timeframe;
+        }
+        $rrdata = main\Core\Helper\DatabaseCache::loadData(
+            $this->graphSettingsKey .$this->timeframe. '_cacheData', [$this, 'loadApiData'], 30, true);
+
+        $labels     = [];
+        $dataSets   = [
+            'loadavg' => []
+        ];
+        $dateFormat = in_array($this->timeframe, ['hour', 'day']) ? "H:i:s" : "Y-m-d";
+        foreach ($rrdata as $rrd)
+        {
+            $labels[] = date($dateFormat, $rrd['time']);
+            $dataSets['loadavg'][]    = (float)$rrd['loadavg'];
+        }
+        //Labels
+        $this->setLabels($labels);
+        //Server Load
+        $lang    = main\Core\ServiceLocator::call('lang');
+        $dataSet = new DataSet();
+        $dataSet->setTitle($lang->absoluteT('Server Load'))
+            ->setData($dataSets['loadavg'])
+            ->setConfigurationDataSet([
+                "backgroundColor" => 'rgba(174, 198, 57, 0.79)',
+                "borderColor"     => 'rgba(174, 198, 57, 1)',
+            ]);
+        $this->addDataSet($dataSet);
+    }
+}

+ 57 - 0
app/UI/NodeDetail/Pages/Subscription.php

@@ -0,0 +1,57 @@
+<?php
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+
+use MGProvision\Proxmox\v2\models\Node;
+use ModulesGarden\ProxmoxAddon\App\Services\BaseService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AjaxElementInterface;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\RawDataJsonResponse;
+
+class Subscription extends Summary implements AdminArea, AjaxElementInterface
+{
+
+    use BaseService;
+
+    protected $id = 'mg-subscription';
+    protected $name = 'mg-subscription-name';
+    protected $title = 'mg-subscription-title';
+
+    protected $vueComponent = true;
+    protected $defaultVueComponentName = 'mg-subscription';
+
+    public function initContent()
+    {
+
+    }
+
+    public function returnAjaxData()
+    {
+        $data = [];
+        $this->setServerId($this->getRequestValue('serverId'))
+            ->getApi()
+            ->setInstance();
+        $node                                = new Node($this->getRequestValue('id'));
+        $this->customTplVars['subscription'] = $node->getSubscription();
+        $data['subscription']                = $node->getSubscription();
+        return (new RawDataJsonResponse(['data' => $data]));
+    }
+}

+ 91 - 0
app/UI/NodeDetail/Pages/Summary.php

@@ -0,0 +1,91 @@
+<?php
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 14, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\NodeDetail\Pages;
+
+
+use MGProvision\Proxmox\v2\models\Node;
+use ModulesGarden\ProxmoxAddon\App\Libs\Format;
+use ModulesGarden\ProxmoxAddon\App\Services\BaseService;
+use ModulesGarden\ProxmoxAddon\Core\UI\Builder\BaseContainer;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AjaxElementInterface;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\RawDataJsonResponse;
+
+class Summary extends BaseContainer implements AdminArea, AjaxElementInterface
+{
+
+    use BaseService;
+
+    protected $id = 'mg-summary';
+    protected $name = 'mg-summary-name';
+    protected $title = 'mg-summary-title';
+
+    protected $vueComponent = true;
+    protected $defaultVueComponentName = 'mg-details';
+
+    public function initContent()
+    {
+
+    }
+
+    public function returnAjaxData()
+    {
+        $data = [];
+        $this->setServerId($this->getRequestValue('serverId'))
+            ->getApi()
+            ->setInstance();
+        $node = new Node($this->getRequestValue('id'));
+        //Summary
+        $status                      = $node->getStatus();
+        $status['memory']['percent'] = round($status['memory']['used'] / $status['memory']['total'] * 100);
+        $status['swap']['percent']   = round($status['swap']['used'] / $status['swap']['total'] * 100);
+        $status['rootfs']['percent'] = round($status['rootfs']['used'] / $status['rootfs']['total'] * 100);
+        $status['rootfs']['free']    = $status['rootfs']['total'] - $status['rootfs']['used'];
+        foreach ($status['memory'] as $k => &$v)
+        {
+            if ($k == 'percent')
+            {
+                break;
+            }
+            $v = Format::convertBytes($v);
+        }
+        foreach ($status['rootfs'] as $k => &$v)
+        {
+            if ($k == 'percent')
+            {
+                break;
+            }
+            $v = Format::convertBytes($v);
+        }
+        foreach ($status['swap'] as $k => &$v)
+        {
+            if ($k == 'percent')
+            {
+                break;
+            }
+            $v = Format::convertBytes($v);
+        }
+        $status['uptime'] = Format::uptime($status['uptime']);
+        $status['cpu']    = $status['cpu'] ? round($status['cpu'] * 100, 2) . " %" : "0 %";
+        $data['status']   = $status;
+        $data['node']     = $node->getNode();
+        return (new RawDataJsonResponse(['data' => $data]));
+    }
+}

+ 5 - 0
app/UI/NodeDetail/Templates/pages/subscription.tpl

@@ -0,0 +1,5 @@
+<mg-component-body-{$elementId|strtolower}
+        component_id='{$elementId}'
+        component_namespace='{$namespace}'
+        component_index='{$rawObject->getIndex()}'
+></mg-component-body-{$elementId|strtolower}>

+ 52 - 0
app/UI/NodeDetail/Templates/pages/subscription_components.js

@@ -0,0 +1,52 @@
+mgJsComponentHandler.addDefaultComponent('mg-subscription', {
+    template: '#t-mg-subscription',
+    props: [
+        'component_id',
+        'component_namespace',
+        'component_index'
+    ],
+    data: function () {
+        return {
+            data: {
+                installation: [],
+                details: {
+                    userins: {
+                        live_ins: {
+                            site_name: null
+                        }
+                    },
+
+                },
+            },
+            loading_state: false,
+            passwordShow: false,
+
+        };
+    },
+    created: function () {
+        var self = this;
+        self.$nextTick(function () {
+            self.loadAjaxData();
+        });
+    },
+    methods: {
+        loadAjaxData: function () {
+            var self = this;
+            self.loading_state = true;
+
+            var requestParams = {
+                loadData: self.component_id,
+                namespace: self.component_namespace,
+                index: self.component_index
+            };
+
+            var response = mgPageControler.vueLoader.vloadData(requestParams);
+            return response.done(function (data) {
+                self.data = data.data.rawData.data;
+                self.loading_state = false;
+            }).fail(function () {
+                self.loading_state = false;
+            });
+        }
+    }
+});

+ 38 - 0
app/UI/NodeDetail/Templates/pages/subscription_components.tpl

@@ -0,0 +1,38 @@
+<script type="text/x-template" id="t-mg-subscription-{$elementId|strtolower}"
+        :component_id="component_id"
+        :component_namespace="component_namespace"
+        :component_index="component_index"
+>
+    <div class="lu-row lu-row--eq-height">
+        <div class="lu-col-lg-12">
+            <div class="lu-widget">
+                <div class="lu-widget__header">
+                    <div class="lu-widget__top lu-top">
+                        <div class="lu-top__title">{$MGLANG->absoluteT('Subscription')} </div>
+                    </div>
+                </div>
+                <div class="lu-widget__body">
+                    <div class="lu-widget__content" v-if="data.subscription">
+                        <ul class="lu-list lu-list--info">
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('Status')}</span>
+                                <span class="lu-list__value"> {{ data.subscription.message }} </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('Server ID')}</span>
+                                <span class="lu-list__value"> {{ data.subscription.serverid }}</span>
+                            </li>
+                        </ul>
+                    </div>
+                    <div v-else style="padding: 15px; text-align: center; border-top: 1px solid #e9ebf0;">
+                        {$MGLANG->absoluteT('noDataAvalible')}
+                    </div>
+                </div>
+                <div class="lu-preloader-container lu-preloader-container--full-screen lu-preloader-container--overlay"
+                     v-show="loading_state">
+                    <div class="lu-preloader lu-preloader--sm"></div>
+                </div>
+            </div>
+        </div>
+    </div>
+</script>

+ 5 - 0
app/UI/NodeDetail/Templates/pages/summary.tpl

@@ -0,0 +1,5 @@
+<mg-component-body-{$elementId|strtolower}
+        component_id='{$elementId}'
+        component_namespace='{$namespace}'
+        component_index='{$rawObject->getIndex()}'
+></mg-component-body-{$elementId|strtolower}>

+ 52 - 0
app/UI/NodeDetail/Templates/pages/summary_components.js

@@ -0,0 +1,52 @@
+mgJsComponentHandler.addDefaultComponent('mg-details', {
+    template: '#t-mg-details',
+    props: [
+        'component_id',
+        'component_namespace',
+        'component_index'
+    ],
+    data: function () {
+        return {
+            data: {
+                installation: [],
+                details: {
+                    userins: {
+                        live_ins: {
+                            site_name: null
+                        }
+                    },
+
+                },
+            },
+            loading_state: false,
+            passwordShow: false,
+
+        };
+    },
+    created: function () {
+        var self = this;
+        self.$nextTick(function () {
+            self.loadAjaxData();
+        });
+    },
+    methods: {
+        loadAjaxData: function () {
+            var self = this;
+            self.loading_state = true;
+
+            var requestParams = {
+                loadData: self.component_id,
+                namespace: self.component_namespace,
+                index: self.component_index
+            };
+
+            var response = mgPageControler.vueLoader.vloadData(requestParams);
+            return response.done(function (data) {
+                self.data = data.data.rawData.data;
+                self.loading_state = false;
+            }).fail(function () {
+                self.loading_state = false;
+            });
+        }
+    }
+});

+ 116 - 0
app/UI/NodeDetail/Templates/pages/summary_components.tpl

@@ -0,0 +1,116 @@
+<script type="text/x-template" id="t-mg-details-{$elementId|strtolower}"
+        :component_id="component_id"
+        :component_namespace="component_namespace"
+        :component_index="component_index"
+>
+    <div class="lu-row lu-row--eq-height">
+        <div class="lu-col-lg-12">
+            <div class="lu-widget">
+                <div class="lu-widget__header">
+                    <div class="lu-widget__top lu-top">
+                        <div class="lu-top__title">{$MGLANG->absoluteT('Summary')} {{  data.node }}</div>
+                    </div>
+                </div>
+                <div class="lu-widget__body">
+                    <div class="lu-widget__content" v-if="data.status">
+                        <ul class="lu-list lu-list--info">
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('Uptime')}</span>
+                                <span class="lu-list__value">{literal}{{ data.status.uptime }}{/literal}</span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('Load Average')}</span>
+                                <span class="lu-list__value">{literal} {{ data.status.loadavg[0] }}, {{  data.status.loadavg[1]  }}, {{  data.status.loadavg[2]  }} {/literal}</span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('CPUs')}</span>
+                                <span class="lu-list__value"
+                                      v-if="data.status.cpuinfo.sockets > 1">{{ data.status.cpuinfo.cpus }}
+                                 x {{ data.status.cpuinfo.model }} {{ data.status.cpuinfo.sockets }} {$MGLANG->T('sockets')}
+                                </span>
+                                <span class="lu-list__value" v-else> {{ data.status.cpuinfo.cpus }}
+                                 x {{ data.status.cpuinfo.model }} {{ data.status.cpuinfo.sockets }} {$MGLANG->T('socket')}
+                                </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('CPUs Usage')}</span>
+                                <span class="lu-list__value"> {{ data.status.cpu }} </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('IO Delay')}</span>
+                                <span class="lu-list__value"> {{ data.status.idle }} % </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">
+                                    {$MGLANG->T('RAM Usage')}
+
+                                </span>
+                                <span class="lu-list__value">
+
+                                     <div class="progress" style="margin-bottom:0px; margin-right: 5px; width: 50%;">
+                                        <div class="progress-bar progress-bar-success"
+                                             role="progressbar" {literal} :aria-valuenow=" data.status.memory.percent " {/literal}
+                                             aria-valuemin="1" aria-valuemax="100"
+                                       {literal} :style=" { width: data.status.memory.percent + '%' }" {/literal}    >
+                                            {{ data.status.memory.percent }}%
+                                        </div>
+                                    </div>
+                                    {{ data.status.memory.used }} / {{ data.status.memory.total }}
+                                </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('SWAP Usage')}
+                                </span>
+                                <span class="lu-list__value">
+                                    <div class="progress" style="margin-bottom:0px; margin-right: 5px; width: 50%;">
+                                        <div class="progress-bar progress-bar-success"
+                                             role="progressbar" {literal} :aria-valuenow=" data.status.swap.percent " {/literal}
+                                             aria-valuemin="1" aria-valuemax="100"
+                                       {literal} :style=" { width: data.status.swap.percent + '%' }" {/literal}    >
+                                            {{ data.status.swap.percent }}%
+                                        </div>
+                                    </div>
+                                     {{ data.status.swap.used }} / {{ data.status.swap.total }}
+                                </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('HD Space (root)')}
+                                </span>
+                                <span class="lu-list__value">
+                                    <div class="progress" style="margin-bottom:0px; margin-right: 5px; width: 50%;">
+                                        <div class="progress-bar progress-bar-success"
+                                             role="progressbar" {literal} :aria-valuenow=" data.status.rootfs.percent " {/literal}
+                                             aria-valuemin="1" aria-valuemax="100"
+                                       {literal} :style=" { width: data.status.rootfs.percent + '%' }" {/literal}    >
+                                            {{ data.status.rootfs.percent }}%
+                                        </div>
+                                    </div>
+                                    {{ data.status.rootfs.used }} / {{ data.status.rootfs.total }}
+                                </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('KSM Sharing')}</span>
+                                <span class="lu-list__value"> {{ data.status.ksm.shared }} </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('PVE Manager Version')}</span>
+                                <span class="lu-list__value"> {{ data.status.pveversion }} </span>
+                            </li>
+                            <li class="lu-list__item">
+                                <span class="lu-list__item-title">{$MGLANG->T('Kernel Version')}</span>
+                                <span class="lu-list__value"> {{ data.status.kversion }} </span>
+                            </li>
+                        </ul>
+                    </div>
+                    <div v-else style="padding: 15px; text-align: center; border-top: 1px solid #e9ebf0;">
+                        {$MGLANG->absoluteT('noDataAvalible')}
+                    </div>
+                </div>
+                <div class="lu-preloader-container lu-preloader-container--full-screen lu-preloader-container--overlay"
+                     v-show="loading_state">
+                    <div class="lu-preloader lu-preloader--sm"></div>
+                </div>
+            </div>
+        </div>
+    </div>
+</script>

+ 41 - 0
app/UI/RecoveryVms/Buttons/DetailButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Modals\DetailModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DetailButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-info-outline';
+
+    public function initContent()
+    {
+        $this->initIds('recoveryVmsDetailButton');
+        $this->initLoadModalAction(new DetailModal());
+    }
+}

+ 120 - 0
app/UI/RecoveryVms/Forms/DetailForm.php

@@ -0,0 +1,120 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\Models\RecoveryVm;
+use ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Sections\TabContent;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseTabsForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\FormConstants;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\TabSection;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+class DetailForm extends BaseTabsForm implements AdminArea
+{
+
+    private $recoveryVm;
+
+    public function initContent()
+    {
+
+        $this->initIds('detailForm');
+        $this->setFormType(FormConstants::READ);
+        if (!$this->getRequestValue('actionElementId'))
+        {
+            return;
+        }
+        $this->recoveryVm = RecoveryVm::findOrFail($this->getRequestValue('actionElementId'));
+        $this->tabConfig();
+        $this->tabStatus();
+        $this->tabDns();
+
+    }
+
+    private function tabConfig()
+    {
+        $section = new TabSection();
+        $section->initIds(__FUNCTION__);
+        $section->enableGroupBySectionName();
+        $section->setMainContainer($this->mainContainer);
+        $section->setName(sl('lang')->T(__FUNCTION__));
+        $this->addSection($section);
+        $content = new TabContent();
+        $section->addSection($content);
+        $vars = $this->recoveryVm->getConfig();
+        foreach ($vars as $k => $v)
+        {
+            if (is_array($v))
+            {
+                unset($vars[$k]);
+            }
+        }
+        $content->setCustomTplVars($vars);
+
+    }
+
+    private function tabStatus()
+    {
+        $section = new TabSection();
+        $section->initIds(__FUNCTION__);
+        $section->enableGroupBySectionName();
+        $section->setMainContainer($this->mainContainer);
+        $section->setName(sl('lang')->T(__FUNCTION__));
+        $this->addSection($section);
+        $content = new TabContent();
+        $section->addSection($content);
+        $vars = $this->recoveryVm->getStatus();
+        unset($vars['pid']);
+        unset($vars['template']);
+        foreach ($vars as $k => $v)
+        {
+            if (is_array($v))
+            {
+                unset($vars[$k]);
+            }
+        }
+        $content->setCustomTplVars($vars);
+
+    }
+
+    private function tabDns()
+    {
+        $section = new TabSection();
+        $section->initIds(__FUNCTION__);
+        $section->enableGroupBySectionName();
+        $section->setMainContainer($this->mainContainer);
+        $section->setName(sl('lang')->T(__FUNCTION__));
+        $this->addSection($section);
+        $content = new TabContent();
+        $section->addSection($content);
+        $vars = $this->recoveryVm->getDns();
+        foreach ($vars as $k => $v)
+        {
+            if (is_array($v))
+            {
+                unset($vars[$k]);
+            }
+        }
+        $content->setCustomTplVars($vars);
+
+    }
+
+}

+ 53 - 0
app/UI/RecoveryVms/Modals/DetailModal.php

@@ -0,0 +1,53 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Modals;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ModalActionButtons\BaseCancelButton;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DetailModal extends main\Core\UI\Widget\Modals\ModalTabsEdit implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmsDetailModal');
+        $this->addForm(new main\App\UI\RecoveryVms\Forms\DetailForm());
+        $this->modalSize = 'lg';
+    }
+
+    protected function initActionButtons()
+    {
+        if (!empty($this->actionButtons))
+        {
+            return $this;
+        }
+        $this->addActionButton(new BaseCancelButton);
+        return $this;
+    }
+
+
+}

+ 122 - 0
app/UI/RecoveryVms/Pages/RecoveryVmsDataTable.php

@@ -0,0 +1,122 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Buttons\DetailButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RecoveryVmsDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('recoveryVms');
+        $this->title = null;
+        $this->addActionButton(new DetailButton);
+        $rd = new main\Core\UI\Widget\Buttons\ButtonRedirect();
+        $rd->setRawUrl('addonmodules.php?module=proxmoxAddon&mg-page=home&mg-action=recoveryToFile');
+        $rd->setIcon('lu-btn__icon lu-zmdi lu-zmdi-download');
+        $rd->setTitle("export");
+        $rd->replaceClasses(['lu-btn lu-btn--primary']);
+        $rd->setShowTitle();
+        $rd->deleteHtmlAttribute('data-toggle');
+        $this->addButton($rd);
+    }
+
+    public function replaceFieldFirstname($key, $row)
+    {
+        return sprintf('<a href="clientssummary.php?userid=%s">%s %s</a>', $row->client_id, $row->firstname, $row->lastname);
+    }
+
+    public function replaceFieldDomain($key, $row)
+    {
+
+        if ($row->domain)
+        {
+            return sprintf('<a href="clientsservices.php?userid=%s&id=%s"> %s</a>', $row->client_id, $row->service_id, $row->domain);
+        }
+        return sprintf('<a href="clientsservices.php?userid=%s&id=%s"> %s</a>', $row->client_id, $row->service_id, $row->service_id);
+    }
+
+    public function replaceFieldServer($key, $row)
+    {
+        return sprintf('<a href="configservers.php?action=manage&id=%s">%s</a>', $row->server, $row->serverName);
+    }
+
+    public function replaceFieldVirtualization($key, $row)
+    {
+        return main\Core\ServiceLocator::call('lang')->absoluteT($row->virtualization);
+    }
+
+    public function replaceFieldlast_update($key, $row)
+    {
+        return fromMySQLDate($row->last_update, true);
+    }
+
+    protected function loadHtml()
+    {
+        $r = (new main\App\Models\RecoveryVm)->getTable();
+        $c = (new main\Core\Models\Whmcs\Client)->getTable();
+        $h = (new Hosting)->getTable();
+        $s = (new Server)->getTable();
+        $this->addColumn((new Column('id', $r))->setOrderable("DESC"))
+            ->addColumn((new Column('firstname', $c))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('domain', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('server')))
+            ->addColumn((new Column('node', $r))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('vmid', $r))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('virtualization', $r))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('last_update', $r))->setSearchable(true, Column::TYPE_DATE)->setOrderable());
+    }
+
+    protected function loadData()
+    {
+        $r        = (new main\App\Models\RecoveryVm)->getTable();
+        $c        = (new main\Core\Models\Whmcs\Client)->getTable();
+        $h        = (new Hosting)->getTable();
+        $s        = (new Server)->getTable();
+        $p        = (new main\Core\Models\Whmcs\Product)->getTable();
+        $query    = (new main\App\Models\RecoveryVm)
+            ->query()
+            ->getQuery()
+            ->leftJoin($c, "{$r}.client_id", '=', "{$c}.id")
+            ->leftJoin($s, "{$r}.server_id", '=', "{$s}.id")
+            ->leftJoin($h, "{$r}.service_id", '=', "{$h}.id")
+            ->leftJoin($p, "{$h}.packageid", '=', "{$p}.id")
+            ->select("{$r}.*", "{$h}.domain", "{$h}.domainstatus", "{$h}.server", "{$c}.firstname", "{$c}.lastname", "{$s}.name AS serverName", "{$p}.name AS productName")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"]);
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'DESC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 39 - 0
app/UI/RecoveryVms/Sections/TabContent.php

@@ -0,0 +1,39 @@
+<?php
+
+/* * ********************************************************************
+ * Proxmox Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\RecoveryVms\Sections;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\RawSection;
+
+class TabContent extends RawSection implements AdminArea
+{
+    protected $customTplVars = [];
+
+    /**
+     * @param array $customTplVars
+     */
+    public function setCustomTplVars($customTplVars)
+    {
+        $this->customTplVars = $customTplVars;
+    }
+
+
+}

+ 28 - 0
app/UI/RecoveryVms/Templates/sections/tabContent.tpl

@@ -0,0 +1,28 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-04)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+<div id="{$rawObject->getId()}" class="lu-col-md-12 {$rawObject->getClasses()}">
+    <ul class="lu-list lu-list--info">
+        {foreach from=$customTplVars item=val key=k}
+            <li class="lu-list__item">
+                <span class="lu-list__item-title">{$k}</span>
+                <span class="lu-list__value"> {$val} </span>
+            </li>
+        {/foreach}
+    </ul>
+</div>

+ 32 - 0
app/UI/ServerDetail/Pages/ServerDetailContainer.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\ServerDetail\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\TabsWidget\TabsWidget;
+
+/**
+ *
+ */
+class ServerDetailContainer extends TabsWidget implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+    protected $id = 'serverDetailContainer';
+    protected $name = 'serverDetailContainer';
+    protected $title = 'serverDetailContainer';
+    protected $vueComponent = true;
+
+    public function initContent()
+    {
+        $this->setServerId($this->getRequestValue('id'));
+        $this->setRawTitle(main\Core\ServiceLocator::call('lang')->absoluteT("Server:") . " " . $this->getServer()->name);
+        $this->addElement(new main\App\UI\Vms\Pages\VmsDataTableRaw());
+        $this->addElement(new main\App\UI\Cluster\Pages\ClusterDataTable());
+        $this->addElement(new main\App\UI\VmCleaner\Pages\VmCleanerDataTable());
+        $this->addElement(new main\App\UI\Templates\Pages\TemplatesDataTable());
+        $this->addElement(new main\App\UI\ServerSettings\Forms\ServerSettingsForm());
+
+    }
+}

+ 92 - 0
app/UI/ServerSettings/Forms/ServerSettingsForm.php

@@ -0,0 +1,92 @@
+<?php
+/* * ********************************************************************
+ * Proxmox Product developed. (Dec 7, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\ServerSettings\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\ServerSettings\Providers\ServerSettingProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\Validators\NumberValidator;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Hidden;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Select;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Text;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields\Textarea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\FormInTab;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\HalfPageSection;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Sections\RawSection;
+
+class ServerSettingsForm extends FormInTab implements AdminArea
+{
+
+    protected $id = 'serverSettingsForm';
+    protected $name = 'serverSettingsForm';
+    protected $title = 'serverSettingsFormTitle';
+
+    public function isRawTitle()
+    {
+        return false;
+    }
+
+    public function initContent()
+    {
+        $this->unsetShowTitle();
+        //SettingProvider
+        $this->setProvider(new ServerSettingProvider());
+        //Add Sections
+        $leftSection  = new HalfPageSection('leftSection');
+        $rightSection = new HalfPageSection('rightSection');
+        $this->addSection($leftSection);
+        $this->addSection($rightSection);
+        $bottomSection = new RawSection('bottomSection');
+        $this->addSection($bottomSection);
+        //server_id
+        $field = new Hidden('server_id');
+        $leftSection->addField($field);
+        //VMID From
+        $field = new Text('vmid_from');
+        $field->setDescription('description');
+        $field->addValidator(new NumberValidator());
+        $leftSection->addField($field);
+        //VMID To
+        $field = new Text('vmid_to');
+        $field->setDescription('description');
+        $field->addValidator(new NumberValidator());
+        $rightSection->addField($field);
+        //sshHost
+        $field = new Text('sshHost');
+        $leftSection->addField($field);
+        //sshPort
+        $field = new Text('sshPort');
+        $rightSection->addField($field);
+        //sshUser
+        $field = new Text('sshUser');
+        $leftSection->addField($field);
+        //sshPassword
+        $field = new Text('sshPassword');
+        $rightSection->addField($field);
+        //sshKey
+        $field = new Textarea('sshKey');
+        $bottomSection->addField($field);
+        //snippetStorage
+        $field = new Select('snippetStorage');
+        $bottomSection->addField($field);
+        $this->loadDataToForm();
+
+    }
+
+}

+ 132 - 0
app/UI/ServerSettings/Providers/ServerSettingProvider.php

@@ -0,0 +1,132 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\ServerSettings\Providers;
+
+use MGProvision\Proxmox\v2\ProxmoxApiException;
+use MGProvision\Proxmox\v2\repository\StorageRepository;
+use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
+use ModulesGarden\ProxmoxAddon\App\Repositories\ServerConfigurationRepository;
+use ModulesGarden\ProxmoxAddon\App\Services\BaseService;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseDataProvider;
+
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class ServerSettingProvider extends BaseDataProvider implements AdminArea
+{
+    use BaseService;
+
+    protected $rangeVm;
+    /**
+     * @var ServerConfigurationRepository
+     */
+    protected $serverConfiguration;
+
+    public function __construct()
+    {
+        $this->rangeVm = DependencyInjection::create(RangeVm::class);
+        $this->serverConfiguration = new ServerConfigurationRepository($this->getRequestValue('id'));
+        parent::__construct();
+    }
+
+    public function read()
+    {
+
+        $dbData = $this->rangeVm->where('server_id', $this->getRequestValue('id'))->first();
+        if ($dbData !== null)
+        {
+            $this->data = $dbData->toArray();
+        }
+        $this->data['server_id'] = $this->getRequestValue('id');
+        foreach ($this->serverConfiguration->all() as $key => $value){
+            $this->data[$key] = $value;
+        }
+        $this->data['sshPassword'] = $this->serverConfiguration->getSshPassword();
+        $this->data['sshKey'] = $this->serverConfiguration->getSshKey();
+        //snippetStorage
+        try{
+            $this->availableValues['snippetStorage'] = [];
+            $this->setServerId($this->getRequestValue('id'));
+            $this->getApi()->setInstance();
+            $repository = new StorageRepository();
+            $repository->findSnippets();
+            foreach (     $repository->fetch() as $storage){
+                $this->availableValues['snippetStorage'][$storage->getStorage()] = $storage->getStorage();
+            }
+        }catch (ProxmoxApiException $ex){
+            //nothing to do
+        }
+    }
+
+    public function update()
+    {
+
+        //update & create range vnm
+        if ($this->getFormDataValues()['vmid_from'] && $this->formData['vmid_to'])
+        {
+            $dbData = $this->rangeVm->where('server_id', $this->formData['server_id'])->first();
+            if ($dbData === null)
+            {
+                $dbData = new RangeVm();
+            }
+            $dbData->fill($this->formData)->save();
+        }
+        else
+        {//delete range vm
+            $this->rangeVm->where('server_id', $this->formData['server_id'])->delete();
+        }
+        $data = [
+            'sshHost' =>  $this->formData['sshHost'],
+            'sshPort' =>  $this->formData['sshPort'],
+            'sshUser' =>  $this->formData['sshUser'],
+            'sshPassword' =>  encrypt($this->formData['sshPassword']),
+            'sshKey' =>  encrypt($this->formData['sshKey']),
+            'snippetStorage' =>  $this->formData['snippetStorage'],
+            'snippetDirectory' => null
+        ];
+        if($this->formData['snippetStorage']){
+            $this->setServerId($this->formData['server_id']);
+            $this->getApi()->setInstance();
+            $repository = new StorageRepository();
+            $repository->findSnippets();
+            foreach (     $repository->fetch() as $storage){
+                if($storage->getStorage() == $this->formData['snippetStorage']){
+                    $data['snippetDirectory'] = sprintf("%s/snippets/", $storage->getPath());
+                    break;
+                }
+            }
+        }
+        $this->serverConfiguration->fillAndSave($data);
+
+
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The server settings has been edited successfully')
+            ->setStatusSuccess()
+            ->setCallBackFunction($this->callBackFunction);
+    }
+}

+ 38 - 0
app/UI/ServerSettings/Sections/GeneralSection.php

@@ -0,0 +1,38 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\ServerSettings\Sections;
+
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of CronSection
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class GeneralSection extends main\Core\UI\Widget\Forms\Sections\BoxSection
+{
+
+    public function __construct($baseId = null)
+    {
+        parent::__construct($baseId);
+        $this->title = null;
+    }
+}

+ 40 - 0
app/UI/Servers/Modals/UpdateModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Servers\Forms\UpdateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('serverUpdateModal');
+        $this->addForm(new UpdateForm());
+    }
+}

+ 61 - 0
app/UI/Servers/Pages/ResourcesRow.php

@@ -0,0 +1,61 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 2, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Pages;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Others\AjaxFieldForDataTable;
+use WHMCS\Service\Service;
+
+/**
+ * Description of ResourcesRow
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class ResourcesRow extends AjaxFieldForDataTable implements AdminArea
+{
+
+    use ServerResources;
+
+    public function prepareAjaxData()
+    {
+        try
+        {
+            session_write_close();
+            $serverId = $this->getRequestValue('index', '0');
+            $this->setServerId($serverId);
+            $this->setHostingStatus(["Active"]);
+            if ($this->getServer()->disabled != 1)
+            {
+                $this->getApi()->setInstance();
+                $this->ramDetails();
+            }
+            if ($this->errors)
+            {
+                throw new \Exception(implode("<br/> ", $this->errors));
+            }
+            $this->ajaxData = "{$this->ramAssignedToSring()} / {$this->ramTotalToSring()} / <span style=\"color:#CCCC33\">{$this->ramFreeToSring()}</span>";
+        }
+        catch (\Exception $ex)
+        {
+            $this->ajaxData = sprintf('<span style="color:red;">%s</span>', $ex->getMessage());
+        }
+    }
+}

+ 169 - 0
app/UI/Servers/Pages/ServerResources.php

@@ -0,0 +1,169 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 4, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Pages;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use WHMCS\Database\Capsule as DB;
+use WHMCS\Service\Service;
+
+/**
+ * Description of ServerResources
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+trait ServerResources
+{
+
+    use main\App\Services\BaseService;
+    use main\Core\UI\Traits\RequestObjectHandler;
+    protected $errors = [];
+    protected $hostingStatus = ["Active"];
+    private $ramAssigned = 0;
+    private $ramTotal = 0;
+    private $ramFree = 0;
+    private $ramSuspended = 0;
+
+    public function getHostingStatus()
+    {
+        return $this->hostingStatus;
+    }
+
+    public function setHostingStatus($hostingStatus)
+    {
+        $this->hostingStatus = $hostingStatus;
+        return $this;
+    }
+
+    public function ramAssignedToSring()
+    {
+        return main\App\Libs\Format::convertBytes($this->ramAssigned);
+    }
+
+    public function ramTotalToSring()
+    {
+        return main\App\Libs\Format::convertBytes($this->ramTotal);
+    }
+
+    public function ramFreeToSring()
+    {
+        return main\App\Libs\Format::convertBytes($this->ramFree);
+    }
+
+    public function ramSuspendedToSring()
+    {
+        return main\App\Libs\Format::convertBytes($this->ramSuspended);
+    }
+
+    protected function ramDetails()
+    {
+        $nodeRepository = new proxmox\repository\NodeRepository();
+        $serverAssignedIps = $this->getServer()->assignedips;
+        $serverAssignedIps = preg_replace('/\s+/', '', $serverAssignedIps);
+        $serverAssignedIps = explode(PHP_EOL, $serverAssignedIps);
+        $privateIp = current($serverAssignedIps);
+        $serverIp =  $privateIp ?  $privateIp :  $this->getServer()->ipaddress;
+            $node           = $nodeRepository->findWithHostOrIp($this->getServer()->hostname, $serverIp );
+        //Get hosting for this server with status active and suspended
+        foreach (Service::where('server', $this->getServerId())->whereIn('domainstatus', $this->hostingStatus)->get() as $hosting)
+        {
+            try
+            {
+                if ($this->getServer()->type == "proxmoxVPS")
+                {
+                    $customFields = new \stdClass;
+                    foreach ($hosting->customFieldValues as $cf)
+                    {
+                        if ($cf->value && preg_match("/node/", $cf->customField->fieldName))
+                        {
+                            $customFields->node = $cf->value;
+                        }
+                        if ($cf->value && preg_match("/vmid/", $cf->customField->fieldName))
+                        {
+                            $customFields->vmid = $cf->value;
+                        }
+                    }
+                    if (!empty($customFields->vmid) && !empty($customFields->node) && $node->getNode() == $customFields->node)
+                    {
+                        $customFields->type = DB::table('ProxmoxAddon_ProductConfiguration')
+                            ->where('product_id', $hosting->packageId)
+                            ->where("setting", "virtualization")
+                            ->value("value");
+                        $customFields->type = \json_decode(strtolower($customFields->type), true);
+                        $customFields->virtualization =  $customFields->type;
+                        $vm = proxmox\Factory::vm($customFields);
+                        $this->vmDetails($vm, $hosting);
+                    }
+                }
+                else
+                {
+                    if ($this->getServer()->type == "ProxmoxCloudVps")
+                    {
+                        $query = DB::table('ProxmoxAddon_Vm')
+                            ->where('hosting_id', $hosting->id)
+                            ->where('node', $node->getNode())
+                            ->select('node', 'vmid', 'virtualization', 'node');
+                        foreach ($query->get() as $vserver)
+                        {
+                            $vm = proxmox\Factory::vm($vserver);
+                            $this->vmDetails($vm, $hosting);
+                        }
+                    }
+                }
+                $this->ramTotal = $node->getStatus()['memory']['total'];
+                $this->ramFree  = $this->ramTotal - $this->ramAssigned;
+            }
+            catch (\Exception $ex)
+            {
+                if( main\App\Models\Job::ofHostingId($hosting->id)->waiting()->count()){
+                    continue;
+                }
+                $this->errors[] = sprintf("Hosting #%s %s", $hosting->id, $ex->getMessage() );
+            }
+        }
+    }
+
+    private function vmDetails(proxmox\interfaces\VmInterface $vm, $hosting)
+    {
+        $stat = $vm->status();
+        if (!empty($stats['maxmem']))
+        {
+            $memory = $stats['maxmem'];
+        }
+        else
+        {
+            $config = $vm->config();
+            $memory = (int)$config['memory'] * 1048576;
+        }
+
+        if ($hosting->domainStatus == "Active")
+        {
+            $this->ramAssigned += $memory;
+        }
+        else
+        {
+            if ($hosting->domainStatus == "Suspended")
+            {
+                $this->ramSuspended += $memory;
+            }
+        }
+    }
+}

+ 129 - 0
app/UI/Servers/Pages/ServersDataTable.php

@@ -0,0 +1,129 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Pages;
+
+use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
+use ModulesGarden\ProxmoxAddon\App\UI\Servers\Buttons\UpdateButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class ServersDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('serverList');
+        $this->title = null;
+        //Manage
+        $rd = new ButtonRedirect;
+        $rd->setRawUrl('configservers.php?action=manage')
+            ->setRedirectParams(['id' => ':id']);
+        $rd->replaceClasses(['lu-btn lu-btn--sm lu-btn--link lu-btn--icon lu-btn--plain lu-btn--default lu-tooltip drop-target drop-element-attached-bottom drop-element-attached-center drop-target-attached-top drop-target-attached-center']);
+        $rd->setIcon('lu-btn-icon lu-zmdi lu-zmdi-edit');
+        $this->addActionButton($rd);
+        //Server details
+        $rd = new ButtonRedirect('serverDetail');
+        $rd->setRawUrl('addonmodules.php?module=proxmoxAddon&mg-page=home&mg-action=serverDetail')
+            ->setRedirectParams(['id' => ':id']);
+        $rd->replaceClasses(['lu-btn lu-btn--sm lu-btn--link lu-btn--icon lu-btn--plain lu-btn--default lu-tooltip drop-target drop-element-attached-bottom drop-element-attached-center drop-target-attached-top drop-target-attached-center']);
+        $rd->setIcon('lu-btn__icon lu-zmdi lu-zmdi-info-outline');
+        $this->addActionButton($rd);
+    }
+
+    public function replaceFieldResources($key, $row)
+    {
+        $resources = new ResourcesRow("resourcesRow" . $row->id);
+        $resources->setIndex($row->id);
+        return $resources->getHtml();
+    }
+
+    public function replaceFieldSuspended($key, $row)
+    {
+        $ajax = new SuspendedRow("suspendedRow" . $row->id);
+        $ajax->setIndex($row->id);
+        return $ajax->getHtml();
+    }
+
+    public function replaceFieldActiveAccounts($key, $row)
+    {
+        $active = Hosting::where('server', $row->id)->where("domainstatus", "Active")->count();
+        return sprintf("%s / %s", $active, $row->maxaccounts);
+    }
+
+    public function replaceFieldDisabled($key, $row)
+    {
+        if ($row->disabled == 1)
+        {
+            return '<span class="lu-label lu-label--default lu-label--status">' . sl('lang')->tr('Disabled') . '</span>';
+        }
+        return '<span class="lu-label lu-label--success lu-label--status">' . sl('lang')->tr('Enabled') . '</span>';
+    }
+
+    public function replaceFieldRange($key, $row)
+    {
+        if ($row->vmid_from && $row->vmid_to)
+        {
+            return sprintf("%s - %s", $row->vmid_from, $row->vmid_to);
+        }
+        return '-';
+    }
+
+    protected function loadHtml()
+    {
+        $s = (new Server)->getTable();
+        $r = (new RangeVm)->getTable();
+        $this->addColumn((new Column('name', $s))->setSearchable(true, Column::TYPE_STRING)->setOrderable('ASC'))
+            ->addColumn((new Column('ipaddress', $s))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('activeAccounts')))
+            ->addColumn((new Column('resources')))
+            ->addColumn((new Column('suspended')))
+            ->addColumn((new Column('disabled', $s)))
+            ->addColumn((new Column('range', $r))
+            );
+    }
+
+    protected function loadData()
+    {
+        $s        = (new Server)->getTable();
+        $r        = (new RangeVm)->getTable();
+        $query    = (new Server)
+            ->query()
+            ->getQuery()
+            ->leftJoin($r, "{$s}.id", '=', "{$r}.server_id")
+            ->select("{$s}.id", "{$s}.name", "{$s}.ipaddress", "{$s}.hostname", "{$s}.active", "{$s}.maxaccounts", "{$s}.disabled", "{$r}.vmid_from", "{$r}.vmid_to")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"]);
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("name", 'ASC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 61 - 0
app/UI/Servers/Pages/SuspendedRow.php

@@ -0,0 +1,61 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Oct 2, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Pages;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Others\AjaxFieldForDataTable;
+
+/**
+ * Description of ResourcesRow
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class SuspendedRow extends AjaxFieldForDataTable implements AdminArea
+{
+
+    use ServerResources;
+
+    public function prepareAjaxData()
+    {
+        session_write_close();
+        try
+        {
+            session_write_close();
+            $serverId = $this->getRequestValue('index', '0');
+            $this->setServerId($serverId);
+            $this->setHostingStatus(["Suspended"]);
+            if ($this->getServer()->disabled != 1)
+            {
+                $this->getApi()->setInstance();
+                $this->ramDetails();
+            }
+            if ($this->errors)
+            {
+                throw new \Exception(implode("<br/> ", $this->errors));
+            }
+            $this->ajaxData = "{$this->ramAssignedToSring()} / {$this->ramSuspendedToSring()}</span>";
+        }
+        catch (\Exception $ex)
+        {
+            $this->ajaxData = sprintf('<span style="color:red;">%s</span>', $ex->getMessage());
+        }
+    }
+}

+ 81 - 0
app/UI/Servers/Providers/RangeVmProvider.php

@@ -0,0 +1,81 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Servers\Providers;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class RangeVmProvider extends BaseModelDataProvider implements AdminArea
+{
+
+    public function __construct()
+    {
+        parent::__construct(RangeVm::class);
+    }
+
+    public function read()
+    {
+        if (!$this->actionElementId)
+        {
+            return false;
+        }
+        $dbData = $this->model->where('server_id', $this->actionElementId)->first();
+        if ($dbData !== null)
+        {
+            $this->data = $dbData->toArray();
+        }
+        $this->data['server_id'] = $this->actionElementId;
+    }
+
+    public function update()
+    {
+
+        //update & create
+        if ($this->formData['vmid_from'] && $this->formData['vmid_to'])
+        {
+            $dbData = $this->model->where('server_id', $this->formData['server_id'])->first();
+            if ($dbData === null)
+            {
+                $dbData = new RangeVm();
+            }
+            $dbData->fill($this->formData)->save();
+        }
+        else
+        {//delete
+            $this->model->where('server_id', $this->formData['server_id'])->delete();
+        }
+        $server = Server::find($this->formData['server_id']);
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('name', $server->name);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The VM range for the server :name: has been edited successfully')
+            ->setStatusSuccess()
+            ->setCallBackFunction($this->callBackFunction);
+    }
+}

+ 92 - 0
app/UI/Settings/Pages/SettingsContainer.php

@@ -0,0 +1,92 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 7, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Settings\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\Settings\Providers\SettingProvider;
+use ModulesGarden\ProxmoxAddon\App\UI\Settings\Sections\CronSection;
+use ModulesGarden\ProxmoxAddon\App\UI\Settings\Sections\GeneralSection;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseStandaloneFormExtSections;
+
+/**
+ * SettingsContainer
+ */
+class SettingsContainer extends BaseStandaloneFormExtSections implements AdminArea
+{
+    protected $id = 'settings';
+    protected $name = 'settings';
+    protected $title = null;
+
+    public function initContent()
+    {
+        //SettingProvider
+        $this->setProvider(new SettingProvider());
+        //Crons
+        $this->addSection(new CronSection('cron'));
+        //General
+        $general = new GeneralSection('general');
+        //LoadBalancer
+        $loadBalancer = new main\App\UI\Settings\Sections\LoadBalancerSection('loadBalancer');
+        //Minimum VMID
+        $field = new main\Core\UI\Widget\Forms\Fields\Text('proxmoxVPSMinimumVMID');
+        $field->addValidator(new main\App\UI\Validators\NumberValidator(1, 100000000 * 100000000 * 100000000));
+        $field->setLabelWidth(12);
+        $field->setDefaultValue(100);
+        $general->addField($field);
+        //debug
+        $field = new main\Core\UI\Widget\Forms\Fields\Switcher('debug');
+        $field->setDescription('description');
+        $general->addField($field);
+        //Count VMs
+        $field = new main\Core\UI\Widget\Forms\Fields\Text('vmsWeight');
+        $field->addValidator(new main\App\UI\Validators\NumberValidator(1, null));
+        $field->setLabelWidth(12);
+        $field->setDescription('description');
+        $field->setDefaultValue(1000);
+        $loadBalancer->addField($field);
+        //Weight CPU
+        $field = new main\Core\UI\Widget\Forms\Fields\Text('cpuWeight');
+        $field->addValidator(new main\App\UI\Validators\NumberValidator(1, null));
+        $field->setLabelWidth(12);
+        $field->setDescription('description');
+        $field->setDefaultValue(1);
+        $loadBalancer->addField($field);
+        //Weight Disk
+        $field = new main\Core\UI\Widget\Forms\Fields\Text('diskWeight');
+        $field->addValidator(new main\App\UI\Validators\NumberValidator(1, null));
+        $field->setLabelWidth(12);
+        $field->setDescription('description');
+        $field->setDefaultValue(1);
+        $loadBalancer->addField($field);
+        //Weight RAM
+        $field = new main\Core\UI\Widget\Forms\Fields\Text('ramWeight');
+        $field->addValidator(new main\App\UI\Validators\NumberValidator(1, null));
+        $field->setLabelWidth(12);
+        $field->setDescription('description');
+        $field->setDefaultValue(1);
+        $loadBalancer->addField($field);
+        //Section
+        $this->addSection($general);
+        $this->addSection($loadBalancer);
+        $this->loadDataToForm();
+    }
+}

+ 65 - 0
app/UI/Settings/Providers/SettingProvider.php

@@ -0,0 +1,65 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Settings\Providers;
+
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of SettingProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class SettingProvider extends main\Core\UI\Widget\Forms\DataProviders\BaseDataProvider
+{
+
+    public function read()
+    {
+        $this->data                          = [];
+        $this->data['proxmoxVPSMinimumVMID'] = main\Core\Models\Whmcs\Configuration::where("setting", "proxmoxVPSMinimumVMID")->value('value');
+        $this->data['vmsWeight']             = main\Core\Models\ModuleSettings\Model::where("setting", 'vmsWeight')->value("value");
+        $this->data['cpuWeight']             = main\Core\Models\ModuleSettings\Model::where("setting", "cpuWeight")->value("value");
+        $this->data['diskWeight']            = main\Core\Models\ModuleSettings\Model::where("setting", 'diskWeight')->value("value");
+        $this->data['ramWeight']             = main\Core\Models\ModuleSettings\Model::where("setting", 'ramWeight')->value("value");
+        $this->data['debug'] = main\Core\Models\ModuleSettings\Model::where("setting", 'debug')->value("value");
+    }
+
+    public function update()
+    {
+        if(main\Core\Models\Whmcs\Configuration::where("setting", "proxmoxVPSMinimumVMID")->count()){
+            main\Core\Models\Whmcs\Configuration::where("setting", "proxmoxVPSMinimumVMID")
+                ->update(['value' => $this->formData['proxmoxVPSMinimumVMID']]);
+        }else{
+            main\Core\Models\Whmcs\Configuration::where("setting", "proxmoxVPSMinimumVMID")
+                ->insert(["setting" => "proxmoxVPSMinimumVMID",'value' => $this->formData['proxmoxVPSMinimumVMID']]);
+        }
+        $filable = ["vmsWeight", "cpuWeight", "diskWeight", "ramWeight", 'debug'];
+        foreach ($filable as $key)
+        {
+            $entity = main\Core\Models\ModuleSettings\Model::where("setting", $key)->first();
+            if (!$entity)
+            {
+                $entity = new main\Core\Models\ModuleSettings\Model();
+            }
+            $entity->fill(['setting'=>  $key, "value" =>  $this->formData[$key]])
+                   ->save();
+        }
+    }
+}

+ 62 - 0
app/UI/Settings/Sections/CronSection.php

@@ -0,0 +1,62 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Settings\Sections;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of CronSection
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class CronSection extends main\Core\UI\Widget\Forms\Sections\BoxSection
+{
+
+    public function __construct($baseId = null)
+    {
+        parent::__construct();
+        $this->initContent();
+    }
+
+    public function initContent()
+    {
+        $this->customTplVars['crons']['vps']          = ModuleConstants::getFullPath("cron", "cron.php") . " queue";
+        $this->customTplVars['crons']['cloud']        = ModuleConstants::getFullPath("cron", "cron.php") . " rrddata";
+        $this->customTplVars['crons']['users']        = ModuleConstants::getFullPath("cron", "cron.php") . " users";
+        $this->customTplVars['crons']['recoveryList'] = ModuleConstants::getFullPath("cron", "cron.php") . " recoveryList";
+        $this->customTplVars['crons']['migrateSync']  = ModuleConstants::getFullPath("cron", "cron.php") . " migrateSync";
+        $this->customTplVars['crons']['task']         = ModuleConstants::getFullPath("cron", "cron.php") . " task";
+        $this->customTplVars['crons']['usege']         = ModuleConstants::getFullPath("cron", "cron.php") . " update-server-usage";
+        $this->customTplVars['crons']['backup']         = ModuleConstants::getFullPath("cron", "cron.php") . " remove-backups";
+        $this->customTplVars['crons']['snapshots']         = ModuleConstants::getFullPath("cron", "cron.php") . " snapshots";
+        $this->customTplVars['proxmoxVps'] = $this->isProxmoxVps();
+        $this->customTplVars['proxmoxCloudVps'] = $this->isProxmoxCloud();
+    }
+
+    private function isProxmoxVps(){
+        return file_exists(ModuleConstants::getFullPathWhmcs("modules", "servers", "proxmoxVPS", "proxmoxVPS.php"));
+    }
+
+    private function isProxmoxCloud(){
+        return file_exists(ModuleConstants::getFullPathWhmcs("modules", "servers", "ProxmoxCloudVps", "ProxmoxCloudVps.php"));
+    }
+}

+ 33 - 0
app/UI/Settings/Sections/GeneralSection.php

@@ -0,0 +1,33 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Settings\Sections;
+
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of CronSection
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class GeneralSection extends main\Core\UI\Widget\Forms\Sections\BoxSection
+{
+
+}

+ 33 - 0
app/UI/Settings/Sections/LoadBalancerSection.php

@@ -0,0 +1,33 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 12, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Settings\Sections;
+
+use ModulesGarden\ProxmoxAddon as main;
+
+/**
+ * Description of CronSection
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class LoadBalancerSection extends main\Core\UI\Widget\Forms\Sections\BoxSection
+{
+
+}

+ 74 - 0
app/UI/Settings/Templates/sections/cronSection.tpl

@@ -0,0 +1,74 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-06)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+<div class="lu-widget">
+    {if ($rawObject->getRawTitle() || $rawObject->getTitle()) && $rawObject->isViewHeader()}
+        <div class="lu-widget__header">
+            <div class="lu-widget__top top">
+                <div class="lu-top__title">
+                    {if $rawObject->getIcon()}<i class="{$rawObject->getIcon()}"></i>{/if}
+                    {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T($rawObject->getTitle())}{/if}
+                </div>
+            </div>
+        </div>
+    {/if}
+    {if $rawObject->haveInternalAlertMessage()}
+        <div class="lu-alert {if $rawObject->getInternalAlertSize() !== ''}lu-alert--{$rawObject->getInternalAlertSize()}{/if} lu-alert--{$rawObject->getInternalAlertMessageType()} lu-alert--faded modal-alert-top">
+            <div class="lu-alert__body">
+                {if $rawObject->isInternalAlertMessageRaw()|unescape:'html'}{$rawObject->getInternalAlertMessage()}{else}{$MGLANG->T($rawObject->getInternalAlertMessage())|unescape:'html'}{/if}
+            </div>
+        </div>
+    {/if}
+    <div class="lu-widget__body">
+        <div class="lu-widget__content">
+            <p>{$MGLANG->T('The cron job for Proxmox VPS & Proxmox Cloud VPS (each 5 minutes suggested):')}
+            <pre>php -q {$customTplVars.crons.vps}</pre>
+            </p>
+            {if $customTplVars.proxmoxCloudVps}
+            <p>{$MGLANG->T('The cron job for Proxmox Cloud VPS (each hour suggested):')}
+            <pre>php -q {$customTplVars.crons.cloud}</pre>
+            </p>
+            {/if}
+            <p>{$MGLANG->T('The cron job for users synchronization (run only once):')}
+            <pre>php -q {$customTplVars.crons.users}</pre>
+            </p>
+            <p>{$MGLANG->T('The cron job for VM recovery synchronization:')}
+            <pre>php -q {$customTplVars.crons.recoveryList}</pre>
+            </p>
+            <p>{$MGLANG->T('The cron job for VMs migration synchronization:')}
+            <pre>php -q {$customTplVars.crons.migrateSync}</pre>
+            </p>
+            <p>{$MGLANG->T('Cron job tasks synchronization:')}
+            <pre>php -q {$customTplVars.crons.task}</pre>
+            </p>
+            <p>{$MGLANG->T('Cron job update server usage (each 5 minutes suggested):')}
+            <pre>php -q {$customTplVars.crons.usege}</pre>
+            {if $customTplVars.proxmoxVps}
+                <p>{$MGLANG->T('Cron job remove backups (each 24 hours suggested):')}
+                <pre>php -q {$customTplVars.crons.backup}</pre>
+                </p>
+                <p>{$MGLANG->T('Cron job snapshot jobs (optional, each 5 minutes suggested):')}
+                <pre>php -q {$customTplVars.crons.snapshots}</pre>
+                </p>
+            {/if}
+        </div>
+    </div>
+</div>

+ 41 - 0
app/UI/Settings/Templates/sections/generalSection.tpl

@@ -0,0 +1,41 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-06)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+
+<div class="lu-widget">
+    {if ($rawObject->getRawTitle() || $rawObject->getTitle()) && $rawObject->isViewHeader()}
+        <div class="widget__header">
+            <div class="lu-widget__top lu-top">
+                <div class="lu-top__title">
+                    {if $rawObject->getIcon()}<i class="{$rawObject->getIcon()}"></i>{/if}
+                    {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T($rawObject->getTitle())}{/if}
+                </div>
+            </div>
+        </div>
+    {/if}
+    <div class="lu-widget__body">
+        <div class="lu-widget__content">
+            {foreach from=$rawObject->getFields() item=field }
+                {$field->getHtml()}
+            {/foreach}
+        </div>
+    </div>
+</div>

+ 41 - 0
app/UI/Settings/Templates/sections/loadBalancerSection.tpl

@@ -0,0 +1,41 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-10-06)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+
+<div class="lu-widget">
+    {if ($rawObject->getRawTitle() || $rawObject->getTitle()) && $rawObject->isViewHeader()}
+        <div class="widget__header">
+            <div class="lu-widget__top lu-top">
+                <div class="lu-top__title">
+                    {if $rawObject->getIcon()}<i class="{$rawObject->getIcon()}"></i>{/if}
+                    {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T($rawObject->getTitle())}{/if}
+                </div>
+            </div>
+        </div>
+    {/if}
+    <div class="lu-widget__body">
+        <div class="lu-widget__content">
+            {foreach from=$rawObject->getFields() item=field }
+                {$field->getHtml()}
+            {/foreach}
+        </div>
+    </div>
+</div>

+ 43 - 0
app/UI/TaskHistory/Buttons/DeleteButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('taskHistoryDeleteButton');
+        $this->initLoadModalAction(new DeleteModal());
+
+        $this->switchToRemoveBtn();
+    }
+}

+ 43 - 0
app/UI/TaskHistory/Buttons/DeleteMassButton.php

@@ -0,0 +1,43 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Modals\DeleteMassModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of DeleteMassButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassButton extends ButtonMassAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+
+        $this->initIds('tashHistoryDeleteMassButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteMassModal());
+    }
+}

+ 62 - 0
app/UI/TaskHistory/Forms/DeleteForm.php

@@ -0,0 +1,62 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Providers\TaskHistoryProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('taskHistoryDeleteForm');
+        $this->setFormType('delete');
+        $this->setProvider(new TaskHistoryProvider());
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $field = new Fields\Hidden();
+        $field->setName('id');
+        $field->setId('id');
+        $this->addField($field);
+        $field = new Fields\Hidden();
+        $field->setName('name');
+        $field->setId('name');
+        $this->addField($field);
+        $this->setConfirmMessage('confirmTaskHistoryDelete', ['name' => null]);
+        $this->loadDataToForm();
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['delete'];
+    }
+}

+ 52 - 0
app/UI/TaskHistory/Forms/DeleteMassForm.php

@@ -0,0 +1,52 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Providers\TaskHistoryProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('taskHistoryDeleteMassForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new TaskHistoryProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setConfirmMessage('confirmTaskHistoryDeleteMass', ['title' => null]);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['deleteMass'];
+    }
+}

+ 40 - 0
app/UI/TaskHistory/Modals/DeleteMassModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Forms\DeleteMassForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('taskHistoryDeleteMassModal');
+        $this->addForm(new DeleteMassForm());
+    }
+}

+ 40 - 0
app/UI/TaskHistory/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('taskHistoryDeleteModal');
+        $this->addForm(new DeleteForm());
+    }
+}

+ 106 - 0
app/UI/TaskHistory/Pages/TaskHistoryDataTable.php

@@ -0,0 +1,106 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Buttons\DeleteMassButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class TaskHistoryDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('taskHistory');
+        $this->title = null;
+        $this->addActionButton(new main\App\UI\TaskHistory\Buttons\DeleteButton());
+        $this->addMassActionButton(new DeleteMassButton);
+    }
+
+    public function replaceFieldDomain($key, $row)
+    {
+        if ($row->domain)
+        {
+            return sprintf('<a href="clientsservices.php?userid=%s&id=%s"> %s</a>', $row->userid, $row->hosting_id, $row->domain);
+        }
+        return sprintf('<a href="clientsservices.php?userid=%s&id=%s"> %s</a>', $row->userid, $row->hosting_id, $row->hosting_id);
+    }
+
+    public function replaceFieldStatus($key, $row)
+    {
+        $backgroundColor = [
+            "0" => "f89406",
+            "1" => "46a546",
+            "2" => "d9534f",
+        ];
+        $label           = new main\Core\UI\Widget\Others\Label();
+        $lang            = main\Core\ServiceLocator::call('lang');
+        $label->setMessage($lang->absoluteT('status', $row->status))
+            ->setTitle($lang->absoluteT('status', $row->status))
+            ->setColor('fcffff')
+            ->setBackgroundColor($backgroundColor[$row->status]);
+        return $label->getHtml();
+    }
+
+    public function replaceFielDstart_trime($key, $row)
+    {
+        return fromMySQLDate($row->last_update, true);
+    }
+
+    protected function loadHtml()
+    {
+        $t = (new main\App\Models\TaskHistory)->getTable();
+        $h = (new Hosting)->getTable();
+        $this->addColumn((new Column('id', $t))->setSearchable(true, Column::TYPE_INT)->setOrderable('DESC'))
+            ->addColumn((new Column('domain', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('node', $t))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('vmid', $t))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('status', $t))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('name', $t))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('description', $t))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('start_time', $t))->setSearchable(true, Column::TYPE_DATE)->setOrderable());
+    }
+
+    protected function loadData()
+    {
+        $t        = (new main\App\Models\TaskHistory)->getTable();
+        $h        = (new Hosting)->getTable();
+        $p        = (new main\Core\Models\Whmcs\Product)->getTable();
+        $query    = (new main\App\Models\TaskHistory)
+            ->query()
+            ->getQuery()
+            ->leftJoin($h, "{$t}.hosting_id", '=', "{$h}.id")
+            ->select("{$t}.*", "{$h}.domain", "{$h}.userid");
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'DESC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 63 - 0
app/UI/TaskHistory/Providers/TaskHistoryProvider.php

@@ -0,0 +1,63 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\TaskHistory\Providers;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class TaskHistoryProvider extends BaseModelDataProvider implements AdminArea
+{
+
+    public function __construct()
+    {
+        parent::__construct(main\App\Models\TaskHistory::class);
+    }
+
+
+    public function delete()
+    {
+        parent::delete();
+        sl('lang')->addReplacementConstant('name', $this->formData['name']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Task History :name: has been deleted successfully');
+    }
+
+    public function deleteMass()
+    {
+
+        if (!$this->getRequestValue('massActions'))
+        {
+            return;
+        }
+        main\App\Models\TaskHistory::destroy($this->getRequestValue('massActions'));
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The selected task history have been deleted successfully')
+            ->setStatusSuccess()
+            ->setCallBackFunction($this->callBackFunction);
+    }
+}

+ 40 - 0
app/UI/Templates/Buttons/CreateButton.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals\CreateModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonCreate;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class CreateButton extends ButtonCreate implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateCreateButton');
+        $this->initLoadModalAction(new CreateModal());
+    }
+}

+ 42 - 0
app/UI/Templates/Buttons/DeleteButton.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteModal());
+    }
+}

+ 42 - 0
app/UI/Templates/Buttons/DeleteMassButton.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals\DeleteMassModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteMassButton extends ButtonMassAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteMassButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteMassModal());
+    }
+}

+ 41 - 0
app/UI/Templates/Buttons/UpdateButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals\UpdateModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class UpdateButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-zmdi lu-zmdi-edit';
+
+    public function initContent()
+    {
+        $this->initIds('templateUpdateButton');
+        $this->initLoadModalAction(new UpdateModal());
+    }
+}

+ 82 - 0
app/UI/Templates/Fields/NodeSelect.php

@@ -0,0 +1,82 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 10, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Fields;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\AjaxFields\Select;
+
+/**
+ * Description of NodeSelect
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class NodeSelect extends Select implements AdminArea
+{
+    private $formData;
+
+    public function returnAjaxData()
+    {
+        $this->formData = $this->getRequestValue('formData', []);
+        $options        = [];
+        foreach ($this->getOptions() as $id => $option)
+        {
+            $options[$id] = [
+                'title'    => $option,
+                'selected' => $this->formData['id'] && main\App\Models\IpAddress::where('id', $this->formData['id'])->where("node", $option)->count()
+            ];
+        }
+        return (new main\Core\UI\ResponseTemplates\RawDataJsonResponse($options));
+    }
+
+    private function getOptions()
+    {
+        $options = ['0' => main\Core\ServiceLocator::call('lang')->absoluteT('Any')];
+        if ($this->formData['sid'] > 0)
+        {
+            $servers = main\Core\Models\Whmcs\Server::where('id', $this->formData['sid']);
+        }
+        else
+        {
+            $servers = main\Core\Models\Whmcs\Server::whereIn("type", ["proxmoxVPS", "ProxmoxCloudVps"])->where('disabled', '0');
+        }
+        $nodeRepository = new proxmox\repository\NodeRepository();
+        foreach ($servers->get() as $server)
+        {
+            /* @var $server main\Core\Models\Whmcs\Server */
+            try
+            {
+                $host = $server->ipaddress ? $server->ipaddres : $server->hostname;
+                $api  = new proxmox\Api($host, $server->username, $server->accesshash, decrypt($server->password));
+                $nodeRepository->setApi($api);
+                foreach ($nodeRepository->fetch() as $node)
+                {
+                    $options[$node->getNode()] = $node->getNode();
+                }
+            }
+            catch (\Exception $ex)
+            {//login to proxmox host failed
+            }
+        }
+        return $options;
+    }
+}

+ 63 - 0
app/UI/Templates/Fields/VmidSelect.php

@@ -0,0 +1,63 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Sep 10, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Fields;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\AjaxFields\Select;
+
+/**
+ * Description of NodeSelect
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class VmidSelect extends Select implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+
+    public function prepareAjaxData()
+    {
+        $this->setServerId($this->getRequestValue('id'))->getApi()->setInstance();
+        $this->getApi()->debug(true);
+        $clusterResurce = new proxmox\repository\ClusterResourcesRepository;
+        $clusterResurce->findVm();
+        $clusterResurce->addfilter(['type' => 'qemu']);
+        foreach ($clusterResurce->fetch() as $resurce)
+        {
+            /* @var $server proxmox\models\ClusterResource */
+            if ($resurce->getTemplate() == "1" || $resurce->getStatus() == "unknown")
+            {
+                continue;
+            }
+            $name = "{$resurce->getVmid()} ";
+            if ($resurce->getName())
+            {
+                $name .= "- {$resurce->getName()}";
+            }
+            $this->availableValues[] = [
+                'key'   => $resurce->getVmid(),
+                'value' => $name
+            ];
+        }
+    }
+}

+ 62 - 0
app/UI/Templates/Forms/CreateForm.php

@@ -0,0 +1,62 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Fields\VmidSelect;
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Providers\TemplateProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class CreateForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateCreateForm');
+        $this->setFormType('create');
+        $this->setProvider(new TemplateProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setInternalAlertMessage("The virtual machine will be converted to a template.");
+        //Server Id
+        $this->addField(new Fields\Hidden('serverId'));
+        //VM
+        $this->addField((new VmidSelect('vmid'))->notEmpty());
+        //Description
+        $this->addField(new Fields\Textarea('description'));
+        //Description
+        $this->loadDataToForm();
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['create'];
+    }
+}

+ 59 - 0
app/UI/Templates/Forms/DeleteForm.php

@@ -0,0 +1,59 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Providers\TemplateProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteMassForm');
+        $this->setFormType('delete');
+        $this->setProvider(new TemplateProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->addField(new Fields\Hidden('vmid'));
+        $this->addField(new Fields\Hidden('node'));
+        $this->addField(new Fields\Hidden('name'));
+        $this->addField(new Fields\Hidden('serverId'));
+        $this->addField(new Fields\Hidden('type'));
+        $this->setConfirmMessage('confirmTemplateDelete', ['vmid' => null, "name" => null]);
+        $this->loadDataToForm();
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['delete'];
+    }
+}

+ 52 - 0
app/UI/Templates/Forms/DeleteMassForm.php

@@ -0,0 +1,52 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Providers\TemplateProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteMassForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new TemplateProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setConfirmMessage('confirmTemplateDeleteMass');
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['deleteMass'];
+    }
+}

+ 61 - 0
app/UI/Templates/Forms/UpdateForm.php

@@ -0,0 +1,61 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Providers\TemplateProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateUpdateForm');
+        $this->setFormType('update');
+        $this->setProvider(new TemplateProvider());
+        $this->initFields();
+        $this->loadDataToForm();
+    }
+
+    protected function initFields()
+    {
+        $this->addField(new Fields\Hidden('vmid'));
+        $this->addField(new Fields\Hidden('node'));
+        $this->addField(new Fields\Hidden('name'));
+        $this->addField(new Fields\Hidden('serverId'));
+        $this->addField(new Fields\Hidden('type'));
+        //ciuser
+        $field = ((new Fields\Text('ciuser')));
+        $this->addField($field);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['update'];
+    }
+}

+ 40 - 0
app/UI/Templates/Modals/CreateModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms\CreateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class CreateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateCreateModal');
+        $this->addForm(new CreateForm());
+    }
+}

+ 40 - 0
app/UI/Templates/Modals/DeleteMassModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms\DeleteMassForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteMassModal');
+        $this->addForm(new DeleteMassForm());
+    }
+}

+ 40 - 0
app/UI/Templates/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateDeleteModal');
+        $this->addForm(new DeleteForm());
+    }
+}

+ 40 - 0
app/UI/Templates/Modals/UpdateModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Forms\UpdateForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class UpdateModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('templateUpdateModal');
+        $this->addForm(new UpdateForm());
+    }
+}

+ 138 - 0
app/UI/Templates/Pages/TemplatesDataTable.php

@@ -0,0 +1,138 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Feb 5, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Pages;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons\CreateButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons\DeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\Templates\Buttons\DeleteMassButton;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Json;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\RawDataTable\RawDataTable;
+
+/**
+ * Description of PluginInstalled
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class TemplatesDataTable extends RawDataTable implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+    protected $id = 'template';
+    protected $name = 'template';
+    protected $title = 'templateTitle';
+
+    public function isRawTitle()
+    {
+        return false;
+    }
+
+    public function initContent()
+    {
+        //Create
+        $this->addButton(new CreateButton);
+        //Update
+        $this->addActionButton(new main\App\UI\Templates\Buttons\UpdateButton());
+        //Delete
+        $this->addActionButton(new DeleteButton);
+        //Delete Mass
+        $this->addMassActionButton(new DeleteMassButton);
+        try
+        {
+            $this->setServerId($this->getRequestValue('id'))->getApi();
+        }
+        catch (\Exception $ex)
+        {
+            $this->setInternalAlertMessage($ex->getMessage())
+                ->setInternalAlertMessageType('danger');
+        }
+    }
+
+    public function replaceFieldOstype($key, $row)
+    {
+        $osType = new Json('ostype.json', ModuleConstants::getFullPath('storage', 'app'));
+        if (is_null($row['ostype']))
+        {
+            return "Unspecified OS";
+        }
+        else
+        {
+            if ($osType->get($row['ostype']))
+            {
+                return $osType->get($row['ostype']);
+            }
+        }
+        return $row['ostype'];
+    }
+
+    public function replaceFieldCiuser($key, $row)
+    {
+        if ($row[$key])
+        {
+            return $row[$key];
+        }
+        return "-";
+    }
+
+    public function replaceFieldDescription($key, $row)
+    {
+        if ($row[$key])
+        {
+            return $row[$key];
+        }
+        return "-";
+    }
+
+    protected function loadHtml()
+    {
+        $this->addColumn((new Column('node'))->setSearchable(true, Column::TYPE_STRING)->setOrderable('ASC'))
+            ->addColumn((new Column('vmid'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('name'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('description'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('ostype'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('ciuser'))->setSearchable(true, Column::TYPE_STRING)->setOrderable());
+    }
+
+    protected function loadData()
+    {
+        $this->setServerId($this->getRequestValue('id'))->getApi()->setInstance();
+        $dataProv          = new main\Core\UI\Widget\DataTable\DataProviders\Providers\ArrayDataProvider();
+        $data              = [];
+        $clusterRepository = new proxmox\repository\ClusterResourcesRepository();
+        $clusterRepository->findKvmTemplate();
+        foreach ($clusterRepository->fetch() as $resurce)
+        {
+            if ($resurce->getTemplate() != '1')
+            {
+                continue;
+            }
+            $vm     = array_merge($resurce->toArray(), $resurce->getVm()->config());
+            $data[] = array_merge($vm, ["id" => base64_encode(json_encode($vm))]);
+        }
+        $dataProv->setDefaultSorting("node", 'ASC');
+        $dataProv->setData((array)$data);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 147 - 0
app/UI/Templates/Providers/TemplateProvider.php

@@ -0,0 +1,147 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Templates\Providers;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseDataProvider;
+
+/**
+ *
+ * Description of RangeTemplateProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class TemplateProvider extends BaseDataProvider implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+
+    public function read()
+    {
+        if (!$this->actionElementId)
+        {
+            return false;
+        }
+        $this->data             = json_decode(base64_decode($this->actionElementId), true);
+        $this->data['serverId'] = $this->getRequestValue('id');
+    }
+
+    public function delete()
+    {
+        $this->setServerId($this->formData['serverId']);
+        $this->getApi()->setInstance();
+        $this->getVm($this->formData['node'], $this->formData['vmid'], $this->formData['type'])->delete();
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('name', $this->formData['name']);
+        sleep(1);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Deleting template :name: in progress');
+    }
+
+    private function getVm($node, $vmid, $type)
+    {
+        switch ($type)
+        {
+            case 'qemu' :
+                $vm = new proxmox\models\Kvm($node, $vmid);
+                break;
+            case 'lxc':
+                $vm = new proxmox\models\Lxc($node, $vmid);
+                break;
+            default :
+                throw new \Exception(sprintf("Unkown virtualization %s type", $type));
+        }
+        return $vm;
+    }
+
+    public function deleteMass()
+    {
+        if (!$this->getRequestValue('massActions'))
+        {
+            return (new HtmlDataJsonResponse())->setMessageAndTranslate('Bug no massActions data!')->setStatusError();
+            return;
+        }
+        $this->setServerId($this->getRequestValue('id'));
+        $this->getApi()->setInstance();
+        foreach ($this->getRequestValue('massActions') as $id)
+        {
+            $data = json_decode(base64_decode($id), true);
+            $this->getVm($data['node'], $data['vmid'], $data['type'])->delete();
+        }
+        sleep(1);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Deleting the selected templates in progress');
+    }
+
+    public function update()
+    {
+        $this->setServerId($this->formData['serverId']);
+        $this->getApi()->setInstance();
+        //Update ciuser
+        if ($this->formData['ciuser'])
+        {
+            $this->getVm($this->formData['node'], $this->formData['vmid'], $this->formData['type'])->updateConfig(["ciuser" => $this->formData['ciuser']]);
+        }
+        else
+        {
+            //Delete ciuser
+            $this->getVm($this->formData['node'], $this->formData['vmid'], $this->formData['type'])->deleteConfig('ciuser');
+        }
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('name', $this->formData['name']);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Template :name: has been updated.');
+    }
+
+    public function create()
+    {
+        $this->setServerId($this->formData['serverId']);
+        $this->getApi()->setInstance();
+        $resurcesRepostiory = new proxmox\repository\ClusterResourcesRepository();
+        $resurcesRepostiory->findVm();
+        foreach ($resurcesRepostiory->fetch() as $resurce)
+        {
+            if ($resurce->getVmid() == $this->formData['vmid'])
+            {
+                $vm = $resurce->getVm();
+                break;
+            }
+        }
+        if (!$vm)
+        {
+            main\Core\ServiceLocator::call('lang')->addReplacementConstant('vmid', $this->formData['vmid']);
+            return (new HtmlDataJsonResponse())->setMessageAndTranslate('Virtual Machine :vmid: has not been found')->setStatusError();
+        }
+        /* @var $vm proxmox\models\Kvm */
+        if ($vm->isRunning())
+        {
+            $vm->stop();
+            sleep(20);
+        }
+        if ($this->formData['description'])
+        {
+            $vm->updateConfig(["description" => $this->formData['description']]);
+            sleep(2);
+        }
+        $vm->convertToTemplate();
+        sleep(1);
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('name', $resurce->getName());
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Virtual Machine :name: has been converted to template successfully.');
+    }
+}

+ 74 - 0
app/UI/Validators/CidrValidator.php

@@ -0,0 +1,74 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Validators;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Validators\BaseValidator;
+
+/**
+ * Description of NumberValidator
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class CidrValidator extends BaseValidator
+{
+    protected $required = false;
+
+    public function __construct($required = true)
+    {
+        $this->required = $required;
+    }
+
+    protected function validate($data, $additionalData = null)
+    {
+        if (!$this->required && empty($data))
+        {
+            return true;
+        }
+        else
+        {
+            if ($this->required && empty($data))
+            {
+                $this->addValidationError('PleaseProvideANumericValue');
+                return false;
+            }
+        }
+        $min = 1;
+        $max = 32;
+        //ipv6 range
+        if ($additionalData && $additionalData->get('formData')['ip'] && preg_match('/\:/', $additionalData->get('formData')['ip']))
+        {
+            $max = 128;
+        }
+        else
+        {
+            if ($additionalData && $additionalData->get('formData')['ipPool'] && preg_match('/\:/', $additionalData->get('formData')['ipPool']))
+            {
+                $max = 128;
+            }
+        }
+        if (!is_numeric($data) || (int)$data < $min || (int)$data > $max)
+        {
+            $this->addValidationError('PleaseProvideANumericValueBetween', false, ['minValue' => $min, 'maxValue' => $max]);
+            return false;
+        }
+        return true;
+    }
+}

+ 60 - 0
app/UI/Validators/IpAddressValidator.php

@@ -0,0 +1,60 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Validators;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Validators\BaseValidator;
+
+/**
+ * Description of NumberValidator
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class IpAddressValidator extends BaseValidator
+{
+    protected $required = false;
+    protected $public = true;
+
+    public function __construct($required = false, $public = true)
+    {
+        $this->required = $required;
+        $this->public = $public;
+    }
+
+    protected function validate($data, $additionalData = null)
+    {
+        if (!$this->required && empty($data))
+        {
+            return true;
+        }
+        if (!filter_var($data, FILTER_VALIDATE_IP))
+        {
+            $this->addValidationError('IP Address is not valid', false);
+            return false;
+        }
+        if ($additionalData && $this->public && $additionalData->get('formData')['private'] == "off" &&
+            !filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE))
+        {
+            $this->addValidationError('The IP Address is not public', false);
+            return false;
+        }
+        return true;
+    }
+}

+ 53 - 0
app/UI/Validators/MacAddressValidator.php

@@ -0,0 +1,53 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Validators;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Validators\BaseValidator;
+
+/**
+ * Description of NumberValidator
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class MacAddressValidator extends BaseValidator
+{
+    protected $required = false;
+
+    public function __construct($required = false)
+    {
+        $this->required = $required;
+    }
+
+    protected function validate($data, $additionalData = null)
+    {
+        if (!$this->required && empty($data) || $data == "auto")
+        {
+            return true;
+        }
+        if (!preg_match("/^([0-9a-fA-F]{1,2}[\.:-]){5}([0-9a-fA-F]{1,2})$/", $data))
+        {
+            $this->addValidationError('Invalid MAC Address', false);
+            return false;
+        }
+
+        return true;
+    }
+}

+ 87 - 0
app/UI/Validators/NumberValidator.php

@@ -0,0 +1,87 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Validators;
+
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Validators\BaseValidator;
+
+/**
+ * Description of NumberValidator
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class NumberValidator extends BaseValidator
+{
+    protected $minValue = 0;
+    protected $maxValue = 0;
+    protected $required = false;
+
+    public function __construct($min = 0, $max = 0, $required = false)
+    {
+        $this->minValue = (int)$min;
+        $this->maxValue = $max;
+        $this->required = $required;
+    }
+
+    protected function validate($data, $additionalData = null)
+    {
+        if (!$this->required && empty($data))
+        {
+            return true;
+        }
+        if (is_numeric($data) && $this->minValue === 0 && $this->maxValue === 0)
+        {
+            return true;
+        }
+        //Min & Max
+        if (is_numeric($data) && $this->minValue <= ((int)$data) && ((int)$data) <= $this->maxValue)
+        {
+            return true;
+        }
+        //Min
+        else
+        {
+            if (is_numeric($data) && !is_numeric($this->maxValue) && $this->minValue <= ((int)$data))
+            {
+                return true;
+            }
+        }
+
+        if ($this->minValue === $this->maxValue)
+        {
+            $this->addValidationError('PleaseProvideANumericValue');
+
+            return false;
+        }
+
+        if (is_numeric($this->minValue) && is_numeric($this->maxValue))
+        {
+            $this->addValidationError('PleaseProvideANumericValueBetween', false, ['minValue' => $this->minValue, 'maxValue' => $this->maxValue]);
+        }
+        else
+        {
+            if (is_numeric($this->minValue) && !is_numeric($this->maxValue))
+            {
+                $this->addValidationError('PleaseProvideANumericValueFrom', false, ['minValue' => $this->minValue]);
+            }
+        }
+        return false;
+    }
+}

+ 42 - 0
app/UI/VmCleaner/Buttons/DeleteButton.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Modals\DeleteModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__icon lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteModal());
+    }
+}

+ 42 - 0
app/UI/VmCleaner/Buttons/DeleteMassButton.php

@@ -0,0 +1,42 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Modals\DeleteMassModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction;
+
+/**
+ * Description of DeleteButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DeleteMassButton extends ButtonMassAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi lu-zmdi-delete';
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteMassButton');
+        $this->switchToRemoveBtn();
+        $this->initLoadModalAction(new DeleteMassModal());
+    }
+}

+ 59 - 0
app/UI/VmCleaner/Forms/DeleteForm.php

@@ -0,0 +1,59 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Providers\VmProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\Fields;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteMassForm');
+        $this->setFormType('delete');
+        $this->setProvider(new VmProvider);
+        $this->initFields();
+        $this->loadDataToForm();
+    }
+
+    protected function initFields()
+    {
+        $this->addField(new Fields\Hidden('vmid'));
+        $this->addField(new Fields\Hidden('node'));
+        $this->addField(new Fields\Hidden('name'));
+        $this->addField(new Fields\Hidden('serverId'));
+        $this->addField(new Fields\Hidden('type'));
+        $this->setConfirmMessage('confirmvmDelete', ['vmid' => null]);
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['delete'];
+    }
+}

+ 52 - 0
app/UI/VmCleaner/Forms/DeleteMassForm.php

@@ -0,0 +1,52 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 11, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Forms;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Providers\VmProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\BaseForm;
+
+/**
+ * Description of CreateForm
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassForm extends BaseForm implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteMassForm');
+        $this->setFormType('deleteMass');
+        $this->setProvider(new VmProvider);
+        $this->initFields();
+    }
+
+    protected function initFields()
+    {
+        $this->setConfirmMessage('confirmvmDeleteMass');
+    }
+
+    protected function getDefaultActions()
+    {
+        return ['deleteMass'];
+    }
+}

+ 40 - 0
app/UI/VmCleaner/Modals/DeleteMassModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Forms\DeleteMassForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteMassModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteMassModal');
+        $this->addForm(new DeleteMassForm());
+    }
+}

+ 40 - 0
app/UI/VmCleaner/Modals/DeleteModal.php

@@ -0,0 +1,40 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Modals;
+
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Forms\DeleteForm;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\ModalConfirmDanger;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DeleteModal extends ModalConfirmDanger implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmCleanerDeleteModal');
+        $this->addForm(new DeleteForm());
+    }
+}

+ 150 - 0
app/UI/VmCleaner/Pages/VmCleanerDataTable.php

@@ -0,0 +1,150 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Feb 5, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Pages;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Buttons\DeleteButton;
+use ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Buttons\DeleteMassButton;
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\RawDataTable\RawDataTable;
+use WHMCS\Database\Capsule;
+use WHMCS\Service\Service;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+use Illuminate\Database\Capsule\Manager as DB;
+
+/**
+ * Description of PluginInstalled
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class VmCleanerDataTable extends RawDataTable implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+    protected $id = 'vmCleaner';
+    protected $name = 'vmCleaner';
+    protected $title = 'vmCleanerTitle';
+    private $proxmoxCloudTable;
+
+
+    public function isRawTitle()
+    {
+        return false;
+    }
+
+    public function initContent()
+    {
+        $this->addActionButton(new DeleteButton);
+        $this->addMassActionButton(new DeleteMassButton);
+        try
+        {
+            $this->setServerId($this->getRequestValue('id'))->getApi();
+        }
+        catch (\Exception $ex)
+        {
+            $this->setInternalAlertMessage($ex->getMessage())
+                ->setInternalAlertMessageType('danger');
+        }
+    }
+
+    public function replaceFieldVirtualization($key, $row)
+    {
+        return Helper\sl('lang')->absoluteT($row['type']);
+    }
+
+    public function replaceFieldStatus($key, $row)
+    {
+        switch ($row['status'])
+        {
+            case 'stopped':
+                return '<span class="lu-label lu-label--default lu-label--status">' . sl('lang')->tr($row['status']) . '</span>';
+            case 'running':
+                return '<span class="lu-label lu-label--success lu-label--status">' . sl('lang')->tr($row['status']) . '</span>';
+                break;
+            case 'unknown':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row['status']) . '</span>';
+                break;
+            case 'io-error':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row['status']) . '</span>';
+                break;
+            default:
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row['status']) . '</span>';
+        }
+    }
+
+    protected function loadHtml()
+    {
+        $this->addColumn((new Column('node'))->setSearchable(true, Column::TYPE_STRING)->setOrderable('ASC'))
+            ->addColumn((new Column('vmid'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('name'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('virtualization'))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('status'))->setSearchable(true, Column::TYPE_STRING)->setOrderable());
+    }
+
+    protected function loadData()
+    {
+        session_write_close();
+        $this->setServerId($this->getRequestValue('id'))->getApi()->setInstance();
+        $dataProv          = new main\Core\UI\Widget\DataTable\DataProviders\Providers\ArrayDataProvider();
+        $data              = [];
+        $clusterRepository = new proxmox\repository\ClusterResourcesRepository();
+        $this->proxmoxCloudVpsTable = Capsule::schema()->hasTable('ProxmoxAddon_Vm');
+        foreach ($clusterRepository->fetch() as $resurce)
+        {
+            if (!in_array($resurce->getType(), ['qemu', 'lxc']) || $resurce->getTemplate() == '1' || $resurce->getStatus() == "unknown" ||
+                $this->exist($resurce))
+            {
+                continue;
+            }
+            $data[] = array_merge($resurce->toArray(), ["id" => base64_encode(json_encode($resurce->toArray()))]);
+        }
+        $dataProv->setDefaultSorting("node", 'ASC');
+        $dataProv->setData((array)$data);
+        $this->setDataProvider($dataProv);
+    }
+
+    private function exist(proxmox\models\ClusterResource $resurce)
+    {
+        //cloud
+        if ($this->proxmoxCloudVpsTable)
+        {
+            if (Capsule::table('ProxmoxAddon_Vm')->where("vmid", $resurce->getVmid())->where("node", $resurce->getNode())->count())
+            {
+                return true;
+            }
+        }
+        //vps
+        $h        = 'tblhosting';
+        $fv = "tblcustomfieldsvalues";
+        $f = "tblcustomfields";
+        $query = main\App\Models\Whmcs\Hosting::rightJoin($fv, "{$fv}.relid", "=","{$h}.id")
+            ->rightJoin($f, "{$f}.id", "=","{$fv}.fieldid")
+            ->where("{$h}.server", $this->getServerId())
+            ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
+            ->where("{$f}.type", "product")
+            ->where("{$f}.fieldname", "LIKE", "%vmid")
+            ->where("{$fv}.value",  $resurce->getVmid());
+        return  $query->count() > 0;
+    }
+}

+ 106 - 0
app/UI/VmCleaner/Providers/VmProvider.php

@@ -0,0 +1,106 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 23, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\VmCleaner\Providers;
+
+use MGProvision\Proxmox\v2 as proxmox;
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseDataProvider;
+
+/**
+ *
+ * Description of RangeVmProvider
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class VmProvider extends BaseDataProvider implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+
+    public function read()
+    {
+        if (!$this->actionElementId)
+        {
+            return false;
+        }
+        $this->data             = json_decode(base64_decode($this->actionElementId), true);
+        $this->data['serverId'] = $this->getRequestValue('id');
+    }
+
+    public function delete()
+    {
+        $this->setServerId($this->formData['serverId']);
+        $this->getApi()->setInstance();
+        $vm        = $this->getVm($this->formData['node'], $this->formData['vmid'], $this->formData['type']);
+        $haResurce = new proxmox\models\HaResource();
+        $haResurce->setSid($vm->getVmid())
+            ->setType($vm->getVirtualization() == "lxc" ? "ct" : "vm");
+        if ($haResurce->exist())
+        {
+            $haResurce->delete();
+        }
+        $vm->delete();
+        main\Core\ServiceLocator::call('lang')->addReplacementConstant('vmid', $this->formData['vmid']);
+        sleep(1);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Deleting Virtual Machine :vmid: in progress');
+    }
+
+    private function getVm($node, $vmid, $type)
+    {
+        switch ($type)
+        {
+            case 'qemu' :
+                $vm = new proxmox\models\Kvm($node, $vmid);
+                break;
+            case 'lxc':
+                $vm = new proxmox\models\Lxc($node, $vmid);
+                break;
+            default :
+                throw new \Exception(sprintf("Unkown virtualization %s type", $type));
+        }
+        return $vm;
+    }
+
+    public function deleteMass()
+    {
+        if (!$this->getRequestValue('massActions'))
+        {
+            return (new HtmlDataJsonResponse())->setMessageAndTranslate('Bug no massActions data!')->setStatusError();
+            return;
+        }
+        $this->setServerId($this->getRequestValue('id'));
+        $this->getApi()->setInstance();
+        foreach ($this->getRequestValue('massActions') as $id)
+        {
+            $data = json_decode(base64_decode($id), true);
+            $this->getVm($data['node'], $data['vmid'], $data['type'])->delete();
+        }
+        sleep(1);
+        return (new HtmlDataJsonResponse())->setMessageAndTranslate('Deleting the selected Virtual Machines in progress');
+    }
+
+    public function update()
+    {
+
+    }
+}

+ 41 - 0
app/UI/Vms/Buttons/DetailButton.php

@@ -0,0 +1,41 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Vms\Buttons;
+
+use ModulesGarden\ProxmoxAddon\App\UI\Vms\Modals\DetailModal;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction;
+
+/**
+ * Description of UpdateButton
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class DetailButton extends ButtonDataTableModalAction implements AdminArea
+{
+    protected $icon = 'lu-btn__con lu-zmdi lu-zmdi-info-outline';
+
+    public function initContent()
+    {
+        $this->initIds('vmsDetailButton');
+        $this->initLoadModalAction(new DetailModal());
+    }
+}

+ 87 - 0
app/UI/Vms/Modals/DetailModal.php

@@ -0,0 +1,87 @@
+<?php
+
+/* * ********************************************************************
+ * Wordpress_Manager Product developed. (Dec 19, 2017)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Vms\Modals;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ModalActionButtons\BaseCancelButton;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Modals\BaseEditModal;
+use WHMCS\Database\Capsule as DB;
+use WHMCS\Service\Service;
+
+/**
+ * Description of CloneModal
+ *
+ * @author Pawel Kopec <pawelk@modulesgarden.com>
+ */
+class DetailModal extends BaseEditModal implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vmsDetailModal');
+        /* @var $request \ModulesGarden\ProxmoxAddon\Core\Http\Request */
+        $request = Helper\sl('request');
+        if (!$request->get('actionElementId'))
+        {
+            return;
+        }
+        $hosting = Service::findOrFail($request->get('actionElementId'));
+        if ($hosting->product->module == "ProxmoxCloudVps")
+        {
+            $this->customTplVars['vms'] = DB::table('ProxmoxAddon_Vm')
+                ->where('hosting_id', $hosting->id)
+                ->select('node', 'vmid', 'virtualization')->get();
+        }
+        else
+        {
+            if ($hosting->product->module == "proxmoxVPS")
+            {
+                $vm = new \stdClass;
+                foreach ($hosting->customFieldValues as $cf)
+                {
+                    if ($cf->value && preg_match("/node/", $cf->customField->fieldName))
+                    {
+                        $vm->node = $cf->value;
+                    }
+                    if ($cf->value && preg_match("/vmid/", $cf->customField->fieldName))
+                    {
+                        $vm->vmid = $cf->value;
+                    }
+                }
+                if ($vm->vmid)
+                {
+                    $this->customTplVars['vms'][] = $vm;
+                }
+            }
+        }
+    }
+
+    protected function initActionButtons()
+    {
+        if (!empty($this->actionButtons))
+        {
+            return $this;
+        }
+        $this->addActionButton(new BaseCancelButton);
+        return $this;
+    }
+}

+ 162 - 0
app/UI/Vms/Pages/VmsDataTable.php

@@ -0,0 +1,162 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Vms\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\Vms\Buttons\DetailButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataTable;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class VmsDataTable extends DataTable implements AdminArea
+{
+
+    public function initContent()
+    {
+        $this->initIds('vms');
+        $this->title = null;
+        $this->addActionButton(new DetailButton);
+    }
+
+    public function replaceFieldName($key, $row)
+    {
+        return sprintf('<a href="configproducts.php?action=edit&id=%s">%s</a>', $row->packageid, $row->productName);
+    }
+
+    public function replaceFieldDomain($key, $row)
+    {
+        if (!$row->domain)
+        {
+            return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->id, '-');
+        }
+        return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->id, $row->domain);
+    }
+
+    public function replaceFieldDomainstatus($key, $row)
+    {
+        switch ($row->domainstatus)
+        {
+            case 'Pending':
+                return '<span class="lu-label lu-label--warning lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            case 'Active':
+            case 'Completed':
+                return '<span class="lu-label lu-label--success lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Suspended':
+                return '<span class="lu-label lu-label--default lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Cancelled':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Fraud':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            case 'Terminated':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            default:
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+
+        }
+    }
+
+    public function replaceFieldDedicatedip($key, $row)
+    {
+        return $row->dedicatedip ? $row->dedicatedip : "-";
+    }
+
+    public function replaceFieldAssignedips($key, $row)
+    {
+        if($row->assignedips){
+            $ips = explode("\n", $row->assignedips);
+            $ips = array_filter($ips);
+            $ips = array_unique($ips);
+            return implode(", ", $ips);
+        }
+        return  "-";
+    }
+
+    public function replaceFieldFirstname($key, $row)
+    {
+        return sprintf('<a href="clientssummary.php?userid=%s">%s %s</a>', $row->userid, $row->firstname, $row->lastname);
+    }
+
+    public function replaceFieldServer($key, $row)
+    {
+        return sprintf('<a href="configservers.php?action=manage&id=%s">%s</a>', $row->server, $row->serverName);
+    }
+
+    public function replaceFieldBwusage($key, $row)
+    {
+        if ($row->bwusage == 0)
+        {
+            return '-';
+        }
+        $bytes = $row->bwusage * pow(1024,2);
+        return main\App\Libs\Format::convertBytes($bytes);
+    }
+
+    protected function loadHtml()
+    {
+        $h = (new Hosting)->getTable();
+        $p = (new main\Core\Models\Whmcs\Product)->getTable();
+        $s = (new Server)->getTable();
+        $c = (new main\Core\Models\Whmcs\Client)->getTable();
+        $this->addColumn((new Column('id', $h))->setSearchable(true, Column::TYPE_INT)->setOrderable("DESC"))
+            ->addColumn((new Column('name', $p))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('domain', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('domainstatus', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('bwusage', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('dedicatedip', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('assignedips', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('firstname', $c))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('server'))
+            );
+    }
+
+    protected function loadData()
+    {
+        $h        = (new Hosting)->getTable();
+        $p        = (new main\Core\Models\Whmcs\Product)->getTable();
+        $s        = (new Server)->getTable();
+        $c        = (new main\Core\Models\Whmcs\Client)->getTable();
+        $query    = (new Hosting)
+            ->query()
+            ->getQuery()
+            ->leftJoin($p, "{$h}.packageid", '=', "{$p}.id")
+            ->leftJoin($c, "{$h}.userid", '=', "{$c}.id")
+            ->leftJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->select("{$h}.id", "{$h}.domain", "{$h}.domainstatus", "{$h}.dedicatedip", "{$h}.assignedips", "{$h}.userid", "{$h}.server", "{$h}.packageid", "{$h}.bwusage",
+                "{$c}.firstname", "{$c}.lastname", "{$s}.name AS serverName", "{$p}.name AS productName")
+            ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"]);
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'DESC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 169 - 0
app/UI/Vms/Pages/VmsDataTableRaw.php

@@ -0,0 +1,169 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Aug 22, 2018)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\App\UI\Vms\Pages;
+
+use ModulesGarden\ProxmoxAddon as main;
+use ModulesGarden\ProxmoxAddon\App\UI\Vms\Buttons\DetailButton;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Hosting;
+use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
+use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\Column;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\DataTable\DataProviders\Providers\QueryDataProvider;
+use ModulesGarden\ProxmoxAddon\Core\UI\Widget\RawDataTable\RawDataTable;
+use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
+
+/**
+ * Description of ServersDataTable
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+class VmsDataTableRaw extends RawDataTable implements AdminArea
+{
+
+    use main\App\Services\BaseService;
+    protected $id = 'vms';
+    protected $name = 'vms';
+    protected $title = 'vmsTitle';
+
+    public function isRawTitle()
+    {
+        return false;
+    }
+
+    public function initContent()
+    {
+        $this->addActionButton(new DetailButton);
+    }
+
+    public function replaceFieldName($key, $row)
+    {
+        return sprintf('<a href="configproducts.php?action=edit&id=%s">%s</a>', $row->packageid, $row->productName);
+    }
+
+    public function replaceFieldDomain($key, $row)
+    {
+        if (!$row->domain)
+        {
+            return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->id, '-');
+        }
+        return sprintf('<a href="clientsservices.php?userid=%s&id=%s">%s</a>', $row->userid, $row->id, $row->domain);
+    }
+
+    public function replaceFieldDomainstatus($key, $row)
+    {
+        switch ($row->domainstatus)
+        {
+            case 'Pending':
+                return '<span class="lu-label lu-label--warning lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            case 'Active':
+            case 'Completed':
+                return '<span class="lu-label lu-label--success lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Suspended':
+                return '<span class="lu-label lu-label--default lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Cancelled':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+                break;
+            case 'Fraud':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            case 'Terminated':
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+            default:
+                return '<span class="lu-label lu-label--danger lu-label--status">' . sl('lang')->tr($row->domainstatus) . '</span>';
+
+        }
+    }
+
+    public function replaceFieldDedicatedip($key, $row)
+    {
+        return $row->dedicatedip ? $row->dedicatedip : "-";
+    }
+
+    public function replaceFieldAssignedips($key, $row)
+    {
+        if($row->assignedips){
+            $ips = explode("\n", $row->assignedips);
+            $ips = array_filter($ips);
+            $ips = array_unique($ips);
+            return implode(", ", $ips);
+        }
+        return  "-";
+    }
+
+    public function replaceFieldFirstname($key, $row)
+    {
+        return sprintf('<a href="clientssummary.php?userid=%s">%s %s</a>', $row->userid, $row->firstname, $row->lastname);
+    }
+
+    public function replaceFieldServer($key, $row)
+    {
+        return sprintf('<a href="configservers.php?action=manage&id=%s">%s</a>', $row->server, $row->serverName);
+    }
+
+    protected function loadHtml()
+    {
+        $h = (new Hosting)->getTable();
+        $p = (new main\Core\Models\Whmcs\Product)->getTable();
+        $s = (new Server)->getTable();
+        $c = (new main\Core\Models\Whmcs\Client)->getTable();
+        $this->addColumn((new Column('id', $h))->setSearchable(true, Column::TYPE_INT)->setOrderable('DESC'))
+            ->addColumn((new Column('name', $p))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('domain', $h)))
+            ->addColumn((new Column('domainstatus', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('bwusage', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('dedicatedip', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('assignedips', $h))->setSearchable(true, Column::TYPE_STRING)->setOrderable())
+            ->addColumn((new Column('firstname', $c))->setSearchable(true, Column::TYPE_STRING)->setOrderable()
+            );
+    }
+
+    public function replaceFieldBwusage($key, $row)
+    {
+        if ($row->bwusage == 0)
+        {
+            return '-';
+        }
+        return $row->bwusage . " MB";
+    }
+
+    protected function loadData()
+    {
+        $h = (new Hosting)->getTable();
+        $p = (new main\Core\Models\Whmcs\Product)->getTable();
+        $s = (new Server)->getTable();
+        $c = (new main\Core\Models\Whmcs\Client)->getTable();
+
+        $query    = (new Hosting)
+            ->query()
+            ->getQuery()
+            ->leftJoin($p, "{$h}.packageid", '=', "{$p}.id")
+            ->leftJoin($c, "{$h}.userid", '=', "{$c}.id")
+            ->leftJoin($s, "{$h}.server", '=', "{$s}.id")
+            ->select("{$h}.id", "{$h}.domain", "{$h}.domainstatus", "{$h}.dedicatedip", "{$h}.assignedips", "{$h}.userid", "{$h}.server", "{$h}.packageid", "{$h}.bwusage",
+                "{$c}.firstname", "{$c}.lastname", "{$s}.name AS serverName", "{$p}.name AS productName")
+            ->where("{$h}.server", $this->getRequestValue('id'));
+        $dataProv = new QueryDataProvider();
+        $dataProv->setDefaultSorting("id", 'DESC');
+        $dataProv->setData($query);
+        $this->setDataProvider($dataProv);
+    }
+}

+ 85 - 0
app/UI/Vms/Templates/modals/detailModal.tpl

@@ -0,0 +1,85 @@
+{**********************************************************************
+* ProxmoxAddon product developed. (2017-11-16)
+* *
+*
+*  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+*  CONTACT                        ->       contact@modulesgarden.com
+*
+*
+* This software is furnished under a license and may be used and copied
+* only  in  accordance  with  the  terms  of such  license and with the
+* inclusion of the above copyright notice.  This software  or any other
+* copies thereof may not be provided or otherwise made available to any
+* other person.  No title to and  ownership of the  software is  hereby
+* transferred.
+*
+*
+**********************************************************************}
+
+{**
+* @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+*}
+<div class="lu-modal show lu-modal--{$rawObject->getModalSize()}" id="confirmationModal"
+     namespace="{$rawObject->getNamespace()}" index="{$rawObject->getId()}">
+    <div class="lu-modal__dialog">
+        <div class="lu-modal__content" id="mgModalContainer">
+            <div class="lu-modal__top lu-top">
+                <div class="lu-top__title lu-type-6">
+                    <span class="lu-text-faded lu-font-weight-normal">
+                {if $rawObject->isRawTitle()}{$rawObject->getRawTitle()}{elseif $rawObject->getTitle()}{$MGLANG->T('modal', $rawObject->getTitle())}{/if}
+            </span>
+                </div>
+                <div class="lu-top__toolbar">
+                    <button class="lu-btn lu-btn--xs lu-btn--default lu-btn--icon lu-btn--link lu-btn--plain closeModal"
+                            data-dismiss="lu-modal" aria-label="Close" @click='closeModal($event)'>
+                        <i class="lu-btn__icon lu-zmdi lu-zmdi-close"></i>
+                    </button>
+                </div>
+            </div>
+            <div class="lu-modal__body">
+                <div class="lu-row">
+                    <div class="lu-col-md-12">
+                        <table class="lu-table lu-table-hover">
+                            <thead>
+                            <tr>
+                                <th>{$MGLANG->T('modal','Node')}</th>
+                                <th>{$MGLANG->T('modal','VMID')}</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            {foreach from=$customTplVars.vms item=vm}
+                                <tr>
+                                    <td>{$vm->node}</td>
+                                    <td>{$vm->vmid} </td>
+                                </tr>
+                                {foreachelse}
+                                <tr>
+                                    <td colspan="2"
+                                        style="text-align: center;">{$MGLANG->T('modal','Virtual Server Not Available')}</td>
+                                </tr>
+                            {/foreach}
+                            </tbody>
+                        </table>
+                        {foreach from=$rawObject->getForms() item=form }
+                            {$form->getHtml()}
+                        {/foreach}
+                    </div>
+                </div>
+            </div>
+            <div class="lu-modal__actions">
+                {foreach from=$rawObject->getActionButtons() item=actionButton}
+                    {$actionButton->getHtml()}
+                {/foreach}
+            </div>
+            {if ($isDebug eq true AND (count($MGLANG->getMissingLangs()) != 0))}{literal}
+                <div class="lu-modal__actions">
+                <div class="lu-row">
+            {/literal}{foreach from=$MGLANG->getMissingLangs() key=varible item=value}{literal}
+                <div class="lu-col-md-12"><b>{/literal}{$varible}{literal}</b> = '{/literal}{$value}{literal}';</div>
+            {/literal}{/foreach}{literal}
+                </div>
+                </div>
+            {/literal}{/if}
+        </div>
+    </div>
+</div>

+ 20 - 0
commands/commands.php

@@ -0,0 +1,20 @@
+<?php
+
+define('DS', DIRECTORY_SEPARATOR);
+$modulePath = dirname(__DIR__);
+$whmcsPath  = dirname(dirname(dirname($modulePath)));
+
+require_once $whmcsPath . DS . 'init.php';
+require_once $modulePath . DS . 'core' . DS . 'Bootstrap.php';
+
+//cause WHMCS
+ini_set('max_execution_time', 0);
+
+$argList = $argv ? $argv : $_SERVER['argv'];
+if (count($argList) === 0)
+{
+    $argList = [__FILE__];
+}
+
+(new \ModulesGarden\ProxmoxAddon\Core\CommandLine\Application())
+        ->run();

+ 40 - 0
composer.json

@@ -0,0 +1,40 @@
+{
+    "name": "ModulesGarden/ProxmoxAddon",
+    "description": "",
+    "version": "2.1.0",
+    "type": "project",
+    "license": "EULA",
+    "homepage": "http://www.modulesgarden.com",
+    "support":
+            {
+                "email": "contact@modulesgarden.com",
+                "issues": "http://www.modulesgarden.com/customers/support",
+                "forum": "http://www.forum.modulesgarden.com/"
+            },
+    "authors": [
+    ],
+    "require":
+            {
+                "php": ">=5.5.9",
+                "symfony/yaml": "^3.3",
+                "piwik/ini": "^1.0",
+                "symfony/cache": "^3.3",
+                "mso/idna-convert": "1.*",
+                "adbario/php-dot-notation": "2.*",
+        "rappasoft/laravel-helpers": "^1.0",
+        "phpseclib/phpseclib": "~3.0"
+            },
+    "autoload": {
+        "psr-4": {
+            "ModulesGarden\\ProxmoxAddon\\Core\\": "./core",
+            "ModulesGarden\\ProxmoxAddon\\App\\": "./app",
+            "MGProvision\\Proxmox\\": "../../../includes/Proxmox",
+            "ModulesGarden\\Servers\\ProxmoxVps\\Core\\": "../../servers/proxmoxVPS/core",
+            "ModulesGarden\\Servers\\ProxmoxVps\\App\\": "../../servers/proxmoxVPS/appcorm "
+        },
+        "files": [
+            "core/Helper/Functions.php",
+            "app/Libs/IPv6.php"
+        ]
+    }
+}

+ 25 - 0
core/Api/AbstractApi.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api;
+
+use \ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl\Request;
+use \ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+
+/**
+ * Description of AbstractApi
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AbstractApi
+{
+    protected $token;
+    protected $code;
+
+    /**
+     * @return Request
+     */
+    protected function getNewRequest()
+    {
+        return DependencyInjection::create(Request::class);
+    }
+}

+ 158 - 0
core/Api/AbstractApi/Curl.php

@@ -0,0 +1,158 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi;
+
+use \ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl\Response;
+use \ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodesLib;
+
+/**
+ * Description of Curl
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+abstract class Curl
+{
+    private $curl;
+    private $options = [
+        CURLOPT_TIMEOUT        => 30,
+        CURLOPT_HEADER         => false,
+        CURLOPT_RETURNTRANSFER => true,
+        CURLINFO_HEADER_OUT    => true
+    ];
+    protected $curlParser;
+
+    public function setCurlParser($curlParser)
+    {
+        $this->curlParser = $curlParser;
+
+        return $this;
+    }
+
+    public function setOptions($options, $value)
+    {
+        $this->options[$options] = $value;
+        return $this;
+    }
+
+    protected function open()
+    {
+        $this->curl = curl_init();
+
+        return $this;
+    }
+
+    protected function close()
+    {
+        curl_close($this->curl);
+
+        return $this;
+    }
+
+    protected function unsetOptions($options)
+    {
+        if (is_array($options))
+        {
+            foreach ($options as $option)
+            {
+                if (isset($this->options[$option]))
+                {
+                    unset($this->options[$option]);
+                }
+            }
+        }
+        else
+        {
+            unset($this->options[$options]);
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Response
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception
+     */
+    protected function send()
+    {
+        $this->includeOptions();
+
+        if (($head = $this->execute()) === false)
+        {
+            throw new Exception(ErrorCodesLib::CORE_CURL_000001, ['lastCurlError' => $this->getLastErrorWithCurl()]);
+        }
+
+        if ($errno = $this->getLastErrorNumber())
+        {
+            throw new Exception(ErrorCodesLib::CORE_CURL_000002, ['curlError' => $this->getLastError($errno)]);
+        }
+
+        list($header, $body) = $this->curlParser->rebuild($head, $this->getHeaderSize());
+
+        return DependencyInjection::create(Response::class)
+                        ->setRequest($this->getHeaderOut())
+                        ->setHeader($header)
+                        ->setCode($this->getHttpCode())
+                        ->setBody($body);
+    }
+
+    private function execute()
+    {
+        return curl_exec($this->curl);
+    }
+
+    private function getLastErrorNumber()
+    {
+        return curl_errno($this->curl);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.5.0, PHP 7)<br/>
+     * Return string describing the given error code
+     * @link http://php.net/manual/en/function.curl-strerror.php
+     * @param int $errornum <p>
+     * One of the cURL error codes constants.
+     * </p>
+     * @return string error description or <b>NULL</b> for invalid error code.
+     */
+    private function getLastError($errmo)
+    {
+        return curl_strerror($errmo);
+    }
+
+    /**
+     * (PHP 4 &gt;= 4.0.3, PHP 5, PHP 7)<br/>
+     * Return a string containing the last error for the current session
+     * @link http://php.net/manual/en/function.curl-error.php
+     * @param resource $ch
+     * @return string the error message or '' (the empty string) if no
+     * error occurred.
+     */
+    private function getLastErrorWithCurl()
+    {
+        return curl_error($this->curl);
+    }
+
+    private function getHeaderSize()
+    {
+        return curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
+    }
+
+    private function getHeaderOut()
+    {
+        return curl_getinfo($this->curl, CURLINFO_HEADER_OUT);
+    }
+
+    private function getHttpCode()
+    {
+        return curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
+    }
+
+    private function includeOptions()
+    {
+        curl_setopt_array($this->curl, $this->options);
+
+        return $this;
+    }
+}

+ 191 - 0
core/Api/AbstractApi/Curl/Request.php

@@ -0,0 +1,191 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl;
+
+use ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl;
+use ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl\Response;
+
+/**
+ * Description of Request
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Request extends Curl
+{
+    protected $url          = '';
+    protected $lastResponse = [];
+    protected $headers      = [
+        "Content-Type: application/x-www-form-urlencoded"
+    ];
+
+    public function getLastResponse()
+    {
+        return $this->lastResponse;
+    }
+
+    public function setUrl($url = "")
+    {
+        $this->url = $url;
+
+        return $this;
+    }
+
+    public function resetHeaders()
+    {
+        $this->headers = [];
+
+        return $this;
+    }
+
+    public function setHeaders(array $headers = [])
+    {
+        $this->headers = $headers;
+
+        return $this;
+    }
+
+    public function addHeaders($headers)
+    {
+        if (is_array($headers))
+        {
+            $this->headers = $headers;
+        }
+        else
+        {
+            $this->headers[] = $headers;
+        }
+
+        return $this;
+    }
+
+    protected function send()
+    {
+        $return               = parent::send();
+        $this->close();
+        $this->lastResponse[] = $return;
+
+        return $return;
+    }
+
+    /**
+     * @param array $data
+     * @return string
+     */
+    protected function httpBuildQuery(array $data = [])
+    {
+        return empty($data) ? "" : http_build_query($data);
+    }
+
+    /**
+     * @param array $data post field
+     * @return Response
+     */
+    public function post($data = [])
+    {
+
+        $postvars = is_array($data) ? $this->httpBuildQuery($data) : $data;
+
+        $this->open()
+                ->setOptions(CURLOPT_SSL_VERIFYPEER, false)
+                ->setOptions(CURLOPT_URL, $this->url)
+                ->setOptions(CURLOPT_POSTFIELDS, $postvars)
+                ->setOptions(CURLOPT_POST, true);
+
+        if (!empty($this->headers))
+        {
+            $this->setOptions(CURLOPT_HTTPHEADER, $this->headers)
+                    ->setOptions(CURLOPT_HEADER, true);
+        }
+
+        return $this->send();
+    }
+
+    /**
+     * @param array $data post field
+     * @return Response
+     */
+    public function put($data = [])
+    {
+
+        $postvars = is_array($data) ? $this->httpBuildQuery($data) : $data;
+
+        $this->open()
+                ->setOptions(CURLOPT_SSL_VERIFYPEER, false)
+                ->setOptions(CURLOPT_URL, $this->url)
+                ->setOptions(CURLOPT_POSTFIELDS, $postvars)
+                ->setOptions(CURLOPT_CUSTOMREQUEST, "PUT");
+
+        if (!empty($this->headers))
+        {
+            $this->setOptions(CURLOPT_HTTPHEADER, $this->headers)
+                    ->setOptions(CURLOPT_HEADER, true);
+        }
+
+        return $this->send();
+    }
+
+    /**
+     * @param array $data post field
+     * @return Response
+     */
+    public function delete($data = [])
+    {
+
+        $deletevars = is_array($data) ? $this->httpBuildQuery($data) : $data;
+
+        $this->open()
+                ->setOptions(CURLOPT_SSL_VERIFYPEER, false)
+                ->setOptions(CURLOPT_URL, $this->url . $deletevars)
+                ->setOptions(CURLOPT_CUSTOMREQUEST, "DELETE");
+
+        if (!empty($this->headers))
+        {
+            $this->setOptions(CURLOPT_HTTPHEADER, $this->headers)
+                    ->setOptions(CURLOPT_HEADER, true);
+        }
+
+        return $this->send();
+    }
+
+    /**
+     * @param array $data post field
+     * @return Response
+     */
+    public function get($data = [])
+    {
+        $getvars = is_array($data) ? $this->httpBuildQuery($data) : $data;
+
+        $this->open()
+                ->setOptions(CURLOPT_URL, $this->url . $getvars);
+
+        if (!empty($this->headers))
+        {
+            $this->setOptions(CURLOPT_HTTPHEADER, $this->headers)
+                    ->setOptions(CURLOPT_HEADER, true);
+        }
+
+        return $this->send();
+    }
+
+    /**
+     * @param array $data post field
+     * @return Response
+     */
+    public function options($data = [])
+    {
+        $deletevars = is_array($data) ? $this->httpBuildQuery($data) : $data;
+
+        $this->open()
+                ->setOptions(CURLOPT_SSL_VERIFYPEER, false)
+                ->setOptions(CURLOPT_URL, $this->url)
+                ->setOptions(CURLOPT_CUSTOMREQUEST, "OPTIONS");
+
+        if (!empty($this->headers))
+        {
+            $this->setOptions(CURLOPT_HTTPHEADER, $this->headers)
+                    ->setOptions(CURLOPT_HEADER, true);
+        }
+
+        return $this->send();
+    }
+}

+ 92 - 0
core/Api/AbstractApi/Curl/Response.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi\Curl;
+
+/**
+ * Description of Respons
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Response
+{
+    protected $body;
+    protected $request;
+    protected $header;
+    protected $code;
+
+    public function setRequest($request)
+    {
+        $this->request = $request;
+
+        return $this;
+    }
+
+    public function setHeader($header)
+    {
+        $this->header = $header;
+
+        return $this;
+    }
+
+    public function setBody($body)
+    {
+        $this->body = $body;
+
+        return $this;
+    }
+
+    public function setCode($code)
+    {
+        $this->code = $code;
+
+        return $this;
+    }
+
+    /**
+     * @param bool $isJson
+     * @return string|\stdClass
+     */
+    public function getBody($isJson = true)
+    {
+
+
+        if ($isJson)
+        {
+            return json_decode($this->body);
+        }
+
+        return $this->body;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRequest()
+    {
+        return $this->request;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHeader()
+    {
+        return $this->header;
+    }
+
+    /**
+     * @return int
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isSuccess()
+    {
+        return (bool) ($this->code >= 200 && $this->code < 300);
+    }
+}

+ 22 - 0
core/Api/AbstractApi/Parser.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api\AbstractApi;
+
+use ModulesGarden\ProxmoxAddon\Core\Interfaces\CurlParser;
+
+/**
+ * Description of Parser
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Parser implements CurlParser
+{
+
+    public function rebuild($head, $size)
+    {
+        return [
+            substr($head, 0, $size),
+            substr($head, $size)
+        ];
+    }
+}

+ 89 - 0
core/Api/Whmcs.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Api;
+
+use \ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Admins;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodesLib;
+
+class Whmcs
+{
+    /**
+     * @var Admins
+     */
+    protected $admins;
+
+    /**
+     * @var string
+     */
+    protected $username;
+
+    /**
+     * @param Admins $admins
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception
+     */
+    public function __construct(Admins $admins)
+    {
+        $this->admins = $admins;
+        $this->getAdminUserName();
+
+        if (function_exists('localAPI') === false)
+        {
+            throw new Exception(ErrorCodesLib::CORE_WAPI_000001);
+        }
+    }
+
+    /**
+     * @return string
+     */
+    protected function getAdminUserName()
+    {
+        if (isset($this->username) === false)
+        {
+            $this->username = $this->admins->first()->toArray()['username'];
+        }
+
+        return $this->username;
+    }
+
+    public function call($command, $config = [])
+    {
+
+        $result = localAPI($command, $config, $this->getAdminUserName());
+
+        if ($result['result'] == 'error')
+        {
+            $exc = new Exception(ErrorCodesLib::CORE_WAPI_000002, ['command' => $command, 'data' => $config, 'result' => $result]);
+            $exc->setCustomMessage($result['message']);
+
+            throw $exc;
+        }
+
+        return $result;
+    }
+
+    public function getAdminDetails($adminId)
+    {
+        $data = $this->admins->where("id", "LIKE", $adminId)->first();
+
+        if ($data === null)
+        {
+            throw new Exception(ErrorCodesLib::CORE_WAPI_000003, ['adminId' => $adminId], ['adminId' => $adminId]);
+        }
+
+        $result = localAPI("getadmindetails", [], $data->toArray()['username']);
+
+        if ($result['result'] == 'error')
+        {
+            $exc = new Exception(ErrorCodesLib::CORE_WAPI_000004, ['command' => "getadmindetails", 'data' => [], 'result' => $result]);
+            $exc->setCustomMessage($result['message']);
+
+            throw $exc;
+        }
+
+        $result['allowedpermissions'] = explode(",", $result['allowedpermissions']);
+        unset($result['result']);
+
+        return $result;
+    }
+}

+ 178 - 0
core/App/AppContext.php

@@ -0,0 +1,178 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App
+{
+
+    use ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsErrorManagerWrapper;
+
+    class AppContext
+    {
+        protected $debugMode = true;
+
+        public function __construct()
+        {
+            require_once __DIR__ . DIRECTORY_SEPARATOR . 'ErrorHandler.php';
+
+            register_shutdown_function([$this, 'handleShutdown']);
+            set_error_handler([$this, 'handleError'], E_ALL);
+
+            $this->loadDebugState();
+
+            //require app bootstrap
+            require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Bootstrap.php';
+
+            if ($this->debugMode)
+            {
+                spl_autoload_register(array('\ModulesGarden\ProxmoxAddon\Core\App\AppContext', 'loadClassLoader'), true, false);
+            }
+        }
+
+        public function runApp($callerName = null, $params = [])
+        {
+            try
+            {
+                $app    = new Application();
+                $result = $app->run($callerName, $params);
+
+                restore_error_handler();
+            }
+            catch (\Exception $exc)
+            {
+                restore_error_handler();
+
+                return [
+                    'status'  => 'error',
+                    'message' => $exc->getMessage()
+                ];
+            }
+
+            return $result;
+        }
+
+        public function handleError($errno, $errstr, $errfile, $errline, $errcontext = null)
+        {
+            if ($this->debugMode || (!in_array($errno, ErrorHandler::WARNINGS) && !in_array($errno, ErrorHandler::NOTICES)))
+            {
+                $handler    = new ErrorHandler();
+                $errorToken = md5(time());
+                $handler->logError($errorToken, $errno, $errstr, $errfile, $errline, $errcontext);
+            }
+
+            return true;
+        }
+
+        public function handleShutdown()
+        {
+            $errorInstance = null;
+            $errManager    = WhmcsErrorManagerWrapper::getErrorManager();
+            if (is_object($errManager) && method_exists($errManager, 'getRunner'))
+            {
+                $runner = $errManager->getRunner();
+                if (is_object($runner) && method_exists($runner, 'getHandlers'))
+                {
+                    $handlers = $runner->getHandlers();
+                    foreach ($handlers as $handler)
+                    {
+                        $rfHandler = new \ReflectionClass($handler);
+                        $method    = $rfHandler->getMethod('getException');
+                        $method->setAccessible(true);
+                        $error     = $method->invoke($handler);
+                        if (is_object($error))
+                        {
+                            $errorInstance = $error;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if ($errorInstance === null)
+            {
+                $errorInstance = error_get_last();
+                if ($errorInstance === null)
+                {
+                    return;
+                }
+
+                $this->handleError($errorInstance['type'], $errorInstance['message'], $errorInstance['file'], $errorInstance['line'], '');
+
+                return;
+            }
+
+            $handler    = new ErrorHandler();
+            $errorToken = md5(time());
+            $handler->logError($errorToken, $errorInstance->getCode(), $errorInstance->getMessage(), $errorInstance->getFile(), $errorInstance->getLine(), $errorInstance->getTrace());
+            if ($errorToken)
+            {
+                echo '<input type="hidden" id="mg-sh-h-492318-64534" value="' . $errorToken . '" mg-sh-h-492318-64534-end >';
+            }
+        }
+
+        public function loadDebugState()
+        {
+            $path = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'debug';
+            if (file_exists($path))
+            {
+                $this->debugMode = true;
+
+                return;
+            }
+
+            $this->debugMode = false;
+        }
+
+        public static function loadClassLoader($class)
+        {
+            $rawClass = trim($class, '\\');
+            $pos      = strpos($rawClass, 'ModulesGarden\ProxmoxAddon');
+            if ($pos === 0)
+            {
+                if (!class_exists($class) && self::DEPRECATED[$rawClass])
+                {
+                    echo 'This class no longer exists: ' . $class . '<br>';
+                    echo 'Use: ' . self::DEPRECATED[$rawClass];
+                    die();
+                }
+            }
+        }
+        const DEPRECATED = [
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseMassActionButton'                      => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassAction',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\AddIconModalButton'                        => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonCreate',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseSubmitButton'                          => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonSubmitForm',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseButton'                                => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonBase',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseDatatableModalButton'                  => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDatatableShowModal',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseModalDataTableActionButton'            => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDataTableModalAction',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\RedirectButton'                            => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\BaseModalButton'                           => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonModal',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\RedirectWithOutTooltipButton'              => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonRedirect',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\OnOffAjaxSwitch'                           => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonSwitchAjax',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\CustomActionButton'                        => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonCustomAction',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\CustomAjaxActionButton'                    => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonAjaxCustomAction',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DatatableModalButtonContextLang'           => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDatatableModalContextLang',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdownButton'                            => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDropdown',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\MassActionButtonContextLang'               => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonMassActionContextLang',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Submit'                                    => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonSubmitForm',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsRegisterLoggin'                            => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsLogsHandler',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\ButtonDropdown'                            => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdown',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemButton'           => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemCustonAjaxButton' => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemCustonButton'     => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemDivider'          => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemModalButton'      => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\Dropdowntems\DropdownItemRedirectButton'   => 'ModulesGarden\ProxmoxAddon\Core\UI\Widget\Buttons\DropdawnButtonWrappers\ButtonDropdownItem',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\ApiException'                        => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\ApiWhmcsException'                   => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\ControllerException'                 => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\DependencyInjectionException'        => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException'                   => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\RegisterException'                   => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\ServiceLocatorException'             => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+            'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\SmartyException'                     => 'ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception',
+        ];
+    }
+
+}
+
+namespace
+{
+}

+ 60 - 0
core/App/Application.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers\Http;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers\Addon;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers\Api;
+use \ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+class Application
+{
+
+    public function run($callerName = null, $params = null)
+    {
+        try
+        {
+            $controller         = $this->getControllerClass($callerName);
+            $controllerInstance = ServiceLocator::call($controller);
+            $result             = $controllerInstance->runController($callerName, $params);
+
+            return $result;
+        }
+        catch (\Exception $exc)
+        {
+            $errorPage = ServiceLocator::call(Controllers\Instances\Http\ErrorPage::class);
+
+            $params['mgErrorDetails'] = $exc;
+
+            $result = $errorPage->execute($params);
+
+            return $result;
+        }
+    }
+
+    public function getControllerClass($callerName = null)
+    {
+        $functionName = str_replace($this->getModuleName() . '_', '', $callerName);
+        switch ($functionName)
+        {
+            //HTTP controllers
+            case 'output':
+                return Http::class;
+            case 'clientarea':
+                return Http::class;
+
+            //API controller
+            case 'api':
+                return Api::class;
+
+            //Addon controllers
+            default:
+                return Addon::class;
+        }
+    }
+
+    public function getModuleName()
+    {
+        return 'proxmoxAddon';
+    }
+}

+ 20 - 0
core/App/Controllers/AppController.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+abstract class AppController
+{
+
+    public function runController($callerName, $params)
+    {
+        $controller = $this->getControllerInstanceClass($callerName, $params);
+
+        $controllerInstance = ServiceLocator::call($controller);
+
+        $result = $controllerInstance->execute($params);
+
+        return $result;
+    }
+}

+ 33 - 0
core/App/Controllers/AppControllers/Addon.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\AddonController;
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AppController;
+
+class Addon extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppController implements AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params)
+    {
+        $functionName = str_replace($this->getModuleName() . '_', '', $callerName);
+        $coreAddon    = '\ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon\\' . ucfirst($functionName);
+        if (class_exists($coreAddon) && is_subclass_of($coreAddon, AddonController::class))
+        {
+            return $coreAddon;
+        }
+
+        $appAddon = '\ModulesGarden\ProxmoxAddon\App\Http\Actions\\' . ucfirst($functionName);
+        if (class_exists($appAddon) && is_subclass_of($appAddon, AddonController::class))
+        {
+            return $appAddon;
+        }
+
+        return null;
+    }
+
+    public function getModuleName()
+    {
+        return 'proxmoxAddon';
+    }
+}

+ 14 - 0
core/App/Controllers/AppControllers/Api.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AppController;
+
+class Api implements AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params)
+    {
+        // TODO: Implement getControllerInstanceClass() method.
+    }
+}

+ 14 - 0
core/App/Controllers/AppControllers/Cron.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AppController;
+
+class Cron implements AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params)
+    {
+        // TODO: Implement runCalledController() method.
+    }
+}

+ 14 - 0
core/App/Controllers/AppControllers/Hooks.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AppController;
+
+class Hooks implements AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params)
+    {
+        // TODO: Implement getControllerInstanceClass() method.
+    }
+}

+ 32 - 0
core/App/Controllers/AppControllers/Http.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppControllers;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AppController;
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http\AdminPageController;
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http\ClientPageController;
+
+class Http extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\AppController implements AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params)
+    {
+        //todo
+        $functionName = str_replace($this->getModuleName() . '_', '', $callerName);
+        switch ($functionName)
+        {
+            //HTTP controllers
+            case 'output':
+                return AdminPageController::class;
+            case 'clientarea':
+                return ClientPageController::class;
+        }
+
+        return null;
+    }
+
+    public function getModuleName()
+    {
+        return 'proxmoxAddon';
+    }
+}

+ 71 - 0
core/App/Controllers/Instances/Addon/Activate.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AddonController;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\Helper\DatabaseHelper;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Activate module actions
+ */
+class Activate extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\AddonController implements AddonController
+{
+    /**
+     * @var null|DatabaseHelper
+     */
+    protected $databaseHelper = null;
+
+    public function execute($params = [])
+    {
+        try
+        {
+            //Before module activation
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate\Before::class)->execute($params);
+            if (!isset($return['status']))
+            {
+                $return['status'] = 'success';
+            }
+
+            //module activation process
+            $return = $this->activate($return);
+
+            //After module activation
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate\After::class)->execute($return);
+
+            return $return;
+        }
+        catch (\Exception $exc)
+        {
+            ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorManager::class)->addError(self::class, $exc->getMessage(), $return);
+            return [
+                'status'      => 'error',
+                'description' => $exc->getMessage()
+            ];
+        }
+    }
+
+    protected function activate($params = [])
+    {
+        $this->databaseHelper = DependencyInjection::call(DatabaseHelper::class);
+
+        if ($params['status'] === 'error')
+        {
+            return $params;
+        }
+
+        $isErrorCore     = $this->databaseHelper->performQueryFromFile(ModuleConstants::getFullPath('core', 'Database', 'schema.sql'));
+        $isErrorApp      = $this->databaseHelper->performQueryFromFile(ModuleConstants::getFullPath('app', 'Database', 'schema.sql'));
+        $isErrorDataCore = $this->databaseHelper->performQueryFromFile(ModuleConstants::getFullPath('core', 'Database', 'data.sql'));
+        $isErrorDataApp  = $this->databaseHelper->performQueryFromFile(ModuleConstants::getFullPath('app', 'Database', 'data.sql'));
+
+        if ($isErrorCore || $isErrorDataCore || $isErrorApp || $isErrorDataApp)
+        {
+            return ['status' => 'error', 'description' => ServiceLocator::call('errorManager')->getFirstError()->getMessage()];
+        }
+
+        return ['status' => 'success'];
+    }
+}

+ 149 - 0
core/App/Controllers/Instances/Addon/Config.php

@@ -0,0 +1,149 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AddonController;
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Data;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Module configuration wrapper
+ */
+class Config extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\AddonController implements AddonController
+{
+    /**
+     * @var array
+     * list of params passed by WHMCS
+     */
+    private $params = [];
+
+    /**
+     * @var array
+     * module configuration list
+     */
+    protected $config = [];
+
+    /**
+     * @var null|\ModulesGarden\ProxmoxAddon\Core\Configuration\Data
+     *
+     */
+    protected $data = null;
+
+    /**
+     * @var array
+     * list of values to be returned as a part of
+     */
+    protected $configFields = [
+        'name',
+        'description',
+        'version',
+        'author',
+        'fields',
+        'systemName',
+        'debug',
+        'moduleIcon',
+        'clientareaName'
+    ];
+
+    /**
+     * @param array $params - WHMCS params for function _config
+     * @return array
+     */
+    public function execute($params = [])
+    {
+        if (!$this->config)
+        {
+            $this->setParams($params);
+            $this->loadConfig();
+
+            return $this->getConfig();
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * @param array $params
+     */
+    protected function setParams($params = [])
+    {
+        if (is_array($params))
+        {
+            $this->params = $params;
+        }
+    }
+
+    /**
+     * loads module configuration from files yaml configs and moduleVersion.php
+     */
+    protected function loadConfig()
+    {
+        if (!$this->data)
+        {
+            $this->data = DependencyInjection::call(Data::class);
+        }
+    }
+
+    /**
+     * parses config data
+     */
+    public function getConfig()
+    {
+        if ($this->config)
+        {
+            return $this->config;
+        }
+
+        try
+        {
+            //Before loading the config
+            $params = [];
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config\Before::class)->execute($params);
+
+            foreach ($this->configFields as $field)
+            {
+                $value = $this->data->{$field};
+                if (isset($return[$field]) === false && $value !== null)
+                {
+                    if (is_numeric($value))
+                    {
+                        $return[$field] = (int) $value;
+                    }
+                    else
+                    {
+                        $return[$field] = $value;
+                    }
+                }
+            }
+
+            //After loading the config
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config\After::class)->execute($return);
+
+            $this->config = $return;
+
+            return $return;
+        }
+        catch (\Exception $ex)
+        {
+            ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorManager::class)->addError(self::class, $ex->getMessage(), $return);
+
+            return $return ?: [];
+        }
+    }
+
+    public function getConfigValue($key, $defaultValue = null)
+    {
+        if (!$this->config)
+        {
+            $this->execute();
+        }
+
+        if (!isset($this->config[$key]))
+        {
+            return $defaultValue;
+        }
+
+        return $this->config[$key];
+    }
+}

+ 44 - 0
core/App/Controllers/Instances/Addon/Deactivate.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AddonController;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Deactivate module action
+ */
+class Deactivate extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\AddonController implements AddonController
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute($params = [])
+    {
+        try
+        {
+            // before
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate\Before::class)->execute($params);
+
+            if (!isset($return['status']))
+            {
+                $return['status'] = 'success';
+            }
+
+            // after
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate\After::class)->execute($return);
+
+            return $return;
+        }
+        catch (\Exception $exc)
+        {
+            ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorManager::class)->addError(self::class, $exc->getMessage(), $return);
+            return [
+                'status'      => 'error',
+                'description' => $exc->getMessage()
+            ];
+        }
+    }
+}

+ 48 - 0
core/App/Controllers/Instances/Addon/Upgrade.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AddonController;
+use ModulesGarden\ProxmoxAddon\Core\Helper\DatabaseHelper;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * module update process
+ */
+class Upgrade extends \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\AddonController implements AddonController
+{
+    /**
+     * @var null|DatabaseHelper
+     */
+    protected $databaseHelper = null;
+
+    public function execute($params = [])
+    {
+        if ($version == '')
+        {
+            $version = isset($this->params['version']) ? $this->params['version'] : $params['version'];
+        }
+
+        try
+        {
+            // after
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\After::class)->execute(['version' => $version]);
+
+            // update
+            if (!isset($return['version']))
+            {
+                $return['version'] = $version;
+            }
+            $patchManager = ServiceLocator::call("patchManager")->run(/* $this->getConfig("version") */ '', $version);
+
+            // before
+            $return = ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\Before::class)->execute($return);
+
+            return $return;
+        }
+        catch (\Exception $ex)
+        {
+            ServiceLocator::call(\ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorManager::class)->addError(self::class, $ex->getMessage(), $return);
+        }
+    }
+}

+ 16 - 0
core/App/Controllers/Instances/AddonController.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances;
+
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Http\PageNotFound;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\DefaultController;
+
+abstract class AddonController implements DefaultController
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\OutputBuffer;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\IsAdmin;
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\ErrorCodesLibrary;
+}

+ 12 - 0
core/App/Controllers/Instances/Api/ApiController.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Api;
+
+class ApiController implements DefaultController
+{
+
+    public function execute()
+    {
+        // TODO: Implement execute() method.
+    }
+}

+ 11 - 0
core/App/Controllers/Instances/Http/AdminPageController.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AdminArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\HttpController;
+
+class AdminPageController extends HttpController implements AdminArea
+{
+    
+}

+ 11 - 0
core/App/Controllers/Instances/Http/ClientPageController.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\ClientArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\HttpController;
+
+class ClientPageController extends HttpController implements ClientArea
+{
+    
+}

+ 82 - 0
core/App/Controllers/Instances/Http/ErrorPage.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AdminArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\ClientArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\HttpController;
+
+class ErrorPage extends HttpController implements AdminArea, ClientArea
+{
+    protected $templateName = 'errorPage';
+
+    public function execute($params = null)
+    {
+        $this->setParams($params);
+
+        $this->parseError();
+
+        return $this->resolveResponse();
+    }
+
+    public function resolveResponse()
+    {
+        if ($this->getRequestValue('ajax') == '1')
+        {
+            return $this->resolveResponseAjax();
+        }
+
+        return $this->responseResolver->setResponse(\ModulesGarden\ProxmoxAddon\Core\Helper\view())
+                        ->setTemplateName($this->getTemplateName())
+                        ->setTemplateDir($this->getTemplateDir())
+                        ->setPageController($this)
+                        ->resolve();
+    }
+
+    public function parseError()
+    {
+        $err = $this->getParam('mgErrorDetails');
+        if (!$err)
+        {
+            return null;
+        }
+
+        if (!($err instanceof \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception))
+        {
+            $nErr = new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception(null, null, null, $err);
+
+            $this->setParam('mgErrorDetails', $nErr);
+        }
+
+        $this->logError();
+    }
+
+    public function logError()
+    {
+        $err = $this->getParam('mgErrorDetails');
+
+        /**
+         * \ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsLogsHandler
+         */
+        $logger = \ModulesGarden\ProxmoxAddon\Core\ServiceLocator::call('whmcsLogger');
+
+        $logger->addModuleLogError($err);
+    }
+
+    public function resolveResponseAjax()
+    {
+        $err = $this->getParam('mgErrorDetails');
+        if (!$err)
+        {
+            return null;
+        }
+
+        $ajaxData = (new \ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\DataJsonResponse($err->getDetailsToDisplay()))->setStatusError();
+
+        return $this->responseResolver->setResponse($ajaxData->getFormatedResponse())
+                        ->setTemplateName($this->getTemplateName())
+                        ->setTemplateDir($this->getTemplateDir())
+                        ->setPageController($this)
+                        ->resolve();
+    }
+}

+ 38 - 0
core/App/Controllers/Instances/Http/Integration.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AdminArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\ClientArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\HttpController;
+
+class Integration extends HttpController implements AdminArea, ClientArea
+{
+    protected $templateName = 'integration';
+
+    public function execute($params = null)
+    {
+        $this->setParams($params);
+
+        if (!$this->controllerResult)
+        {
+            return '';
+        }
+
+        return $this->resolveResponse();
+    }
+
+    public function resolveResponse()
+    {
+        if ($this->controllerResult instanceof \ModulesGarden\ProxmoxAddon\Core\Http\Response)
+        {
+            $this->controllerResult->setForceHtml();
+        }
+
+        return $this->responseResolver->setResponse($this->controllerResult)
+                        ->setTemplateName($this->getTemplateName())
+                        ->setTemplateDir($this->getTemplateDir())
+                        ->setPageController($this)
+                        ->resolve();
+    }
+}

+ 28 - 0
core/App/Controllers/Instances/Http/PageNotFound.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Http;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AdminArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\ClientArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\HttpController;
+
+class PageNotFound extends HttpController implements AdminArea, ClientArea
+{
+    protected $templateName = 'pageNotFound';
+
+    public function execute($params = null)
+    {
+        $this->setParams($params);
+
+        return $this->resolveResponse();
+    }
+
+    public function resolveResponse()
+    {
+        return $this->responseResolver->setResponse(\ModulesGarden\ProxmoxAddon\Core\Helper\view())
+                        ->setTemplateName($this->getTemplateName())
+                        ->setTemplateDir($this->getTemplateDir())
+                        ->setPageController($this)
+                        ->resolve();
+    }
+}

+ 166 - 0
core/App/Controllers/Instances/HttpController.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\AdminArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\ClientArea;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces\DefaultController;
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\ResponseResolver;
+use ModulesGarden\ProxmoxAddon\Core\App\Controllers\Router;
+use \ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+abstract class HttpController implements DefaultController
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\OutputBuffer;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\IsAdmin;
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\ErrorCodesLibrary;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Params;
+    const ADMIN                       = 'admin';
+    const CLIENT                      = 'client';
+    protected $templateName     = 'main';
+    protected $templateContext  = 'default';
+    protected $templateDir      = null;
+    protected $controllerResult = null;
+
+    /**
+     * @var Router|null
+     *
+     */
+    protected $router           = null;
+    protected $responseResolver = null;
+
+    public function __construct()
+    {
+        $this->loadSmarty();
+        $this->isAdmin();
+
+        $this->router           = new Router();
+        $this->responseResolver = new ResponseResolver();
+    }
+
+    public function execute($params = null)
+    {
+        $this->setParams($params);
+
+        if (!$this->router->isControllerCallable() || !$this->isAdminContextValid())
+        {
+            return $this->controllerResult = $this->getPageNotFound();
+        }
+        else
+        {
+            $this->controllerResult = $this->getControllerResponse();
+        }
+
+        return $this->resolveResponse();
+    }
+
+    public function resolveResponse()
+    {
+        return $this->responseResolver->setResponse($this->controllerResult)
+                        ->setTemplateName($this->getTemplateName())
+                        ->setTemplateDir($this->getTemplateDir())
+                        ->setPageController($this)
+                        ->resolve();
+    }
+
+    public function isAdminContextValid()
+    {
+        if ($this->isAdmin() && !($this instanceof AdminArea))
+        {
+            return false;
+        }
+
+        if (!$this->isAdmin() && !($this instanceof ClientArea))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    public function getPageNotFound()
+    {
+        $notFound = new Http\PageNotFound();
+
+        return $notFound->execute();
+    }
+
+    protected function getControllerResponse()
+    {
+        $this->loadLang();
+        $this->lang->setContext(($this->getType() . ($this->isAdmin() ? 'AA' : 'CA')), lcfirst($this->getControllerClass(true)));
+
+        $result = DependencyInjection::create(
+                        $this->router->getControllerClass(), $this->router->getControllerMethod()
+        );
+
+        return $result;
+    }
+
+    public function getTemplateName()
+    {
+        return $this->templateName;
+    }
+
+    public function getTemplateDir()
+    {
+        if ($this->templateDir === null)
+        {
+            $this->templateDir = ModuleConstants::getTemplateDir() . DIRECTORY_SEPARATOR .
+                    ($this->isAdmin() ? self::ADMIN : (self::CLIENT . DIRECTORY_SEPARATOR . $this->getTemplateContext()));
+        }
+
+        return $this->templateDir;
+    }
+
+    public function getTemplateContext()
+    {
+        return $this->templateContext;
+    }
+
+    public function getControllerClass($raw = false)
+    {
+        if ($raw)
+        {
+            $namespaceParts = explode('\\', $this->getControllerClass());
+
+            return end($namespaceParts);
+        }
+
+        return $this->router->getControllerClass();
+    }
+
+    public function getControllerMethod()
+    {
+        return $this->router->getControllerMethod();
+    }
+
+    /**
+     * @param null $controllerResult
+     */
+    public function setControllerResult($controllerResult)
+    {
+        $this->controllerResult = $controllerResult;
+    }
+
+    /**
+     * @return null
+     */
+    public function getControllerResult()
+    {
+        return $this->controllerResult;
+    }
+
+    /**
+     * @return string
+     */
+    protected function getType()
+    {
+        return 'addon';
+    }
+}

+ 9 - 0
core/App/Controllers/Interfaces/AddonController.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces;
+
+interface AddonController
+{
+
+    public function execute($params = []);
+}

+ 8 - 0
core/App/Controllers/Interfaces/AdminArea.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces;
+
+interface AdminArea
+{
+    
+}

+ 9 - 0
core/App/Controllers/Interfaces/AppController.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces;
+
+interface AppController
+{
+
+    public function getControllerInstanceClass($callerName, $params);
+}

+ 8 - 0
core/App/Controllers/Interfaces/ClientArea.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces;
+
+interface ClientArea
+{
+    
+}

+ 9 - 0
core/App/Controllers/Interfaces/DefaultController.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers\Interfaces;
+
+interface DefaultController
+{
+
+    public function execute($params = null);
+}

+ 137 - 0
core/App/Controllers/ResponseResolver.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers;
+
+use \ModulesGarden\ProxmoxAddon\Core\UI\View;
+use \ModulesGarden\ProxmoxAddon\Core\Http\JsonResponse;
+use \ModulesGarden\ProxmoxAddon\Core\Http\RedirectResponse;
+use \ModulesGarden\ProxmoxAddon\Core\Http\Response;
+
+class ResponseResolver
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\OutputBuffer;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\IsAdmin;
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Template;
+    protected $response = null;
+
+    /**
+     * @var null|HttpController
+     */
+    protected $pageController = null;
+
+    public function __construct($response = null)
+    {
+        $this->setResponse($response);
+
+        $this->loadSmarty();
+    }
+
+    /**
+     * @param null $response
+     * @return $this
+     */
+    public function setResponse($response = null)
+    {
+        if ($response)
+        {
+            $this->response = $response;
+        }
+
+        return $this;
+    }
+
+    /**
+     * resolves the response
+     */
+    public function resolve()
+    {
+        if ($this->response instanceof View)
+        {
+            $this->resolveView();
+        }
+
+        if ($this->response instanceof JsonResponse)
+        {
+            $this->resolveJson();
+        }
+        elseif ($this->response instanceof RedirectResponse)
+        {
+            $this->resolveRedirect();
+        }
+        elseif ($this->response instanceof Response)
+        {
+            $this->prepareResponse();
+
+            return $this->resolveResponse();
+        }
+    }
+
+    /**
+     * resolve View object to the processable response
+     */
+    public function resolveView()
+    {
+        /**
+         * @var $this->response \ModulesGarden\ProxmoxAddon\Core\UI\View
+         */
+        $this->response->validateAcl($this->isAdmin());
+
+        $this->response = $this->response->getResponse();
+    }
+
+    public function prepareResponse()
+    {
+        $this->response->setLang($this->lang);
+        $this->response->setTemplateName($this->getTemplateName());
+        $this->response->setTemplateDir($this->getTemplateDir());
+        //$this->smarty->setTemplateDir();
+    }
+
+    /**
+     * resolve \ModulesGarden\ProxmoxAddon\Core\Http\JsonResponse
+     */
+    public function resolveJson()
+    {
+        $this->cleanOutputBuffer();
+        /**
+         * @var \ModulesGarden\ProxmoxAddon\Core\Http\JsonResponse
+         */
+        $this->response->send();
+        die();
+    }
+
+    public function resolveRedirect()
+    {
+        /**
+         * @var \ModulesGarden\ProxmoxAddon\Core\Http\RedirectResponse
+         */
+        die($this->response->send());
+    }
+
+    public function resolveResponse()
+    {
+        return $this->response->getHtmlResponse($this);
+    }
+
+    /**
+     * @param null|HttpController $pageController
+     */
+    public function setPageController($pageController)
+    {
+        $this->pageController = $pageController;
+
+        return $this;
+    }
+
+    /**
+     * @return HttpController|null
+     */
+    public function getPageController()
+    {
+        return $this->pageController;
+    }
+}

+ 102 - 0
core/App/Controllers/Router.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Controllers;
+
+class Router
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\OutputBuffer;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\IsAdmin;
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    const ADMIN                       = 'admin';
+    const CLIENT                      = 'client';
+    protected $controllerClass  = null;
+    protected $controllerMethod = null;
+
+    public function __construct()
+    {
+        $this->isAdmin();
+        $this->loadController();
+    }
+
+    /**
+     * load routing params
+     */
+    public function loadController()
+    {
+        $this->getControllerClass();
+        $this->getControllerMethod();
+    }
+
+    /**
+     * @return string
+     * class name of the controller
+     */
+    public function getControllerClass()
+    {
+        if ($this->controllerClass === null)
+        {
+            $this->controllerClass = '\ModulesGarden\ProxmoxAddon\App\Http\\' . ucfirst($this->getControllerType()) . '\\' . ucfirst($this->getController());
+        }
+
+        return $this->controllerClass;
+    }
+
+    /**
+     * @return string
+     * admin/client context type
+     */
+    public function getControllerType()
+    {
+        return $this->isAdminStatus ? self::ADMIN : self::CLIENT;
+    }
+
+    /**
+     * @return string
+     * get controller name
+     */
+    public function getController()
+    {
+        return filter_var($this->getRequestValue('mg-page', 'Home'), FILTER_SANITIZE_SPECIAL_CHARS);
+    }
+
+    /**
+     * @return string
+     * controller method name
+     */
+    public function getControllerMethod()
+    {
+        if ($this->controllerMethod === null)
+        {
+            $this->controllerMethod = $this->request->get('mg-action', 'index');
+        }
+
+        return $this->controllerMethod;
+    }
+
+    /**
+     * @return bool
+     * determines if controller can be called
+     */
+    public function isControllerCallable()
+    {
+        if (!class_exists($this->controllerClass))
+        {
+            return false;
+        }
+
+        if (!method_exists($this->controllerClass, $this->controllerMethod))
+        {
+            return false;
+        }
+
+        if (!is_callable([$this->controllerClass, $this->controllerMethod]))
+        {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 46 - 0
core/App/ErrorHandler.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App;
+
+class ErrorHandler
+{
+    const ERRORS   = [1, 4, 16, 64, 256, 4096];
+    const WARNINGS = [2, 32, 128, 512, 2048];
+    const NOTICES  = [8, 1024, 8192, 16384];
+
+    public function __construct()
+    {
+        require_once __DIR__ . DIRECTORY_SEPARATOR . 'LowLevelLog.php';
+    }
+
+    public function logError($errorToken, $errno, $errstr, $errfile, $errline, $errcontext = null)
+    {
+        $logType      = $this->getLogType($errno);
+        $errorTime    = date('d.m.Y H:i:s', time());
+        $errorDetails = [
+            'errno'      => $errno,
+            'errstr'     => $errstr,
+            'errfile'    => $errfile,
+            'errline'    => $errline,
+            'errcontext' => $logType === 'error' ? $errcontext : null
+        ];
+
+        $log = new LowLevelLog($logType, $errorToken, $errorTime);
+        $log->makeLogs($errorDetails);
+    }
+
+    public function getLogType($errno = null)
+    {
+        if (in_array($errno, self::WARNINGS))
+        {
+            return 'warning';
+        }
+
+        if (in_array($errno, self::NOTICES))
+        {
+            return 'notice';
+        }
+
+        return 'error';
+    }
+}

+ 88 - 0
core/App/LowLevelLog.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App;
+
+class LowLevelLog
+{
+    protected $logType    = null;
+    protected $logToken   = null;
+    protected $logTime    = null;
+    protected $moduleName = null;
+
+    public function __construct($logType, $logToken, $logTime)
+    {
+        $this->logType  = $logType;
+        $this->logToken = $logToken;
+        $this->logTime  = $logTime;
+    }
+
+    public function makeLogs($logDetails)
+    {
+        if ($this->logType === 'error')
+        {
+            $this->logToDb($logDetails);
+        }
+
+        $this->logToFile($logDetails);
+
+        if ($this->logType === 'error')
+        {
+            $this->logToPHPLog($logDetails);
+        }
+    }
+
+    public function logToDb($logDetails = [])
+    {
+        \logModuleCall(
+                $this->getModuleName() . ' Error', 'Token: ' . $this->logToken, ['time' => $this->logTime], var_export($logDetails, true)
+        );
+    }
+
+    public function logToFile($logDetails)
+    {
+        return;
+        $logData = date('d.m.Y H:i:s', time()) . ' - Token: ' . $this->logToken . ' - ' . var_export($logDetails, true) . PHP_EOL;
+        $logFile = $this->getLogFile($this->logType);
+
+        file_put_contents($logFile, $logData, FILE_APPEND);
+    }
+
+    public function logToPHPLog($logDetails)
+    {
+        
+    }
+
+    public function getLogFile($logType = 'error')
+    {
+        $logDir  = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'logs';
+        $logFile = $logDir . DIRECTORY_SEPARATOR . $logType . '.log';
+        if (!file_exists($logFile) && !is_writable($logDir))
+        {
+            //throw new \Exception('Insufficient permissions for directory: ' . $logDir);
+        }
+        if (file_exists($logFile) && !is_writable($logFile))
+        {
+            //throw new \Exception('Insufficient permissions for file: ' . $logFile);
+        }
+
+        return $logFile;
+    }
+
+    public function getModuleName()
+    {
+        if (!$this->moduleName)
+        {
+            $className = trim(self::class, '\\');
+            if (strpos($className, 'ModulesGarden') === 0)
+            {
+                $pt1 = str_replace('ModulesGarden\\', '', $className);
+                if (strpos($pt1, '\Core\App'))
+                {
+                    $this->moduleName = substr($pt1, 0, strpos($pt1, '\Core\App'));
+                }
+            }
+        }
+
+        return $this->moduleName;
+    }
+}

+ 97 - 0
core/App/Requirements/Checker.php

@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Requirements;
+
+/**
+ * Description of Handler
+ *
+ * @author INBSX-37H
+ */
+class Checker
+{
+    /**
+     * \ModulesGarden\ProxmoxAddon\Core\FileReader\PathValidator
+     * @var type null|\ModulesGarden\ProxmoxAddon\Core\FileReader\|\ModulesGarden\ProxmoxAddon\Core\FileReader\Directory
+     */
+    protected $directoryHandler = null;
+    protected $requirementsList = [];
+    protected $checkResaults    = [];
+    /**
+     * Paths for Requirements class to be placed in
+     * @var type array
+     */
+    const PATHS                       = [
+        'app' . DIRECTORY_SEPARATOR . 'Configuration' . DIRECTORY_SEPARATOR . 'Requirements',
+        'core' . DIRECTORY_SEPARATOR . 'Configuration' . DIRECTORY_SEPARATOR . 'Requirements'
+    ];
+
+    public function __construct()
+    {
+        $this->directoryHandler = new \ModulesGarden\ProxmoxAddon\Core\FileReader\Directory();
+
+        $this->loadRequirements();
+
+        $this->checkRequirements();
+    }
+
+    protected function loadRequirements()
+    {
+        foreach (self::PATHS as $path)
+        {
+            $this->loadClassesByPath($path);
+        }
+    }
+
+    protected function loadClassesByPath($path)
+    {
+        $fullPath       = \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getModuleRootDir() . DIRECTORY_SEPARATOR . $path;
+        $files          = $this->directoryHandler->getFilesList($fullPath, '.php', true);
+        $classNamespace = $this->getClassNamespaceByPath($path);
+        foreach ($files as $file)
+        {
+            $className = $classNamespace . $file;
+            if (!class_exists($className) || !is_subclass_of($className, RequirementInterface::class))
+            {
+                continue;
+            }
+
+            $this->requirementsList[] = $className;
+        }
+    }
+
+    protected function getClassNamespaceByPath($path)
+    {
+        $contextParts = explode('\\', self::class);
+        $coreParts    = explode(DIRECTORY_SEPARATOR, $path);
+
+        $allParts = array_merge([$contextParts[0], $contextParts[1]], $coreParts);
+        array_walk($allParts, function(&$item)
+        {
+            $item = ucfirst($item);
+        });
+
+        return '\\' . implode('\\', $allParts) . '\\';
+    }
+
+    protected function checkRequirements()
+    {
+        foreach ($this->requirementsList as $requirement)
+        {
+            $instance = \ModulesGarden\ProxmoxAddon\Core\DependencyInjection\DependencyInjection::call($requirement);
+            $handler  = $instance->getHandler();
+
+            $this->checkResaults = array_merge($this->checkResaults, $handler->getUnfulfilledRequirements());
+        }
+    }
+
+    public function getUnfulfilledRequirements()
+    {
+        return $this->checkResaults;
+    }
+}

+ 14 - 0
core/App/Requirements/HandlerInterface.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Requirements;
+
+/**
+ * Description of RequirementInterface
+ *
+ * @author INBSX-37H
+ */
+interface HandlerInterface
+{
+
+    public function handleRequirements();
+}

+ 123 - 0
core/App/Requirements/Handlers/Files.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Requirements\Handlers;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Requirements\Instances\Files as FilesInstance;
+
+/**
+ * Description of Files
+ *
+ * @author INBSX-37H
+ */
+class Files implements \ModulesGarden\ProxmoxAddon\Core\App\Requirements\HandlerInterface
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    protected $fileList                = [];
+    protected $unfulfilledRequirements = [];
+
+    public function __construct(array $fileList = [])
+    {
+        $this->fileList = $fileList;
+
+        $this->handleRequirements();
+    }
+
+    public function handleRequirements()
+    {
+        foreach ($this->fileList as $record)
+        {
+            if (!$this->isValidPath($record[FilesInstance::PATH]))
+            {
+                continue;
+            }
+
+            $this->handleRequirement($record);
+        }
+    }
+
+    public function isValidPath($path)
+    {
+        if (stripos($path, FilesInstance::WHMCS_PATH) === 0 || stripos($path, FilesInstance::MODULE_PATH) === 0)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    protected function handleRequirement($record)
+    {
+        $filePath = $this->getFullPath($record[FilesInstance::PATH]);
+
+        switch ($record[FilesInstance::TYPE])
+        {
+            case FilesInstance::REMOVE:
+                $this->removeFile($filePath);
+                break;
+            case FilesInstance::IS_WRITABLE:
+                $this->checkIfWritable($filePath);
+                break;
+        }
+    }
+
+    public function getFullPath($recordPath = null)
+    {
+        if (stripos($recordPath, FilesInstance::WHMCS_PATH) === 0)
+        {
+            return str_replace(FilesInstance::WHMCS_PATH, \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getFullPathWhmcs(), str_replace('/', DIRECTORY_SEPARATOR, $recordPath));
+        }
+
+        if (stripos($recordPath, FilesInstance::MODULE_PATH) === 0)
+        {
+            return str_replace(FilesInstance::MODULE_PATH, \ModulesGarden\ProxmoxAddon\Core\ModuleConstants::getModuleRootDir(), str_replace('/', DIRECTORY_SEPARATOR, $recordPath));
+        }
+
+        return null;
+    }
+
+    protected function removeFile($filePath = null)
+    {
+        $fileValidator = new \ModulesGarden\ProxmoxAddon\Core\FileReader\PathValidator();
+        if (!$fileValidator->pathExists($filePath))
+        {
+            return null;
+        }
+
+        unlink($filePath);
+
+        if (!$fileValidator->pathExists($filePath))
+        {
+            return null;
+        }
+
+        $this->addUnfulfilledRequirement('In order for the module to work correctly, please remove the following file: :remove_file_requirement:', $filePath);
+    }
+
+    protected function checkIfWritable($filePath = null)
+    {
+        $fileValidator = new \ModulesGarden\ProxmoxAddon\Core\FileReader\PathValidator();
+        if ($fileValidator->isPathWritable($filePath))
+        {
+            return null;
+        }
+
+        $this->addUnfulfilledRequirement('In order for the module to work correctly, please set up permissions to the :remove_file_requirement: directory as writable.',
+            $filePath);
+    }
+
+    protected function addUnfulfilledRequirement($message = null, $path = null)
+    {
+        if ($message && $path)
+        {
+            $this->loadLang();
+
+            $this->unfulfilledRequirements[] = str_replace(':remove_file_requirement:', $path, $this->lang->absoluteTranslate('unfulfilledRequirement', $message));
+        }
+    }
+
+    public function getUnfulfilledRequirements()
+    {
+        return $this->unfulfilledRequirements;
+    }
+}

+ 26 - 0
core/App/Requirements/Instances/Files.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Requirements\Instances;
+
+/**
+ * Description of Files
+ *
+ * @author INBSX-37H
+ */
+abstract class Files implements \ModulesGarden\ProxmoxAddon\Core\App\Requirements\RequirementInterface
+{
+    const WHMCS_PATH          = ':WHMCS_PATH:';
+    const MODULE_PATH         = ':MODULE_PATH:';
+    const PATH                = 'path';
+    const TYPE                = 'type';
+    const REMOVE              = 'remove';
+    const EXISTS              = 'exists';
+    const IS_WRITABLE         = 'isWritable';
+    const IS_READABLE         = 'isReadable';
+    protected $fileList = [];
+
+    final public function getHandler()
+    {
+        return new \ModulesGarden\ProxmoxAddon\Core\App\Requirements\Handlers\Files($this->fileList);
+    }
+}

+ 13 - 0
core/App/Requirements/RequirementInterface.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\App\Requirements;
+
+/**
+ * Description of RequirementInterface
+ *
+ * @author INBSX-37H
+ */
+interface RequirementInterface
+{
+    
+}

+ 50 - 0
core/Bootstrap.php

@@ -0,0 +1,50 @@
+<?php
+
+use \ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use \ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use \ModulesGarden\ProxmoxAddon\Core\FileReader\PathValidator;
+use \ModulesGarden\ProxmoxAddon\Core\FileReader\File;
+use \ModulesGarden\ProxmoxAddon\Core\DependencyInjection\Builder;
+use \ModulesGarden\ProxmoxAddon\Core\DependencyInjection\Services;
+
+if (!defined('DS'))
+    define('DS', DIRECTORY_SEPARATOR);
+if (!defined('PS'))
+    define('PS', PATH_SEPARATOR);
+if (!defined('CRLF'))
+    define('CRLF', "\r\n");
+
+require_once dirname(__DIR__) . DS . "vendor" . DS . "autoload.php";
+
+/**
+ * Initialize base values
+ */
+ModuleConstants::initialize();
+
+/**
+ * Initailize DI builder
+ */
+new Builder();
+
+/**
+ * Initialize Services
+ */
+new Services();
+
+/**
+ * Check file permission
+ */
+// if (is_dir(ModuleConstants::getFullPath('storage')) === false || is_writable(ModuleConstants::getFullPath('storage')) === false || is_writable(ModuleConstants::getFullPath('storage', 'app')) === false || is_writable(ModuleConstants::getFullPath('storage', 'crons')) === false || is_writable(ModuleConstants::getFullPath('storage', 'logs')) === false)
+// {
+//     File::createPaths(
+//             ['full' => ModuleConstants::getFullPath('storage'), 'permission' => 0777], ['full' => ModuleConstants::getFullPath('storage', 'app'), 'permission' => 0777], ['full' => ModuleConstants::getFullPath('storage', 'crons'), 'permission' => 0777], ['full' => ModuleConstants::getFullPath('storage', 'logs'), 'permission' => 0777]
+//     );
+// }
+
+// $pathValidator = new PathValidator();
+// if (!$pathValidator->validatePath(ModuleConstants::getFullPath('storage', 'logs'), true, true, true))
+// {
+//     ServiceLocator::call('errorManager')->addError(
+//             'Bootstrap', PHP_EOL . ServiceLocator::call('lang')->absoluteT('permissionsStorage'), ['path' => ModuleConstants::getFullPath('storage')]
+//     );
+// }

+ 207 - 0
core/Cache/CacheManager.php

@@ -0,0 +1,207 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Cache;
+
+use Symfony\Component\Cache\Simple\FilesystemCache;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\Interfaces\CacheManagerInterface;
+
+/**
+ * Description of CacheManager
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class CacheManager implements CacheManagerInterface
+{
+    protected static $instance = null;
+
+    /**
+     * @var CacheManagerInterface
+     */
+    protected $manager;
+
+    /**
+     * @var string
+     */
+    protected $dir = '';
+
+    /**
+     * @var string
+     */
+    protected $namespace = '';
+
+    /**
+     * @var string
+     */
+    protected $key;
+
+    /**
+     * @var string|array|object
+     */
+    protected $data;
+
+    public function __construct()
+    {
+        $this->namespace = ModuleConstants::getRootNamespace();
+        $this->dir       = ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'framework';
+        $this->manager   = new FilesystemCache('', 0, $this->dir);
+    }
+
+    private function __clone()
+    {
+        
+    }
+
+    /**
+     * @param string $key
+     * @return $this
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function setKey($key = 'default')
+    {
+        try
+        {
+            $this->key = $key;
+            $this->setData($this->manager->get($this->key));
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getKey()
+    {
+        return $this->key;
+    }
+
+    /**
+     * @param mixed $data
+     * @return $this
+     */
+    public function setData($data = null)
+    {
+        if ($data !== null)
+        {
+            $this->data = $data;
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function getData()
+    {
+        try
+        {
+            if (isset($this->key))
+            {
+                $this->data = $this->manager->get($this->key);
+            }
+            return $this->data;
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+    }
+
+    /**
+     * @return $this
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function save()
+    {
+        try
+        {
+            $this->manager->set($this->key, $this->data);
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+        return $this;
+    }
+
+    /**
+     * @return $this
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function remove()
+    {
+        try
+        {
+            $this->manager->delete($this->key);
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+        return $this;
+    }
+
+    /**
+     * @return $this
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function clearAll()
+    {
+        try
+        {
+            $this->manager->clear();
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+        return $this;
+    }
+
+    /**
+     * @return type
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException
+     */
+    public function exist()
+    {
+        try
+        {
+            return $this->manager->has($this->key);
+        }
+        catch (\Exception $ex)
+        {
+            throw new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\MGModuleException(self::class, $ex->getMessage(), $ex->getCode(), $ex);
+        }
+    }
+
+    /**
+     * @return CacheManager
+     */
+    public static function instance()
+    {
+        if (CacheManager::$instance === null)
+        {
+            CacheManager::$instance = new CacheManager();
+        }
+        return CacheManager::$instance;
+    }
+
+    public static function cache($key = null)
+    {
+        $istance = self::instance();
+        if ($key)
+        {
+            $istance->setKey($key);
+        }
+
+        return $istance;
+    }
+}

+ 107 - 0
core/CommandLine/AbstractCommand.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Class Command
+ * @package ModulesGarden\ProxmoxAddon\Core\CommandLine
+ */
+class AbstractCommand extends \Symfony\Component\Console\Command\Command
+{
+    /**
+     * Command name
+     * @var string
+     */
+    protected $name = null;
+
+    /**
+     * Command description
+     * @var string
+     */
+    protected $description = '';
+
+    /**
+     * Command help text. Use --help to show
+     * @var string
+     */
+    protected $help = '';
+
+    /**
+     * minimal command configuration
+     */
+    protected function configure()
+    {
+        $this
+                ->setName($this->name)
+                ->setDescription($this->description)
+                ->setHelp($this->help)
+                ->addOption('force', 'f', InputOption::VALUE_OPTIONAL, 'Force script to run, without checking if another instance is running', false);
+
+        $this->setup();
+    }
+
+    /**
+     * Execute command
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        try
+        {
+            $this->beforeProcess($input, $output);
+
+            $this->process($input, $output, new SymfonyStyle($input, $output));
+
+            $this->afterProcess($input, $output);
+        }
+        catch (\Exception $ex)
+        {
+            (new SymfonyStyle($input, $output))->error($ex->getMessage());
+        }
+
+        return 0;
+    }
+
+    /**
+     * Add some custom actions here based on documentation  https://symfony.com/doc/current/console.html
+     */
+    protected function setup()
+    {
+        
+    }
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @param SymfonyStyle $io
+     */
+    protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
+    {
+        
+    }
+
+    /**
+     * Function will be called before executing "process" function
+     * @throws \Exception
+     */
+    protected function beforeProcess(InputInterface $input, OutputInterface $output)
+    {
+        
+    }
+
+    /**
+     * Function will be called after executing "process" function
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     */
+    protected function afterProcess(InputInterface $input, OutputInterface $output)
+    {
+        
+    }
+}

+ 47 - 0
core/CommandLine/AbstractCronController.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+/**
+ * Description of AbstractCronController
+ *
+ * @author Rafał
+ */
+if (class_exists(\Thread::class))
+{
+
+    class AbstractCronController extends \Thread
+    {
+        protected $className;
+        protected $cronManager;
+
+        public function setCronManager($cronManager)
+        {
+            $this->cronManager = $cronManager;
+
+            return $this;
+        }
+
+        public function setClassName($className)
+        {
+            $this->className = $className;
+
+            return $this;
+        }
+
+        protected function updatePid()
+        {
+            CronManager::updatePids($this->className);
+        }
+    }
+
+}
+else
+{
+
+    class AbstractCronController
+    {
+        
+    }
+
+}

+ 219 - 0
core/CommandLine/AbstractCronControllerWithoutThread.php

@@ -0,0 +1,219 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use \ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use \ModulesGarden\ProxmoxAddon\Core\Models\CronTabs;
+
+/**
+ * Description of AbstractCronController
+ *
+ * @author Rafał
+ */
+abstract class AbstractCronControllerWithoutThread
+{
+    protected $exit    = true;
+    protected $error   = false;
+    protected $isChild = false;
+    protected $className;
+    protected $child   = 0;
+    protected $childId;
+    protected $parentId;
+    protected $cronManager;
+    protected $parentUserId;
+    protected $parentGroupId;
+
+    public function setParentId($parentId)
+    {
+        $this->parentId = $parentId;
+
+        return $this;
+    }
+
+    public function setChildId($childId)
+    {
+        $this->childId = $childId;
+
+        return $this;
+    }
+
+    public function ischild($isChild)
+    {
+        $this->isChild = $isChild;
+
+        return $this;
+    }
+
+    public function setClassName($className)
+    {
+        $this->className = $className;
+
+        return $this;
+    }
+
+    protected function canRunScript()
+    {
+        $run = true;
+        foreach (CronTabs::get() as $cronTab)
+        {
+            if ($cronTab->name == CronTabs::DEFAULT_CRON_MANAGER_NAME && $cronTab->status == 'stop')
+            {
+                $run = false;
+            }
+            elseif ($cronTab->name == $this->className && $cronTab->status == 'stop')
+            {
+                $run = false;
+            }
+        }
+
+        return $run;
+    }
+
+    protected function updatePid()
+    {
+        if ($this->isChild)
+        {
+            CronManager::updatePids($this->className, $this->childId);
+            if ($this->parentGroupId === getmygid() && $this->parentUserId === getmyuid())
+            {
+                $this->removePid();
+            }
+        }
+
+        return $this;
+    }
+
+    protected function isExistPid()
+    {
+        if ($this->isChild)
+        {
+            return CronManager::existPids($this->className, $this->childId);
+        }
+
+        return $this->canRunScript();
+    }
+
+    protected function removePid()
+    {
+        if ($this->isChild)
+        {
+            CronManager::removePids($this->className, $this->childId);
+        }
+
+        return $this;
+    }
+
+    abstract public function run();
+
+    public function isChildrenCreate()
+    {
+        return false;
+    }
+
+    public function getChildrenCount()
+    {
+        return $this->child;
+    }
+
+    public function setCronManager($cronManager)
+    {
+        $this->cronManager = $cronManager;
+
+        return $this;
+    }
+
+    public function getIndexElements()
+    {
+        $childrenListIds = [];
+
+        for ($key = 1; $key <= $this->getChildrenCount(); $key++)
+        {
+            $childrenListIds[] = $key;
+        }
+
+        return $childrenListIds;
+    }
+
+    public function start()
+    {
+        try
+        {
+            if ($this->isStart() === false)
+            {
+                if ($this->child > 0 && $this->isChild === false)
+                {
+                    while ($this->canRunScript())
+                    {
+                        if ($this->isChildrenCreate())
+                        {
+                            $parentId  = getmypid();
+                            $parentUId = $this->parentUserId ? $this->parentUserId : getmyuid();
+                            $parentGId = $this->parentGroupId ? $this->parentGroupId : getmygid();
+                            foreach ($this->getIndexElements() as $key)
+                            {
+                                if ($this->cronManager->isChildRunning($this->className, $key))
+                                {
+                                    continue;
+                                }
+
+                                $phpInterpreter       = \PHP_BINARY ?: 'php';
+                                $internalCronDumpFile = ModuleConstants::getFullPath('storage', 'crons', 'cronLog');
+                                exec($phpInterpreter . " " . ModuleConstants::getModuleRootDir() . DS . 'cron' . DS . 'cron.php ' . $this->className
+                                        . " --parent {$parentId} --parentuid {$parentUId} --parentgid {$parentGId} --child {$key} >> {$internalCronDumpFile} &");
+                            }
+                        }
+                        $this->wait(4000000);
+                    }
+                }
+                elseif ($this->child === 0 && $this->isChild === false)
+                {
+                    $this->exit = false;
+                    $this->run();
+                }
+                elseif ($this->isChild === true)
+                {
+                    $this->exit = false;
+                    $this->run();
+                    $this->removePid();
+                    exit;
+                }
+            }
+        }
+        catch (\Exception $ex)
+        {
+            $this->error = $ex->getMessage();
+        }
+    }
+
+    public function setParentGroupId($parentGroupId)
+    {
+        $this->parentGroupId = $parentGroupId;
+
+        return $this;
+    }
+
+    public function setParentUserId($parentUserId)
+    {
+        $this->parentUserId = $parentUserId;
+
+        return $this;
+    }
+
+    public function isStart()
+    {
+        return ($this->exit === false);
+    }
+
+    public function wait($seconds = 500000)
+    {
+        usleep($seconds);
+    }
+
+    function __destruct()
+    {
+        if ($this->error)
+        {
+            CronTabs::ifName($this->className)->addError($this->error);
+        }
+    }
+}

+ 59 - 0
core/CommandLine/Application.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
+use Symfony\Component\DependencyInjection\Tests\Compiler\DInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Application extends \Symfony\Component\Console\Application
+{
+    protected $dir = '';
+
+    /**
+     * @override
+     */
+    public function run(InputInterface $input = null, OutputInterface $output = null)
+    {
+        $this->loadCommandsControllers($this->getCommands());
+
+        parent::run($input, $output);
+    }
+
+    /**
+     * Get all available files
+     * @return array
+     */
+    protected function getCommands()
+    {
+        $files    = glob(ModuleConstants::getFullPath('app', $this->dir) .DIRECTORY_SEPARATOR. '*.php');
+        $commands = [];
+
+        foreach ($files as $file)
+        {
+            $file = substr($file, strrpos($file, DIRECTORY_SEPARATOR) + 1);
+            $file = substr($file, 0, strrpos($file, '.'));
+
+            $commands[] = $file;
+        }
+
+        return $commands;
+    }
+
+    /**
+     * Create new objects and add it
+     * @param $commands
+     */
+    protected function loadCommandsControllers($commands)
+    {
+        $this->dir = str_replace('/', DIRECTORY_SEPARATOR, $this->dir);
+        foreach ($commands as $command)
+        {
+            $class = ModuleConstants::getRootNamespace() . '\App\\' . $this->dir . '\\' . $command;
+
+            $this->add(new $class);
+        }
+    }
+}

+ 54 - 0
core/CommandLine/Command.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Class Command
+ * @package ModulesGarden\ProxmoxAddon\Core\CommandLine
+ */
+class Command extends AbstractCommand
+{
+
+    /**
+     *
+     */
+    final protected function configure()
+    {
+        parent::configure();
+    }
+
+    /**
+     * Execute command
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     */
+    final protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        return parent::execute($input, $output, new SymfonyStyle($input, $output));
+    }
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @throws \Exception
+     */
+    protected function beforeProcess(InputInterface $input, OutputInterface $output)
+    {
+        (new Hypervisor($this->getName(), $input->getOptions()))
+                ->lock();
+    }
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     */
+    protected function afterProcess(InputInterface $input, OutputInterface $output)
+    {
+        (new Hypervisor($this->getName(), $input->getOptions()))
+                ->unlock();
+    }
+}

+ 86 - 0
core/CommandLine/CommandLoop.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class CommandLoop extends AbstractCommand
+{
+    /**
+     * Loop interval in seconds
+     * @var int
+     */
+    protected $loopInterval = 5;
+
+    /**
+     * Loop counter
+     * @var int
+     * @TODO remove me
+     */
+    private $loopCounter = 0;
+
+    /**
+     *
+     */
+    final protected function configure()
+    {
+        parent::configure();
+    }
+
+    /**
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     * @return int|null|void
+     * @throws \Exception
+     */
+    final protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $hypervisor = new Hypervisor($this->getName(), $input->getOptions());
+
+        /**
+         * Lock command in database. We want to run only one command in the same time
+         */
+        $hypervisor->lock();
+
+        do
+        {
+            /**
+             * Let's do something funny!
+             */
+            parent::execute($input, $output);
+
+            /**
+             * Ping... Pong... Ping...
+             */
+            $hypervisor->ping();
+
+            /**
+             * Time to sleep!
+             */
+            $hypervisor->sleep($this->loopInterval);
+
+            /**
+             * @TODO remove me
+             */
+            $this->loopCounter++;
+        }
+        while (!$hypervisor->shouldBeStopped());
+
+        /**
+         * Unlock command in database
+         */
+        $hypervisor->unlock();
+    }
+
+    /**
+     * @return int
+     */
+    final protected function getLoopCounter()
+    {
+        return $this->loopCounter;
+    }
+}
+
+//

+ 12 - 0
core/CommandLine/CommandManager.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
+use Symfony\Component\DependencyInjection\Tests\Compiler\DInterface;
+
+class CommandManager extends Application
+{
+    
+}

+ 8 - 0
core/CommandLine/CronManager.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+class CronManager extends Application
+{
+    protected $dir = 'Cron';
+}

+ 348 - 0
core/CommandLine/CronManager.php_old

@@ -0,0 +1,348 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Validator;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\File;
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\ReaderCronTask;
+use ModulesGarden\ProxmoxAddon\Core\Models\CronTabs;
+
+/**
+ * Description of CronManager
+ *
+ * @author Rafał
+ */
+class CronManager {
+    
+    const AVAILABLE_MODES = ['cli', 'cgi-fcgi'];
+    
+    protected $arguments = [];
+    
+    protected $appCronPath = '';
+    
+    protected $coreCronPath = '';
+    
+    protected $parentUId;
+    
+    protected $parentGId;
+    
+    protected $cronPids = '';
+    
+    protected $isParent = true;
+    
+    protected $parentId;
+    
+    protected $childId;
+
+    /**
+     * @var Validator
+     */
+    protected $fileValidator;
+    
+    public function __construct(Validator $fileValidator) {
+        $this->fileValidator = $fileValidator;
+        $this->appCronPath   = ModuleConstants::getModuleRootDir() . DS . 'app' . DS . 'Cron' . DS;
+        $this->coreCronPath  = ModuleConstants::getModuleRootDir() . DS . 'core' . DS . 'Cron' . DS;
+        $this->cronPidsPath  = ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS;
+        
+        if($this->fileValidator->isExist($this->cronPidsPath) === false)
+        {
+            mkdir($this->cronPidsPath);
+            File::setPermission($this->cronPidsPath);
+        }
+
+        if (self::isThread())
+        {
+            $this->appCronPath  .= "Thread" . DS;
+            $this->coreCronPath .= "Thread" . DS;
+        }
+        else 
+        {
+            $this->appCronPath  .= "WithoutThread" . DS;
+            $this->coreCronPath .= "WithoutThread" . DS;
+        }
+        
+        $this->parentGId = getmygid();
+        $this->parentUId = getmyuid();
+    }
+    
+    public function setArgv($arguments)
+    {
+        $this->arguments = $arguments;
+        
+        return $this;
+    }
+    
+    public function execute()
+    {
+        $this->loadArguments();
+        
+        if($this->isInCliMode() === false)
+        {
+            return $this;
+        }
+        if (count($this->arguments) == 0)
+        {
+            $this->arguments = ReaderCronTask::get();
+        }
+        
+        $this->loadDefaultCronManager();
+        foreach ($this->arguments as $className)
+        {
+            $className = trim(trim($className, '--'), '-');
+            $class = ModuleConstants::getRootNamespace() . "\\";
+            if ($this->fileValidator->isExist($this->appCronPath . $className . ".php"))
+            {
+                $class .= "App\Cron\\" . $this->getType() . "\\" . $className;
+                $this->runTask($class, $className);
+            }
+            elseif ($this->fileValidator->isExist($this->coreCronPath . $className . ".php"))
+            {
+                $class .= "Core\Cron\\" . $this->getType() . "\\" . $className;
+                $this->runTask($class, $className);
+            }
+        }
+        
+        return $this;
+    }
+    
+    protected function runTask($class, $className)
+    {
+        if ($this->classExist($class) === true && $this->isCronRunning($className) === false)
+        {
+            if ($this->beforeRunTask($className) === false)
+            {
+                return;
+            }
+            
+            $instant = DependencyInjection::create($class, null, (class_exists(\Thread::class)?false:true));
+            $instant->setCronManager($this);
+            $instant->setClassName($className);
+            if (self::isThread() === false)
+            {
+                $instant->ischild(!$this->isParent)
+                        ->setChildId($this->childId)
+                        ->setParentId($this->parentId)
+                        ->setParentGroupId($this->parentGId)
+                        ->setParentUserId($this->parentUId);
+
+            }
+            $instant->start();
+            unset($instant);
+            $this->afterRunTask($className);
+        }
+    }
+    
+    protected function getType()
+    {
+        return (self::isThread()?'Thread':'WithoutThread');
+    }
+    
+    public static function isThread()
+    {
+        return class_exists(\Thread::class);
+    }
+    
+    protected function loadArguments()
+    {
+        $deleteClass = [];
+        foreach ($this->arguments as $key => $name) {
+            if (strpos($name, 'cron.php') !== false)
+            {
+                $deleteClass[] = $key; 
+            }
+            elseif ($name === '--parent')
+            {
+                $this->parentId = $this->arguments[$key + 1];
+                $this->isParent = false;
+                $deleteClass[] = $key; 
+                $deleteClass[] = $key + 1; 
+            }
+            elseif ($name === '--child')
+            {
+                $this->childId = $this->arguments[$key + 1];
+                $this->isParent = false;
+                $deleteClass[] = $key; 
+                $deleteClass[] = $key + 1; 
+            }
+            elseif ($name === '--parentuid')
+            {
+                $this->parentUId  = $this->arguments[$key + 1];
+                $deleteClass[]  = $key;
+                $deleteClass[]  = $key + 1;
+            }
+            elseif ($name === '--parentgid')
+            {
+                $this->parentGId  = $this->arguments[$key + 1];
+                $deleteClass[]  = $key;
+                $deleteClass[]  = $key + 1;
+            }
+        }
+        
+        foreach ($deleteClass as $clasKey)
+        {
+            unset($this->arguments[$clasKey]);
+        }
+        
+        return $this;
+    }
+    
+    protected function classExist($name)
+    {
+        return class_exists($name);
+    }
+    
+    public static function updatePids($name, $id = '')
+    {
+        touch(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
+    }
+    
+    public static function removePids($name, $id = '')
+    {
+        unlink(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
+    }
+    
+    public static function existPids($name, $id = '')
+    {
+        return file_exists(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
+    }
+
+
+    public function createPids($file)
+    {
+        return file_put_contents($file, '<?php die(); '.getmypid());
+    }
+    
+    public function isChildRunning($name, $id)
+    {
+        $file = $this->cronPidsPath . $name. 'Pid' . $id . '.php';
+        
+        if($this->fileValidator->isExist($file) && filemtime($file) > time() - 7200)
+        {
+            return true;
+        }
+        return false;
+    }
+    
+    private function isCronRunning($name)
+    {
+        if ($this->isParent === false)
+        {
+            $file = $this->cronPidsPath . $name. 'Pid' . $this->childId . '.php';
+        
+            if($this->fileValidator->isExist($file) && filemtime($file) > time() - 7200)
+            {
+                return true;
+            }
+
+            if($this->createPids($file) === false)
+            {
+                return true;
+            }
+        }
+        
+        return false;
+        
+        /**
+        if(PHP_OS == "Linux")
+        {
+            $out = null;
+            exec("ps aux | grep 'php -q.*modules/addons/AdvancedBilling/cron/cron.php$' | grep -v 'grep'", $out);
+            if(count($out) > 1)
+            {
+                return true;
+            }
+        }
+        **/
+    }
+
+    
+    private function isInCliMode()
+    { 
+        if (in_array(php_sapi_name(), self::AVAILABLE_MODES)) 
+        {
+            return true;
+        } 
+        else 
+        {
+            return false;
+        }
+    }
+    
+    protected function loadDefaultCronManager()
+    {
+        $defaultCron = CronTabs::ifDefaultCronManager()->first();
+        if ($this->isParent && $defaultCron && $defaultCron->status != 'start')
+        {
+            CronTabs::ifDefaultCronManager()->updateStatus('start');
+            unset($defaultCron);
+        }
+        elseif ($this->isParent && !$defaultCron)
+        {
+            CronTabs::create([
+                'name'   => CronTabs::DEFAULT_CRON_MANAGER_NAME,
+                'status' => 'start',
+                'params' => [],
+                'errors' => []
+            ]);
+        }
+        
+        return $this;
+    }
+    
+    protected function beforeRunTask($className)
+    {
+        if ($this->isParent)
+        {
+            if (CronTabs::ifDefaultCronManager()->getQuery()->first()->status == 'stop')
+            {
+                exit;
+            }
+            $cronTask = CronTabs::ifName($className)->getQuery()->first();
+            if ($cronTask && $cronTask->status != 'stop')
+            {
+                return false;
+            }
+
+            $params = [
+                'parentId'  => getmypid(),
+                'parentUId' => $this->parentUId,
+                'parentGId' => $this->parentGId
+            ];
+            if ($cronTask)
+            {
+                CronTabs::ifName($className)->updateStatus('start', $params);
+            }
+            else
+            {
+                CronTabs::create([
+                    'name'   => $className,
+                    'status' => 'start',
+                    'params' => $params,
+                    'errors' => []
+                ]);
+            }
+
+            usleep(500000);
+            $cronTask = (new CronTabs())->ifName($className)->getQuery()->first();
+            if ($this->isParent && json_decode($cronTask->params)->parentId != getmypid())
+            {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    protected function afterRunTask($className)
+    {
+        if ($this->isParent)
+        {
+            CronTabs::ifName($className)->updateStatus();
+        }
+        
+        return $this;
+    }
+}

+ 258 - 0
core/CommandLine/Hypervisor.php

@@ -0,0 +1,258 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\Models\Commands;
+
+/**
+ * We need that for calculating interval between operations and for killing process for PHP < 7.1
+ * Don't remove it!
+ */
+declare(ticks = 1);
+
+/**
+ * Class Hypervisor
+ * @package ModulesGarden\ProxmoxAddon\Core\CommandLine
+ * @todo - clean up!
+ */
+class Hypervisor
+{
+    /**
+     * command name
+     * @var null
+     */
+    protected $name = null;
+
+    /**
+     * command params
+     * @var null
+     */
+    protected $params = null;
+
+    /**
+     * @var int
+     */
+    protected $counter = 0;
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\Models\Commands
+     */
+    protected $entity   = null;
+    /*
+     * 10 min.
+     */
+    protected $tickTime = 120;
+
+    /**
+     * @var int
+     */
+    protected $sleepInterval = 5;
+
+    /**
+     * Hypervisor constructor.
+     * @param $name
+     * @param $params
+     */
+    public function __construct($name, $params)
+    {
+        $this->name   = $name;
+        $this->params = $params;
+    }
+
+    /**
+     * Lock script
+     * @return $this
+     * @throws \Exception
+     */
+    public function lock()
+    {
+        if ($this->isActive())
+        {
+            throw new \Exception('Cannot create lock. Command already running');
+        }
+
+        $this->registerTickHandler();
+        //not working since 1.7.0
+        //$this->registerSignalHandler();
+
+        $this->getEntity()->setRunning();
+
+        return $this;
+    }
+
+    /**
+     * Unlock script
+     * @return $this
+     */
+    public function unlock()
+    {
+        $this->getEntity()
+                ->setStopped()
+                ->clearAction();
+
+        return $this;
+    }
+
+    /**
+     * Update information about running command
+     * @return $this
+     */
+    public function ping()
+    {
+        $this->getEntity()
+                ->ping();
+
+        return $this;
+    }
+
+    /**
+     * @param $interval
+     * @return $this
+     */
+    public function sleep($interval)
+    {
+        $this->getEntity()
+                ->setSleeping();
+
+        /**
+         * We need run tick function during sleep, so we run many sleep function in loop
+         */
+        $counter = 0;
+        while ($counter < $interval)
+        {
+            if ($interval <= $this->sleepInterval)
+            {
+                $time = $interval;
+            }
+            else
+            {
+                $time = $this->sleepInterval;
+            }
+
+
+            $counter += $time;
+
+            sleep($time);
+        }
+
+
+        $this->getEntity()
+                ->setRunning();
+
+        return $this;
+    }
+
+    public function shouldBeStopped()
+    {
+        return $this->getEntity()
+                        ->shouldBeStopped();
+    }
+
+    /**
+     * @return bool
+     * @throws \Exception
+     */
+    public function checkIfRunning()
+    {
+        if (!$this->isRunning())
+        {
+            throw new \Exception('Script is not running');
+        }
+
+        return true;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isStopped()
+    {
+        $this->getEntity()
+                ->shouldBeStopped();
+    }
+
+    /**
+     * Returns true if command is running or sleeping
+     * @return bool
+     */
+    public function isActive()
+    {
+        $entity = $this->getEntity();
+        $diff   = time() - $entity->updated_at->timestamp;
+
+        if ($entity->isSleeping() && $diff <= $this->sleepInterval)
+        {
+            return true;
+        }
+
+        if ($entity->isRunning() && $entity->updated_at->timestamp + $this->tickTime > time())
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param bool $force
+     * @return ModulesGarden\ProxmoxAddon\Core\Models\Commands|Commands
+     */
+    protected function getEntity($force = false)
+    {
+        if ($this->entity && $force === false)
+        {
+            return $this->entity;
+        }
+
+        $this->entity = Commands::where('name', $this->name)->first();
+
+        if (!$this->entity)
+        {
+            $this->entity       = new Commands();
+            $this->entity->name = $this->name;
+            $this->entity->uuid = uniqid();
+            $this->entity->save();
+        }
+
+        return $this->entity;
+    }
+
+    /**
+     *
+     */
+    protected function registerTickHandler()
+    {
+        $tick = new Tick(function()
+        {
+            $this->ping();
+        }, 10);
+        $tick->start();
+    }
+
+    /**
+     * Mark process as stopped and exit. This function will be called when you press ctrl c from console
+     */
+    protected function registerSignalHandler()
+    {
+        if (!function_exists('pcntl_signal'))
+        {
+            return;
+        }
+
+        //works from PHP 7.1
+        if (function_exists('pcntl_async_signals'))
+        {
+            pcntl_async_signals(true);
+        }
+
+        $exit = function()
+        {
+            $this->getEntity()->setStopped();
+            exit;
+        };
+
+        pcntl_signal(SIGINT, $exit);
+        pcntl_signal(SIGHUP, $exit);
+        pcntl_signal(SIGUSR1, $exit);
+    }
+}

+ 8 - 0
core/CommandLine/Job.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+class Job
+{
+    
+}

+ 89 - 0
core/CommandLine/JobsLoop.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\CommandLine\AbstractCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+
+class JobsLoop extends AbstractCommand
+{
+    /**
+     * Loop interval in seconds
+     * @var int
+     */
+    protected $loopInterval = 5;
+
+    /**
+     * @var int
+     */
+    protected $maxChildren = 5;
+
+    /**
+     * @var int
+     */
+    protected $tasksPerChildren = 10;
+
+    /**
+     *
+     */
+    final protected function configure()
+    {
+        parent::configure();
+    }
+
+    /**
+     *
+     */
+    final protected function setup()
+    {
+        $this->addOption('parent', InputOption::VALUE_OPTIONAL, 'Parent id', null);
+        $this->addOption('id', InputOption::VALUE_OPTIONAL, 'Child id', null);
+    }
+
+    /**
+     * Execute command in the loop
+     * @param InputInterface $input
+     * @param OutputInterface $output
+     */
+    final protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $parent          = $input->getOption('parent');
+        $id              = $input->getOption('id');
+        $parentHypervior = new Hypervisor($this->getName());
+
+        //Lock parent
+        if (!$parent)
+        {
+            $parentHypervior->lock();
+        }
+        else
+        {
+            $parentHypervior->checkIfRunning();
+        }
+
+        if ($id)
+        {
+            
+        }
+
+        do
+        {
+            parent::process($input, $output, new SymfonyStyle($input, $output));
+
+            sleep($this->loopInterval);
+        }
+        while (!$hypervisor->isStopped());
+    }
+
+    /**
+     * @param $jobs[]
+     */
+    protected function processJobs($jobs)
+    {
+        foreach ($jobs as $job)
+        {
+            
+        }
+    }
+}

+ 63 - 0
core/CommandLine/ReaderCronTask.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of AbstractReaderYml
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class ReaderCronTask
+{
+    /**
+     * @var array
+     */
+    protected $data = [];
+
+    public function __construct()
+    {
+        if (count($this->data) == 0)
+        {
+            $this->load();
+        }
+    }
+
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    protected function readYml($name)
+    {
+        return Reader::read($name)->get();
+    }
+
+    public static function get()
+    {
+
+        $instance = new static;
+        return $instance->getData();
+    }
+
+    protected function load()
+    {
+        $this->data = $this->rebuildData($this->readYml(ModuleConstants::getFullPath('app', 'Config', 'cron.yml')));
+    }
+
+    protected function rebuildData($data)
+    {
+        $return = [];
+        foreach ($data['list'] as $name => $isRun)
+        {
+            if ((bool) $isRun)
+            {
+                $return[] = $name;
+            }
+        }
+
+        return $return;
+    }
+}

+ 71 - 0
core/CommandLine/Tick.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;
+
+/**
+ * Count time between callback time. It requires declare(ticks = 1);
+ * @package ModulesGarden\ProxmoxAddon\Core\CommandLine
+ */
+class Tick
+{
+    /**
+     * @var callable
+     */
+    protected $callback = null;
+
+    /**
+     * @var int
+     */
+    protected $callbackTime = 0;
+
+    /**
+     * @var int
+     */
+    protected $callbackInterval = 5;
+
+    /**
+     * Tick constructor.
+     * @param $callback
+     * @param $time
+     */
+    public function __construct($callback, $time)
+    {
+        $this->callback     = $callback;
+        $this->callbackTime = $time;
+    }
+
+    /**
+     *
+     */
+    public function start()
+    {
+        register_tick_function(function()
+        {
+            $this->tick();
+        }, true);
+    }
+
+    /**
+     * @return int
+     */
+    public function getTimeFromLastCallback()
+    {
+        return time() - $this->callbackTime;
+    }
+
+    /**
+     * Call callback if time diffeence is
+     */
+    public function tick()
+    {
+        $difference = $this->getTimeFromLastCallback();
+
+        if ($difference > $this->callbackInterval)
+        {
+            $this->callbackTime = time();
+
+            $callback   = $this->callback;
+            $callback();
+        }
+    }
+}

+ 9 - 0
core/Config/configuration.yml

@@ -0,0 +1,9 @@
+debug: true
+version: '1.0.0'
+systemName: 'proxmoxAddon'
+name: 'ProxmoxAddon'
+description: 'Modules Garden Module Framework.<br>For more info visit our <a href="http://www.docs.modulesgarden.com/CHANGE_ME" style="color: #4169E1;" target="_blank">Wiki</a>.'
+clientareaName: 'MG Demo'
+author: '<a href="http://www.modulesgarden.com" targer="_blank">ModulesGarden</a>'
+fields: []
+moduleIcon: 'crm'

+ 1 - 0
core/Config/cron.yml

@@ -0,0 +1 @@
+class:

+ 6 - 0
core/Config/di/buildWithDefaultMethod.yml

@@ -0,0 +1,6 @@
+class:
+    - {name: "\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\Request", method: "build",                        args: []}
+    - {name: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\View\\Smarty',  method: "get",                    args: [NULL,NULL]}
+    - {name: '\\ModulesGarden\\ProxmoxAddon\\Core\\HandlerError\\Logger',  method: "get",                  args: ["default","debug.log","warning.log","error.log"]}
+    - {name: '\\ModulesGarden\\ProxmoxAddon\\Core\\Cache\\CacheManager',  method: "cache",                 args: []}
+    - {name: '\\ModulesGarden\\ProxmoxAddon\\Core\\RegisterManager\\Register',  method: "getInstace",      args: []}

+ 1 - 0
core/Config/di/interface.yml

@@ -0,0 +1 @@
+interface: []

+ 28 - 0
core/Config/di/register.yml

@@ -0,0 +1,28 @@
+class:
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Config\\After',            alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Activate\\After',          alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Deactivate\\After',        alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Update\\After',            alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Config\\Before',           alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Activate\\Before',         alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Deactivate\\Before',       alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Update\\Before',           alias: "", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\App\\Controllers\\Instances\\Addon\\Config', alias: "configurationAddon", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Data',           alias: "configurationData", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Lang\\Lang',                               alias: "lang", singleton: false ,auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\View\\Smarty',                       alias: "smarty", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\HandlerError\\Logger',                     alias: "logger", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\HandlerError\\ErrorManager',         alias: "errorManager", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\Request',                           alias: "request", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\UI\\View',                                   alias: "view", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\UI\\ViewAjax',                           alias: "viewAjax", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\AbstractController',             alias: "controller", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\AbstractClientController', alias: "clientController", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Http\\AbstractHooksClient',alias: "clientHookController", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Configuration\\Addon\\Update\\PatchManager',alias: "patchManager",singleton: true ,auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\HandlerError\\WhmcsLogsHandler',      alias: "whmcsLogger", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Logger\\Entity',                     alias: "entityLogger", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\CommandLine\\CronManager',            alias: "cronManager", singleton: true , auto: true }
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\RegisterManager\\Register',              alias: "register", singleton: true , auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\UI\\Widget\\Sidebar\\SidebarService',    alias: "sidebar", singleton: true ,auto: false}
+    - {namespace: '\\ModulesGarden\\ProxmoxAddon\\Core\\Helper\\WhmcsParams',                 alias: "whmcsParams", singleton: false ,auto: false}

+ 8 - 0
core/Config/di/services.php

@@ -0,0 +1,8 @@
+<?php
+
+return [
+    \ModulesGarden\ProxmoxAddon\Core\Lang\Lang::class,
+    \ModulesGarden\ProxmoxAddon\Core\Events\Dispatcher::class,
+    \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon\Config::class,
+    \ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarService::class
+];

+ 4 - 0
core/Config/di/services.yml

@@ -0,0 +1,4 @@
+- \ModulesGarden\ProxmoxAddon\Core\Lang\Lang
+- \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon\Config
+- \ModulesGarden\ProxmoxAddon\Core\UI\Widget\Sidebar\SidebarService
+- \ModulesGarden\ProxmoxAddon\Core\Helper\WhmcsParams

+ 19 - 0
core/Configuration/Addon/AbstractAfter.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon;
+
+/**
+ * Description of AbstractAfter
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+abstract class AbstractAfter
+{
+
+    public function __construct()
+    {
+        
+    }
+
+    abstract public function execute(array $params = []);
+}

+ 17 - 0
core/Configuration/Addon/AbstractBefore.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon;
+
+/**
+ * Description of AbstractBefore
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+abstract class AbstractBefore
+{
+
+    public function __construct()
+    {
+        
+    }
+}

+ 23 - 0
core/Configuration/Addon/Activate/After.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractAfter;
+
+/**
+ * Runs after module activation actions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends AbstractAfter
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        return $params;
+    }
+}

+ 35 - 0
core/Configuration/Addon/Activate/Before.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Activate;
+
+use \ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractBefore;
+use \ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use \ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Runs before module activation actions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends AbstractBefore
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        $path = ModuleConstants::getModuleRootDir() . DS . 'storage';
+
+        if (is_writable($path) === false || is_readable($path) === false)
+        {
+            $params['status']      = 'error';
+            $params['description'] .= PHP_EOL . ServiceLocator::call('lang')
+                            ->addReplacementConstant('storage_path', ModuleConstants::getFullPath('storage'))
+                            ->absoluteT('permissionsStorage');
+        }
+
+        return $params;
+    }
+}

+ 24 - 0
core/Configuration/Addon/Config/After.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractAfter;
+
+/**
+ * Runs after loading module configuration
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends AbstractAfter
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+
+        return $params;
+    }
+}

+ 23 - 0
core/Configuration/Addon/Config/Before.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Config;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractBefore;
+
+/**
+ * Runs before loading module configuration
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends AbstractBefore
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+        return $params;
+    }
+}

+ 24 - 0
core/Configuration/Addon/Deactivate/After.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractAfter;
+
+/**
+ * Runs after addon deactivation
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends AbstractAfter
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+
+        return $params;
+    }
+}

+ 24 - 0
core/Configuration/Addon/Deactivate/Before.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Deactivate;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractBefore;
+
+/**
+ * Runs before addon deactivation
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends AbstractBefore
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+
+        return $params;
+    }
+}

+ 24 - 0
core/Configuration/Addon/Update/After.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractAfter;
+
+/**
+ * runs after module update actions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class After extends AbstractAfter
+{
+
+    /**
+     * @param array $params
+     * @return array
+     */
+    public function execute(array $params = [])
+    {
+
+        return $params;
+    }
+}

+ 22 - 0
core/Configuration/Addon/Update/Before.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update;
+
+use ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\AbstractBefore;
+
+/**
+ * runs after module update actions
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Before extends AbstractBefore
+{
+
+    /**
+     * @return array
+     */
+    public function execute($version)
+    {
+        return [];
+    }
+}

+ 73 - 0
core/Configuration/Addon/Update/Patch/AbstractPatch.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update\Patch;
+
+use ModulesGarden\ProxmoxAddon\Core\Helper\DatabaseHelper;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of AbstractPatch
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class AbstractPatch
+{
+    /**
+     * @var DatabaseHelper 
+     */
+    protected $databaseHelper;
+
+    /**
+     * @var string
+     */
+    private $path;
+
+    /**
+     * @var string
+     */
+    private $versionName;
+    protected $version;
+
+    /**
+     * @param DatabaseHelper $databaseHelper
+     */
+    public function __construct(DatabaseHelper $databaseHelper)
+    {
+        $this->databaseHelper = $databaseHelper;
+        $this->path           = ModuleConstants::getModuleRootDir() . DS . 'app' . DS . 'Database';
+        $this->versionName    = end(explode("\\", get_called_class()));
+    }
+
+    /**
+     * @return bool
+     */
+    protected function runSchema()
+    {
+        return ($this->databaseHelper->performQueryFromFile($this->path . DS . $this->versionName . DS . 'schema.sql') === true) ? false : true;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function runData()
+    {
+        return ($this->databaseHelper->performQueryFromFile($this->path . DS . $this->versionName . DS . 'data.sql') === true) ? false : true;
+    }
+
+    public function setVersion($version = null)
+    {
+        $this->version = $version;
+
+        return $this;
+    }
+
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    public function execute()
+    {
+        
+    }
+}

+ 101 - 0
core/Configuration/Addon/Update/PatchManager.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Addon\Update;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of PatchManager
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class PatchManager
+{
+    protected $updatePath;
+    protected $updateFiles = [];
+
+    public function __construct()
+    {
+        $this->updatePath = ModuleConstants::getModuleRootDir() . DS . "app" . DS . "Configuration" . DS . "Addon" . DS . "Update" . DS . "Patch";
+        $this->loadUpdatePath();
+    }
+
+    public function run($newVersion, $oldVersion)
+    {
+        $fullPath = $this->getUpdatePath();
+        array_map(function ($version, $fileName) use($newVersion, $oldVersion, $fullPath)
+        {
+            if ($this->checkVersion($newVersion, $oldVersion, $version))
+            {
+                try
+                {
+                    $className = ModuleConstants::getRootNamespace() . "\App\Configuration\Addon\Update\Patch\\" . $fileName;
+                    if(method_exists($className,'setVersion')){
+                        DependencyInjection::create($className)->setVersion($version)->execute();
+                    }
+
+                }
+                catch (\Exception $ex)
+                {
+                    ServiceLocator::call("errorManager")
+                            ->addError(
+                                    self::class, $ex->getMessage(), [
+                                "newVersion"    => $newVersion,
+                                "oldVersion"    => $oldVersion,
+                                "updateVersion" => $version,
+                                "fullFileName"  => $fullPath . DS . $fileName . ".php"
+                                    ]
+                    );
+                }
+            }
+        }, array_keys($this->getUpdateFiles()), $this->getUpdateFiles()
+        );
+
+        return $this;
+    }
+
+    protected function checkVersion($newVersion, $oldVersion, $fileVersion)
+    {
+        if (version_compare($oldVersion, $fileVersion, "<"))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    protected function getUpdatePath()
+    {
+        return $this->updatePath;
+    }
+
+    protected function getUpdateFiles()
+    {
+        return $this->updateFiles;
+    }
+
+    protected function loadUpdatePath()
+    {
+        $files = scandir($this->getUpdatePath(), 1);
+
+        if (count($files) != 0)
+        {
+            foreach ($files as $file)
+            {
+                if ($file !== "." && $file !== "..")
+                {
+                    $version                     = $this->generateVersion($file);
+                    $this->updateFiles[$version] = explode(".", $file)[0];
+                }
+            }
+        }
+    }
+
+    protected function generateVersion($fileName)
+    {
+        $name = explode('.', $fileName)[0];
+        return str_replace(["M", "P"], ".", substr($name, 1));
+    }
+}

+ 122 - 0
core/Configuration/Data.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration;
+
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Description of Data
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Data
+{
+    protected static $data = [];
+
+    public function __construct()
+    {
+        $this->load();
+    }
+
+    public function __get($name)
+    {
+        $this->load();
+        if (array_key_exists(lcfirst($name), self::$data))
+        {
+            return self::$data[lcfirst($name)];
+        }
+        return null;
+    }
+
+    public function getAll()
+    {
+        return self::$data;
+    }
+
+    private function load()
+    {
+        if (count(self::$data) == 0)
+        {
+            $this->loadConfig();
+        }
+    }
+
+    /**
+     * Loads YML configuration files
+     */
+    private function loadConfig()
+    {
+        $dataDev  = $this->read(ModuleConstants::getDevConfigDir() . DS . 'configuration.yml');
+        $dataCore = $this->read(ModuleConstants::getCoreConfigDir() . DS . 'configuration.yml');
+
+        $data = $this->parseConfigData($dataDev, $dataCore);
+
+        $this->loadPackageConfig($data);
+
+        self::$data = $data ?: [];
+    }
+
+    /**
+     * @param self::$data $data
+     *
+     * overwrites module version and wiki url basing on moduleVersion.php file,
+     * this file is added automatically during package creation
+     */
+    private function loadPackageConfig(&$data)
+    {
+        if (file_exists(ModuleConstants::getModuleRootDir() . DS . 'moduleVersion.php'))
+        {
+            include ModuleConstants::getModuleRootDir() . DS . 'moduleVersion.php';
+            if ($moduleVersion)
+            {
+                $data['version'] = $moduleVersion;
+            }
+        }
+
+        if ($data['description'] && strpos($data['description'], ':WIKI_URL:'))
+        {
+            $data['description'] = str_replace(':WIKI_URL:', ($moduleWikiUrl ?: 'https://www.docs.modulesgarden.com/'), $data['description']);
+        }
+
+        $data['debug'] = (file_exists(ModuleConstants::getModuleRootDir() . DIRECTORY_SEPARATOR . '.debug'))
+                ? true : false;
+    }
+
+    private function parseConfigData($dataDev, $dataCore)
+    {
+        if (!$dataDev && $dataCore)
+        {
+            return $dataCore;
+        }
+
+        if (!$dataDev && $dataCore)
+        {
+            return $dataCore;
+        }
+
+        foreach ($dataCore as $coreKey => $core)
+        {
+            $isFind = false;
+            foreach ($dataDev as $devKey => $dev)
+            {
+                if ($devKey === $coreKey)
+                {
+                    $isFind = true;
+                    break;
+                }
+            }
+            if (!$isFind)
+            {
+                $dataDev[$coreKey] = $core;
+            }
+        }
+
+        return $dataDev;
+    }
+
+    private function read($name)
+    {
+        return Reader::read($name)->get();
+    }
+}

+ 13 - 0
core/Configuration/Data/Version.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Configuration\Data;
+
+/**
+ * Description of Version
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Version
+{
+    //put your code here
+}

+ 0 - 0
core/Database/data.sql


+ 75 - 0
core/Database/schema.sql

@@ -0,0 +1,75 @@
+--
+-- `#prefix#Logger`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Logger` (
+    `id`            int(10) unsigned NOT NULL AUTO_INCREMENT,
+    `id_ref`        int(10) unsigned NOT NULL,
+    `id_type`       VARCHAR(255) NOT NULL,
+    `type`          VARCHAR(255) NOT NULL,
+    `level`         VARCHAR(255) NOT NULL,
+    `date`          DATETIME DEFAULT null,
+    `request`       TEXT NOT NULL,
+    `response`      TEXT NOT NULL,
+    `before_vars`   TEXT NOT NULL,
+    `vars`          TEXT NOT NULL,
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+--
+-- `#prefix#ModuleSettings`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#ModuleSettings` (
+    `setting`              VARCHAR(64) NOT NULL UNIQUE,
+    `value`            TEXT NOT NULL,
+    PRIMARY KEY (`setting`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+
+--
+-- `#prefix#Commands`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Commands` (
+    `name`             VARCHAR(64) NOT NULL UNIQUE,
+    `uuid`             VARCHAR(64) NOT NULL UNIQUE,
+    `parent_uuid`      VARCHAR(64) DEFAULT NULL,
+    `status`           enum('stopped', 'running', 'error', 'sleeping') DEFAULT 'stopped',
+    `action`           enum('none', 'stop', 'reboot') DEFAULT 'none',
+    `params`           TEXT NOT NULL,
+    `created_at`       timestamp DEFAULT CURRENT_TIMESTAMP,
+    `updated_at`       timestamp,
+    PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+
+
+--
+-- `#prefix#Job`
+--
+CREATE TABLE IF NOT EXISTS `#prefix#Job` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `retry_after` datetime NOT NULL,
+    `retry_count` int(10) unsigned NOT NULL,
+    `job` varchar(255) NOT NULL,
+    `data` text,
+    `parent_id` int(10) unsigned DEFAULT NULL,
+    `rel_id` int(10) unsigned DEFAULT NULL,
+    `rel_type` varchar(32) DEFAULT NULL,
+    `custom_id` int(10) unsigned DEFAULT NULL,
+    `status` varchar(32) NOT NULL,
+    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+    PRIMARY KEY (`id`),
+    KEY(`parent_id`),
+    KEY(`rel_type`, `rel_id`, `custom_id`),
+    KEY(`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;
+
+CREATE TABLE IF NOT EXISTS `#prefix#JobLog` (
+    `id` int(10) unsigned  NOT NULL AUTO_INCREMENT,
+    `job_id` int(10) unsigned NOT NULL,
+    `type` varchar(32) NOT NULL,
+    `message` varchar(512) NOT NULL,
+    `additional` text,
+    `created_at` datetime NOT NULL,
+    `updated_at` datetime NOT NULL,
+    PRIMARY KEY (`id`),
+    KEY `job_id` (`job_id`),
+    KEY `type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=#charset# DEFAULT COLLATE #collation#;

+ 13 - 0
core/DependencyInjection.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core;
+
+/**
+ * Description of DependencyInjection
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class DependencyInjection extends \ModulesGarden\ProxmoxAddon\Core\DependencyInjection\DependencyInjection
+{
+    
+}

+ 76 - 0
core/DependencyInjection/Builder.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+
+use \ModulesGarden\ProxmoxAddon\Core\SL\Data\DataSL;
+
+class Builder
+{
+    /**
+     * @var DataSL
+     */
+    protected $data = null;
+
+    /**
+     * @var array
+     */
+    protected $registers = [];
+
+    public function __construct()
+    {
+        $this->init();
+        $this->loadAliases();
+        $this->loadRewrites();
+        $this->loadInstances();
+    }
+
+    protected function init()
+    {
+        Container::setInstance(new Container());
+
+        $this->data = new DataSL();
+
+        $this->registers = $this->data->getRegisters();
+    }
+
+    protected function loadRewrites()
+    {
+        foreach ($this->data->getRewrites() as $alias => $className)
+        {
+            Container::getInstance()->alias($className, $alias);
+        }
+    }
+
+    protected function loadAliases()
+    {
+        foreach ($this->data->getAllAlias() as $className => $alias)
+        {
+            Container::getInstance()->alias($className, $alias);
+        }
+    }
+
+    protected function loadInstances()
+    {
+        foreach ($this->data->getConfigurations() as $config)
+        {
+            $className = $config['name'];
+            $method    = $config['method'];
+            $arguments = $config['args'];
+
+
+            if (!$method)
+            {
+                $obj = Container::getInstance()->make($className);
+            }
+            else
+            {
+                $obj = call_user_func_array("$className::$method", $arguments);
+            }
+
+            if (array_key_exists($className, $this->registers) && $this->registers[$className]['singleton'])
+            {
+                Container::getInstance()->instance($className, $obj);
+            }
+        }
+    }
+}

+ 204 - 0
core/DependencyInjection/Container.php

@@ -0,0 +1,204 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+
+use Illuminate\Contracts\Container\Container as ContainerContract;
+use ModulesGarden\ProxmoxAddon\Core\Helper\WhmcsVersionComparator;
+
+class Container extends \Illuminate\Container\Container
+{
+    protected static $instance = null;
+
+    public static function getInstance()
+    {
+        if (is_null(self::$instance))
+        {
+            self::$instance = new static;
+        }
+
+        return static::$instance;
+    }
+
+    public static function setInstance(ContainerContract $container = null)
+    {
+        self::$instance = $container;
+    }
+
+    /**
+     * @param $parameters
+     * @param array $primitives
+     * @return array
+     */
+    protected function getDependencies($parameters, array $primitives = array())
+    {
+        $dependencies = array();
+        foreach ($parameters as $parameter)
+        {
+            if($parameter->isOptional())
+            {
+                break;
+            }
+
+            $dependency = $parameter->getClass();
+            // If the class is null, it means the dependency is a string or some other
+            // primitive type which we can not resolve since it is not a class and
+            // we will just bomb out with an error since we have no-where to go.
+            if (array_key_exists($parameter->name, $primitives))
+            {
+                $dependencies[] = $primitives[$parameter->name];
+            }
+            elseif (is_null($dependency))
+            {
+                $dependencies[] = $this->resolveNonClass($parameter);
+            }
+            else
+            {
+                $dependencies[] = $this->resolveClass($parameter);
+            }
+        }
+        return (array) $dependencies;
+    }
+
+    /**
+     * Set null value as default parameter when cannot find default value
+     * @param ReflectionParameter $parameter
+     * @return null
+     */
+    protected function resolveNonClass(\ReflectionParameter $parameter)
+    {
+        if ($parameter->isDefaultValueAvailable())
+        {
+            return $parameter->getDefaultValue();
+        }
+
+        return null;
+    }
+
+    /* --------------------------------------- WHMCS 8 --------------------------------------- */
+    /**
+     * Resolve the given type from the container.
+     *
+     * @param  string  $abstract
+     * @param  array  $parameters
+     * @return mixed
+     *
+     * @throws \Illuminate\Contracts\Container\BindingResolutionException
+     */
+    public function make($abstract, array $parameters = [])
+    {
+        /* If $abstract contains namespace without slash at the begging, we need to add it */
+        $explodedAbstract = explode('\\', $abstract);
+        if($explodedAbstract[0] == 'ModulesGarden' && count($explodedAbstract) > 1)
+        {
+            $abstract = '\\'.$abstract;
+        }
+
+        /* This function executes a different code, depending on the version of the container - WHMCS 8 has a much newer version */
+        $version8OrHigher = (new WhmcsVersionComparator)->isWVersionHigherOrEqual('8.0.0');
+        if($version8OrHigher)
+        {
+            return $this->resolve($abstract, $parameters);
+        }
+
+        $abstract = $this->getAlias($this->normalize($abstract));
+        if (isset($this->instances[$abstract])) {
+            return $this->instances[$abstract];
+        }
+
+        $concrete = $this->getConcrete($abstract);
+        if ($this->isBuildable($concrete, $abstract)) {
+            $object = $this->build($concrete, $parameters);
+        } else {
+            $object = $this->make($concrete, $parameters);
+        }
+
+        foreach ($this->getExtenders($abstract) as $extender) {
+            $object = $extender($object, $this);
+        }
+
+        if ($this->isShared($abstract)) {
+            $this->instances[$abstract] = $object;
+        }
+
+        $this->fireResolvingCallbacks($abstract, $object);
+        $this->resolved[$abstract] = true;
+
+        return $object;
+    }
+
+    /**
+     * Resolve all of the dependencies from the ReflectionParameters.
+     *
+     * @param  array  $dependencies
+     * @return array
+     *
+     * @throws \Illuminate\Contracts\Container\BindingResolutionException
+     */
+    protected function resolveDependencies(array $dependencies)
+    {
+        $results = [];
+        foreach ($dependencies as $dependency) {
+            if($dependency->isOptional())
+            {
+                break;
+            }
+
+            if ($this->hasParameterOverride($dependency)) {
+                $results[] = $this->getParameterOverride($dependency);
+
+                continue;
+            }
+
+            $result = is_null($dependency->getClass())
+                ? $this->resolvePrimitive($dependency)
+                : $this->resolveClass($dependency);
+
+            if ($dependency->isVariadic()) {
+                $results = array_merge($results, $result);
+            } else {
+                $results[] = $result;
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * Resolve a non-class hinted primitive dependency.
+     *
+     * @param  \ReflectionParameter  $parameter
+     * @return mixed
+     *
+     * @throws \Illuminate\Contracts\Container\BindingResolutionException
+     */
+    protected function resolvePrimitive(\ReflectionParameter $parameter)
+    {
+        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
+            return $concrete instanceof Closure ? $concrete($this) : $concrete;
+        }
+
+        if ($parameter->isDefaultValueAvailable()) {
+            return $parameter->getDefaultValue();
+        }
+
+        if($parameter->hasType())
+        {
+            $returnEmptyType = [];
+            switch(strtolower($parameter->getType()->getName()))
+            {
+                case 'string':
+                    $returnEmptyType  = '';
+                    break;
+                case 'array':
+                    $returnEmptyType  = [];
+                    break;
+                default:
+                    return null;
+            }
+
+            return $returnEmptyType;
+        }
+
+        return null;
+    }
+}

+ 58 - 0
core/DependencyInjection/DependencyInjection.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+
+/**
+ * Class DependencyInjection
+ * @package ModulesGarden\ProxmoxAddon\Core\DependencyInjection
+ */
+class DependencyInjection
+{
+
+    /**
+     * @param null $className
+     * @param null $methodName
+     * @param bool $canClone
+     * @return mixed
+     */
+    public static function get($className = null, $methodName = null, $canClone = true)
+    {
+        if ($methodName)
+        {
+            return Container::getInstance()->call("$className@$methodName");
+        }
+
+        return Container::getInstance()->make($className);
+    }
+
+    /**
+     * @param null $className
+     * @param null $methodName
+     * @param bool $canClone
+     * @return mixed
+     */
+    public static function create($className = null, $methodName = null, $canClone = true)
+    {
+        if ($methodName)
+        {
+            return Container::getInstance()->call("$className@$methodName");
+        }
+
+        return Container::getInstance()->make($className);
+    }
+
+    /**
+     * @param null $className
+     * @param null $methodName
+     * @return mixed
+     */
+    public static function call($className = null, $methodName = null)
+    {
+        if ($methodName)
+        {
+            return Container::getInstance()->call("$className@$methodName");
+        }
+
+        return Container::getInstance()->make($className);
+    }
+}

+ 65 - 0
core/DependencyInjection/Services.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+/**
+ * Load all services from yml file and mark them as shared in DI container
+ * @author Mariusz Miodowski <mariusz@modulesgarden.com>
+ * @package ModulesGarden\DomainOrdersExtended\Core\Services
+ */
+class Services
+{
+
+    /**
+     * Services constructor.
+     */
+    public function __construct()
+    {
+        $this->load();
+    }
+
+    /**
+     * Load all needed servies to DI container
+     */
+    protected function load()
+    {
+        foreach ($this->getFilesList() as $file)
+        {
+            $servicesList = Reader::read($file)->get();
+            ;
+            if (!is_array($servicesList) || empty($servicesList))
+            {
+                continue;
+            }
+
+            $this->registerServices($servicesList);
+        }
+    }
+
+    /**
+     * Register all services in DI container
+     * @param $servicesList
+     */
+    protected function registerServices($servicesList)
+    {
+        foreach ($servicesList as $service)
+        {
+            Container::getInstance()->singleton($service);
+        }
+    }
+
+    /**
+     * Get file list with servies configuration
+     * @return array
+     */
+    protected function getFilesList()
+    {
+        return [
+            ModuleConstants::getFullPath('app', 'Config', 'di', 'services.yml'),
+            ModuleConstants::getFullPath('core', 'Config', 'di', 'services.yml')
+        ];
+    }
+}

+ 34 - 0
core/Enum/Enum.php

@@ -0,0 +1,34 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Jan 8, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\Core\Enum;
+
+/**
+ * Description of Enum
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+abstract class Enum
+{
+    static function getKeys(){
+        $class = new ReflectionClass(get_called_class());
+        return array_keys($class->getConstants());
+    }
+}

+ 33 - 0
core/Enum/Level.php

@@ -0,0 +1,33 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Jan 8, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\Core\Enum;
+
+/**
+ * Description of Level
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+final class Level extends Enum
+{
+    const LOW     = "low";
+    const MEDIUM  = "medium";
+    const HIGHT   = "hight";
+}

+ 37 - 0
core/Enum/Status.php

@@ -0,0 +1,37 @@
+<?php
+
+/* * ********************************************************************
+ * WordPress Manager product developed. (Jan 8, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\Core\Enum;
+
+/**
+ * Description of Status
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+final class Status extends Enum
+{
+
+    const DEBUG    = "debug";
+    const ERROR    = "error";
+    const INFO     = "info";
+    const SUCCESS  = "success";
+    const CRITICAL = "critical";
+    
+}

+ 47 - 0
core/Enum/StatusColor.php

@@ -0,0 +1,47 @@
+<?php
+
+/* * ********************************************************************
+ * ProxmoxAddon product developed. (Jan 23, 2019)
+ * *
+ *
+ *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
+ *  CONTACT                        ->       contact@modulesgarden.com
+ *
+ *
+ * This software is furnished under a license and may be used and copied
+ * only  in  accordance  with  the  terms  of such  license and with the
+ * inclusion of the above copyright notice.  This software  or any other
+ * copies thereof may not be provided or otherwise made available to any
+ * other person.  No title to and  ownership of the  software is  hereby
+ * transferred.
+ *
+ *
+ * ******************************************************************** */
+
+namespace ModulesGarden\ProxmoxAddon\Core\Enum;
+
+/**
+ * Description of StatusColor
+ *
+ * @author Pawel Kopec <pawelk@modulesgardne.com>
+ */
+final class StatusColor extends Enum
+{
+    const PENDING   = "f89406";
+    const ACTIVE    = "46a546";
+    const COMPLETED = "008b8b";
+    const SUSPENDED = "0768b8";
+    const CANCELLED = "bfbfbf";
+    const FRAUD     = "000";
+    
+    public static function getColors(){
+        return [
+            "Pending"   => self::PENDING,
+            "Active"    => self::ACTIVE,
+            "Completed" => self::COMPLETED,
+            "Suspended" => self::SUSPENDED,
+            "Cancelled" => self::CANCELLED,
+            "Fraud"     => self::FRAUD
+        ];
+    }
+}

+ 168 - 0
core/Events/Dispatcher.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Events;
+
+use ModulesGarden\ProxmoxAddon\App\Events\MyTestEvent;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection\Container;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\Queue\DatabaseQueue;
+use ModulesGarden\ProxmoxAddon\Core\Helper\WhmcsVersionComparator;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast as ShouldBroadcast;
+
+
+class Dispatcher extends \Illuminate\Events\Dispatcher
+{
+
+    public function __construct(Container $container)
+    {
+        $this->container = $container;
+
+        $this->initialize();
+    }
+
+    /**
+     * Fire an event and call the listeners.
+     *
+     * @param  string|object  $event
+     * @param  mixed  $payload
+     * @param  bool  $halt
+     * @return array|null
+     */
+    public function fire($event, $payload = [], $halt = false)
+    {
+        /* This function executes a different code, depending on the version of the container - WHMCS 8 has a much newer version */
+        $version8OrHigher = (new WhmcsVersionComparator)->isWVersionHigherOrEqual('8.0.0');
+        if($version8OrHigher)
+        {
+            return $this->fireForWhmcs8OrNewer($event, $payload, $halt);
+        }
+
+        /* ----------------------------- OLDER WHMCS -------------------------------------------- */
+        if (is_object($event)) {
+            list($payload, $event) = [[$event], get_class($event)];
+        }
+
+        $responses = [];
+
+        if (! is_array($payload)) {
+            $payload = [$payload];
+        }
+
+        $this->firing[] = $event;
+
+        if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) {
+            $this->broadcastEvent($payload[0]);
+        }
+
+        foreach ($this->getListeners($event) as $listener) {
+            $response = call_user_func_array($listener, $payload);
+            if (! is_null($response) && $halt) {
+                array_pop($this->firing);
+
+                return $response;
+            }
+
+            if ($response === false) {
+                break;
+            }
+
+            $responses[] = $response;
+        }
+
+        array_pop($this->firing);
+
+        return $halt ? null : $responses;
+    }
+
+    private function fireForWhmcs8OrNewer($event, $payload, $halt)
+    {
+        [$event, $payload] = $this->parseEventAndPayload(
+            $event, $payload
+        );
+
+        if ($this->shouldBroadcast($payload)) {
+            $this->broadcastEvent($payload[0]);
+        }
+
+        $responses = [];
+
+        foreach ($this->getListeners($event) as $listener) {
+            $response = $listener($event, $payload);
+            if ($halt && ! is_null($response)) {
+                return $response;
+            }
+
+            if ($response === false) {
+                break;
+            }
+
+            $responses[] = $response;
+        }
+
+        return $halt ? null : $responses;
+    }
+
+    /**
+     *
+     */
+    protected function initialize()
+    {
+        /**
+         * Load available events
+         */
+        $path   = ModuleConstants::getFullPath('app', 'Config', 'events.php');
+        $config = include($path);
+
+
+        foreach ($config as $event => $listeners)
+        {
+            foreach ($listeners as $listener)
+            {
+                $this->listen($event, $listener);
+            }
+        }
+
+        /**
+         * Set queue resolver
+         */
+        $this->setQueueResolver(function()
+        {
+            return DependencyInjection::create(DatabaseQueue::class);
+        });
+    }
+
+    /**
+     * @param $class
+     * @param $arguments
+     */
+    public function queue($class, $arguments, $parentId = null, $relType = null, $relId = null, $customId = null)
+    {
+        $class  = implode('@', $this->parseClassCallable($class));
+
+        return $this->resolveQueue()->push("$class", serialize($arguments), $parentId, $relType, $relId, $customId);
+    }
+
+    /**
+     * @param $class
+     * @param $method
+     * @override
+     * @return \Closure
+     */
+    protected function createQueuedHandlerCallable($class, $method)
+    {
+        return function () use ($class, $method)
+        {
+            $arguments = $this->cloneArgumentsForQueueing(func_get_args());
+
+            if (method_exists($class, 'queue'))
+            {
+                $this->callQueueMethodOnHandler($class, $method, $arguments);
+            }
+            else
+            {
+                $this->resolveQueue()->push("{$class}@{$method}", serialize($arguments));
+            }
+        };
+    }
+}

+ 8 - 0
core/Events/Event.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Events;
+
+class Event
+{
+    
+}

+ 8 - 0
core/Events/Listener.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Events;
+
+class Listener
+{
+    
+}

+ 42 - 0
core/FileReader/Directory.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader;
+
+/**
+ * Description of Directory
+ *
+ * @author INBSX-37H
+ */
+class Directory extends PathValidator
+{
+
+    public function getFilesList($path, $extension = null, $trimExtensions = false)
+    {
+        if (!$this->pathExists($path) || !$this->isPathReadable($path))
+        {
+            return [];
+        }
+
+        $list  = [];
+        $files = scandir($path, 1);
+        if (!$files)
+        {
+            return [];
+        }
+
+        foreach ($files as $key => $value)
+        {
+            //remove dots and a files with unwanted extensions
+            if ($value === "." || $value === ".." ||
+                    (is_string($extension) && $extension !== '' && !(stripos($value, '.php') > 0)))
+            {
+                unset($files[$key]);
+                continue;
+            }
+
+            $list[] = $trimExtensions ? str_replace($extension, '', $value) : $value;
+        }
+
+        return $list;
+    }
+}

+ 72 - 0
core/FileReader/File.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader;
+
+/**
+ * Description of File
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class File
+{
+
+    public static function createFile()
+    {
+        // next version
+    }
+
+    public static function createPaths()
+    {
+        foreach (func_get_args() as $path)
+        {
+            if (is_array($path) && isset($path['permission']) && self::createPath($path['full'], $path['permission']) === false)
+            {
+                $parentPath = explode(DS, $path['full']);
+                array_pop($pathFull);
+                $parentPath = implode(DS, $parentPath);
+                self::setPermission($parentPath);
+                self::setUser($path['full'], 'www-data');
+                self::createPath($path['full'], $path['permission']);
+            }
+            elseif (is_array($path) && self::createPath($path['full']) === false)
+            {
+                $parentPath = explode(DS, $path['full']);
+                array_pop($pathFull);
+                $parentPath = implode(DS, $parentPath);
+                self::setPermission($parentPath);
+                self::setUser($path['full'], 'www-data');
+                self::createPath($path['full']);
+            }
+            else
+            {
+                self::createPath($path);
+            }
+        }
+    }
+
+    public static function createPath($path, $permission = 0777)
+    {
+        if (!is_string($path) || file_exists($path)) {
+            return;
+        }
+        return mkdir($path, $permission);
+    }
+
+    public static function setPermission($file, $permission = 0777)
+    {
+        return chmod($file, $permission);
+    }
+
+    public static function setUser($file, $user)
+    {
+        return chown($file, $user);
+    }
+
+    /**
+     * @return PathValidator
+     */
+    public static function getValidator()
+    {
+        return new PathValidator();
+    }
+}

+ 86 - 0
core/FileReader/PathValidator.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader;
+
+/**
+ * Wrapper for files and directories validation methods
+ *
+ * @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+ */
+class PathValidator
+{
+
+    /**
+     * @param string $path -a a path to be validated, can be a file path or a dir path
+     * @param bool $isReadable - states if path should be readable
+     * @param bool $isWritable - states if path should be writable
+     * @param bool $create - states if should try to create a directory if not exists
+     * @return bool
+     */
+    public function validatePath($path = "", $isReadable = true, $isWritable = false, $create = true)
+    {
+        //try to create a dir if does not exist
+        if ($create)
+        {
+            $this->createDirIfNotExists($path);
+        }
+
+        //if path does not exists
+        if (!$this->pathExists($path))
+        {
+            return false;
+        }
+
+        //if should be readable and it is not
+        if ($isReadable && !$this->isPathReadable($path))
+        {
+            return false;
+        }
+
+        //if should be writable and it is not
+        if ($isWritable && !$this->isPathWritable($path))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $path - if provided path does not exist, a dir will be created
+     */
+    public function createDirIfNotExists($path)
+    {
+        if (!$this->pathExists($path))
+        {
+            mkdir($path);
+        }
+    }
+
+    /**
+     * @param string $path -a a path to be validated, can be a file path or a dir path
+     * @return bool
+     */
+    public function pathExists($path)
+    {
+        return file_exists($path);
+    }
+
+    /**
+     * @param $path -a a path to be validated, can be a file path or a dir path
+     * @return bool
+     */
+    public function isPathReadable($path)
+    {
+        return is_readable($path);
+    }
+
+    /**
+     * @param $path -a a path to be validated, can be a file path or a dir path
+     * @return bool
+     */
+    public function isPathWritable($path)
+    {
+        return is_writable($path);
+    }
+}

+ 84 - 0
core/FileReader/Reader.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader;
+
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Ini;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Json;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Xml;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Yml;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Php;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Sql;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Js;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Css;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\Html;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader\AbstractType;
+
+/**
+ * Description of Reader
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Reader
+{
+
+    /**
+     * @param string $file
+     * @return AbstractType
+     */
+    public static function read($file, array $renderData = [])
+    {
+        $path     = explode(DIRECTORY_SEPARATOR, $file);
+        $file     = end($path);
+        array_pop($path);
+        $path     = implode(DIRECTORY_SEPARATOR, $path);
+        $instance = null;
+        $type     = self::getType($file);
+
+        switch ($type)
+        {
+            case "xml":
+                $instance = new Xml($file, $path, $renderData);
+                break;
+            case "ini":
+                $instance = new Ini($file, $path, $renderData);
+                break;
+            case "yml":
+                $instance = new Yml($file, $path, $renderData);
+                break;
+            case "json":
+                $instance = new Json($file, $path, $renderData);
+                break;
+            case "php":
+                $instance = new Php($file, $path, $renderData);
+                break;
+            case "sql":
+                $instance = new Sql($file, $path, $renderData);
+                break;
+            case "js":
+                $instance = new Js($file, $path, $renderData);
+                break;
+            case "css":
+                $instance = new Css($file, $path, $renderData);
+                break;
+            case "html":
+                $instance = new Html($file, $path, $renderData);
+                break;
+            default:
+                throw new \Exception('Can\'t read file: ' . $file);
+        }
+
+        return $instance;
+    }
+
+    private static function getType($file)
+    {
+        $type  = null;
+        $array = explode('.', $file);
+        if (is_array($array))
+        {
+            $type = end($array);
+        }
+
+        return strtolower($type);
+    }
+}

+ 49 - 0
core/FileReader/Reader/AbstractType.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+/**
+ * Description of AbstractType
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+abstract class AbstractType
+{
+    protected $data       = [];
+    protected $renderData = [];
+    protected $file       = '';
+    protected $path       = '';
+
+    public function __construct($file, $path, $renderData = [])
+    {
+        $this->file       = $file;
+        $this->path       = $path;
+        $this->renderData = $renderData;
+        $this->loadFile();
+    }
+
+    abstract protected function loadFile();
+
+    public function get($key = null, $default = null)
+    {
+        if ($key == null)
+        {
+            return $this->data;
+        }
+
+        if ($this->isExist($key))
+        {
+            return $this->data[$key];
+        }
+
+        return $default;
+    }
+
+    protected function isExist($key)
+    {
+        if (isset($this->data[$key]) || array_key_exists($key, $this->data))
+        {
+            return true;
+        }
+    }
+}

+ 36 - 0
core/FileReader/Reader/Css.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Json
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Css extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = '';
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = file_get_contents($this->path . DS . $this->file);
+                foreach ($this->renderData as $key => $value)
+                {
+                    $return = str_replace("#$key#", $value, $return);
+                }
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 36 - 0
core/FileReader/Reader/Html.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Json
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Html extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = '';
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = file_get_contents($this->path . DS . $this->file);
+                foreach ($this->renderData as $key => $value)
+                {
+                    $return = str_replace("#$key#", $value, $return);
+                }
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 35 - 0
core/FileReader/Reader/Ini.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use Piwik\Ini\IniReader;
+use Piwik\Ini\IniReadingException;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Ini
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Ini extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = [];
+        $reader = new IniReader();
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = $reader->readFile($this->path . DS . $this->file);
+            }
+        }
+        catch (IniReadingException $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 36 - 0
core/FileReader/Reader/Js.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Json
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Js extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = '';
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = file_get_contents($this->path . DS . $this->file);
+                foreach ($this->renderData as $key => $value)
+                {
+                    $return = str_replace("#$key#", $value, $return);
+                }
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 33 - 0
core/FileReader/Reader/Json.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Json
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Json extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = [];
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $readFile = file_get_contents($this->path . DS . $this->file);
+                $return   = json_decode($readFile, true);
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 36 - 0
core/FileReader/Reader/Php.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Json
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Php extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = '';
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = file_get_contents($this->path . DS . $this->file);
+                foreach ($this->renderData as $key => $value)
+                {
+                    $return = str_replace("#$key#", $value, $return);
+                }
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+}

+ 67 - 0
core/FileReader/Reader/Sql.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use Illuminate\Database\Capsule\Manager as DB;
+/**
+ * Description of Sql
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Sql extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = '';
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $collation = $this->getWHMCSTablesCollation();
+                $charset   = $this->getWHMCSTablesCharset();
+                $return    = file_get_contents($this->path . DS . $this->file);
+                $return    = str_replace("#collation#", $collation, $return);
+                $return    = str_replace("#charset#", $charset, $return);
+                $return    = str_replace("#prefix#", ModuleConstants::getPrefixDataBase(), $return);
+                foreach ($this->renderData as $key => $value)
+                {
+                    $return = str_replace("#$key#", $value, $return);
+                }
+            }
+        }
+        catch (\Exception $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+
+    protected function getWHMCSTablesCollation()
+    {
+        $pdo    = \Illuminate\Database\Capsule\Manager::connection()->getPdo();
+        $query  = $pdo->prepare("SHOW TABLE STATUS WHERE name = 'tblclients'");
+        $query->execute();
+        $result = $query->fetchObject();
+        return $result->Collation;
+    }
+
+    protected function getWHMCSTablesCharset()
+    {
+        $pdo    = \Illuminate\Database\Capsule\Manager::connection()->getPdo();
+        $query  = $pdo->prepare("SELECT CCSA.character_set_name as Charset FROM information_schema.`TABLES` T,
+            information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA
+            WHERE CCSA.collation_name = T.table_collation
+            AND T.table_schema = '{$GLOBALS['db_name']}'
+            AND T.table_name = 'tblclients';");
+        $query->execute();
+        $result = $query->fetchObject();
+        if($result->Charset){
+            return $result->Charset;
+        }
+        return 'utf8';
+    }
+}

+ 23 - 0
core/FileReader/Reader/Xml.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Xml
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Xml extends AbstractType
+{
+
+    protected function loadFile()
+    {
+
+        // https://packagist.org/packages/sabre/xml
+
+        $this->data = [];
+        ServiceLocator::call('errorManager')->addError(self::class, "First install composer sabre/xml", ['url' => 'https://packagist.org/packages/sabre/xml']);
+    }
+}

+ 46 - 0
core/FileReader/Reader/Yml.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+use Symfony\Component\Yaml\Yaml;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of Yml
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Yml extends AbstractType
+{
+
+    protected function loadFile()
+    {
+        $return = [];
+        try
+        {
+            if (file_exists($this->path . DS . $this->file))
+            {
+                $return = Yaml::parse(file_get_contents($this->path . DS . $this->file));
+                $return = array_map(self::class . '::replaceBackslash', $return ?: []);
+            }
+        }
+        catch (\Symfony\Component\Yaml\Exception\ParseException $e)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $e->getMessage(), $e->getTrace());
+        }
+
+        $this->data = $return;
+    }
+
+    protected static function replaceBackslash($data)
+    {
+        if (is_array($data))
+        {
+            return array_map(self::class . '::replaceBackslash', $data);
+        }
+        else
+        {
+            return str_replace('\\\\', '\\', $data);
+        }
+    }
+}

+ 109 - 0
core/HandlerError/ErrorCodes/ErrorCode.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes;
+
+class ErrorCode
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    protected $code    = null;
+    protected $message = null;
+    protected $token   = null;
+    protected $logable = false;
+
+    public function __construct($code = null, $codeDetails = null, $token = null)
+    {
+        $this->setToken($token);
+        $this->setCode($code);
+
+        $this->setDetails($codeDetails);
+    }
+
+    /**
+     * @param null $code
+     */
+    public function setCode($code = null)
+    {
+        if (is_string($code))
+        {
+            $this->code = $code;
+        }
+    }
+
+    /**
+     * @param null $message
+     */
+    public function setMessage($message = null)
+    {
+        if (is_string($message) && $message !== '')
+        {
+            $this->message = $message;
+        }
+    }
+
+    /**
+     * @param null $token
+     */
+    public function setToken($token = null)
+    {
+        if (is_string($token))
+        {
+            $this->token = $token;
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getCode()
+    {
+        return $this->code === null ? '' : $this->code;
+    }
+
+    /**
+     * @return string
+     */
+    public function getToken()
+    {
+        return $this->token === null ? '' : $this->token;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->message === null ? '' : $this->message;
+    }
+
+    public function getRawErrorMessage()
+    {
+        return 'Error Code: ' . ($this->getCode() ?: 'none') . ' Error Token: ' . ($this->getToken() ?: 'none') . 'Error Message: ' . $this->getMessage() ?: 'none.';
+    }
+
+    public function setLogable($logable)
+    {
+        if (is_bool($logable))
+        {
+            $this->logable = $logable;
+        }
+    }
+
+    public function isLogable()
+    {
+        return $this->logable;
+    }
+
+    public function setDetails($codeDetails)
+    {
+        if (is_string($codeDetails))
+        {
+            $this->setMessage($codeDetails);
+
+            return;
+        }
+
+        $this->setMessage($codeDetails[ErrorCodes::MESSAGE]);
+        $this->setLogable($codeDetails[ErrorCodes::LOG]);
+    }
+}

+ 73 - 0
core/HandlerError/ErrorCodes/ErrorCodes.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes;
+
+abstract class ErrorCodes
+{
+    const MESSAGE     = 'message';
+    const LOG         = 'log';
+    const CODE        = 'code';
+    const DEV_MESSAGE = 'dev_message';
+
+    /**
+     * @param null $code
+     * @param null $errorToken
+     * @return ErrorCode|null
+     */
+    public function getErrorMessageByCode($code = null, $errorToken = null)
+    {
+        $constantName = get_class($this) . '::' . $code;
+        if (!defined($constantName))
+        {
+            return $this->getUndefinedErrorMessage($code, $errorToken);
+        }
+
+        return $this->getErrorCode($code, constant($constantName), $errorToken);
+    }
+
+    /**
+     * @param null|string $code
+     * @param null|string $errorToken
+     * @return ErrorCode|null
+     */
+    public function getUndefinedErrorMessage($code = null, $errorToken = null)
+    {
+        return $this->getErrorCode($code, 'Invalid Error Code!', $errorToken);
+    }
+
+    /**
+     * @param null|string $code
+     * @param null|string $message
+     * @param null|string $token
+     * @return ErrorCode|null
+     */
+    protected function getErrorCode($code = null, $message = null, $token = null)
+    {
+        $token = new ErrorCode($code, $message, ($token ?: $this->genToken()));
+
+        return $token;
+    }
+
+    /**
+     * returns a string
+     */
+    protected function genToken()
+    {
+        return md5(time());
+    }
+
+    /**
+     * @param null|string $code
+     * @return bool
+     */
+    public function errorCodeExists($code = null)
+    {
+        $constantName = get_class($this) . '::' . $code;
+        if (!defined($constantName))
+        {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 94 - 0
core/HandlerError/ErrorCodes/ErrorCodesLib.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes;
+
+class ErrorCodesLib extends ErrorCodes
+{
+    const CORE_CT_000001  = 'Provided controller does not exists';
+    const CORE_CT_000002  = 'Provided controller does not exists';
+    const CORE_ACT_000001 = 'Database error';
+    const CORE_ACT_000002 = 'Database error';
+    const CORE_ACT_000003 = 'Database error';
+    const CORE_ACT_000004 = 'Database error';
+
+    /**
+     * Default error, used when no error code defined
+     */
+    const CORE_ERR_000001 = [
+        self::MESSAGE     => 'Uncategorised error occured',
+        self::CODE        => 'CORE_ERR_000001',
+        self::DEV_MESSAGE => "It's a default error code, used when no error code was specified"
+    ];
+
+    /**
+     * Logs class
+     */
+    const CORE_LOG_000001 = [
+        self::MESSAGE => 'Method does not exist in the Logger class',
+        self::CODE    => 'CORE_LOG_000001',
+    ];
+
+    /**
+     * Register cache
+     */
+    const CORE_CREG_000001 = [
+        self::MESSAGE => 'Register key already exists',
+        self::CODE    => 'CORE_CREG_000001',
+    ];
+
+    /**
+     * Database cache
+     */
+    const CORE_CDB_000001 = [
+        self::MESSAGE => 'The callback needs to ba a callable',
+        self::CODE    => 'CORE_CDB_000001',
+    ];
+
+    /**
+     * GRAPHS
+     */
+    const CORE_GRA_000001 = [
+        self::MESSAGE => 'Tooltip mode does not exists',
+        self::CODE    => 'CORE_GRA_000001',
+    ];
+    const CORE_GRA_000002 = [
+        self::MESSAGE => 'Width value is not numeric(:width:)',
+        self::CODE    => 'CORE_GRA_000002',
+    ];
+    const CORE_GRA_000003 = [
+        self::MESSAGE => 'Height value is not numeric(:height:)',
+        self::CODE    => 'CORE_GRA_000003',
+    ];
+
+    /**
+     * CURL
+     */
+    const CORE_CURL_000001 = [
+        self::MESSAGE => 'CURL error',
+        self::CODE    => 'CORE_CURL_000001',
+    ];
+    const CORE_CURL_000002 = [
+        self::MESSAGE => 'CURL error',
+        self::CODE    => 'CORE_CURL_000002',
+    ];
+
+    /**
+     * WHMCS API core lib
+     */
+    const CORE_WAPI_000001 = [
+        self::MESSAGE => 'No WHMCS files found',
+        self::CODE    => 'CORE_WAPI_000001',
+    ];
+    const CORE_WAPI_000002 = [
+        self::MESSAGE => 'WHMCS API error',
+        self::CODE    => 'CORE_WAPI_000002',
+    ];
+    const CORE_WAPI_000003 = [
+        self::MESSAGE => 'There is no admin with ID equal to ":adminId:"',
+        self::CODE    => 'CORE_WAPI_000003',
+    ];
+    const CORE_WAPI_000004 = [
+        self::MESSAGE => 'WHMCS API error',
+        self::CODE    => 'CORE_WAPI_000004',
+    ];
+}

+ 261 - 0
core/HandlerError/ErrorManager.php

@@ -0,0 +1,261 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+
+/**
+ * Description of ErrorManager
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class ErrorManager
+{
+    const TYPE_ERROR                  = "Error";
+    const TYPE_WARNING                = "Warning";
+    protected static $lastToken = null;
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error[]
+     */
+    protected static $errors = [];
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Warning[]
+     */
+    protected static $warnings = [];
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsLogsHandler
+     */
+    protected static $whmcsLogger;
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\Interfaces\LoggerInterface
+     */
+    protected $logger;
+
+    /**
+     * @var bool
+     */
+    protected $withWarning = false;
+
+    /**
+     * @param Logger $logger
+     */
+    public function __construct(Logger $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * @param string $classname
+     * @param string $message
+     * @param array $trace
+     */
+    public function addError($classname = null, $message = '', $trace = [])
+    {
+        $error = $this->createNewModel(self::TYPE_ERROR, $classname, $message, $trace);
+        $this->_addError($error)
+        ->logger->addError($error->getFullMessage(), $error->getTrace());
+        $this->getWhmcsLogger()->addModuleLog(
+                [
+                    "type"    => "ERROR",
+                    "class"   => $error->getClass(),
+                    "message" => $error->getMessage(),
+                    "data"    => $error->getTrace()
+                ]
+        );
+
+        return $this;
+    }
+
+    public static function getLastErrorToken()
+    {
+        return self::$lastToken;
+    }
+
+    public static function setLastErrorToken($token)
+    {
+        self::$lastToken = $token;
+
+        return self;
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error
+     */
+    public static function getFirstError()
+    {
+        return (count(self::$errors) > 0) ? self::$errors[0] : null;
+    }
+
+    /**
+     * @param string $classname
+     * @param string $message
+     * @param array $trace
+     */
+    public function addWarning($classname = null, $message = '', $trace = [])
+    {
+        $warning = $this->createNewModel(self::TYPE_WARNING, $classname, $message, $trace);
+        $this->_addWarning($warning)
+        ->logger->addWarning($warning->getFullMessage(), $warning->getTrace());
+        $this->getWhmcsLogger()->addModuleLog(
+                [
+                    "type"    => "WARNING",
+                    "class"   => $warning->getClass(),
+                    "message" => $warning->getMessage(),
+                    "data"    => $warning->getTrace()
+                ]
+        );
+
+        return $this;
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error[]
+     */
+    public static function getErrors()
+    {
+        return self::$errors;
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Warning[]
+     */
+    public static function getWarnings()
+    {
+        return self::$warnings;
+    }
+
+    public static function reset()
+    {
+        self::$warnings = [];
+        self::$errors   = [];
+
+        return self;
+    }
+
+    /**
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\WhmcsLogsHandler
+     */
+    public function getWhmcsLogger()
+    {
+        if (isset(self::$whmcsLogger) === false)
+        {
+            self::$whmcsLogger = ServiceLocator::call("whmcsLogger");
+        }
+
+        return self::$whmcsLogger;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hesError()
+    {
+        return (bool) (count(self::$errors) != 0);
+    }
+
+    /**
+     * @return bool
+     */
+    public function hesWarning()
+    {
+        return (bool) (count(self::$warnings) != 0);
+    }
+
+    public function withWarnings()
+    {
+        $this->withWarning = true;
+
+        return $this;
+    }
+
+    public function withoutWarnings()
+    {
+        $this->withWarning = false;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        $string = '';
+        if (count(self::getErrors()) != 0 || (count(self::getWarnings()) != 0 ) && $this->withWarning)
+        {
+            $string .= '<div class="list-group">';
+            foreach (self::getErrors() as $error)
+            {
+                $string .= '<div class="alert alert-danger">';
+                $string .= '<h4>Class: <strong>';
+                $string .= $error->getClass();
+                $string .= '<span class="pull-right">' . $error->getDate() . " " . $error->getTime() . '</span>';
+                $string .= '</strong></h4>';
+                $string .= '<p>' . $error->getMessage() . '</p>';
+                $string .= '</div>';
+            }
+            if ($this->withWarning)
+            {
+                foreach (self::getWarnings() as $warning)
+                {
+                    $string .= '<div class="alert alert-warning">';
+                    $string .= '<h4>Class: <strong>';
+                    $string .= $warning->getClass();
+                    $string .= '<span class="pull-right">' . $warning->getDate() . " " . $warning->getTime() . '</span>';
+                    $string .= '</strong></h4>';
+                    $string .= '<p>' . $warning->getMessage() . '</p>';
+                    $string .= '</div>';
+                }
+            }
+
+            $string .= '</div>';
+            self::reset();
+        }
+        return $string;
+    }
+
+    private function createNewModel($type = self::TYPE_ERROR, $class = "", $massage = "", $trace = [])
+    {
+        return $this->{"createNewModel" . $type}($class, $massage, $trace);
+    }
+
+    /**
+     * @param string $class
+     * @param string $massage
+     * @param array $trace
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error
+     */
+    private function createNewModelError($class = "", $massage = "", $trace = [])
+    {
+        return new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error($class, $massage, $trace);
+    }
+
+    /**
+     * @param string $class
+     * @param string $massage
+     * @param array $trace
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Warning
+     */
+    private function createNewModelWarning($class = "", $massage = "", $trace = [])
+    {
+        return new \ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error($class, $massage, $trace);
+    }
+
+    protected function _addError(\ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Error $error)
+    {
+        array_push(self::$errors, $error);
+
+        return $this;
+    }
+
+    protected function _addWarning(\ModulesGarden\ProxmoxAddon\Core\HandlerError\Model\Warning $warning)
+    {
+        array_push(self::$warnings, $warning);
+
+        return $this;
+    }
+}

+ 318 - 0
core/HandlerError/Exceptions/Exception.php

@@ -0,0 +1,318 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions;
+
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorManager;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodesLib;
+
+/**
+ * Base module Exception type
+ *
+ * @author Sławomir Miśkowicz <rafal.os@modulesgarden.com>
+ */
+class Exception extends \Exception
+{
+
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\ErrorCodesLibrary;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\Lang;
+    use \ModulesGarden\ProxmoxAddon\Core\Traits\IsDebugOn;
+    use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+    /**
+     * A default error code number, selected when no code number provided
+     */
+    const DEFAULT_ERROR_CODE   = 'CORE_ERR_000001';
+    /**
+     * An error code object
+     * 
+     * @var type \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCode
+     */
+    protected $errorCode = null;
+
+    /**
+     * Every Exception which can be caught as \Exception
+     * @var type \Exception
+     */
+    protected $originalException = null;
+
+    /**
+     * An array of additionall data that will be logged with the Exception in order to help debug
+     * @var type array
+     */
+    protected $additionalData = [];
+
+    /**
+     * An array of strings to be replaced in translate process, eg. for message:
+     * "An error :xyz: occured" in order to replace key ':xyz:' with a '123' set this
+     * param to: ['xyz' => '123']
+     * 
+     * @var type array
+     */
+    protected $toTranslate = [];
+
+    /**
+     * This is a way to replace standard ErrorCode message, use it when no original exception
+     * is present and the ErrorCode message, needs to be replaced, eg. API string error responses
+     * 
+     * @var type string
+     */
+    protected $customMessage = null;
+
+    public function __construct($errorCode = null, $additionalData = null, $toTranslate = null, $originalException = null)
+    {
+        $this->errorCode = $this->genErrorCode(($errorCode ?: self::DEFAULT_ERROR_CODE));
+
+        $this->setAdditionalData($additionalData);
+        $this->setToTranslate($toTranslate);
+
+        $this->setOriginalException($originalException);
+    }
+
+    /**
+     * Returns an error code for the exception
+     * @return type string
+     */
+    public function getMgCode()
+    {
+        return $this->errorCode->getCode();
+    }
+
+    /**
+     * Returns an error token for the exception, an unique string based on exception occurence timestamp
+     * @return type string
+     */
+    public function getMgToken()
+    {
+        return $this->errorCode->getToken();
+    }
+
+    /**
+     * Returns a date for the exception occurence
+     * @return type string
+     */
+    public function getMgTime()
+    {
+        return date("Y-m-d H:i:s", time());
+    }
+
+    /**
+     * Returns a translated or raw error message
+     * @param type bool $translate
+     * @return type string
+     */
+    public function getMgMessage($translate = true)
+    {
+        if ($translate && !$this->isDebugOn())
+        {
+            $this->loadLang();
+
+            $message = $this->lang->absoluteTranslate(
+                    $this->errorCode === self::DEFAULT_ERROR_CODE ? 'errorMessage' : 'errorCodeMessage', $this->selectProperMessage());
+        }
+        else
+        {
+            $message = $this->selectProperMessage();
+        }
+
+        return $this->replaceMessageVars($message);
+    }
+
+    /**
+     * Replaces provided vars in the message string
+     * 
+     * @param type $message string
+     */
+    public function replaceMessageVars($message)
+    {
+        foreach ($this->toTranslate as $key => $value)
+        {
+            $message = str_replace(':' . $key . ':', $value, $message);
+        }
+
+        return $message;
+    }
+
+    /**
+     * Returns an originall exception object if such was provided
+     * 
+     * @return type \Exception
+     */
+    public function getOriginalException()
+    {
+        return $this->originalException;
+    }
+
+    /**
+     * Returns an array of data to be displayed when exception occured
+     * 
+     * @return type array
+     */
+    public function getDetailsToDisplay()
+    {
+        $errorDetails = [];
+
+        if ($this->isDebugOn() && $this->isAdminLogedIn())
+        {
+            $errorDetails['errorCode']  = $this->getMgCode();
+            $errorDetails['errorToken'] = $this->getMgToken();
+            $errorDetails['errorTime']  = $this->getMgTime();
+        }
+
+        $errorDetails['errorMessage'] = $this->getMgMessage(true);
+
+        return $errorDetails;
+    }
+
+    /**
+     * Returns an array of data to be logged when exception occured
+     * 
+     * @return type array
+     */
+    public function getDetailsToLog()
+    {
+        $errorDetails = [];
+
+        $errorDetails['errorCode']      = $this->getMgCode();
+        $errorDetails['errorToken']     = $this->getMgToken();
+        $errorDetails['errorTime']      = $this->getMgTime();
+        $errorDetails['errorMessage']   = $this->getMgMessage(false);
+        $errorDetails['additionalData'] = $this->getAdditionalData();
+
+        return $errorDetails;
+    }
+
+    /**
+     * Select a proper message for the exepction
+     * Priority:
+     * 1 custom message
+     * 2 original Exception message
+     * 3 error code message
+     * 
+     * @return type string
+     */
+    protected function selectProperMessage()
+    {
+        if (is_string($this->customMessage))
+        {
+            return $this->customMessage;
+        }
+
+        if ($this->originalException !== null)
+        {
+            return $this->originalException->getMessage();
+        }
+
+        return $this->errorCode->getMessage();
+    }
+
+    /**
+     * Sets a $originalException param, so you can wrap other exception in this one,
+     * in order to log and parse them automatically
+     * 
+     * @param \Exception $originalException
+     */
+    public function setOriginalException($originalException)
+    {
+        if ($originalException instanceof \Exception)
+        {
+            $this->originalException = $originalException;
+
+            parent::__construct($originalException->getMessage(), $originalException->getCode(), $originalException->getPrevious());
+        }
+    }
+
+    /**
+     * 
+     * @param type $data array
+     * @return $this
+     */
+    public function setAdditionalData($data = [])
+    {
+        if (is_array($data))
+        {
+            $this->additionalData = $data;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 
+     * @return type array
+     */
+    public function getAdditionalData()
+    {
+        return $this->additionalData;
+    }
+
+    /**
+     * 
+     * @param type $data array
+     * @return $this
+     */
+    public function setToTranslate($data = [])
+    {
+        if (is_array($data))
+        {
+            $this->toTranslate = $data;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 
+     * @param type $message string
+     * @return $this
+     */
+    public function setCustomMessage($message = null)
+    {
+        if (is_string($message) && $message !== '')
+        {
+            $this->customMessage = $message;
+            $this->message =  $message;
+        }
+
+
+        return $this;
+    }
+
+    /**
+     * Check if the exception should be logged or not
+     * 
+     * @return boolean
+     */
+    public function isLogable()
+    {
+        if ($this->errorCode->isLogable())
+        {
+            return true;
+        }
+
+        if ($this->isAdminLogedIn() && $this->isDebugOn())
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check if the administrator user is logged in current session
+     *
+     * @return boolean
+     */
+    public function isAdminLogedIn()
+    {
+        $this->loadRequestObj();
+
+        $adminId = $this->request->getSession('adminid');
+
+        if (is_int($adminId) && $adminId > 0)
+        {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 243 - 0
core/HandlerError/Logger.php

@@ -0,0 +1,243 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError;
+
+use \Monolog\Formatter\LineFormatter;
+use \Monolog\Handler\StreamHandler;
+use \ModulesGarden\ProxmoxAddon\Core\Interfaces\LoggerInterface;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodesLib;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception;
+
+/**
+ * Description of Logger
+ * 
+ * @author Rafal Ossowski <rafal.os@modulesgarden.com>
+ */
+class Logger implements LoggerInterface
+{
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\HandlerError\Logger
+     */
+    protected static $instance;
+    protected $name;
+
+    /**
+     * @var \Monolog\Logger
+     */
+    protected $logger;
+    protected $mainPath = null;
+    protected $handlers = [];
+
+    /**
+     * @param string $name
+     * @param string $debugName
+     * @param string $warningName
+     * @param string $errorName
+     */
+    protected function __construct(
+    $name = '', $debugName = '', $warningName = '', $errorName = ''
+    )
+    {
+//        $this->name     = $name;
+//        $this->mainPath = ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'logs' . DS;
+//        if (is_dir($this->mainPath) === false)
+//        {
+//            mkdir($this->mainPath);
+//        }
+//        $this->logger   = new \Monolog\Logger($this->name);
+//        
+//        foreach ([
+//            $this->mainPath . $debugName   => \Monolog\Logger::DEBUG,
+//            $this->mainPath . $warningName => \Monolog\Logger::WARNING,
+//            $this->mainPath . $errorName   => \Monolog\Logger::ERROR
+//        ] as $path => $status)
+//        {
+//            if (file_exists($path) === false)
+//            {
+//                $myfile = fopen($path, "w");
+//                fclose($myfile);
+//                File::setPermission($path);
+//            }
+//            $this->handlers[] = $this->buildHandlar($path, $status);
+//        }
+//
+//        $this->addHandlerToLogger();
+    }
+
+    private function __clone()
+    {
+        
+    }
+
+    public function isLoggerExist()
+    {
+        return isset($this->logger);
+    }
+
+    public function createLogger()
+    {
+        $this->logger = new \Monolog\Logger($this->name);
+        //$this->addHandlerToLogger();
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @param array $arguments
+     * @return mixed
+     * @throws \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception
+     */
+    public function __call($name, $arguments)
+    {
+        if (method_exists($this->logger, $name))
+        {
+            return $this->logger->{$name}(
+                            (isset($arguments[0]) ? $arguments[0] : ''), (isset($arguments[1]) ? $arguments[1] : [])
+            );
+        }
+
+        throw new Exception(ErrorCodesLib::CORE_LOG_000001, ['functionName' => $name]);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function debug($message, array $context = [])
+    {
+        //return $this->logger->debug($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function error($message, array $context = [])
+    {
+        //return $this->logger->error($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function warning($message, array $context = [])
+    {
+        //return $this->logger->warning($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function err($message, array $context = [])
+    {
+        //return $this->logger->err($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function warn($message, array $context = [])
+    {
+        //return $this->logger->warn($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function addDebug($message, array $context = [])
+    {
+        //return $this->logger->addDebug($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function addWarning($message, array $context = [])
+    {
+        //return $this->logger->addWarning($message, $context);
+    }
+
+    /**
+     * @param string $message
+     * @param array $context
+     * @return bool
+     */
+    public function addError($message, array $context = [])
+    {
+        //return $this->logger->addError($message, $context);
+    }
+
+    private function addHandlerToLogger()
+    {
+        $formatter = $this->getFormatter();
+
+        foreach ($this->handlers as $handler)
+        {
+            $handler->setFormatter($formatter);
+            $this->logger->pushHandler($handler);
+        }
+    }
+
+    private function buildHandlar($path, $type)
+    {
+        return new StreamHandler($path, $type);
+    }
+
+    /**
+     * @return LineFormatter
+     */
+    private function getFormatter()
+    {
+        return new LineFormatter(null, null, false, true);
+    }
+
+    /**
+     * @param string $name
+     * @param string $debugName
+     * @param string $warningName
+     * @param string $errorName
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Logger
+     */
+    protected static function create(
+    $name = 'default', $debugName = 'debug.log', $warningName = 'warning.log', $errorName = 'error.log'
+    )
+    {
+        return new static($name, $debugName, $warningName, $errorName);
+    }
+
+    /**
+     * @param string $name
+     * @param string $debugName
+     * @param string $warningName
+     * @param string $errorName
+     * @return \ModulesGarden\ProxmoxAddon\Core\HandlerError\Logger
+     */
+    public static function get(
+    $name = 'default', $debugName = 'debug.log', $warningName = 'warning.log', $errorName = 'error.log'
+    )
+    {
+        if (!isset(self::$instance))
+        {
+            self::$instance = self::create($name, $debugName, $warningName, $errorName);
+        }
+
+        if (self::$instance->isLoggerExist())
+        {
+            self::$instance->createLogger();
+        }
+
+        return self::$instance;
+    }
+}

+ 114 - 0
core/HandlerError/Model/AbstractModel.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\Model;
+
+/**
+ * Description of AbstractModel
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+abstract class AbstractModel
+{
+    /**
+     * @var string
+     */
+    protected $class;
+
+    /**
+     * @var string 
+     */
+    protected $message;
+
+    /**
+     * @var array
+     */
+    protected $trace;
+
+    /**
+     * @var string 
+     */
+    protected $date;
+
+    /**
+     * @var string
+     */
+    protected $time;
+
+    /**
+     * @param string $class
+     * @param string $massage
+     * @param array $trace
+     */
+    public function __construct($class = "", $massage = "", $trace = [])
+    {
+        $this->date = date('Y-m-d');
+        $this->time = date('H:i:s');
+
+        $this->setClass($class)->setMessage($massage)->setTrace($trace);
+    }
+
+    public function getClass()
+    {
+        return $this->class;
+    }
+
+    public function setClass($class = "")
+    {
+        $this->class = $class;
+
+        return $this;
+    }
+
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    public function setMessage($message = "")
+    {
+        $this->message = $message;
+
+        return $this;
+    }
+
+    public function getTrace()
+    {
+        return $this->trace;
+    }
+
+    public function setTrace($trace = [])
+    {
+        $this->trace = $trace;
+
+        return $this;
+    }
+
+    public function getDate()
+    {
+        return $this->date;
+    }
+
+    public function setDate($date = '')
+    {
+        $this->date = $date;
+
+        return $this;
+    }
+
+    public function getTime()
+    {
+        return $this->time;
+    }
+
+    public function setTime($time = "")
+    {
+        $this->time = $time;
+
+        return $this;
+    }
+
+    public function getFullMessage()
+    {
+        return "[ " . $this->getClass() . " ]( {$this->getDate()} {$this->getMessage()} ) :\n{$this->getMessage()}";
+    }
+}

+ 13 - 0
core/HandlerError/Model/Error.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\Model;
+
+/**
+ * Description of Error
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Error extends AbstractModel
+{
+    
+}

+ 13 - 0
core/HandlerError/Model/Warning.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError\Model;
+
+/**
+ * Description of Warning
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class Warning extends AbstractModel
+{
+    
+}

+ 24 - 0
core/HandlerError/WhmcsErrorManagerWrapper.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError;
+
+class WhmcsErrorManagerWrapper
+{
+    protected static $errorManager = null;
+
+    public static function setErrorManager(&$errManager = null)
+    {
+        if ($errManager)
+        {
+            self::$errorManager = $errManager;
+        }
+    }
+
+    /**
+     * @return null
+     */
+    public static function getErrorManager()
+    {
+        return self::$errorManager;
+    }
+}

+ 142 - 0
core/HandlerError/WhmcsLogsHandler.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\HandlerError;
+
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon\Config;
+use \ModulesGarden\ProxmoxAddon\Core\Http\Request;
+
+/**
+ * Handles adding new records to WHMCS Module and Activity Logs
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class WhmcsLogsHandler
+{
+    /**
+     * @var Config
+     */
+    private $addon;
+
+    /**
+     * @var Request
+     */
+    private $request;
+
+    /**
+     * @param Addon $addon
+     * @param Request $request
+     */
+    public function __construct(Config $addon, Request $request)
+    {
+        $this->addon   = $addon;
+        $this->request = $request;
+    }
+
+    /**
+     * @param mixed $responseData
+     * @param array $replaceVars
+     * @return $this
+     */
+    public function addModuleLog($responseData = [], array $replaceVars = [])
+    {
+        if ($this->isDebugin())
+        {
+            if (is_array($responseData) === false)
+            {
+                if (is_object($responseData))
+                {
+                    $responseData = print_r($responseData, true);
+                }
+            }
+
+            logModuleCall(
+                    $this->getModuleName(), $this->getFullAction(), $this->getRequestData(), $responseData, print_r($responseData, true), $replaceVars
+            );
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param mixed $message
+     * @param int $userId
+     * @return $this
+     */
+    public function addActiveLog($message, $userId = 0)
+    {
+        if ($this->isDebugin())
+        {
+            if (is_string($message) === false)
+            {
+                $message = print_r($message, true);
+            }
+
+            logActivity($message, $userId);
+        }
+        return $this;
+    }
+
+    /**
+     * @return array
+     */
+    private function getRequestData()
+    {
+        return array_merge($this->request->request->all(), $this->request->query->all());
+    }
+
+    /**
+     * @return string
+     */
+    private function getModuleName()
+    {
+        return $this->addon->getConfigValue("name", 'proxmoxAddon');
+    }
+
+    /**
+     * @return bool
+     */
+    private function isDebugin()
+    {
+        return (bool) ((int) $this->addon->getConfigValue("debug", "0"));
+    }
+
+    /**
+     * @return string
+     */
+    private function getFullAction()
+    {
+        return $this->request->get('mg-page', 'Home') . $this->request->get('mg-action', 'Index');
+    }
+
+    public function addModuleLogError(Exceptions\Exception $exception, array $replaceVars = [])
+    {
+        $responseData = $exception->getOriginalException();
+        if (!$exception->isLogable())
+        {
+            return;
+        }
+
+        if (is_object($responseData))
+        {
+            $responseData = print_r($responseData, true);
+        }
+
+        if (!$responseData)
+        {
+            $responseData = [
+                'message' => $exception->getMgMessage(false),
+                'trace'   => $exception->getTrace()
+            ];
+        }
+
+        logModuleCall(
+                $this->getModuleName(), $this->getFullAction(), [
+            'deteils' => $exception->getDetailsToLog(),
+            'request' => $this->getRequestData()
+                ], $responseData, print_r($responseData, true), $replaceVars
+        );
+
+
+        return $this;
+    }
+}

+ 37 - 0
core/Helper/AdvancedUserHelper.php

@@ -0,0 +1,37 @@
+<?php
+
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+
+use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\CustomField;
+
+class AdvancedUserHelper
+{
+
+    protected $userid;
+
+    /**
+     * AdvancedUserHelper constructor.
+     * @param $userid
+     */
+    public function __construct($userid)
+    {
+        $this->userid = $userid;
+    }
+
+
+    public  function isAdvanced(){
+        if(!CustomField::ofClient()->ofName(\ModulesGarden\Servers\ProxmoxCloudVps\App\Enum\CustomField::ADVANCED_USER)->count()){
+            return false;
+        }
+        return CustomField::ofClient()
+                     ->ofName(\ModulesGarden\Servers\ProxmoxCloudVps\App\Enum\CustomField::ADVANCED_USER)
+                    ->first()
+                    ->customFieldValue
+                    ->where('relid',$this->userid)
+                    ->first()
+                    ->value == "on";
+    }
+
+}

+ 101 - 0
core/Helper/BuildUrl.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use \ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use \ModulesGarden\ProxmoxAddon\Core\App\Controllers\Instances\Addon\Config;
+use function \ModulesGarden\ProxmoxAddon\Core\Helper\isAdmin;
+use function \ModulesGarden\ProxmoxAddon\Core\Helper\getModuleName;
+
+/**
+ * Description of BuildUrl
+ *
+ * @author Rafał Ossowski <rafal.os@modulesgarden.com>
+ */
+class BuildUrl
+{
+
+    public static function getUrl($controller = null, $action = null, array $params = [], $isFullUrl = true)
+    {
+        if (isAdmin())
+        {
+            $url = 'addonmodules.php?module=' . getModuleName();
+        }
+        else
+        {
+            $url = 'index.php?m=' . getModuleName();
+        }
+
+        if ($controller)
+        {
+            $url .= '&mg-page=' . $controller;
+            if ($action)
+            {
+                $url .= '&mg-action=' . $action;
+            }
+
+            if ($params)
+            {
+                $url .= '&' . http_build_query($params);
+            }
+        }
+
+        if ($isFullUrl)
+        {
+            $baseUrl = self::baseUrl();
+            $url     = $baseUrl . $url;
+        }
+
+        return $url;
+    }
+
+    public static function getBaseUrl()
+    {
+        return self::baseUrl();
+    }
+
+    public static function getNewUrl($protocol = 'http', $host = 'modulesgarden.com', $params = [])
+    {
+        $url = "{$protocol}://{$host}";
+        if ($params)
+        {
+            $url .= '?' . http_build_query($params);
+        }
+        return $url;
+    }
+
+    public static function getAssetsURL()
+    {
+        $addon    = ServiceLocator::call(Config::class);
+        $name     = $addon->getConfigValue('systemName');
+        $template = $addon->getConfigValue('template', 'default');
+
+        if (isAdmin())
+        {
+            return '../modules/' . self::getType() . '/' . $name . '/templates/admin/assets';
+        }
+
+        return 'modules/' . self::getType() . '/' . $name . '/templates/client/' . $template . '/assets';
+    }
+
+    public static function getType()
+    {
+        if (strpos(trim(self::class, '\\'), 'ModulesGarden\Servers') === 0)
+        {
+            return 'servers';
+        }
+
+        return 'addons';
+    }
+
+    private static function baseUrl()
+    {
+        $host   = $GLOBALS['CONFIG']['SystemURL'] ? $GLOBALS['CONFIG']['SystemURL'] : $_SERVER['HTTP_HOST'];
+        $url = \parse_url( $host );
+        $surfix = $_SERVER['PHP_SELF'];
+        $surfix = explode('/', $surfix);
+        array_pop($surfix);
+        $surfix = implode('/', $surfix);
+        return "{$url['scheme']}://{$url['host']}{$surfix}/";
+    }
+}

+ 40 - 0
core/Helper/Converter/Json.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper\Converter;
+
+class Json
+{
+
+    public static function encodeUTF8($array)
+    {
+        array_walk_recursive($array, function(&$item, $key)
+        {
+            if (is_array($item) || is_object($item))
+            {
+                foreach ($item as &$param)
+                {
+                    if (is_array($param) || is_object($param))
+                    {
+                        $param = self::encodeUTF8($param);
+                    }
+                    else
+                    {
+                        if (!mb_detect_encoding($param, 'utf-8', true))
+                        {
+                            $param = utf8_encode($param);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if (!mb_detect_encoding($item, 'utf-8', true))
+                {
+                    $item = utf8_encode($item);
+                }
+            }
+        });
+
+        return $array;
+    }
+}

+ 125 - 0
core/Helper/Country.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+
+/**
+ * Description of Country
+ *
+ * @author inbs
+ */
+class Country
+{
+    protected static $instance = null;
+    protected $path            = '';
+    protected $type            = '';
+    protected $country         = [];
+
+    public function __construct()
+    {
+        global $GLOBALS;
+        $varsionArray = explode('.', $GLOBALS['CONFIG']['Version']);
+
+        $varsion = $varsionArray[0] . "." . $varsionArray[1] . "." . $this->getOnlyNumber($varsionArray[2]);
+
+        if (version_compare($varsion, '7.0.0', '>='))
+        {
+            $this->path = ModuleConstants::getFullPathWhmcs('resources', 'country', 'dist.countries.json');
+            $this->type = 'json';
+        }
+        else
+        {
+            $this->path = ModuleConstants::getFullPathWhmcs('includes', 'countries.php');
+            $this->type = 'php';
+        }
+
+        $this->initCountry();
+    }
+
+    protected function getOnlyNumber($string)
+    {
+        $length = strlen($string);
+        $return = '';
+        for ($i = 0; $i < $length; $i++)
+        {
+            if (is_numeric($string[$i]))
+            {
+                $return .= $string[$i];
+                continue;
+            }
+            break;
+        }
+
+        return $return;
+    }
+
+    protected function initCountry()
+    {
+        if ($this->type === 'json')
+        {
+            foreach (Reader::read($this->path)->get() as $code => $data)
+            {
+                $this->country[$code] = $data['name'];
+            }
+        }
+        else
+        {
+            ModuleConstants::requireFile($this->path);
+            $this->country = $countries;
+        }
+    }
+
+    public function getFullName($code)
+    {
+        if (isset($this->country[$code]))
+        {
+            return $this->country[$code];
+        }
+
+        return null;
+    }
+
+    public function getCountry($withKey = true)
+    {
+        if ($withKey)
+        {
+            return $this->country;
+        }
+
+        $country = [];
+        foreach ($this->country as $code => $name)
+        {
+            $country[] = [
+                'code' => $code,
+                'name' => $name
+            ];
+        }
+
+        return $country;
+    }
+
+    public function getCode($fullName)
+    {
+        if (in_array($fullName, $this->country, true))
+        {
+            return array_search($fullName, $this->country, true);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return Country
+     */
+    public static function getInstance()
+    {
+        if (self::$instance === null)
+        {
+            self::$instance = new self();
+        }
+
+        return self::$instance;
+    }
+}

+ 210 - 0
core/Helper/DatabaseCache.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\Exceptions\Exception;
+use \ModulesGarden\ProxmoxAddon\Core\HandlerError\ErrorCodes\ErrorCodesLib;
+use \ModulesGarden\ProxmoxAddon\Core\UI\Traits\RequestObjectHandler;
+
+/**
+ * Helper for caching data in database
+ * stores json in the $modelClass
+ *
+ * @author inbs
+ */
+class DatabaseCache
+{
+
+    use RequestObjectHandler;
+    /**
+     * @var string
+     */
+    protected $modelClass = '\ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model';
+
+    /**
+     * @var misc
+     * whatever you need to store
+     */
+    protected $data = null;
+
+    /**
+     * @var int
+     * timestamp of the last update
+     */
+    protected $lastDataUpdate = null;
+
+    /**
+     * @var int
+     * valid period for stored data, after this it will be autoupdated by callback
+     * in secounds like a timestamp
+     */
+    protected $validPeriod = 300;
+
+    /**
+     * @var string
+     * key for data store
+     */
+    protected $dataKey = null;
+
+    /**
+     * @var \ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model
+     */
+    protected $model = null;
+
+    /**
+     * @var callable
+     * function returning data for the key
+     */
+    protected $callback        = null;
+    protected $assocJsonDecode = false;
+
+    public function __construct($key, $callback, $timeout = 300, $assoc = false, $forceReload = false)
+    {
+        $this->model           = sl($this->modelClass);
+        $this->dataKey         = $key;
+        $this->validPeriod     = (int) $timeout;
+        $this->callback        = $callback;
+        $this->assocJsonDecode = $assoc;
+
+        $this->initLoadProcess($forceReload);
+    }
+
+    /**
+     * wrapper for loading data process
+     * @param bool $forceReload
+     */
+    protected function initLoadProcess($forceReload = false)
+    {
+        if ($forceReload)
+        {
+            $this->updateRemoteData();
+
+            return;
+        }
+
+        $this->loadDataFromDb();
+
+        if (!$this->isDataValid())
+        {
+            $this->updateRemoteData();
+        }
+    }
+
+    /**
+     * loads remote data and updates to local storage
+     */
+    protected function updateRemoteData()
+    {
+        $data = $this->loadRemoteData();
+        $time = \time();
+
+        $this->updateDbCache($data, $time);
+
+        $this->data           = $data;
+        $this->lastDataUpdate = $time;
+    }
+
+    /**
+     * updates data in database
+     */
+    protected function updateDbCache($data, $time)
+    {
+        $dbData = $this->model->where('setting', $this->dataKey)->first();
+        if ($dbData)
+        {
+            $dbData->update(['value' => json_encode($data)]);
+        }
+        else
+        {
+            $this->model->create([
+                'setting' => $this->dataKey,
+                'value'   => json_encode($data)
+            ]);
+        }
+
+        $dbDataTime = $this->model->where('setting', $this->dataKey . '_lastDataUpdate')->first();
+        if ($dbDataTime)
+        {
+            $dbDataTime->update(['value' => $time]);
+        }
+        else
+        {
+            $this->model->create([
+                'setting' => $this->dataKey . '_lastDataUpdate',
+                'value'   => $time
+            ]);
+        }
+    }
+
+    /**
+     * using callback function to load data from custom source
+     */
+    protected function loadRemoteData()
+    {
+        if (!is_callable($this->callback))
+        {
+            throw new Exception(ErrorCodesLib::CORE_CDB_000001, ['callback' => $this->callback]);
+        }
+
+        $data = call_user_func_array($this->callback, []);
+
+        return $data;
+    }
+
+    /**
+     * returns loaded data
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * static wrpper for creating instance and retriving data
+     */
+    public static function loadData($key, $callback, $timeout = 300, $assoc = false, $forceReload = false)
+    {
+        $loader = new DatabaseCache($key, $callback, $timeout, $assoc, $forceReload);
+
+        return $loader->getData();
+    }
+
+    /**
+     * loads data stored in DB
+     */
+    protected function loadDataFromDb()
+    {
+        $dbData = $this->model->where('setting', $this->dataKey)->first();
+
+        if (!$dbData)
+        {
+            return false;
+        }
+
+        $this->data = json_decode($dbData->value, $this->assocJsonDecode);
+
+        $lastUpdate = $this->model->where('setting', $this->dataKey . '_lastDataUpdate')->first();
+        if ($lastUpdate)
+        {
+            $this->lastDataUpdate = (int) $lastUpdate->value;
+        }
+    }
+
+    /**
+     * Check if data is still befeore renewal time
+     */
+    protected function isDataValid()
+    {
+        if (!$this->data || !$this->lastDataUpdate)
+        {
+            return false;
+        }
+
+        if (time() > ($this->lastDataUpdate + $this->validPeriod))
+        {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 57 - 0
core/Helper/DatabaseHelper.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use ModulesGarden\ProxmoxAddon\Core\FileReader\Reader;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use Illuminate\Database\Capsule\Manager;
+
+/**
+ * Autometes some of database queries
+ *
+ * @author
+ */
+class DatabaseHelper
+{
+
+    /**
+     * Helper to perform raw queries for module
+     *
+     * @param string $file
+     * @return array
+     */
+    public function performQueryFromFile($file = '')
+    {
+        return $this->checkIsAllSuccess(array_map([$this, "execute"], $this->getQueries($file)));
+    }
+
+    protected function checkIsAllSuccess(array $array = [])
+    {
+        return in_array(false, $array, true);
+    }
+
+    protected function execute(&$query)
+    {
+        try
+        {
+            $pdo = Manager::connection()->getPdo();
+            if (empty($query) === false)
+            {
+                $statement = $pdo->prepare($query);
+                $statement->execute();
+            }
+            $query = true;
+        }
+        catch (\PDOException $ex)
+        {
+            ServiceLocator::call('errorManager')->addError(self::class, $ex->getMessage(), ['query' => $query]);
+            $query = false;
+        }
+        return $query;
+    }
+
+    protected function getQueries($file)
+    {
+        return explode(';', Reader::read($file)->get());
+    }
+}

+ 151 - 0
core/Helper/DomainHelper.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use \ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+
+/**
+ * Klasa walidująca domenę oraz rozbijająca je na poszczególne cześci
+ */
+class DomainHelper
+{
+    private $fullName;
+    private $subdomain;
+    private $domain;
+    private $tld;
+    private static $list = [];
+
+    /**
+     * 
+     * @param string $domain
+     */
+    public function __construct($domain)
+    {
+        $this->fullName = trim(strtolower($domain));
+        $this->split();
+    }
+
+    private function loadTldList()
+    {
+        if (!empty(self::$list))
+        {
+            return;
+        }
+
+        $path = ModuleConstants::getCoreConfigDir() . DS . 'tldList' . DS . 'tld.list';
+        if (!file_exists($path))
+        {
+            return;
+        }
+
+        $data = file($path);
+        foreach ($data as $line)
+        {
+            /* Ignore blank lines and comments. */
+            if (preg_match('#(^//)|(^\s*$)#', $line))
+            {
+                continue;
+            }
+
+            self::$list[] = preg_replace('/[\r\n]/', '', $line);
+        }
+    }
+
+    private function split()
+    {
+        $this->loadTldList();
+        $components = array_reverse((explode('.', $this->fullName)));
+
+        $lastMatch = '';
+        $con       = '';
+        foreach ($components as $part)
+        {
+            $con = "{$part}.{$con}";
+            $con = trim($con, '.');
+
+            if (in_array($con, self::$list))
+            {
+                $lastMatch = $con;
+            }
+        }
+
+        $this->tld       = $lastMatch;
+        $noTld           = preg_replace('/' . preg_quote($lastMatch) . '$/', '', $this->fullName);
+        $noTld           = trim($noTld, '.');
+        $components      = explode('.', $noTld);
+        $this->domain    = array_pop($components);
+        $this->subdomain = implode('.', $components);
+    }
+
+    /**
+     * Pobieranie pełnej nazwy domeny
+     * @return string 
+     */
+    public function getFullName()
+    {
+        return $this->fullName;
+    }
+
+    /**
+     * Pobieranie tld (bez kropki)
+     * @return string
+     */
+    public function getTLD()
+    {
+        return $this->tld;
+    }
+
+    /**
+     * Pobieranie tld
+     * @return string
+     */
+    public function getTLDWithDot()
+    {
+        return ($this->tld != '') ? ("." . $this->tld) : "";
+    }
+
+    /**
+     * Pobieranie nazwy głównej domeny (bez tld)
+     * @return string
+     */
+    public function getDomain()
+    {
+        return $this->domain;
+    }
+
+    /**
+     * Pobieranie nazwy głównej domeny z tld
+     * @return string
+     */
+    public function getDomainWithTLD()
+    {
+        return $this->domain . '.' . $this->tld;
+    }
+
+    /**
+     * Pobieranie subdomeny; zwraca pustry string jeżeli nie istnieje
+     * @return string
+     */
+    public function getSubdomain()
+    {
+        return $this->subdomain;
+    }
+
+    /**
+     * Sprawdzanie czy nazwa zawiera w sobie subdomenę
+     * @return boolean
+     */
+    public function isSubdamain()
+    {
+        return !empty($this->subdomain);
+    }
+
+    /**
+     * Sprawdzanie czy domena jest poprawna
+     * @return boolean
+     */
+    public function isValid()
+    {
+        return !empty($this->domain) && !empty($this->tld);
+    }
+}

+ 379 - 0
core/Helper/Functions.php

@@ -0,0 +1,379 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+use ModulesGarden\ProxmoxAddon\App\Services\Vm;
+use ModulesGarden\ProxmoxAddon\Core\Events\Dispatcher;
+use ModulesGarden\ProxmoxAddon\Core\Http\JsonResponse;
+use ModulesGarden\ProxmoxAddon\Core\Http\RedirectResponse;
+use ModulesGarden\ProxmoxAddon\Core\Http\Response;
+use ModulesGarden\ProxmoxAddon\Core\ServiceLocator;
+use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
+use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
+use ModulesGarden\ProxmoxAddon\Core\Cache\CacheManager;
+use ModulesGarden\ProxmoxAddon\Core\Logger\Entity;
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\json'))
+{
+
+    /**
+     * @param array $data
+     * @return JsonResponse
+     */
+    function json(array $data = [])
+    {
+        $data = ['data' => $data];
+        return JsonResponse::create($data);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\response'))
+{
+
+    /**
+     * @param array $data
+     * @return \ModulesGarden\ProxmoxAddon\Core\Http\Response
+     */
+    function response(array $data = [])
+    {
+        return Response::create()->setData($data);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\cache'))
+{
+
+    /**
+     * @param array $data
+     * @return \ModulesGarden\ProxmoxAddon\Core\Interfaces\CacheManagerInterface
+     */
+    function cache($key = null)
+    {
+        return ($key === null) ? CacheManager::cache() : CacheManager::cache($key);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\redirect'))
+{
+
+    /**
+     * @param array $data
+     * @return JsonResponse
+     */
+    function redirect($controller, $action, array $params = [])
+    {
+        return RedirectResponse::createMG($controller, $action, $params);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\redirectByUrl'))
+{
+
+    /**
+     * @param array $data
+     * @return JsonResponse
+     */
+    function redirectByUrl($url, array $params = [])
+    {
+        return RedirectResponse::createByUrl($url, $params);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\t'))
+{
+
+    /**
+     * @param array $data
+     * @return JsonResponse
+     */
+    function t(array $data = [])
+    {
+        return ServiceLocator::call('lang');
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\errorLog'))
+{
+
+    function errorLog($message = "", $request = [], $response = [], $vars = [], $beforeVars = [], $level = Entity::LEVEL_HIGHT, $reference = [])
+    {
+        $entitylogger = sl('entityLogger');
+        $entitylogger
+                ->setMessage($message)
+                ->setLevel($level)
+                ->setType(Entity::TYPE_ERROR)
+                ->setReference(Entity::convertToId($reference), Entity::convertToClassName($reference))
+                ->setRequest($request)
+                ->setResponse($response)
+                ->setBeforeVars($beforeVars)
+                ->setVars($vars);
+        return $entitylogger->save();
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\successLog'))
+{
+
+    function successLog($message = "", $request = [], $response = [], $vars = [], $beforeVars = [], $level = Entity::LEVEL_LOW, $reference = [])
+    {
+        $entitylogger = sl('entityLogger');
+        $entitylogger
+                ->setMessage($message)
+                ->setLevel($level)
+                ->setType(Entity::TYPE_SUCCESS)
+                ->setReference(Entity::convertToId($reference), Entity::convertToClassName($reference))
+                ->setRequest($request)
+                ->setResponse($response)
+                ->setBeforeVars($beforeVars)
+                ->setVars($vars);
+        return $entitylogger->save();
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\infoLog'))
+{
+
+    function infoLog($message = "", $request = [], $response = [], $vars = [], $beforeVars = [], $level = Entity::LEVEL_LOW, $reference = [])
+    {
+        $entitylogger = sl('entityLogger');
+        $entitylogger
+                ->setMessage($message)
+                ->setLevel($level)
+                ->setType(Entity::TYPE_INFO)
+                ->setReference(Entity::convertToId($reference), Entity::convertToClassName($reference))
+                ->setRequest($request)
+                ->setResponse($response)
+                ->setBeforeVars($beforeVars)
+                ->setVars($vars);
+        return $entitylogger->save();
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\criticalLog'))
+{
+
+    function criticalLog($message = "", $request = [], $response = [], $vars = [], $beforeVars = [], $level = Entity::LEVEL_LOW, $reference = [])
+    {
+        $entitylogger = sl('entityLogger');
+        $entitylogger
+                ->setMessage($message)
+                ->setLevel($level)
+                ->setType(Entity::TYPE_CRITICAL)
+                ->setReference(Entity::convertToId($reference), Entity::convertToClassName($reference))
+                ->setRequest($request)
+                ->setResponse($response)
+                ->setBeforeVars($beforeVars)
+                ->setVars($vars);
+        return $entitylogger->save();
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\debugLog'))
+{
+
+    function debugLog($message = "", $request = [], $response = [], $vars = [], $beforeVars = [], $level = Entity::LEVEL_LOW, $reference = [])
+    {
+        $entitylogger = sl('entityLogger');
+        $entitylogger
+                ->setMessage($message)
+                ->setLevel($level)
+                ->setType(Entity::TYPE_DEBUG)
+                ->setReference(Entity::convertToId($reference), Entity::convertToClassName($reference))
+                ->setRequest($request)
+                ->setResponse($response)
+                ->setBeforeVars($beforeVars)
+                ->setVars($vars);
+        return $entitylogger->save();
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\sl'))
+{
+
+    /**
+     * @param string $class
+     * @param string|null $method
+     * @return Vm
+     */
+    function sl($class, $method = null)
+    {
+        $return = null;
+
+        if ($class != null && $method == null)
+        {
+            $return = ServiceLocator::call($class);
+        }
+        elseif ($class != null && $method != null)
+        {
+            $return = ServiceLocator::call($class, $method);
+        }
+        return $return;
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\di'))
+{
+
+    /**
+     * @param string|null $class
+     * @param string|null $method
+     * @param bool $isCreate
+     * @return type
+     */
+    function di($class, $method = null, $isCreate = true)
+    {
+        $return     = null;
+        $methodName = ($isCreate) ? 'create' : 'get';
+
+        if ($class != null && $method == null)
+        {
+            $return = DependencyInjection::{$methodName}($class);
+        }
+        elseif ($class != null && $method != null)
+        {
+            $return = DependencyInjection::{$methodName}($class, $method);
+        }
+
+        return $return;
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\isAdmin'))
+{
+
+    /**
+     * @return bool
+     */
+    function isAdmin()
+    {
+        $request = sl('request');
+        $server  = $request->server->all();
+        $session = $request->getSession();
+
+        if (isset($session['adminid']) && isset($server['REQUEST_URI']) && strpos($server['REQUEST_URI'], '/' . getAdminDirName() . '/') !== false)
+        {
+            return true;
+        }
+
+        return false;
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\getAdminDirName'))
+{
+
+    /**
+     * @return string
+     */
+    function getAdminDirName()
+    {
+        $fileName = 'configuration.php';
+        $filePath = ModuleConstants::getFullPathWhmcs();
+
+        global $customadminpath;
+        if (!$customadminpath && file_exists($filePath . DIRECTORY_SEPARATOR . $fileName))
+        {
+            include_once $filePath . DIRECTORY_SEPARATOR . $fileName;
+        }
+
+        if ($customadminpath && is_string($customadminpath))
+        {
+            return $customadminpath;
+        }
+
+        return 'admin';
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\getModuleName'))
+{
+
+    /**
+     * @return string
+     */
+    function getModuleName()
+    {
+        $request = sl('request');
+
+        if (isAdmin())
+        {
+            $data = $request->get('module', $request->query->get('module', 'proxmoxAddon'));
+            return isset($data) ? $data : '';
+        }
+
+        $data = $request->get('m', $request->query->get('m', 'proxmoxAddon'));
+        return isset($data) ? $data : '';
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\view'))
+{
+
+    /**
+     * main View Controler
+     * 
+     * @return \ModulesGarden\ProxmoxAddon\Core\UI\View
+     */
+    function view($template = null)
+    {
+        $request = sl('request');
+
+        if ($request->get('ajax') && $request->get('namespace') != null && $request->get('namespace') != '' && $request->get('namespace') != 'undefined')
+        {
+            $view      = sl('viewAjax');
+            $namespace = $request->get('namespace');
+            $view->initAjaxElementContext($namespace);
+
+            return $view;
+        }
+
+        $view = sl('view');
+
+        if ($template)
+        {
+            $view->setTemplate($template);
+        }
+
+        return $view;
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\convertStringToNamespace'))
+{
+
+    function convertStringToNamespace($string)
+    {
+        return str_replace("_", "\\", $string);
+    }
+}
+
+if (!function_exists('\ModulesGarden\ProxmoxAddon\Core\Helper\getRequest'))
+{
+
+    function getRequest()
+    {
+        return sl(\ModulesGarden\ProxmoxAddon\Core\Http\Request::class);
+    }
+}
+
+if (!function_exists('fire'))
+{
+
+    /**
+     * Fire event!
+     * @param $event
+     */
+    function fire($event)
+    {
+        return DependencyInjection::call(Dispatcher::class)->fire($event);
+    }
+}
+
+if(!function_exists('queue'))
+{
+    function queue($job, $arguments, $parentId = null, $relType = null, $relId = null, $customId = null)
+    {
+        return DependencyInjection::call(Dispatcher::class)->queue($job, $arguments, $parentId, $relType, $relId, $customId);
+    }
+
+}

+ 107 - 0
core/Helper/RandomStringGenerator.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace ModulesGarden\ProxmoxAddon\Core\Helper;
+
+/**
+ * Helper for generating random strings
+ *
+ * @author Sławomir Miśkowicz <slawomir@modulesgarden.com>
+ */
+class RandomStringGenerator
+{
+    protected $stringLength   = 10;
+    protected $charSet        = '';
+    protected $upperCharSet   = 'QWERTYUIOPLKJHGFDSAZXCVBNM';
+    protected $lowerCharSet   = 'qwertyuioplkjhgfdsazxcvbnm';
+    protected $numbersCharSet = '0123456789';
+    protected $useUppercase   = false;
+    protected $useLowercase   = true;
+    protected $useNumbers     = true;
+
+    public function __construct($stringLength = null, $useNumbers = true, $useLowercase = true, $useUppercase = false)
+    {
+        $this->setLength($stringLength);
+
+        $this->setUseNumbers($useNumbers);
+        $this->setUseUppercase($useUppercase);
+        $this->setUseLowercase($useLowercase);
+    }
+
+    public function setLength($stringLength)
+    {
+        if ((int) $stringLength > 0)
+        {
+            $this->stringLength = (int) $stringLength;
+        }
+    }
+
+    public function genRandomString($const = null)
+    {
+        $randString = '';
+        while (strlen($randString) < $this->stringLength)
+        {
+            $number     = rand(0, strlen($this->charSet) - 1);
+            $randString .= $this->charSet[$number];
+        }
+
+        if (is_string($const))
+        {
+            $randString = $const . '_' . $randString;
+        }
+
+        return $randString;
+    }
+
+    public function loadCharSet()
+    {
+        $this->charSet = '';
+        if ($this->useNumbers)
+        {
+            $this->charSet .= $this->numbersCharSet;
+        }
+        if ($this->useLowercase)
+        {
+            $this->charSet .= $this->lowerCharSet;
+        }
+        if ($this->useUppercase)
+        {
+            $this->charSet .= $this->upperCharSet;
+        }
+
+        //use default set if someone disables all sets
+        if ($this->charSet === '')
+        {
+            $this->charSet = $this->numbersCharSet . $this->lowerCharSet;
+        }
+    }
+
+    public function setUseNumbers($value = true)
+    {
+        if (is_bool($value))
+        {
+            $this->useNumbers = $value;
+        }
+
+        $this->loadCharSet();
+    }
+
+    public function setUseLowercase($value = true)
+    {
+        if (is_bool($value))
+        {
+            $this->useLowercase = $value;
+        }
+
+        $this->loadCharSet();
+    }
+
+    public function setUseUppercase($value = true)
+    {
+        if (is_bool($value))
+        {
+            $this->useUppercase = $value;
+        }
+
+        $this->loadCharSet();
+    }
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels