tools.php 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202
  1. <?php
  2. namespace MGModule\DNSManager2\controllers\addon\admin;
  3. use MGModule\DNSManager2 as main;
  4. use MGModule\DNSManager2\helpers\WHMCS\Domain;
  5. use MGModule\DNSManager2\helpers\WHMCS\Service;
  6. use MGModule\DNSManager2\mgLibs\custom;
  7. use MGModule\DNSManager2\mgLibs\custom\AjaxResponse;
  8. use MGModule\DNSManager2\mgLibs\custom\dns;
  9. use MGModule\DNSManager2\mgLibs\custom\helpers;
  10. use MGModule\DNSManager2\mgLibs\MySQL as mysql;
  11. use MGModule\DNSManager2\models\custom\clientfiles;
  12. use MGModule\DNSManager2\models\custom\globalsetting;
  13. use MGModule\DNSManager2\models\custom\server;
  14. use MGModule\DNSManager2\models\custom\task;
  15. class tools extends main\mgLibs\process\abstractController{
  16. function indexHTML($input, $vars = array()) {
  17. $cron_last_run = globalsetting\GlobalSetting::byKey(globalsetting\GlobalSettingEnum::CRON_LAST_RUN)->value;
  18. $vars['cron_path'] = main\addon::getMainDIR() . DS . 'cron' . DS . 'cron.php';
  19. $vars['cron_last_run'] = custom\helpers\TimeDiffHelper::diff($cron_last_run)->minutes;
  20. $vars['cron_run_time_period'] = globalsetting\GlobalSetting::byKey(globalsetting\GlobalSettingEnum::CRON_RUN_TIME_PERIOD)->value;
  21. $vars['available_record_types'] = dns\Core::getAvailableRecordTypes();
  22. $vars['available_no_active_domains'] = Domain::getDomainsStatusesAvailableToDelete();
  23. $vars['available_no_active_service'] = Service::getServicesStatusesAvailableToDelete();
  24. $vars['minutes'] = array(0,5,10,15,20,40,60,120,180,360,720,1440,2880,4320,10080,43200);
  25. $vars['settings'] = array();
  26. foreach(globalsetting\Repository::factory()->get() as $setting) {
  27. $vars['settings'][$setting->key] = $setting->value;
  28. }
  29. $vars['settings']['cron_cleaner_zone_at_least_one'] = unserialize($vars['settings']['cron_cleaner_zone_at_least_one']);
  30. $vars['settings']['cron_cleaner_no_active_domains'] = unserialize($vars['settings']['cron_cleaner_no_active_domains']);
  31. $vars['settings']['cron_cleaner_no_active_service'] = unserialize($vars['settings']['cron_cleaner_no_active_service']);
  32. $vars['cron_last_run'] = intval($vars['cron_last_run']);
  33. $then = new \DateTime(date('Y-m-d H:i:s', time() - $vars['cron_last_run'] * 60));
  34. $now = new \DateTime(date('Y-m-d H:i:s',time()));
  35. $diff = $then->diff($now);
  36. $vars['cron_last_run_years'] = $diff->y;
  37. $vars['cron_last_run_months'] = $diff->m;
  38. $vars['cron_last_run_days'] = $diff->d;
  39. $vars['cron_last_run_hours'] = $diff->h;
  40. $vars['cron_last_run_minutes'] = $diff->i;
  41. return array(
  42. 'tpl' => 'main',
  43. 'vars' => $vars
  44. );
  45. }
  46. public function cronJSON($input, $vars = array()) {
  47. return AjaxResponse::I()->refreshPage($this->cronHTML($input, $vars))->toArray();
  48. }
  49. public function cronHTML($input, $vars = array())
  50. {
  51. return $this->indexHTML($input, $vars);
  52. }
  53. public function saveCronSettingsJSON($input, $vars = array())
  54. {
  55. $input['settings']['cron_cleaner_zone_at_least_one'] = serialize($input['settings']['cron_cleaner_zone_at_least_one']);
  56. $input['settings']['cron_cleaner_no_active_service'] = serialize($input['settings']['cron_cleaner_no_active_service']);
  57. $input['settings']['cron_cleaner_no_active_domains'] = serialize($input['settings']['cron_cleaner_no_active_domains']);
  58. foreach($input['settings'] as $k => $v)
  59. {
  60. $v = ($k == 'cron_cleaner_notify_only' && empty($v)) ? 0 : $v;
  61. globalsetting\GlobalSetting::set($k, $v);
  62. }
  63. $response = AjaxResponse::I()->addInfo('changes_saved');
  64. if((int)$input['settings']['cron_cleaner_run_each'] > 0 && $input['settings']['cron_cleaner_notify_only'] != 'on')
  65. {
  66. $response->addWarning('cron_cleaner_warning');
  67. }
  68. return $response->toArray();
  69. }
  70. //===========================================================================================================
  71. //===========================================MIGRATION=======================================================
  72. //===========================================================================================================
  73. public function migrationJSON($input, $vars = array()) {
  74. return AjaxResponse::I()->refreshPage($this->migrationHTML($input, $vars))->toArray();
  75. }
  76. public function migrationHTML($input, $vars = array()) {
  77. $vars['imports'] = array();
  78. $new_migrations = custom\TaskManager::getXTasksObjects(0, 'Migration:main', 0, task\TaskStatusEnum::START);
  79. foreach($new_migrations as $migration) {
  80. $desc = $migration->description();
  81. $vars['migrations'][] = array(
  82. 'id' => $migration->obj()->id,
  83. 'desc' => $desc,
  84. 'zones_moved' => '-',
  85. 'zones_total' => '-',
  86. 'progress' => -1,
  87. 'error' => $desc == 'Migration:main'?main\mgLibs\lang::T('one_of_servers_has_been_removed'):false,
  88. );
  89. }
  90. $in_progress_migrations = custom\TaskManager::getXTasksObjects(0, 'Migration:main', 0, task\TaskStatusEnum::IN_PROGRESS);
  91. $finished_migrations = custom\TaskManager::getXTasksObjects(0, 'Migration:main', 0, task\TaskStatusEnum::FINISHED);
  92. $migrations = array_merge($in_progress_migrations,$finished_migrations);
  93. foreach($migrations as $migration) {
  94. $rep = new task\Repository();
  95. $rep->byParentID($migration->obj()->id)->byName('Migration:migrate');
  96. $zones_total = $rep->count();
  97. $rep->byStatus(task\TaskStatusEnum::FINISHED);
  98. $zones_moved = $rep->count();
  99. $task = custom\TaskManager::getXTasksObjects(1, 'Migration:fetchZonesList', $migration->obj()->id, false);
  100. if(end($task))
  101. {
  102. $error2 = $this->getTaskError(end($task));
  103. }
  104. $desc = $migration->description();
  105. $error = $desc == 'Migration:main'?main\mgLibs\lang::T('one_of_servers_has_been_removed'):false;
  106. $vars['migrations'][] = array(
  107. 'id' => $migration->obj()->id,
  108. 'desc' => $desc,
  109. 'zones_moved' => $zones_moved,
  110. 'zones_total' => $zones_total,
  111. 'progress' => $zones_total == 0 ? 0 : ceil($zones_moved * 100 / $zones_total),
  112. 'error' => $error?:$error2,
  113. );
  114. }
  115. return array(
  116. 'tpl' => 'migration',
  117. 'vars' => $vars
  118. );
  119. }
  120. public function addMigrationJSON($input, $vars = array()) {
  121. $vars['servers'] = server\Repository::factory()->get();
  122. return AjaxResponse::I()->modal('add-migration', $vars)->toArray();
  123. }
  124. public function addMigrationSaveJSON($input, $vars = array()) {
  125. if( !isset($input['from_server'], $input['to_server']) )
  126. {
  127. return AjaxResponse::I()->toArray();
  128. }
  129. if($input['from_server'] == $input['to_server']) {
  130. AjaxResponse::I()->addError('you_have_to_choose_two_diffrent_servers');
  131. return AjaxResponse::I()->toArray();
  132. }
  133. server\Server::factory($input['from_server'])->getModule()->testConnection();
  134. server\Server::factory($input['to_server'])->getModule()->testConnection();
  135. $moveZone = $input['cron_migration_move_zone'] === 'on';
  136. custom\TaskManager::addTask('Migration:main', ['from' => $input['from_server'], 'to' => $input['to_server'], 'move_zone' => $moveZone]);
  137. AjaxResponse::I()->refreshPage($this->migrationHTML($input, $vars));
  138. AjaxResponse::I()->addInfo('new_task_added');
  139. return AjaxResponse::I()->toArray();
  140. }
  141. public function removeMigrationJSON($input, $vars = array()) {
  142. custom\TaskManager::removeTask($input['id']);
  143. AjaxResponse::I()->refreshPage($this->migrationHTML($input, $vars));
  144. AjaxResponse::I()->addInfo('migration_removed');
  145. return AjaxResponse::I()->toArray();
  146. }
  147. public function showListMigrationJSON($input, $vars = array()) {
  148. $vars['migrationid'] = $input['id'];
  149. $task = custom\TaskManager::getXTasks(1, 'Migration:fetchZonesList', $input['id'], false);
  150. $task = end($task);
  151. $vars['list'] = custom\TaskManager::getTaskResults($task->id);
  152. AjaxResponse::I()->refreshPage(array('tpl' => 'migration-list', 'vars' => $vars));
  153. return AjaxResponse::I()->toArray();
  154. }
  155. function refreshMigrationTableJSON($input, $vars = array()) {
  156. $task = custom\TaskManager::getXTasks(1, 'Migration:fetchZonesList', $input['id'], false);
  157. $task = end($task);
  158. $rep = task\result\Repository::factory()->byTaskID($task->id);
  159. $helper = new main\mgLibs\custom\RepoTableHelper($rep, $input, array('data'));
  160. $vars = $helper->getDataTableArray();
  161. foreach($helper->get() as $task) {
  162. $vars['data'][] = $this->dataTablesParseRow('migration-row', array('item' => $task, 'migrationid' => $input['id']));
  163. }
  164. return $vars;
  165. }
  166. public function migrateZonesJSON($input, $vars = array()) {
  167. foreach($input['zone'] as $result_id => $data) {
  168. if($data['migrate'] != 'on' && !isset($input['justOne']))
  169. continue;
  170. $result = new task\result\TaskResult($result_id);
  171. $task = custom\TaskManager::getTaskObjectByID($result->taskid);
  172. $main_task = $task->getParent(); //TODO: można zmniejszyć ilość danych poprzez odwołanie się do resulta
  173. $child = $main_task->addChild('migrate', array(
  174. 'domain' => $result->data['domain'],
  175. 'ip' => $result->data['ip'],
  176. 'resultid' => $result_id
  177. ));
  178. $result->data['status'] = 'migrating';
  179. $result->data['taskid'] = $child->obj()->id;
  180. $result->save();
  181. if(isset($input['runNow'])) {
  182. $child->run();
  183. if($child->getStatus() != 'finished') {
  184. AjaxResponse::I()->addError('something_went_wrong_during_migration');
  185. } else {
  186. AjaxResponse::I()->addInfo('zone_migrated');
  187. }
  188. }
  189. }
  190. if(!isset($input['runNow'])) {
  191. AjaxResponse::I()->addInfo('new_task_added');
  192. }
  193. return AjaxResponse::I()->toArray();
  194. //return $this->showListMigrationJSON($input, $vars);
  195. }
  196. public function scheduleMigrationJSON($input, $vars = array()) {
  197. custom\TaskManager::getTaskObjectByID($input['id'])->run();
  198. AjaxResponse::I()->addInfo('migration_scheduled');
  199. AjaxResponse::I()->refreshPage($this->migrationHTML($input, $vars));
  200. return AjaxResponse::I()->toArray();
  201. }
  202. //===========================================================================================================
  203. //===========================================IMPORT==========================================================
  204. //===========================================================================================================
  205. public function importJSON($input, $vars = array()) {
  206. return AjaxResponse::I()->refreshPage($this->importHTML($input, $vars))->toArray();
  207. }
  208. public function getClientsListJSON( $input, $vars = [] )
  209. {
  210. $repo = new main\models\whmcs\clients\repository();
  211. $search = explode(' ', trim($input['q']));
  212. if( count($search) >= 2 )
  213. {
  214. $repo = new main\models\whmcs\clients\repository();
  215. $repo->addAndSearch(['firstname', 'lastname', 'companyname'], [$search[0], $search[1]]);
  216. }
  217. else
  218. {
  219. $repo->search($input['q'], ['id', 'firstname', 'lastname', 'companyname']);
  220. $total = $repo->count();
  221. $repo = new main\models\whmcs\clients\repository();
  222. $repo->search($input['q'], ['id', 'firstname', 'lastname', 'companyname']);
  223. }
  224. $repo->limit(30);
  225. $repo->offset(($input['page'] - 1) * 30);
  226. $items = [];
  227. foreach( $repo->get() as $client )
  228. {
  229. $name = $client->firstname . ' ' . $client->lastname;
  230. $name .= $client->companyname ? ' ( ' . $client->companyname . ' )' : '';
  231. $items[] = [
  232. 'id' => $client->id,
  233. 'name' => $name,
  234. ];
  235. }
  236. AjaxResponse::I()->items = $items;
  237. AjaxResponse::I()->total = $total;
  238. echo json_encode(AjaxResponse::I()->toArray(), JSON_HEX_QUOT | JSON_HEX_TAG);
  239. die();
  240. }
  241. public function importHTML($input, $vars = array())
  242. {
  243. $vars['imports'] = array();
  244. $newImports = custom\TaskManager::getXTasksObjects(0, 'Import:main', 0, task\TaskStatusEnum::START);
  245. $this->parseNewImportTaskList($newImports, $vars['imports']);
  246. $newImportsToFile = custom\TaskManager::getXTasksObjects(0, 'ImportToFile:main', 0, task\TaskStatusEnum::START);
  247. $this->parseNewImportTaskList($newImportsToFile, $vars['imports']);
  248. $imports = custom\TaskManager::getXTasksObjects(0, 'Import:main', 0, task\TaskStatusEnum::IN_PROGRESS);
  249. $this->parseInProgresImportTaskList($imports, $vars['imports']);
  250. $toFileImports = custom\TaskManager::getXTasksObjects(0, 'ImportToFile:main', 0, task\TaskStatusEnum::IN_PROGRESS);
  251. $this->parseInProgresImportTaskList($toFileImports, $vars['imports'], 'ImportToFile');
  252. return array(
  253. 'tpl' => 'import',
  254. 'vars' => $vars
  255. );
  256. }
  257. private function getTaskError($task) {
  258. $erorr = false;
  259. if($task->getStatus() == task\TaskStatusEnum::ERROR) {
  260. $erorr = 'Error';
  261. $results = $task->getResults();
  262. $result = end($results);
  263. if(isset($result->data['error'])) {
  264. $erorr .= ' (' . $result->data['error'] . ')';
  265. }
  266. }
  267. return $erorr;
  268. }
  269. public function showListImportJSON($input, $vars = array())
  270. {
  271. $vars['importid'] = $input['id'];
  272. $autocheckMatches = globalsetting\Repository::factory()->byKey(
  273. globalsetting\GlobalSettingEnum::IMPORT_AUTOCHECK_MATCHES)->one();
  274. $vars['autocheckMatches'] = $autocheckMatches->value == 'on' ? true : false;
  275. AjaxResponse::I()->refreshPage(array('tpl' => 'import-list', 'vars' => $vars));
  276. return AjaxResponse::I()->toArray();
  277. }
  278. function refreshImportTableJSON($input, $vars = array())
  279. {
  280. $pRep = task\Repository::factory()->byParentID($input['id'])->one();
  281. $pRepName = explode(':', $pRep->name);
  282. $task = custom\TaskManager::getXTasks(1, $pRepName[0].':fetchZonesList', $input['id'], false);
  283. $task = end($task);
  284. $rep = task\result\Repository::factory()->byTaskID($task->id);
  285. $helper = new main\mgLibs\custom\RepoTableHelper($rep, $input, array('data'));
  286. $vars = $helper->getDataTableArray();
  287. foreach($helper->get() as $task)
  288. {
  289. $userid = $this->getUserIdByHostingDomain($task->data['domain']);
  290. if(empty($userid))
  291. {
  292. $userid = $this->getUserIdByDomain($task->data['domain']);
  293. }
  294. $user = false;
  295. if(!empty($userid))
  296. {
  297. $user = new main\models\whmcs\clients\client($userid);
  298. }
  299. $vars['data'][] = $this->dataTablesParseRow('import-row',
  300. array(
  301. 'item' => $task,
  302. 'importid' => $input['id'],
  303. 'user' => $user
  304. )
  305. );
  306. }
  307. return $vars;
  308. }
  309. public function importZonesJSON($input, $vars = array()) {
  310. $add = false;
  311. foreach($input['zone'] as $result_id => $data) {
  312. if($data['import'] != 'on' && !isset($input['justOne']) || empty($data['client']))
  313. continue;
  314. $result = new task\result\TaskResult($result_id);
  315. $task = custom\TaskManager::getTaskObjectByID($result->taskid);
  316. $main_task = $task->getParent(); //TODO: można zmniejszyć ilość danych poprzez odwołanie się do resulta
  317. $ex = explode('::', $data['service']);
  318. $child = $main_task->addChild('import', array(
  319. 'clientid' => $data['client'],
  320. 'domain' =>helpers\IdnaHelper::idnaDecode( $result->data['domain'] ),
  321. 'ip' => $result->data['ip'],
  322. 'resultid' => $result_id,
  323. 'type' => $ex[0],
  324. 'relid' => $ex[1],
  325. ));
  326. $result->data['status'] = 'importing';
  327. $result->data['taskid'] = $child->obj()->id;
  328. $result->save();
  329. if(isset($input['runNow'])) {
  330. $child->run();
  331. if($child->getStatus() != 'finished') {
  332. AjaxResponse::I()->addError('something_went_wrong_during_import');
  333. } else {
  334. AjaxResponse::I()->addInfo('zone_imported');
  335. }
  336. } else {
  337. if($add === false) {
  338. AjaxResponse::I()->addInfo('new_task_added');
  339. $add = true;
  340. }
  341. }
  342. }
  343. return AjaxResponse::I()->toArray();
  344. //return $this->showListImportJSON($input, $vars);
  345. }
  346. public function addImportJSON($input, $vars = array())
  347. {
  348. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.'bulkZones');
  349. $vars['isWritable'] = $fileManager->isStorageWritable();
  350. $vars['storagePath'] = $fileManager->getStoragePath();
  351. $rep = new server\Repository();
  352. $vars['servers'] = $rep->get();
  353. AjaxResponse::I()->modal('add-import', $vars);
  354. return AjaxResponse::I()->toArray();
  355. }
  356. public function addImportSaveJSON($input, $vars = array())
  357. {
  358. if(!isset($input['from_server']))
  359. {
  360. return ;
  361. }
  362. $server = server\Server::factory($input['from_server']);
  363. $server->getModule()->testConnection();
  364. if($input['toFile'] == 'on')
  365. {
  366. $fileName = helpers\ImportExportFileHelper::generateNewExportFileName($server->name);
  367. custom\TaskManager::addTask('ImportToFile:main', array('from' => $input['from_server'], 'toFile' => $fileName));
  368. }
  369. else
  370. {
  371. custom\TaskManager::addTask('Import:main', array('from' => $input['from_server']));
  372. }
  373. AjaxResponse::I()->refreshPage($this->importHTML($input, $vars));
  374. AjaxResponse::I()->addInfo('new_task_added');
  375. return AjaxResponse::I()->toArray();
  376. }
  377. public function removeImportJSON($input, $vars = array()) {
  378. custom\TaskManager::removeTask($input['id']);
  379. AjaxResponse::I()->refreshPage($this->importHTML($input, $vars));
  380. AjaxResponse::I()->addInfo('import_removed');
  381. return AjaxResponse::I()->toArray();
  382. }
  383. public function scheduleImportJSON($input, $vars = array()) {
  384. custom\TaskManager::getTaskObjectByID($input['id'])->run(true);
  385. AjaxResponse::I()->addInfo('import_scheduled');
  386. AjaxResponse::I()->refreshPage($this->importHTML($input, $vars));
  387. return AjaxResponse::I()->toArray();
  388. }
  389. public function getUserServicesJSON($input, $vars = array()) {
  390. $services = custom\manager\Par::getSortedArray($input['clientid']);
  391. $services[] = array(
  392. 'group_name' => main\mgLibs\lang::T('none'),
  393. 'package_name' => '',
  394. 'type' => 0,
  395. 'relid' => 0,
  396. );
  397. $vars['domain'] = strtolower($input['domain']);
  398. $vars['services'] = $services;
  399. $vars['id'] = $input['itemid'];
  400. AjaxResponse::I()->html = main\mgLibs\smarty::I()->view('user-services', $vars, main\addon::getModuleTemplatesDir().DS.'pages' . DS . 'tools');
  401. session_write_close();
  402. return AjaxResponse::I()->toArray();
  403. }
  404. //===========================================================================================================
  405. //===========================================TASKS===========================================================
  406. //===========================================================================================================
  407. public function tasksHTML($input, $vars = array()) {
  408. return array(
  409. 'tpl' => 'tasks',
  410. 'vars' => $vars
  411. );
  412. }
  413. public function tasksJSON($input, $vars = array()) {
  414. return AjaxResponse::I()->refreshPage($this->tasksHTML($input, $vars))->toArray();
  415. }
  416. public function refreshTasksTableJSON($input, $vars = array()) {
  417. $query = "SELECT dns_manager2_task.id AS id, dns_manager2_task.name AS name, dns_manager2_task.status AS status, dns_manager2_task.lastrun AS lastrun,
  418. dns_manager2_task.nextrun AS nextrun, dns_manager2_task.date AS date
  419. FROM dns_manager2_task
  420. WHERE parentid = 0";
  421. $columns = array('id', 'name', 'status', 'lastrun', 'nextrun', 'date');
  422. $helper = new main\mgLibs\custom\RawQueryTableHelper($query, $input, $columns);
  423. $vars = $helper->getDataTableArray();
  424. $vars['setRecord'] = array();
  425. foreach($helper->get() as $task) {
  426. $obj = custom\TaskManager::getTaskObject(new task\Task($task['id']));
  427. $setRecords = $this->parseInProgresSetRecordList($obj, $vars['setRecord']);
  428. if($task['name'] != 'DnsRecord:main')
  429. {
  430. $setRecords[0] = null;
  431. }
  432. $vars['data'][] = $this->dataTablesParseRow('task-row', array('task' => $task,'setRecord' => $setRecords[0], 'name' => $obj->description()));
  433. }
  434. return $vars;
  435. }
  436. public function taskRunNowJSON($input, $vars = array()) {
  437. custom\TaskManager::getTaskObjectByID($input['id'])->run(true);
  438. return AjaxResponse::I()->addInfo('task_finished')->toArray();
  439. }
  440. public function removeTaskJSON($input, $vars = array()) {
  441. custom\TaskManager::getTaskObjectByID($input['id'])->remove();
  442. return AjaxResponse::I()->addInfo('task_removed')->toArray();
  443. }
  444. public function removeTasksWithErrorsJSON() {
  445. $rep = main\models\custom\task\Repository::factory()->byStatus(main\models\custom\task\TaskStatusEnum::ERROR);
  446. foreach($rep->get() as $task) {
  447. $obj = custom\TaskManager::getTaskObject($task);
  448. $obj->remove();
  449. }
  450. return AjaxResponse::I()->addInfo('success')->toArray();
  451. }
  452. public function getUserIdByHostingDomain($domainName)
  453. {
  454. if(helpers\IdnaHelper::isDomainIdn($domainName))
  455. {
  456. $userid = mysql\query::query("SELECT userid FROM tblhosting WHERE BINARY domain = BINARY :domain OR BINARY domain = BINARY :idnDomain",
  457. array('domain' => $domainName, 'idnDomain' => helpers\IdnaHelper::idnaDecode($domainName))
  458. )->fetchColumn("userid");
  459. }
  460. else
  461. {
  462. $userid = mysql\query::query("SELECT userid FROM tblhosting WHERE BINARY domain = BINARY :domain",
  463. array('domain' => $domainName))->fetchColumn("userid");
  464. }
  465. return $userid;
  466. }
  467. public function getUserIdByDomain($domainName)
  468. {
  469. if(helpers\IdnaHelper::isDomainIdn($domainName))
  470. {
  471. $userid = mysql\query::query("SELECT userid FROM tbldomains WHERE BINARY domain = BINARY :domain OR BINARY domain = BINARY :idnDomain",
  472. array('domain' => $domainName, 'idnDomain' => helpers\IdnaHelper::idnaDecode($domainName))
  473. )->fetchColumn("userid");
  474. }
  475. else
  476. {
  477. $userid = mysql\query::query("SELECT userid FROM tbldomains WHERE BINARY domain = BINARY ?",
  478. array($domainName))->fetchColumn("userid");
  479. }
  480. return $userid;
  481. }
  482. public function parseNewImportTaskList($list, &$vars)
  483. {
  484. foreach($list as $import)
  485. {
  486. $vars[] = array(
  487. 'id' => $import->obj()->id,
  488. 'desc' => $import->description(),
  489. 'parsedDesc' => $this->praseTaskDescription($import->description()),
  490. 'zones_imported' => '-',
  491. 'zones_total' => '-',
  492. 'progress' => -1,
  493. 'type' => $import->getTaskTypeCode()
  494. );
  495. }
  496. }
  497. private function praseTaskDescription($desc)
  498. {
  499. $parts = explode(' from: ', $desc);
  500. $parts[0] .= ' from: ';
  501. $tmpParts = explode(' to: ', $parts[1]);
  502. if(count($tmpParts) > 1)
  503. {
  504. $parts[1] = $tmpParts[0];
  505. $parts[2] = ' to: ';
  506. $parts[3] = $tmpParts[1];
  507. }
  508. return $parts;
  509. }
  510. public function parseInProgresImportTaskList($list, &$vars, $type = 'Import')
  511. {
  512. foreach($list as $import)
  513. {
  514. $rep = new task\Repository();
  515. $childTaskType = strpos($type, 'Export') === 0 ? 'export' : 'import';
  516. $rep->byParentID($import->obj()->id)->byName($type.':'.$childTaskType);
  517. $zones_total = $rep->count();
  518. $rep->byStatus(task\TaskStatusEnum::FINISHED);
  519. $zones_imported = $rep->count();
  520. $task = custom\TaskManager::getXTasksObjects(1, $type.':fetchZonesList', $import->obj()->id, false);
  521. if(end($task))
  522. {
  523. $error = $this->getTaskError(end($task));
  524. }
  525. $vars[] = array(
  526. 'id' => $import->obj()->id,
  527. 'desc' => $import->description(),
  528. 'zones_imported' => $zones_imported,
  529. 'zones_total' => $zones_total,
  530. 'progress' => $zones_total == 0 ? 0 : ceil($zones_imported * 100 / $zones_total),
  531. 'error' => $error,
  532. 'parsedDesc' => $this->praseTaskDescription($import->description()),
  533. 'type' => $import->getTaskTypeCode()//strpos($import->description(), 'Import') === 0 ? 'Import' : 'Export'
  534. );
  535. }
  536. }
  537. public function addExportJSON($input, $vars = array())
  538. {
  539. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.'bulkZones');
  540. $vars['isReadable'] = $fileManager->isStorageReadable();
  541. $vars['storagePath'] = $fileManager->getStoragePath();
  542. $rep = new server\Repository();
  543. $vars['servers'] = $rep->get();
  544. $filesList = main\mgLibs\custom\helpers\ImportExportFileHelper::listFilesForBulkExport($fileManager);
  545. $vars['bFiles'] = $filesList;
  546. AjaxResponse::I()->modal('add-export', $vars);
  547. return AjaxResponse::I()->toArray();
  548. }
  549. public function addExportSaveJSON($input, $vars = array())
  550. {
  551. if(!isset($input['toServer']))
  552. {
  553. return ;
  554. }
  555. $server = server\Server::factory($input['toServer']);
  556. $server->getModule()->testConnection();
  557. custom\TaskManager::addTask('ExportFromFile:main', array('fromFile' => $input['fromFile'], 'toServer' => $input['toServer']));
  558. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  559. AjaxResponse::I()->addInfo('new_task_added');
  560. return AjaxResponse::I()->toArray();
  561. }
  562. public function backupsHTML($input, $vars = array())
  563. {
  564. $vars['backupsTasks'] = array();
  565. $newImportsToFile = custom\TaskManager::getXTasksObjects(0, 'ImportToFileWHMCS:main', 0, task\TaskStatusEnum::START);
  566. $this->parseNewImportTaskList($newImportsToFile, $vars['backupsTasks']);
  567. $toFileImports = custom\TaskManager::getXTasksObjects(0, 'ImportToFileWHMCS:main', 0, task\TaskStatusEnum::IN_PROGRESS);
  568. $this->parseInProgresImportTaskList($toFileImports, $vars['backupsTasks'], 'ImportToFileWHMCS');
  569. $newImportsToFile = custom\TaskManager::getXTasksObjects(0, 'ExportFromFile:main', 0, task\TaskStatusEnum::START);
  570. $this->parseNewImportTaskList($newImportsToFile, $vars['backupsTasks']);
  571. $toFileImports = custom\TaskManager::getXTasksObjects(0, 'ExportFromFile:main', 0, task\TaskStatusEnum::IN_PROGRESS);
  572. $this->parseInProgresImportTaskList($toFileImports, $vars['backupsTasks'], 'ExportFromFile');
  573. return array(
  574. 'tpl' => 'backups',
  575. 'vars' => $vars
  576. );
  577. }
  578. public function backupsJSON($input, $vars = array())
  579. {
  580. return AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars))->toArray();
  581. }
  582. public function downloandBackupFileJSON($input, $vars = array())
  583. {
  584. $type = $input['type'];
  585. $fileName = $input['id'];
  586. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.$type.'Zones');
  587. if(!$fileManager->fileExists($fileName))
  588. {
  589. AjaxResponse::I()-> addError('fileDoesNotExist', array('file' => $fileName));
  590. return AjaxResponse::I()->toArray();
  591. }
  592. $filePatch = $fileManager->getStoragePath().$fileName;
  593. $url = html_entity_decode(str_replace('mg-action=backups', 'mg-action=downloandBackupFile', $_SERVER['HTTP_REFERER']));
  594. return array('seturlto' => $url.'&path='.$filePatch);
  595. }
  596. public function downloandBackupFileHTML($input, $vars = array())
  597. {
  598. $filePatch = $_GET['path'];
  599. if(!strpos($filePatch, 'DNSManager2'.DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'zonesFilesStorage'))
  600. {
  601. $filePatch = false;
  602. }
  603. ob_clean();
  604. header('Content-Description: File Transfer');
  605. header('Content-Type: application/octet-stream');
  606. header('Content-Disposition: attachment; filename="'.basename($filePatch).'"');
  607. header('Expires: 0');
  608. header('Cache-Control: must-revalidate');
  609. header('Pragma: public');
  610. header('Content-Length: '.filesize($filePatch));
  611. readfile($filePatch);
  612. die();
  613. }
  614. public function removeBackupFileJSON($input, $vars = array())
  615. {
  616. $type = $input['type'];
  617. $fileName = $input['id'];
  618. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.$type.'Zones');
  619. if(!$fileManager->fileExists($fileName))
  620. {
  621. AjaxResponse::I()-> addError('fileDoesNotExist', array('file' => $fileName));
  622. return AjaxResponse::I()->toArray();
  623. }
  624. if($fileManager->deleteFile($fileName))
  625. {
  626. $clientId = explode('_', $fileName)[0];
  627. main\helpers\custom\ClientFilesManage::delete($clientId, $fileName);
  628. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  629. AjaxResponse::I()->addInfo('fileRemoved');
  630. return AjaxResponse::I()->toArray();
  631. }
  632. AjaxResponse::I()-> addError('fileDeleteFailed', array('file' => $fileName));
  633. return AjaxResponse::I()->toArray();
  634. }
  635. public function addBackupFileJSON($input, $vars = array())
  636. {
  637. AjaxResponse::I()->modal('add-backupFile', $vars);
  638. return AjaxResponse::I()->toArray();
  639. }
  640. public function uploadBackupFileJSON($input, $vars = array())
  641. {
  642. $fileName = main\mgLibs\custom\FileManager::removeFakePath($input['fileName']);
  643. if(!main\mgLibs\custom\FileManager::checkIfFileWasSent($fileName))
  644. {
  645. AjaxResponse::I()-> addError('sendingFailed', array('file' => $fileName));
  646. return AjaxResponse::I()->toArray();
  647. }
  648. $type = main\mgLibs\custom\FileManager::checkTypeByContent($fileName);
  649. if($type !== 'single' && $type !== 'bulk')
  650. {
  651. AjaxResponse::I()->addError('invalidContent', array('file' => $fileName));
  652. return AjaxResponse::I()->toArray();
  653. }
  654. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.$type.'Zones');
  655. if(!$fileManager->isStorageReadable() || !$fileManager->isStorageWritable())
  656. {
  657. AjaxResponse::I()-> addError('directoryPermissionWritableReadable', array('storageDir' => $fileManager->getStoragePath()));
  658. return AjaxResponse::I()->toArray();
  659. }
  660. if($fileManager->fileExists($fileName))
  661. {
  662. AjaxResponse::I()-> addError('fileAlreadyExist', array('file' => $fileName));
  663. return AjaxResponse::I()->toArray();
  664. }
  665. if($fileManager->uploadFile($fileName))
  666. {
  667. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  668. AjaxResponse::I()->addInfo('uploadSuccesfull');
  669. return AjaxResponse::I()->toArray();
  670. }
  671. if(!main\mgLibs\custom\FileManager::checkIfFileWasSent($fileName))
  672. {
  673. AjaxResponse::I()-> addError('sendingFailed', array('file' => $fileName));
  674. return AjaxResponse::I()->toArray();
  675. }
  676. }
  677. public function refreshBackupsTableJSON($input, $vars = array())
  678. {
  679. $vars['data'] = array();
  680. $vars['recordsTotal'] = 0;
  681. $vars['recordsFiltered'] = 0;
  682. $limit = $input['limit'];
  683. $offset = $input['offset'];
  684. $order = $input['order']['dir'] == 'desc' ? 1 : 0;
  685. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.'bulkZones');
  686. $filesList = main\mgLibs\custom\helpers\ImportExportFileHelper::listFilesForBulkExport($fileManager, false, $order);
  687. foreach($filesList as $fileName)
  688. {
  689. $fileRecord = clientfiles\Repository::factory()->byFile($fileName)->one();
  690. $client = !$fileRecord ? '-' : custom\manager\ClientHelper::getClientLink($fileRecord->clientid);
  691. $fileNameDisplay = $fileName;
  692. $result = explode('_', $fileNameDisplay);
  693. if($client != '-')
  694. {
  695. unset($result[count($result) -1]);
  696. }
  697. $fileNameDisplay = implode("_", $result);
  698. $vars['recordsTotal']++;
  699. if($input['search'] && $this->isFilenameMatch($fileName, $input['search']))
  700. {
  701. $vars['data'][] = $this->dataTablesParseRow('backup-row', array(
  702. 'fileName' => $fileName,
  703. 'fileNameDisplay' => $fileNameDisplay,
  704. 'client' => $client,
  705. 'type' => 'bulk'
  706. ));
  707. $vars['recordsFiltered']++;
  708. }
  709. elseif(!$input['search'])
  710. {
  711. $vars['data'][] = $this->dataTablesParseRow('backup-row', array(
  712. 'fileName' => $fileName,
  713. 'fileNameDisplay' => $fileNameDisplay,
  714. 'client' => $client,
  715. 'type' => 'bulk'
  716. ));
  717. }
  718. }
  719. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.'singleZones');
  720. $filesList = main\mgLibs\custom\helpers\ImportExportFileHelper::listFilesForBulkExport($fileManager);
  721. foreach($filesList as $fileName)
  722. {
  723. $vars['recordsTotal']++;
  724. if($input['search'] && $this->isFilenameMatch($fileName, $input['search']))
  725. {
  726. $vars['data'][] = $this->dataTablesParseRow('backup-row', array('fileName' => $fileName, 'client' => '-','type' => 'single'));
  727. $vars['recordsFiltered']++;
  728. }
  729. elseif(!$input['search'])
  730. {
  731. $vars['data'][] = $this->dataTablesParseRow('backup-row', array('fileName' => $fileName, 'client' => '-', 'type' => 'single'));
  732. }
  733. }
  734. $vars['recordsFiltered'] = $input['search'] ? $vars['recordsFiltered'] : $vars['recordsTotal'];
  735. $vars['data'] = array_slice($vars['data'], $offset, $limit);
  736. return $vars;
  737. }
  738. private function isFilenameMatch($fileName, $search)
  739. {
  740. if(strpos($fileName, $search) || strpos($fileName, $search) === 0)
  741. {
  742. return true;
  743. }
  744. return false;
  745. }
  746. public function addExportToFIleJSON($input, $vars = array())
  747. {
  748. $fileManager = new main\mgLibs\custom\FileManager('zonesFilesStorage'.DIRECTORY_SEPARATOR.'bulkZones');
  749. $vars['isWritable'] = $fileManager->isStorageWritable();
  750. $vars['storagePath'] = $fileManager->getStoragePath();
  751. $rep = new server\Repository();
  752. $vars['servers'] = $rep->get();
  753. AjaxResponse::I()->modal('add-exportToFile', $vars);
  754. return AjaxResponse::I()->toArray();
  755. }
  756. public function addExportToFileSaveJSON($input, $vars = array())
  757. {
  758. if(!isset($input['from_server']))
  759. {
  760. return ;
  761. }
  762. $server = server\Server::factory($input['from_server']);
  763. $server->getModule()->testConnection();
  764. $fileName = helpers\ImportExportFileHelper::generateNewExportFileName($server->name);
  765. custom\TaskManager::addTask('ImportToFile:main', array('from' => $input['from_server'], 'toFile' => $fileName));
  766. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  767. AjaxResponse::I()->addInfo('new_task_added');
  768. return AjaxResponse::I()->toArray();
  769. }
  770. public function backupScheduleImportJSON($input, $vars = array())
  771. {
  772. custom\TaskManager::getTaskObjectByID($input['id'])->run(true);
  773. AjaxResponse::I()->addInfo('import_scheduled');
  774. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  775. return AjaxResponse::I()->toArray();
  776. }
  777. public function backupScheduleExportJSON($input, $vars = array())
  778. {
  779. custom\TaskManager::getTaskObjectByID($input['id'])->run(true);
  780. AjaxResponse::I()->addInfo('exportScheduled');
  781. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  782. return AjaxResponse::I()->toArray();
  783. }
  784. public function backupRemoveTaskJSON($input, $vars = array())
  785. {
  786. custom\TaskManager::removeTask($input['id']);
  787. AjaxResponse::I()->refreshPage($this->backupsHTML($input, $vars));
  788. AjaxResponse::I()->addInfo('taskRemoved');
  789. return AjaxResponse::I()->toArray();
  790. }
  791. public function showListExportJSON($input, $vars = array())
  792. {
  793. $vars['exportId'] = $input['id'];
  794. $autocheckMatches = globalsetting\Repository::factory()->byKey(
  795. globalsetting\GlobalSettingEnum::IMPORT_AUTOCHECK_MATCHES)->one();
  796. $vars['autocheckMatches'] = $autocheckMatches->value == 'on' ? true : false;
  797. AjaxResponse::I()->refreshPage(array('tpl' => 'export-list', 'vars' => $vars));
  798. return AjaxResponse::I()->toArray();
  799. }
  800. function refreshExportTableJSON($input, $vars = array())
  801. {
  802. $pRep = task\Repository::factory()->byParentID($input['id'])->one();
  803. $pRepName = explode(':', $pRep->name);
  804. $task = custom\TaskManager::getXTasks(1, $pRepName[0].':fetchZonesList', $input['id'], false);
  805. $task = end($task);
  806. $rep = task\result\Repository::factory()->byTaskID($task->id);
  807. $helper = new main\mgLibs\custom\RepoTableHelper($rep, $input, array('data'));
  808. $vars = $helper->getDataTableArray();
  809. foreach($helper->get() as $task)
  810. {
  811. $userid = $this->getUserIdByHostingDomain($task->data['domain']);
  812. if(empty($userid))
  813. {
  814. $userid = $this->getUserIdByDomain($task->data['domain']);
  815. }
  816. $user = false;
  817. if(!empty($userid))
  818. {
  819. $user = new main\models\whmcs\clients\client($userid);
  820. }
  821. $vars['data'][] = $this->dataTablesParseRow('export-row',
  822. array(
  823. 'item' => $task,
  824. 'exportId' => $input['id'],
  825. 'user' => $user
  826. )
  827. );
  828. }
  829. return $vars;
  830. }
  831. public function showListImportToFileJSON($input, $vars = array())
  832. {
  833. $vars['importid'] = $input['id'];
  834. $autocheckMatches = globalsetting\Repository::factory()->byKey(
  835. globalsetting\GlobalSettingEnum::IMPORT_AUTOCHECK_MATCHES)->one();
  836. $vars['autocheckMatches'] = $autocheckMatches->value == 'on' ? true : false;
  837. AjaxResponse::I()->refreshPage(array('tpl' => 'import-list-to-file', 'vars' => $vars));
  838. return AjaxResponse::I()->toArray();
  839. }
  840. public function backupImportZonesJSON($input, $vars = array())
  841. {
  842. $add = false;
  843. foreach($input['zone'] as $resultId => $data)
  844. {
  845. if($data['import'] != 'on' && !isset($input['justOne']))
  846. {
  847. continue;
  848. }
  849. $result = new task\result\TaskResult($resultId);
  850. $task = custom\TaskManager::getTaskObjectByID($result->taskid);
  851. $main_task = $task->getParent();
  852. $ex = explode('::', $data['service']);
  853. $child = $main_task->addChild('import', array(
  854. 'clientid' => $data['client'],
  855. 'domain' => $result->data['domain'],
  856. 'ip' => $result->data['ip'],
  857. 'resultid' => $resultId,
  858. 'type' => $ex[0],
  859. 'relid' => $ex[1],
  860. ));
  861. $result->data['status'] = 'importing';
  862. $result->data['taskid'] = $child->obj()->id;
  863. $result->save();
  864. if(isset($input['runNow']))
  865. {
  866. $child->run();
  867. if($child->getStatus() !== 'finished')
  868. {
  869. AjaxResponse::I()->addError('something_went_wrong_during_import');
  870. }
  871. else
  872. {
  873. AjaxResponse::I()->addInfo('zone_imported');
  874. }
  875. }
  876. else
  877. {
  878. if($add === false)
  879. {
  880. AjaxResponse::I()->addInfo('new_task_added');
  881. $add = true;
  882. }
  883. }
  884. }
  885. return AjaxResponse::I()->toArray();
  886. }
  887. public function exportZonesJSON($input, $vars = array())
  888. {
  889. $add = false;
  890. foreach($input['zone'] as $result_id => $data)
  891. {
  892. if($data['export'] != 'on' && !isset($input['justOne']))
  893. {
  894. continue;
  895. }
  896. $result = new task\result\TaskResult($result_id);
  897. $task = custom\TaskManager::getTaskObjectByID($result->taskid);
  898. $main_task = $task->getParent(); //TODO: można zmniejszyć ilość danych poprzez odwołanie się do resulta
  899. $ex = explode('::', $data['service']);
  900. $child = $main_task->addChild('export', array(
  901. 'clientid' => $data['client'],
  902. 'domain' => $result->data['domain'],
  903. 'ip' => $result->data['ip'],
  904. 'resultid' => $result_id,
  905. 'type' => $ex[0],
  906. 'relid' => $ex[1],
  907. ));
  908. $result->data['status'] = 'exporting';
  909. $result->data['taskid'] = $child->obj()->id;
  910. $result->save();
  911. if(isset($input['runNow']))
  912. {
  913. $child->run();
  914. if($child->getStatus() !== 'finished')
  915. {
  916. AjaxResponse::I()->addError('something_went_wrong_during_import');
  917. }
  918. else
  919. {
  920. AjaxResponse::I()->addInfo('zone_imported');
  921. }
  922. }
  923. else
  924. {
  925. if($add === false)
  926. {
  927. AjaxResponse::I()->addInfo('new_task_added');
  928. $add = true;
  929. }
  930. }
  931. }
  932. return AjaxResponse::I()->toArray();
  933. }
  934. public function parseInProgresSetRecordList($record, $vars, $type = 'DnsRecord')
  935. {
  936. $rep = new task\Repository();
  937. $rep->byParentID($record->obj()->id)->byName($type.':changeRecordSet');
  938. $tasks_total = $rep->count();
  939. $rep->byStatus(task\TaskStatusEnum::FINISHED);
  940. $tasks_done = $rep->count();
  941. $task = custom\TaskManager::getXTasksObjects(1, $type.':changeRecordSet', $record->obj()->id, false);
  942. if(end($task))
  943. {
  944. $error = $this->getTaskError(end($task));
  945. }
  946. $vars[] = array(
  947. 'id' => $record->obj()->id,
  948. 'tasks_done' => $tasks_done,
  949. 'tasks_total' => $tasks_total,
  950. 'progress' => $tasks_total == 0 ? 0 : ceil($tasks_done * 100 / $tasks_total),
  951. 'error' => $error,
  952. );
  953. return $vars;
  954. }
  955. }