control.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. <?php
  2. /**
  3. * The control file of upgrade module 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 Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package upgrade
  9. * @version $Id: control.php 5119 2013-07-12 08:06:42Z wyd621@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. class upgrade extends control
  13. {
  14. /**
  15. * @param string $moduleName
  16. * @param string $methodName
  17. * @param string $appName
  18. */
  19. public function __construct($moduleName = '', $methodName = '', $appName = '')
  20. {
  21. parent::__construct($moduleName, $methodName, $appName);
  22. $statusFile = $this->loadModel('common')->checkSafeFile();
  23. if($statusFile)
  24. {
  25. $this->view->title = $this->lang->upgrade->common;
  26. $this->view->statusFile = $statusFile;
  27. $this->display('upgrade', 'setStatusFile');
  28. }
  29. else
  30. {
  31. $this->session->set('upgrading', true);
  32. }
  33. }
  34. /**
  35. * The index page.
  36. *
  37. * @access public
  38. * @return void
  39. */
  40. public function index()
  41. {
  42. /* 如果没有升级入口文件,跳转到应用的首页。*/
  43. /* Locate to index page of my module, if upgrade.php does not exist. */
  44. $upgradeFile = $this->app->wwwRoot . 'upgrade.php';
  45. if(!file_exists($upgradeFile)) $this->locate($this->createLink('my', 'index'));
  46. $openVersion = $this->upgrade->getOpenVersion(str_replace('.', '_', $this->config->installedVersion));
  47. if(version_compare($openVersion, '6.4', '<=')) $this->locate(inlink('license'));
  48. $this->locate(inlink('backup'));
  49. }
  50. /**
  51. * 授权协议页面。
  52. * Check agree license.
  53. *
  54. * @param int $agree
  55. * @access public
  56. * @return void
  57. */
  58. public function license($agree = 0)
  59. {
  60. if($agree == 1) $this->locate(inlink('backup'));
  61. $this->view->title = $this->lang->upgrade->common;
  62. $this->view->license = $this->loadModel('install')->getLicense();
  63. $this->display();
  64. }
  65. /**
  66. * 提示备份数据库。
  67. * Prompt to backup database.
  68. *
  69. * @access public
  70. * @return void
  71. */
  72. public function backup()
  73. {
  74. $this->view->title = $this->lang->upgrade->common;
  75. $this->display();
  76. }
  77. /**
  78. * 选择升级前的禅道版本。
  79. * Select the version of old zentao.
  80. *
  81. * @access public
  82. * @return void
  83. */
  84. public function selectVersion()
  85. {
  86. $version = str_replace(array(' ', '.'), array('', '_'), $this->config->installedVersion);
  87. $version = strtolower($version);
  88. /* 处理迅捷版的版本。*/
  89. /* Process the lite version. */
  90. if($this->config->visions == ',lite,')
  91. {
  92. $installedVersion = str_replace('.', '_', $this->config->installedVersion);
  93. $version = array_search($installedVersion, $this->config->upgrade->liteVersion);
  94. foreach($this->lang->upgrade->fromVersions as $key => $value)
  95. {
  96. if(strpos($key, 'lite') === false) unset($this->lang->upgrade->fromVersions[$key]);
  97. }
  98. $this->config->version = ($this->config->edition == 'biz' ? 'LiteVIP' : 'Lite') . $this->config->liteVersion;
  99. }
  100. if($_POST)
  101. {
  102. /* 把选择的版本写入数据库便于后续通过 $config->installedVersion 调用。*/
  103. $this->loadModel('setting')->updateVersion(str_replace('_', '.', $this->post->fromVersion));
  104. /* 假如升级过程中断,重新升级时需要知道升级前是哪个版本,因此需要把第一次升级前的版本存入配置,便于后续使用。*/
  105. if(empty($this->config->upgrade->fromVersion)) $this->setting->setItem('system.upgrade.fromVersion', $this->post->fromVersion);
  106. $fromVersion = $this->config->upgrade->fromVersion ?? $this->post->fromVersion;
  107. if(strpos($fromVersion, 'lite') !== false) $fromVersion = $this->config->upgrade->liteVersion[$fromVersion];
  108. $this->locate(inlink('execute', "fromVersion={$fromVersion}"));
  109. }
  110. $this->view->title = $this->lang->upgrade->common . $this->lang->hyphen . $this->lang->upgrade->selectVersion;
  111. $this->view->version = $version;
  112. $this->display();
  113. }
  114. /**
  115. * 确认要执行的SQL语句。
  116. * Confirm the upgrade sql.
  117. *
  118. * @param string $fromVersion
  119. * @access public
  120. * @return void
  121. */
  122. public function confirm($fromVersion = '')
  123. {
  124. $this->view->fromVersion = $fromVersion;
  125. if(strpos($fromVersion, 'lite') !== false) $fromVersion = $this->config->upgrade->liteVersion[$fromVersion];
  126. if($_POST) $this->locate(inlink('execute', "fromVersion={$fromVersion}"));
  127. $confirmSql = $this->upgrade->getConfirm($fromVersion);
  128. /* When sql is empty then skip it. */
  129. if(empty($confirmSql)) $this->locate(inlink('execute', "fromVersion={$fromVersion}"));
  130. $this->view->title = $this->lang->upgrade->confirm;
  131. $this->view->confirm = $confirmSql;
  132. $this->display();
  133. }
  134. /**
  135. * 执行升级的 SQL。
  136. * Execute the upgrading sql.
  137. *
  138. * @param string $fromVersion
  139. * @access public
  140. * @return void
  141. */
  142. public function execute($fromVersion = '')
  143. {
  144. if(version_compare($this->config->version, $this->config->installedVersion, '='))
  145. {
  146. $url = $this->upgradeZen->getRedirectUrlAfterExecute($fromVersion);
  147. return $this->locate($url);
  148. }
  149. $this->view->title = $this->lang->upgrade->execute;
  150. $this->view->fromVersion = $fromVersion;
  151. /* 显示升级失败的信息。*/
  152. if(!empty($_POST['errors']))
  153. {
  154. $this->view->errors = $_POST['errors'];
  155. return $this->display('upgrade', 'sqlfail');
  156. }
  157. $script = $this->app->getTmpRoot() . 'deleteFiles.sh';
  158. $command = $this->upgrade->deleteFiles($script);
  159. if($command) return $this->displayCommand($command);
  160. $upgradeVersions = $this->upgradeZen->getUpgradeVersions(str_replace('.', '_', $this->config->installedVersion));
  161. $versionsKey = array_keys($upgradeVersions);
  162. $toVersion = reset($versionsKey);
  163. $upgradeChanges = $this->upgradeZen->getUpgradeChanges($fromVersion, $toVersion);
  164. /* 把需要执行的变更记录到数据库,便于后续调用。*/
  165. $sessionChanges = [];
  166. foreach($upgradeChanges as $change)
  167. {
  168. if($change['type'] == 'sql') $sessionChanges[] = ['version' => $change['version'], 'executed' => false, 'type' => 'sql', 'fileMd5' => $change['fileMd5'], 'sqlMd5' => $change['sqlMd5']];
  169. if($change['type'] == 'method') $sessionChanges[] = ['version' => $change['version'], 'executed' => false, 'type' => 'method', 'method' => $change['method']];
  170. }
  171. $this->loadModel('setting')->setItem('system.upgrade.upgradeChanges', json_encode($sessionChanges));
  172. $this->view->toVersion = $toVersion;
  173. $this->view->upgradeVersions = $upgradeVersions;
  174. $this->view->upgradeChanges = $upgradeChanges;
  175. $this->display();
  176. }
  177. /**
  178. * 通过 ajax 请求执行升级程序。
  179. * Ajax execute upgrade.
  180. *
  181. * @param string $fromVersion
  182. * @param string $toVersion
  183. * @access public
  184. * @return void
  185. */
  186. public function ajaxExecute($fromVersion, $toVersion)
  187. {
  188. session_write_close();
  189. if(version_compare($this->config->version, $this->config->installedVersion, '=')) return $this->sendSuccess();
  190. $this->upgrade->execute($fromVersion, $toVersion);
  191. if($this->upgrade->isError()) return $this->sendError(implode("\n", $this->upgrade->getError()));
  192. if(version_compare($this->config->version, $toVersion, '='))
  193. {
  194. $load = $this->upgradeZen->getRedirectUrlAfterExecute($fromVersion);
  195. return $this->sendSuccess(['load' => $load]);
  196. }
  197. return $this->sendSuccess();
  198. }
  199. /**
  200. * 获取已执行的变更。
  201. * Ajax get executed changes.
  202. *
  203. * @access public
  204. * @return void
  205. */
  206. public function ajaxGetExecutedChanges()
  207. {
  208. /* 如果没有需要执行的变更,直接返回全部执行完成。*/
  209. $upgradeChanges = empty($this->config->upgrade->upgradeChanges) ? [] : json_decode($this->config->upgrade->upgradeChanges, true);
  210. if(empty($upgradeChanges)) return print(json_encode(['version' => $this->config->installedVersion, 'executedKeys' => [], 'allChangesExecuted' => true]));
  211. /* 如果没有已执行的变更,直接返回未全部执行完成。*/
  212. $executedChanges = empty($this->config->upgrade->executedChanges) ? [] : json_decode($this->config->upgrade->executedChanges, true);
  213. if(empty($executedChanges)) return print(json_encode(['version' => $this->config->installedVersion, 'executedKeys' => [], 'allChangesExecuted' => false]));
  214. foreach($upgradeChanges as $key => $change)
  215. {
  216. if($change['type'] == 'sql' && isset($executedChanges[$change['version']]['sqls'][$change['fileMd5']][$change['sqlMd5']])) $upgradeChanges[$key]['executed'] = true;
  217. if($change['type'] == 'method' && isset($executedChanges[$change['version']]['methods'][$change['method']])) $upgradeChanges[$key]['executed'] = true;
  218. }
  219. $executedKeys = array_keys(array_filter($upgradeChanges, function($change)
  220. {
  221. return isset($change['executed']) && $change['executed'];
  222. }));
  223. $allChangesExecuted = count($executedKeys) == count($upgradeChanges);
  224. /**
  225. * 升级到最终版本之前,每升级完一个版本就清除掉需要执行的和已执行的变更。
  226. * 升级到最终版本之后,则在执行完所有后续的数据处理流程后再清除需要执行的和已执行的变更。
  227. */
  228. if($allChangesExecuted && version_compare($this->config->version, $this->config->installedVersion, '<'))
  229. {
  230. $this->loadModel('setting')->deleteItems('owner=system&module=upgrade&key=upgradeChanges');
  231. $this->setting->deleteItems('owner=system&module=upgrade&key=executedChanges');
  232. }
  233. return print(json_encode(['version' => $this->config->installedVersion, 'executedKeys' => $executedKeys, 'allChangesExecuted' => $allChangesExecuted]));
  234. }
  235. /**
  236. * 引导升级到 18 版本。
  237. * Guide to 18 version.
  238. *
  239. * @param string $fromVersion
  240. * @param string $mode
  241. * @access public
  242. * @return void
  243. */
  244. public function to18Guide($fromVersion, $mode = '')
  245. {
  246. if($_POST || $mode)
  247. {
  248. if($this->post->mode) $mode = $this->post->mode;
  249. if($this->config->edition == 'ipd') $mode = 'PLM';
  250. $this->loadModel('setting')->setItem('system.common.global.mode', $mode);
  251. $this->loadModel('custom')->disableFeaturesByMode($mode);
  252. /* 更新迭代的概念。*/
  253. /* Update sprint concept. */
  254. $this->upgradeZen->setSprintConcept();
  255. if($mode == 'light') $this->upgradeZen->setDefaultProgram();
  256. $this->locate(inlink('selectMergeMode', "fromVersion={$fromVersion}&mode={$mode}"));
  257. }
  258. $this->app->loadLang('install');
  259. list($disabledFeatures, $enabledScrumFeatures, $disabledScrumFeatures) = $this->loadModel('custom')->computeFeatures();
  260. $this->view->title = $this->lang->custom->selectUsage;
  261. $this->view->edition = $this->config->edition;
  262. $this->view->disabledFeatures = $disabledFeatures;
  263. $this->view->enabledScrumFeatures = $enabledScrumFeatures;
  264. $this->view->disabledScrumFeatures = $disabledScrumFeatures;
  265. $this->display();
  266. }
  267. /**
  268. * 归并项目集。
  269. * Merge program.
  270. *
  271. * @param string $type
  272. * @param int $programID
  273. * @param string $projectType project|execution
  274. * @access public
  275. * @return void
  276. */
  277. public function mergeProgram($type = 'productline', $programID = 0, $projectType = 'project')
  278. {
  279. set_time_limit(0);
  280. $this->app->loadLang('program');
  281. $this->app->loadLang('project');
  282. $this->session->set('upgrading', true);
  283. if($_POST)
  284. {
  285. $projectType = isset($_POST['projectType']) ? $_POST['projectType'] : 'project';
  286. if($type == 'productline') $this->upgradeZen->mergeByProductline($projectType);
  287. if($type == 'product') $this->upgradeZen->mergeByProduct($projectType);
  288. if($type == 'sprint') $this->upgradeZen->mergeBySprint($projectType);
  289. if($type == 'moreLink') $this->upgradeZen->mergeByMoreLink($projectType);
  290. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('upgrade', 'mergeProgram', "type={$type}&programID={$programID}&projectType={$projectType}")));
  291. }
  292. $noMergedProductCount = $this->upgrade->getNoMergedProductCount();
  293. $noMergedSprintCount = $this->upgrade->getNoMergedSprintCount();
  294. /* 当产品和项目都归并完成后,完成后续操作。*/
  295. /* When all products and projects merged then finish and locate afterExec page. */
  296. if(empty($noMergedProductCount) && empty($noMergedSprintCount)) $this->upgradeZen->upgradeAfterMerged();
  297. $this->view->noMergedProductCount = $noMergedProductCount;
  298. $this->view->noMergedSprintCount = $noMergedSprintCount;
  299. /* 获取产品线下的产品和项目。*/
  300. /* Get products and projects group by product line. */
  301. if($type == 'productline') $this->upgradeZen->assignProductsAndProjectsGroupByProductline($projectType);
  302. /* 获取产品下的项目。*/
  303. /* Get projects group by product. */
  304. if($type == 'product') $this->upgradeZen->assignProjectsGroupByProduct($projectType);
  305. $systemMode = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=mode');
  306. /* Get no merged projects that is not linked product. */
  307. if($type == 'sprint')
  308. {
  309. $this->upgradeZen->assignSprintsWithoutProduct();
  310. if(!$programID && $systemMode == 'light') $programID = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=defaultProgram');
  311. }
  312. /* Get no merged projects that link more than two products. */
  313. if($type == 'moreLink') $this->upgradeZen->assignSprintsWithMoreProducts();
  314. $programs = $this->dao->select('id, name')->from(TABLE_PROGRAM)->where('type')->eq('program')->andWhere('deleted')->eq('0')->orderBy('id_desc')->fetchPairs();
  315. $currentProgramID = $programID ? $programID : key($programs);
  316. $this->view->title = $this->lang->upgrade->mergeProgram;
  317. $this->view->type = $type;
  318. $this->view->programs = $programs;
  319. $this->view->programID = $programID;
  320. $this->view->projects = $currentProgramID ? $this->upgrade->getProjectPairsByProgram($currentProgramID) : array();
  321. $this->view->lines = $currentProgramID ? $this->loadModel('product')->getLinePairs($currentProgramID) : array();
  322. $this->view->users = $this->loadModel('user')->getPairs('noclosed|noempty');
  323. $this->view->groups = $this->loadModel('group')->getPairs();
  324. $this->view->systemMode = $systemMode;
  325. $this->view->projectType = $projectType;
  326. $this->display();
  327. }
  328. /**
  329. * 选择数据归并的方式。
  330. * Select the merge mode when upgrading to zentaopms 18.0.
  331. *
  332. * @param string $fromVersion
  333. * @param string $mode light | ALM | PLM
  334. * @access public
  335. * @return void
  336. */
  337. public function selectMergeMode($fromVersion, $mode = 'light')
  338. {
  339. if($_POST)
  340. {
  341. $mergeMode = $this->post->projectType;
  342. if($mergeMode == 'manually') $this->locate(inlink('mergeProgram'));
  343. if($mode == 'light') $programID = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=defaultProgram');
  344. if($mode == 'ALM' || $mode == 'PLM') $programID = $this->loadModel('program')->createDefaultProgram();
  345. if($mergeMode == 'project') $this->upgrade->upgradeInProjectMode($programID);
  346. if($mergeMode == 'execution') $this->upgrade->upgradeInExecutionMode($programID);
  347. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  348. $this->upgrade->computeObjectMembers();
  349. $this->upgrade->initUserView();
  350. $this->upgrade->setDefaultPriv();
  351. $this->dao->update(TABLE_CONFIG)->set('value')->eq('0_0')->where('`key`')->eq('productProject')->exec();
  352. $hourPoint = $this->loadModel('setting')->getItem('owner=system&module=custom&key=hourPoint');
  353. if(empty($hourPoint)) $this->setting->setItem('system.custom.hourPoint', 0);
  354. $sprints = $this->dao->select('id')->from(TABLE_PROJECT)->where('type')->eq('sprint')->fetchAll('id');
  355. $this->dao->update(TABLE_ACTION)->set('objectType')->eq('execution')->where('objectID')->in(array_keys($sprints))->andWhere('objectType')->eq('project')->exec();
  356. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  357. $this->locate(inlink('afterExec', "fromVersion={$fromVersion}"));
  358. }
  359. $this->view->title = $this->lang->upgrade->selectMergeMode;
  360. $this->view->fromVersion = $fromVersion;
  361. $this->view->systemMode = $mode;
  362. $this->display();
  363. }
  364. /**
  365. * 同一个项目集内项目名称不能重复,调整重名的项目名称。
  366. * Rename the projects that have the same name in the same program.
  367. *
  368. * @param string $type project|product|execution
  369. * @param string $duplicateList
  370. * @access public
  371. * @return void
  372. */
  373. public function renameObject($type = 'project', $duplicateList = '')
  374. {
  375. $this->app->loadLang($type);
  376. if($_POST)
  377. {
  378. foreach($this->post->project as $projectID => $projectName)
  379. {
  380. if(!$projectName) continue;
  381. $this->dao->update(TABLE_PROJECT)->set('name')->eq($projectName)->where('id')->eq($projectID)->exec();
  382. }
  383. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
  384. }
  385. $objectGroup = array();
  386. if($type == 'project' || $type == 'execution') $objectGroup = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('id')->in($duplicateList)->orderBy('name')->fetchAll();
  387. $this->view->type = $type;
  388. $this->view->objectGroup = $objectGroup;
  389. $this->display();
  390. }
  391. /**
  392. * 合并代码库。
  393. * Merge Repos.
  394. *
  395. * @access public
  396. * @return void
  397. */
  398. public function mergeRepo()
  399. {
  400. if($_POST)
  401. {
  402. $postData = form::data($this->config->upgrade->form->mergetRepo)->get();
  403. $this->upgrade->mergeRepo(array_keys($postData->repoes), $postData->products);
  404. return $this->send(array('result' => 'success', 'load' => inlink('mergeRepo')));
  405. }
  406. $repoes = $this->dao->select('id, name')->from(TABLE_REPO)->where('deleted')->eq(0)->andWhere('product')->eq('')->fetchPairs();
  407. $products = $this->dao->select('id, name')->from(TABLE_PRODUCT)->where('deleted')->eq(0)->fetchPairs();
  408. if(empty($repoes) || empty($products))
  409. {
  410. $this->dao->delete()->from(TABLE_BLOCK)->exec();
  411. $this->dao->delete()->from(TABLE_CONFIG)->where('`key`')->eq('blockInited')->exec();
  412. $this->loadModel('setting')->deleteItems('owner=system&module=common&section=global&key=upgradeStep');
  413. $this->locate(inlink('afterExec', 'fromVersion=&processed=no'));
  414. }
  415. $this->view->title = $this->lang->upgrade->mergeRepo;
  416. $this->view->repoes = $repoes;
  417. $this->view->products = $products;
  418. $this->view->programs = $this->dao->select('id, name')->from(TABLE_PROGRAM)->where('deleted')->eq('0')->andWhere('type')->eq('program')->fetchPairs();
  419. $this->display();
  420. }
  421. /**
  422. * 获取修复冲突的记录。
  423. * Ajax get fix consistency logs.
  424. *
  425. * @param int $offset
  426. * @access public
  427. * @return void
  428. */
  429. public function ajaxGetFixLogs($offset = 0)
  430. {
  431. $logFile = $this->upgrade->getConsistencyLogFile();
  432. $lines = !file_exists($logFile) ? array() : file($logFile);
  433. $total = (int)array_shift($lines);
  434. $progress = 0;
  435. if($total) $progress = round((count($lines) / $total) * 100);
  436. if($progress >= 100) $progress = 99;
  437. $log = array_slice($lines, $offset);
  438. $finished = ($log && end($log) == 'Finished') ? true : false;
  439. if($finished) $progress = 100;
  440. return print(json_encode(array('log' => '<p>' . implode('</p><p>', $log) . '</p>', 'finished' => $finished, 'progress' => $progress, 'offset' => count($lines))));
  441. }
  442. /**
  443. * 为保持数据库一致,执行修复sql。
  444. * Ajax fix for consistency.
  445. *
  446. * @param string $version
  447. * @access public
  448. * @return void
  449. */
  450. public function ajaxFixConsistency($version)
  451. {
  452. set_time_limit(0);
  453. session_write_close();
  454. $this->upgrade->fixConsistency($version);
  455. }
  456. /**
  457. * 获取某个项目集下的项目。
  458. * Get the project of the program it belongs to.
  459. *
  460. * @param int $programID
  461. * @access public
  462. * @return void
  463. */
  464. public function ajaxGetProjectPairsByProgram($programID = 0)
  465. {
  466. $projects = $this->upgrade->getProjectPairsByProgram($programID);
  467. $result = array();
  468. foreach($projects as $projectID => $projectName) $result[] = array('text' => $projectName, 'value' => $projectID);
  469. return $this->send(array('result' => 'success', 'projects' => $result));
  470. }
  471. /**
  472. * 获取项目集下的产品线。
  473. * Get the lines of the program it belongs to.
  474. *
  475. * @param int $programID
  476. * @access public
  477. * @return void
  478. */
  479. public function ajaxGetLinesPairsByProgram($programID = 0)
  480. {
  481. $lines = $this->loadModel('product')->getLinePairs((int)$programID);
  482. $result = array();
  483. foreach($lines as $lineID => $lineName) $result[] = array('text' => $lineName, 'value' => $lineID);
  484. return $this->send(array('result' => 'success', 'lines' => $result));
  485. }
  486. /**
  487. * After execute.
  488. *
  489. * @param string $fromVersion
  490. * @param string $processed
  491. * @param string $skipMoveFile
  492. * @param string $skipUpdateDocs
  493. * @param string $skipUpdateDocTemplates
  494. * @param string $skipUpdateProjectReports
  495. * @access public
  496. * @return void
  497. */
  498. public function afterExec($fromVersion, $processed = 'no', $skipMoveFile = 'no', $skipUpdateDocs = 'no', $skipUpdateDocTemplates = 'no', $skipUpdateProjectReports = 'no')
  499. {
  500. /* 如果数据库有冲突,显示更改的 sql。*/
  501. /* If there is a conflict with the standard database, display the changed sql. */
  502. $alterSQL = in_array($this->config->db->driver, $this->config->mysqlDriverList) ? $this->upgrade->checkConsistency($this->config->version) : array();
  503. if(!empty($alterSQL)) return $this->displayConsistency($alterSQL);
  504. /* 如果有扩展文件并且需要移除文件,显示需要移除的文件。*/
  505. /* If there are extendtion files and need to move them, display them. */
  506. $extFiles = $this->upgrade->getExtFiles();
  507. if(!empty($extFiles) && $skipMoveFile == 'no') $this->locate(inlink('moveExtFiles', "fromVersion={$fromVersion}"));
  508. /* 移除收费版本目录,如果有错误,显示移除命令。*/
  509. /* Remove encrypted directories. */
  510. $script = $this->app->getTmpRoot() . 'deleteFiles.sh';
  511. $command = $this->upgrade->removeEncryptedDir($script);
  512. if($command) return $this->displayCommand($command);
  513. if(is_file($script)) unlink($script);
  514. /* 如果有需要升级的文档,显示升级文档界面。*/
  515. /* If there are documents that need to be upgraded, display upgrade docs ui. */
  516. if($skipUpdateDocs == 'no')
  517. {
  518. $upgradeDocs = $this->upgrade->getUpgradeDocs();
  519. if(!empty($upgradeDocs))
  520. {
  521. $this->session->set('upgradeDocs', $upgradeDocs);
  522. $this->locate(inlink('upgradeDocs', "fromVersion={$fromVersion}"));
  523. }
  524. }
  525. /* 如果有需要升级的文档模板,显示升级文档模板界面。*/
  526. /* If there are templates that need to be upgraded, display upgrade doc templates ui. */
  527. if($skipUpdateDocTemplates == 'no' && strpos(',max,ipd,', ",{$this->config->edition},") !== false)
  528. {
  529. $this->loadModel('doc');
  530. $this->doc->addBuiltInScopes();
  531. if(!$this->doc->checkIsTemplateUpgraded()) $this->doc->upgradeTemplateTypes();
  532. $this->doc->addBuiltInDocTemplateByType();
  533. $upgradeDocTemplates = $this->upgrade->getUpgradeDocTemplates();
  534. $copiedTemplateList = $this->doc->copyTemplate(zget($upgradeDocTemplates, 'all', array()));
  535. $mergedTemplateList = array_merge_recursive($upgradeDocTemplates, $copiedTemplateList);
  536. if(!empty($mergedTemplateList))
  537. {
  538. $this->session->set('upgradeDocTemplates', $mergedTemplateList);
  539. return $this->locate(inlink('upgradeDocTemplates', "fromVersion={$fromVersion}"));
  540. }
  541. }
  542. /* 如果有需要升级的周报、里程碑报告,显示升级周报、里程碑报告界面。*/
  543. $openVersion = $this->upgrade->getOpenVersion(str_replace('.', '_', $fromVersion));
  544. if($skipUpdateProjectReports == 'no' && version_compare($openVersion, '21.7.6', '<'))
  545. {
  546. $upgradeProjectReports = $this->upgrade->getUpgradeProjectReports();
  547. if(!empty($upgradeProjectReports))
  548. {
  549. $this->session->set('upgradeProjectReports', $upgradeProjectReports);
  550. $this->locate(inlink('upgradeProjectReports', "fromVersion={$fromVersion}"));
  551. }
  552. }
  553. unset($_SESSION['user']);
  554. /**
  555. * 升级到最终版本并执行完所有后续的数据处理流程后,更新版本号、清除升级前的版本记录、需要执行的和已执行的变更。
  556. * After upgrading to the final version and completing all subsequent data processing, update the version number, clear the pre-upgrade version records, the changes to be executed and the executed changes.
  557. */
  558. $this->loadModel('setting')->updateVersion($this->config->version);
  559. $this->setting->deleteItems('owner=system&module=upgrade&key=fromVersion');
  560. $this->setting->deleteItems('owner=system&module=upgrade&key=upgradeChanges');
  561. $this->setting->deleteItems('owner=system&module=upgrade&key=executedChanges');
  562. /* 检查是否还有需要处理的。*/
  563. /* Check if there is anything else that needs to be processed. */
  564. $needProcess = $this->upgrade->checkProcess();
  565. if($processed == 'no') return $this->displayExecuteProcess($fromVersion, $needProcess);
  566. if(empty($needProcess) || $processed == 'yes') $this->processAfterExecSuccessfully();
  567. }
  568. /**
  569. * 数据库一致性检查。
  570. * Check database consistency.
  571. *
  572. * @param int $netConnect
  573. * @access public
  574. * @return void
  575. */
  576. public function consistency($netConnect = 1)
  577. {
  578. $logFile = $this->upgrade->getConsistencyLogFile();
  579. $hasError = $this->upgrade->hasConsistencyError();
  580. if(file_exists($logFile)) unlink($logFile);
  581. $alterSQL = in_array($this->config->db->driver, $this->config->mysqlDriverList) ? $this->upgrade->checkConsistency() : array();
  582. if(empty($alterSQL))
  583. {
  584. /* 能访问禅道官网插件接口跳转到检查插件页面,否则跳转到选择版本页面。*/
  585. /* If you can access the ZenTao official website extension interface, locate to the check extension page, otherwise locate to the version selection page. */
  586. if($netConnect) $this->locate(inlink('checkExtension'));
  587. $this->locate(inlink('selectVersion'));
  588. }
  589. $this->view->title = $this->lang->upgrade->consistency;
  590. $this->view->hasError = $hasError;
  591. $this->view->alterSQL = $alterSQL;
  592. $this->view->version = $this->config->installedVersion;
  593. $this->display();
  594. }
  595. /**
  596. * 检查扩展。
  597. * Check extension.
  598. *
  599. * @access public
  600. * @return void
  601. */
  602. public function checkExtension()
  603. {
  604. /* 如果没有已安装的扩展,跳转到选择版本页面。*/
  605. /* If there is no installed extensions, locate to the version selection page. */
  606. $extensions = $this->loadModel('extension')->getLocalExtensions('installed');
  607. if(empty($extensions)) $this->locate(inlink('selectVersion'));
  608. $versions = array();
  609. foreach($extensions as $code => $extension) $versions[$code] = $extension->version;
  610. /* 如果没有不兼容的扩展,跳转到选择版本页面。*/
  611. /* If there is no incompatible extensions, locate to the version selection page. */
  612. $incompatibleExts = $this->extension->checkIncompatible($versions);
  613. if(empty($incompatibleExts)) $this->locate(inlink('selectVersion'));
  614. $removeCommands = array();
  615. $extensionsName = array();
  616. foreach($incompatibleExts as $extension)
  617. {
  618. $this->extension->updateExtension(array('code' => $extension, 'status' => 'deactivated'));
  619. $removeCommands[$extension] = $this->extension->removePackage($extension);
  620. $extensionsName[$extension] = $extensions[$extension]->name;
  621. }
  622. $this->view->title = $this->lang->upgrade->checkExtension;
  623. $this->view->extensionsName = $extensionsName;
  624. $this->view->removeCommands = $removeCommands;
  625. $this->display();
  626. }
  627. /**
  628. * 更新文件。
  629. * Ajax update file.
  630. *
  631. * @param string $type
  632. * @param int $lastID
  633. * @access public
  634. * @return void
  635. */
  636. public function ajaxUpdateFile($type = '', $lastID = 0)
  637. {
  638. set_time_limit(0);
  639. $this->app->loadLang('search');
  640. $result = $this->upgrade->updateFileObjectID($type, $lastID);
  641. $response = array();
  642. $response['type'] = $type;
  643. $response['count'] = $result['count'];
  644. if($result['type'] == 'finish')
  645. {
  646. $response['result'] = 'finished';
  647. $response['message'] = $this->lang->search->buildSuccessfully;
  648. }
  649. else
  650. {
  651. $response['result'] = 'continue';
  652. $response['next'] = inlink('ajaxUpdateFile', "type={$result['type']}&lastID={$result['lastID']}");
  653. $response['nextType'] = $result['type'];
  654. $response['message'] = zget($this->lang->searchObjects, $result['type']) . " <span class='{$result['type']}-num'>0</span>";
  655. }
  656. echo json_encode($response);
  657. }
  658. /**
  659. * 获取项目集的状态。
  660. * Ajax get program status.
  661. *
  662. * @param int $programID
  663. * @access public
  664. * @return void
  665. */
  666. public function ajaxGetProgramStatus($programID)
  667. {
  668. echo $this->dao->select('status')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch('status');
  669. }
  670. /**
  671. * 迁移扩展文件。
  672. * Move Extent files.
  673. *
  674. * @param string $fromVersion
  675. * @access public
  676. * @return void
  677. */
  678. public function moveExtFiles($fromVersion)
  679. {
  680. $command = '';
  681. $result = 'success';
  682. if(strtolower($this->server->request_method) == 'post')
  683. {
  684. if(!empty($_POST['files']))
  685. {
  686. $response = $this->upgrade->moveExtFiles();
  687. $result = $response['result'];
  688. if($result == 'fail') $command = $response['command'];
  689. }
  690. if($result == 'success') $this->locate(inlink('afterExec', "fromVersion={$fromVersion}&processed=no&skipMoveFile=yes"));
  691. }
  692. $this->view->title = $this->lang->upgrade->common;
  693. $this->view->files = $this->upgrade->getExtFiles();
  694. $this->view->result = $result;
  695. $this->view->command = $command;
  696. $this->view->fromVersion = $fromVersion;
  697. $this->view->upgradeDocs = $this->session->upgradeDocs;
  698. $this->display();
  699. }
  700. /**
  701. * 删除安装和升级文件。
  702. * Safe delete install and upgrade files.
  703. *
  704. * @access public
  705. * @return void
  706. */
  707. public function safeDelete()
  708. {
  709. $files = [];
  710. $wwwRoot = $this->app->getWwwRoot();
  711. foreach(['install', 'upgrade'] as $file)
  712. {
  713. if(is_file($wwwRoot . $file . '.php')) $files[] = $wwwRoot . $file . '.php';
  714. }
  715. if($files)
  716. {
  717. $command = 'rm -f ' . implode(' ', $files);
  718. $tips = $this->lang->upgrade->safeDeleteFile . ' ' . $this->lang->upgrade->execCommand;
  719. return $this->displayCommand($command, $tips);
  720. }
  721. $this->locate($this->config->webRoot);
  722. }
  723. /**
  724. * 定时任务:处理内置关联关系。
  725. * AJAX: Process object relation.
  726. *
  727. * @access public
  728. * @return void
  729. */
  730. public function ajaxProcessObjectRelation()
  731. {
  732. $this->upgrade->processObjectRelation();
  733. echo 'ok';
  734. }
  735. /**
  736. * 定时任务:处理任务关联关系。
  737. * AJAX: Process task relation.
  738. *
  739. * @access public
  740. * @return void
  741. */
  742. public function ajaxInitTaskRelation()
  743. {
  744. $this->upgrade->initTaskRelation();
  745. echo 'ok';
  746. }
  747. /**
  748. * 定时任务:处理发布关联数据。
  749. * AJAX: Process related objects of release.
  750. *
  751. * @access public
  752. * @return void
  753. */
  754. public function ajaxInitReleaseRelated()
  755. {
  756. $this->upgrade->initReleaseRelated();
  757. echo 'ok';
  758. }
  759. /**
  760. * 升级文档数据。
  761. * Upgrade docs.
  762. *
  763. * @access public
  764. * @return void
  765. * @param string $fromVersion
  766. * @param string $processed
  767. */
  768. public function upgradeDocs($fromVersion = '', $processed = 'no')
  769. {
  770. $upgradeDocs = $this->session->upgradeDocs;
  771. if($processed === 'yes' || empty($upgradeDocs))
  772. {
  773. if(!empty($upgradeDocs)) $this->session->set('upgradeDocs', true);
  774. $this->locate(inlink('afterExec', "fromVersion={$fromVersion}&processed=no&skipMoveFile=yes&skipUpdateDocs=yes"));
  775. }
  776. $this->view->title = $this->lang->upgrade->upgradeDocs;
  777. $this->view->upgradeDocs = $upgradeDocs;
  778. $this->view->fromVersion = $fromVersion;
  779. $this->display();
  780. }
  781. /**
  782. * 升级文档数据。
  783. * Upgrade docs.
  784. *
  785. * @param int $docID
  786. * @access public
  787. * @return void
  788. */
  789. public function ajaxUpgradeDoc($docID)
  790. {
  791. $doc = $this->dao->select('t1.*,t2.title,t2.content,t2.type as contentType,t2.rawContent,t1.version')->from(TABLE_DOC)->alias('t1')
  792. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id=t2.doc && t1.version=t2.version')
  793. ->where('t1.id')->eq($docID)
  794. ->fetch();
  795. if(empty($doc)) return $this->send(array('result' => 'fail', 'message' => $this->lang->notFound));
  796. if(!empty($_POST))
  797. {
  798. $html = isset($_POST['html']) ? $_POST['html'] : '';
  799. $content = empty($_POST['content']) ? $html : $_POST['content'];
  800. $result = $this->upgrade->upgradeDoc($docID, $doc->version, $content);
  801. if(!$result) return $this->send(array('result' => 'fail', 'message' => $this->lang->saveFailed));
  802. return $this->send(array('result' => 'success', 'doc' => $docID));
  803. }
  804. $this->send(array('result' => 'success', 'data' => $doc));
  805. }
  806. /**
  807. * 升级老版 wiki 数据。
  808. * Upgrade wikis.
  809. *
  810. * @access public
  811. * @return void
  812. */
  813. public function ajaxUpgradeWikis()
  814. {
  815. if($_POST)
  816. {
  817. $wikis = isset($_POST['wikis']) ? $_POST['wikis'] : array();
  818. if(is_string($wikis)) $wikis = explode(',', $wikis);
  819. if($wikis) $this->upgrade->upgradeWikis($wikis);
  820. $this->send(array('result' => 'success'));
  821. }
  822. }
  823. /**
  824. * 升级文档模板数据。
  825. * Upgrade doc templates.
  826. *
  827. * @param string $fromVersion
  828. * @param string $processed
  829. * @access public
  830. * @return void
  831. */
  832. public function upgradeDocTemplates($fromVersion = '', $processed = 'no')
  833. {
  834. $this->loadModel('doc');
  835. $upgradeDocTemplates = $this->session->upgradeDocTemplates;
  836. if($processed === 'yes' || empty($upgradeDocTemplates))
  837. {
  838. if(!empty($upgradeDocTemplates))
  839. {
  840. $this->session->set('upgradeDocTemplates', true);
  841. $this->doc->upgradeTemplateLibAndModule($upgradeDocTemplates['all']);
  842. /* 记录文档模板的更新时间。*/
  843. /* Record the time of upgrade doc template. */
  844. $this->loadModel('setting')->setItem("system.doc.upgradeTime", helper::now());
  845. }
  846. return $this->locate(inlink('afterExec', "fromVersion={$fromVersion}&processed=no&skipMoveFile=yes&skipUpdateDocs=yes&skipUpdateDocTemplates=yes"));
  847. }
  848. $this->view->title = $this->lang->upgrade->upgradeDocTemplates;
  849. $this->view->upgradeDocTemplates = $upgradeDocTemplates;
  850. $this->view->fromVersion = $fromVersion;
  851. $this->display();
  852. }
  853. /**
  854. * 升级文档模板数据。
  855. * Upgrade doc template.
  856. *
  857. * @param int $docID
  858. * @access public
  859. * @return void
  860. */
  861. public function ajaxUpgradeDocTemplate($docID)
  862. {
  863. $docTemplate = $this->dao->select('t1.*, t2.title, t2.content, t2.type as contentType, t1.version')->from(TABLE_DOC)->alias('t1')
  864. ->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id=t2.doc && t1.version=t2.version')
  865. ->where('t1.id')->eq($docID)
  866. ->fetch();
  867. if(empty($docTemplate)) return $this->send(array('result' => 'fail', 'message' => $this->lang->notFound));
  868. if(!empty($_POST))
  869. {
  870. $result = $this->upgrade->upgradeDocTemplate($docID, $docTemplate->version);
  871. if(!$result) return $this->send(array('result' => 'fail', 'message' => $this->lang->saveFailed));
  872. return $this->send(array('result' => 'success', 'doc' => $docID));
  873. }
  874. $this->send(array('result' => 'success', 'data' => $docTemplate));
  875. }
  876. /**
  877. * 升级wiki类型的文档模板。
  878. * Upgrade templates of wiki.
  879. *
  880. * @access public
  881. * @return void
  882. */
  883. public function ajaxUpgradeWikiTemplates()
  884. {
  885. if($_POST)
  886. {
  887. $wikis = isset($_POST['wikis']) ? $_POST['wikis'] : array();
  888. if(is_string($wikis)) $wikis = explode(',', $wikis);
  889. if($wikis) $this->upgrade->upgradeWikiTemplates($wikis);
  890. $this->send(array('result' => 'success'));
  891. }
  892. }
  893. /**
  894. * 升级项目报告数据。
  895. * Upgrade project reports.
  896. *
  897. * @param string $fromVersion
  898. * @param string $processed
  899. * @access public
  900. * @return void
  901. */
  902. public function upgradeProjectReports($fromVersion = '', $processed = 'no')
  903. {
  904. $upgradeReports = $this->session->upgradeProjectReports;
  905. if($processed === 'yes' || empty($upgradeReports))
  906. {
  907. if(!empty($upgradeReports)) $this->session->set('upgradeProjectReports', true);
  908. $this->locate(inlink('afterExec', "fromVersion={$fromVersion}&processed=no&skipMoveFile=yes&skipUpdateDocs=yes&skipUpdateDocTemplates=yes&skipUpdateProjectReports=yes"));
  909. }
  910. $this->view->title = $this->lang->upgrade->upgradeProjectReports;
  911. $this->view->upgradeReports = $upgradeReports;
  912. $this->view->fromVersion = $fromVersion;
  913. $this->display();
  914. }
  915. /**
  916. * 升级项目报告数据。
  917. * Upgrade project reports.
  918. *
  919. * @access public
  920. * @return void
  921. */
  922. public function ajaxUpgradeProjectReport()
  923. {
  924. if($_POST)
  925. {
  926. $data = isset($_POST['data']) ? $_POST['data'] : array();
  927. if($data) $this->upgrade->upgradeProjectReport($data);
  928. $this->send(array('result' => 'success'));
  929. }
  930. }
  931. }