control.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <?php
  2. /**
  3. * The control file of backup of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yidong Wang <yidong@cnezsoft.com>
  8. * @package backup
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class backup extends control
  13. {
  14. protected $backupPath;
  15. /**
  16. * __construct
  17. *
  18. * @param string $moduleName
  19. * @param string $methodName
  20. * @access public
  21. * @return void
  22. */
  23. public function __construct($moduleName = '', $methodName = '')
  24. {
  25. parent::__construct($moduleName, $methodName);
  26. $this->backupPath = $this->backup->getBackupPath();
  27. if($this->app->methodName != 'setting')
  28. {
  29. if(!is_dir($this->backupPath))
  30. {
  31. if(!mkdir($this->backupPath, 0777, true)) $this->view->error = sprintf($this->lang->backup->error->noWritable, dirname($this->backupPath));
  32. }
  33. else
  34. {
  35. if(!is_writable($this->backupPath)) $this->view->error = sprintf($this->lang->backup->error->noWritable, $this->backupPath);
  36. }
  37. if(!is_writable($this->app->getTmpRoot())) $this->view->error = sprintf($this->lang->backup->error->noWritable, $this->app->getTmpRoot());
  38. }
  39. }
  40. /**
  41. * Index
  42. *
  43. * @access public
  44. * @return void
  45. * @param int $recTotal
  46. * @param int $recPerPage
  47. * @param int $pageID
  48. */
  49. public function index($recTotal = 0, $recPerPage = 20, $pageID = 1)
  50. {
  51. $this->loadModel('action');
  52. $backups = array();
  53. if($this->config->inQuickon)
  54. {
  55. $this->loadModel('instance');
  56. $instance = $this->config->instance->zentaopaas;
  57. $backupResult = $this->loadModel('system')->getBackupList($instance);
  58. if($backupResult['result'] == 'success')
  59. {
  60. $operating = false;
  61. $backups = !empty($backupResult['data']) ? $backupResult['data'] : array();
  62. foreach($backups as $backup)
  63. {
  64. $backup->time = isset($backup->create_time) ? $backup->create_time : '';
  65. $backup->creator = isset($backup->creator) ? $backup->creator : '';
  66. $backup->type = isset($backup->mode) ? $backup->mode : 'manual';
  67. $backup->id = str_replace('-', '_', $backup->name);
  68. if(in_array(strtolower($backup->status), array('pending', 'inprogress', 'processing'))) $operating = true;
  69. }
  70. function cmp($left, $right){return $left->create_time < $right->create_time ? 1 : -1;}
  71. usort($backups, 'cmp');
  72. if(empty($operating)) $this->system->unsetMaintenance();
  73. $this->view->operating = $operating;
  74. }
  75. $this->app->loadClass('pager', true);
  76. $pager = pager::init($recTotal, $recPerPage, $pageID);
  77. $this->view->pager = $pager;
  78. }
  79. else
  80. {
  81. if(empty($this->view->error)) $backups = $this->backupZen->getBackupList();
  82. }
  83. $this->view->title = $this->lang->backup->common;
  84. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  85. $this->view->backups = $backups;
  86. $this->view->users['SYSTEM'] = $this->lang->admin->system;
  87. $this->view->users['system'] = $this->lang->admin->system;
  88. if(trim($this->config->visions, ',') == 'lite')
  89. {
  90. $version = $this->config->liteVersion;
  91. $versionName = $this->lang->liteName . $this->config->liteVersion;
  92. }
  93. else
  94. {
  95. $version = $this->config->version;
  96. if($this->config->edition == 'open') $versionName = $this->lang->pmsName . $this->config->version;
  97. if($this->config->edition != 'open') $versionName = $this->lang->{$this->config->edition . 'Name'} . str_replace($this->config->edition, '', $this->config->version);
  98. }
  99. $latestVersionList = array();
  100. if(isset($this->config->global->latestVersionList)) $latestVersionList = json_decode($this->config->global->latestVersionList, true);
  101. $latestVersion = $latestVersionList && version_compare(array_reverse(array_keys($latestVersionList))[0], $version, 'gt') ? array_reverse(array_keys($latestVersionList))[0] : $version;
  102. $this->app->loadLang('install');
  103. $this->app->loadLang('instance');
  104. $this->app->loadLang('system');
  105. $systemInfo = new stdclass();
  106. $systemInfo->name = $versionName;
  107. $systemInfo->status = $this->lang->instance->statusList['running'];
  108. $systemInfo->currentVersion = $version;
  109. $systemInfo->versionHint = $version;
  110. $systemInfo->latestVersion = $latestVersion;
  111. $systemInfo->upgradeable = $version != $latestVersion || $this->loadModel('system')->isUpgradeable();
  112. $systemInfo->upgradeHint = $systemInfo->upgradeable ? $this->lang->system->backup->versionInfo: null;
  113. $systemInfo->latestURL = !empty($latestVersionList[$version]->link) ? $latestVersionList[$version]->link : $this->lang->install->officeDomain;
  114. if($this->config->inQuickon)
  115. {
  116. $latestRelease = $this->loadModel('system')->getLatestRelease();
  117. $systemInfo->currentVersionTitle = getenv('CHART_VERSION') ?: '';
  118. $systemInfo->latestVersionTitle = !empty($latestRelease->version) ? $latestRelease->version : '';
  119. }
  120. $this->view->systemInfo = $systemInfo;
  121. if(!is_writable($this->backupPath)) $this->view->backupError = sprintf($this->lang->backup->error->plainNoWritable, $this->backupPath);
  122. if(!is_writable($this->app->getTmpRoot())) $this->view->backupError = sprintf($this->lang->backup->error->plainNoWritable, $this->app->getTmpRoot());
  123. $this->display();
  124. }
  125. /**
  126. * Ajax get disk space.
  127. *
  128. * @access public
  129. * @return void
  130. */
  131. public function ajaxGetDiskSpace()
  132. {
  133. set_time_limit(0);
  134. session_write_close();
  135. $diskSpace = $this->backup->getDiskSpace($this->backupPath);
  136. $diskSpace = explode(',', $diskSpace);
  137. $space = new stdclass();
  138. $space->freeSpace = intval($diskSpace[0]);
  139. $space->needSpace = intval($diskSpace[1]);
  140. echo json_encode($space);
  141. }
  142. /**
  143. * Backup.
  144. *
  145. * @param string $reload yes|no
  146. * @param string $mode |manual|system|upgrade|downgrade
  147. * @access public
  148. * @return void
  149. */
  150. public function backup($reload = 'no', $mode = 'manual')
  151. {
  152. if($reload == 'yes') session_write_close();
  153. set_time_limit(0);
  154. if($this->config->inQuickon)
  155. {
  156. $this->loadModel('instance');
  157. $instance = $this->config->instance->zentaopaas;
  158. $result = $this->loadModel('system')->backup($instance, $mode);
  159. $this->loadModel('action')->create('system', 0, 'createBackup');
  160. if($result['result'] == 'success')
  161. {
  162. $backupName = $result['data']->backup_name;
  163. $this->send($result + array('callback' => "backupInProgress('$backupName')"));
  164. }
  165. else
  166. {
  167. $this->send($result);
  168. }
  169. }
  170. $fileName = date('YmdHis') . mt_rand(0, 9) . str_replace('.', '_', $this->config->version);
  171. $result = $this->backupZen->backupSQL($fileName, $reload);
  172. if($result['result'] == 'fail')
  173. {
  174. if($reload == 'yes') return print($result['message']);
  175. printf($result['message']);
  176. }
  177. $nofile = strpos($this->config->backup->setting, 'nofile') !== false;
  178. if(!$nofile)
  179. {
  180. $result = $this->backupZen->backupFile($fileName, $reload);
  181. if($result['result'] == 'fail')
  182. {
  183. if($reload == 'yes') return print($result['message']);
  184. printf($result['message']);
  185. }
  186. if(!$this->config->inContainer)
  187. {
  188. $result = $this->backupZen->backupCode($fileName, $reload);
  189. if($result['result'] == 'fail')
  190. {
  191. if($reload == 'yes') return print($result['message']);
  192. printf($result['message']);
  193. }
  194. }
  195. }
  196. /* Delete expired backup. */
  197. $this->backupZen->removeExpiredFiles();
  198. if($reload == 'yes') return print($this->lang->backup->success->backup);
  199. echo $this->lang->backup->success->backup . "\n";
  200. }
  201. /**
  202. * Restore.
  203. *
  204. * @param string $fileName
  205. * @access public
  206. * @return void
  207. */
  208. public function restore($fileName)
  209. {
  210. set_time_limit(0);
  211. /* Restore database. */
  212. $result = $this->backupZen->restoreSQL($fileName);
  213. if($result['result'] == 'fail') return $this->send($result);
  214. /* Restore attachments. */
  215. $result = $this->backupZen->restoreFile($fileName);
  216. if($result['result'] == 'fail') return $this->send($result);
  217. if(!empty($_SESSION['gotoUpgrade'])) $this->send(array('result' => 'success', 'message' => $this->lang->backup->notice->gotoUpgrade, 'load' => true));
  218. return $this->send(array('result' => 'success', 'closeModal' => true, 'callback' => "zui.Modal.alert('{$this->lang->backup->success->restore}').then(() => {loadCurrentPage()})"));
  219. }
  220. /**
  221. * remove PHP header.
  222. *
  223. * @param string $fileName
  224. * @access public
  225. * @return void
  226. */
  227. public function rmPHPHeader($fileName)
  228. {
  229. if(file_exists($this->backupPath . $fileName . '.sql.php'))
  230. {
  231. $this->backup->removeFileHeader($this->backupPath . $fileName . '.sql.php');
  232. rename($this->backupPath . $fileName . '.sql.php', $this->backupPath . $fileName . '.sql');
  233. }
  234. return $this->send(array('result' => 'success', 'load' => true));
  235. }
  236. /**
  237. * Delete.
  238. *
  239. * @param string $fileName
  240. * @access public
  241. * @return void
  242. */
  243. public function delete($fileName)
  244. {
  245. $version = str_replace('.', '_', $this->config->version);
  246. if(preg_match("/^\d{15}" . preg_quote($version) . "$/", $fileName) == 0) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->backup->error->noDelete, $fileName)));
  247. $suffixes = ['.code', '.file', '.sql', '.sql.php'];
  248. foreach($suffixes as $suffix)
  249. {
  250. $backupFile = $this->backupPath . $fileName . $suffix;
  251. if(is_dir($backupFile))
  252. {
  253. $zfile = $this->app->loadClass('zfile');
  254. $zfile->removeDir($backupFile);
  255. $this->backup->processSummary($backupFile, 0, 0, array(), 0, 'delete');
  256. }
  257. elseif(file_exists($backupFile) && !unlink($backupFile))
  258. {
  259. return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->backup->error->noDelete, $backupFile)));
  260. }
  261. }
  262. return $this->sendSuccess(array('load' => true));
  263. }
  264. /**
  265. * Change hold days.
  266. *
  267. * @access public
  268. * @return void
  269. */
  270. public function change()
  271. {
  272. if($_POST)
  273. {
  274. $data = fixer::input('post')->get();
  275. $this->loadModel('setting')->setItem('system.backup.holdDays', $data->holdDays);
  276. return $this->sendSuccess(array('load' => true, 'closeModal' => true));
  277. }
  278. $this->display();
  279. }
  280. /**
  281. * Setting backup
  282. *
  283. * @access public
  284. * @return void
  285. */
  286. public function setting()
  287. {
  288. /* Check safe file. */
  289. $statusFile = $this->loadModel('common')->checkSafeFile();
  290. if($statusFile)
  291. {
  292. $okFile = str_replace($this->app->getBasePath(), '', $statusFile);
  293. $this->view->error = sprintf($this->lang->noticeOkFile, $okFile, $statusFile);
  294. return print($this->display());
  295. }
  296. /* Get Zentao Info on the quickon platform. */
  297. $this->loadModel('instance');
  298. $instance = $this->config->inQuickon ? $this->instance->getByName('ZenTao') : new stdClass();
  299. if(strtolower($this->server->request_method) == "post")
  300. {
  301. $data = fixer::input('post')->join('setting', ',')->get();
  302. /* 1. Setting holdDays. */
  303. if(isset($data->holdDays))
  304. {
  305. $this->backupZen->setHoldDays($data);
  306. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  307. }
  308. $setting = '';
  309. if(isset($data->setting)) $setting = $data->setting;
  310. $this->loadModel('setting')->setItem('system.backup.setting', $setting);
  311. /* 2. Setting dir. */
  312. $settingDir = zget($data, 'settingDir', '');
  313. if($settingDir)
  314. {
  315. $settingDir = rtrim($settingDir, DS) . DS;
  316. if(!is_dir($settingDir) and mkdir($settingDir, 0777, true)) return $this->send(array('result' => 'fail', 'message' => $this->lang->backup->error->noCreateDir));
  317. if(!is_writable($settingDir)) return $this->send(array('result' => 'fail', 'message' => strip_tags(sprintf($this->lang->backup->error->noWritable, $settingDir))));
  318. if($data->settingDir == $this->app->getTmpRoot() . 'backup' . DS) $settingDir = '';
  319. }
  320. $this->setting->setItem('system.backup.settingDir', $settingDir);
  321. /* 3. Setting instance backup settings. */
  322. $_POST['backupKeepDays'] = $data->holdDays;
  323. if(!empty($instance->id)) $this->loadModel('instance')->saveBackupSettings($instance);
  324. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  325. return $this->sendSuccess(array('load' => true, 'closeModal' => true));
  326. }
  327. $this->view->instance = $instance;
  328. $this->view->backupSettings = !empty($instance->id) ? $this->instance->getBackupSettings($instance->id) : new stdClass();
  329. $this->display();
  330. }
  331. /**
  332. * Ajax get progress.
  333. *
  334. * @access public
  335. * @return void
  336. */
  337. public function ajaxGetProgress()
  338. {
  339. session_write_close();
  340. if($this->config->inQuickon) return print('');
  341. $files = glob($this->backupPath . '/*.*');
  342. rsort($files);
  343. $fileName = basename($files[0]);
  344. $fileName = substr($fileName, 0, strpos($fileName, '.'));
  345. $sqlFileName = $this->backupPath . $fileName . '.sql';
  346. if(!file_exists($sqlFileName)) $sqlFileName .= '.php';
  347. $sqlFileName = $this->backup->getBackupFile($fileName, 'sql');
  348. if($sqlFileName)
  349. {
  350. $summary = $this->backup->getBackupSummary($sqlFileName);
  351. $message = sprintf($this->lang->backup->progressSQL, $this->backup->processFileSize($summary['size']));
  352. }
  353. $attachFileName = $this->backup->getBackupFile($fileName, 'file');
  354. if($attachFileName)
  355. {
  356. $log = $this->backup->getBackupDirProgress($attachFileName);
  357. if(empty($log)) return print('');
  358. $message = sprintf($this->lang->backup->progressAttach, zget($log, 'allCount', 0), zget($log, 'count', 0));
  359. if(empty($log)) $message = '';
  360. }
  361. $codeFileName = $this->backup->getBackupFile($fileName, 'code');
  362. if($codeFileName)
  363. {
  364. $log = $this->backup->getBackupDirProgress($codeFileName);
  365. if(empty($log)) return print('');
  366. $message = sprintf($this->lang->backup->progressCode, zget($log, 'allCount', 0), zget($log, 'count', 0));
  367. if(empty($log)) $message = '';
  368. }
  369. return print($message);
  370. }
  371. /**
  372. * AJAX: Check the version of the backup.
  373. *
  374. * @param string $name
  375. * @return void
  376. */
  377. public function ajaxCheckBackupVersion($name)
  378. {
  379. if(!$this->config->inContainer) $this->send(array('result' => 'success', 'message' => $this->lang->backup->confirmRestore, 'canRestore' => true));
  380. $matched = preg_match('/\d{15}(.*)$/', $name, $matches);
  381. if ($matched == 1 && !empty($matches[1]))
  382. {
  383. $backupVersion = str_replace('_', '.', explode('.', $matches[1])[0]);
  384. $compareResult = version_compare($backupVersion, $this->config->version);
  385. switch($compareResult)
  386. {
  387. case -1:
  388. $_SESSION['gotoUpgrade'] = common::getSysURL() . '/upgrade.php';
  389. $message = $this->lang->backup->notice->lowerVersion;
  390. break;
  391. case 1:
  392. $message = sprintf($this->lang->backup->notice->higherVersion, $this->app->getVersionName($backupVersion));
  393. break;
  394. default:
  395. $message = $this->lang->backup->confirmRestore;
  396. }
  397. $canRestore = $compareResult == 1 ? false : true;
  398. $this->send(array('result' => 'success', 'message' => $message, 'canRestore' => $canRestore));
  399. }
  400. else
  401. $this->send(array('result' => 'fail', 'message' => $this->lang->backup->notice->unknownVersion));
  402. }
  403. }