zen.php 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. <?php
  2. class upgradeZen extends upgrade
  3. {
  4. /**
  5. * 升级变更内容缓存。
  6. * Upgrade changes cache.
  7. *
  8. * @var array
  9. * @access private
  10. */
  11. private $upgradeChanges = [];
  12. /**
  13. * 获取要升级到的版本列表。
  14. * Get upgrade versions.
  15. *
  16. * @param string $fromVersion
  17. * @access protected
  18. * @return string[]
  19. */
  20. protected function getUpgradeVersions($fromVersion)
  21. {
  22. $upgradeVersions = [];
  23. $currentEdition = $this->config->edition;
  24. $fromEdition = $this->upgrade->getEditionByVersion($fromVersion);
  25. $fromOpenVersion = $this->upgrade->getOpenVersion(str_replace('.', '_', $this->config->installedVersion));
  26. if($currentEdition == 'open')
  27. {
  28. /**
  29. * 如果当前版本是开源版,则列出所有大于 fromVersion 的开源版版本。
  30. * 比如 fromVersion 为 21_7_6,当前版本为 21_7_8,则列出 21_7_7 和 21_7_8。
  31. * If the current edition is open, list all versions greater than fromVersion.
  32. * For example, if fromVersion is 21_7_6 and the current version is 21_7_8, list 21_7_7 and 21_7_8.
  33. */
  34. foreach($this->lang->upgrade->fromVersions as $version => $label)
  35. {
  36. if(!is_numeric($version[0])) continue;
  37. if(version_compare($version, $fromOpenVersion, '<=')) continue;
  38. $upgradeVersions[$version] = $label;
  39. }
  40. return $upgradeVersions;
  41. }
  42. if($currentEdition == $fromEdition)
  43. {
  44. /**
  45. * 如果当前版本和 fromVersion 版本属于同一付费版,则列出所有大于 fromVersion 的该付费版版本。
  46. * 比如 fromVersion 为 biz12_6,当前版本为 biz12_8,则列出 biz12_7 和 biz12_8。
  47. * If the current edition is the same as fromEdition, list all versions greater than fromVersion of the same edition.
  48. * For example, if fromVersion is biz12_6 and the current version is biz12_8, list biz12_7 and biz12_8.
  49. */
  50. foreach($this->lang->upgrade->fromVersions as $version => $label)
  51. {
  52. if(strpos($version, $currentEdition) !== 0) continue;
  53. if(version_compare($version, $fromVersion, '<=')) continue;
  54. $upgradeVersions[$version] = $label;
  55. }
  56. }
  57. else
  58. {
  59. $currentMapVersions = $this->config->upgrade->{$currentEdition . 'Version'};
  60. if(array_search($fromOpenVersion, $currentMapVersions))
  61. {
  62. /**
  63. * 如果当前版本和 fromVersion 版本属于不同付费版,但 fromVersion 可以映射到当前付费版,则列出所有大于等于 fromVersion 映射版本的该付费版版本。
  64. * 比如 fromVersion 为 biz12_6,当前版本为 max12_8,则列出 max12_6、max12_7 和 max12_8。
  65. * If the current edition is different from fromEdition, but fromVersion can be mapped to the current edition, list all versions greater than or equal to the mapped version of the current edition.
  66. */
  67. foreach($currentMapVersions as $version => $openVersion)
  68. {
  69. if(version_compare($openVersion, $fromOpenVersion, '<')) continue;
  70. $upgradeVersions[$version] = $this->lang->upgrade->fromVersions[$version] ?? '';
  71. }
  72. }
  73. else
  74. {
  75. /**
  76. * 查找距离 fromVersion 最近的当前付费版版本和开源版版本。
  77. * 比如 fromVersion 为 18_1,当前版本为 ipd4_7,则 currentMappedVersion 为 ipd1_0_beta1, currentMappedOpenVersion 为 18_4_alpha1
  78. * Find the nearest current edition version and open version from fromVersion.
  79. * For example, if fromVersion is 18_1 and the current version is ipd4_7, then currentMappedVersion is ipd1_0_beta1 and currentMappedOpenVersion is 18_4_alpha1.
  80. */
  81. $currentMappedVersion = '';
  82. $currentMappedOpenVersion = '';
  83. foreach($currentMapVersions as $version => $openVersion)
  84. {
  85. if(version_compare($openVersion, $fromOpenVersion, '>'))
  86. {
  87. $currentMappedVersion = $version;
  88. $currentMappedOpenVersion = $openVersion;
  89. break;
  90. }
  91. }
  92. if($fromEdition == 'open')
  93. {
  94. /**
  95. * 如果 fromVersion 是开源版,则列出所有大于 fromVersion 且小于距离 fromVersion 最近的当前付费版映射的开源版版本。
  96. * 比如 fromVersion 为 18_1,当 前版本为 ipd4_7,则列出大于 18_1 且小于 18_4_alpha1 的所有开源版版本,即 18_2 和 18_3。
  97. * If fromEdition is open, list all versions greater than fromVersion and less than the open version mapped by the current edition nearest to fromVersion.
  98. * For example, if fromVersion is 18_1 and the current version is ipd4_7, list all open versions greater than 18_1 and less than 18_4_alpha1, i.e., 18_2 and 18_3.
  99. */
  100. foreach($this->lang->upgrade->fromVersions as $version => $label)
  101. {
  102. if(!is_numeric($version[0])) continue;
  103. if(version_compare($version, $fromOpenVersion, '<=')) continue;
  104. if(!empty($currentMappedOpenVersion) && version_compare($version, $currentMappedOpenVersion, '>=')) break;
  105. $upgradeVersions[$version] = $label;
  106. }
  107. }
  108. else
  109. {
  110. /**
  111. * 如果 fromVersion 是付费版,则列出所有大于 fromVersion 映射的开源版版本且小于距离 fromVersion 最近的当前付费版映射的开源版版本对应的付费版版本。
  112. * 比如 fromVersion 为 biz8_1,当前版本为 ipd4_7,则列出大于 18_1 且小于 18_4_alpha1 的所有开源版版本对应的付费版版本,即 biz8_2 和 biz8_3。
  113. * If fromEdition is charged, list all open versions greater than the open version mapped by fromVersion and less than the open version mapped by the current edition nearest to fromVersion.
  114. * For example, if fromVersion is biz8_1 and the current version is ipd4_7, list all open versions greater than 18_1 and less than 18_4_alpha1, i.e., 18_2 and 18_3.
  115. */
  116. $fromMapVersions = $this->config->upgrade->{$fromEdition . 'Version'};
  117. foreach($fromMapVersions as $version => $openVersion)
  118. {
  119. if(version_compare($openVersion, $fromOpenVersion, '<=')) continue;
  120. if(!empty($currentMappedOpenVersion) && version_compare($openVersion, $currentMappedOpenVersion, '>=')) break;
  121. $upgradeVersions[$version] = $this->lang->upgrade->fromVersions[$version] ?? '';
  122. }
  123. }
  124. /**
  125. * 列出所有大于等于距离 fromVersion 最近的当前付费版版本。
  126. * 比如 fromVersion 为 biz8_1,当前版本为 ipd4_7,则列出 ipd1_0_beta1 及之后的所有版本。
  127. * List all versions greater than or equal to the current edition version nearest to fromVersion.
  128. * For example, if fromVersion is biz8_1 and the current version is ipd4_7, list ipd1_0_beta1 and all subsequent versions.
  129. */
  130. foreach(array_keys($currentMapVersions) as $version)
  131. {
  132. if(!empty($currentMappedVersion) && version_compare($version, $currentMappedVersion, '<')) continue;
  133. $upgradeVersions[$version] = $this->lang->upgrade->fromVersions[$version] ?? '';
  134. }
  135. }
  136. }
  137. $currentVersion = str_replace('.', '_', $this->config->version);
  138. if($currentEdition == 'ipd' && empty($upgradeVersions[$currentVersion])) $upgradeVersions[$currentVersion] = ucfirst($this->config->version);
  139. return $upgradeVersions;
  140. }
  141. /**
  142. * 获取升级变更内容列表。
  143. * Get upgrade changes.
  144. *
  145. * @param string $fromVersion
  146. * @param string $toVersion
  147. * @access protected
  148. * @return array[]
  149. */
  150. protected function getUpgradeChanges($fromVersion, $toVersion)
  151. {
  152. $this->upgradeChanges = [];
  153. $currentVersion = str_replace('.', '_', $this->config->installedVersion);
  154. $fromEdition = $this->upgrade->getEditionByVersion($fromVersion);
  155. $fromOpenVersion = $this->upgrade->getOpenVersion($currentVersion);
  156. $toOpenVersion = $this->upgrade->getOpenVersion($toVersion);
  157. $upgradeVersions = $this->upgrade->getVersionsToUpdate($fromOpenVersion, $fromEdition);
  158. $changes = [];
  159. foreach($upgradeVersions as $openVersion => $chargedVersions)
  160. {
  161. if(version_compare($openVersion, $fromOpenVersion, '<')) continue;
  162. if(version_compare($openVersion, $toOpenVersion, '>=')) continue;
  163. $sqlFile = $this->upgrade->getUpgradeFile(str_replace('_', '.', $openVersion));
  164. $changes = array_merge($changes, $this->getChangesBySql($openVersion, $sqlFile));
  165. $changes = array_merge($changes, $this->getChangesByConfig($openVersion));
  166. foreach($chargedVersions as $chargedVersion)
  167. {
  168. foreach($chargedVersion as $version)
  169. {
  170. $sqlFile = $this->upgrade->getUpgradeFile(str_replace('_', '.', $version));
  171. $changes = array_merge($changes, $this->getChangesBySql($version, $sqlFile));
  172. $changes = array_merge($changes, $this->getChangesByConfig($version));
  173. }
  174. }
  175. }
  176. /* 如果此次升级到最终版本,则执行额外的数据处理流程。*/
  177. if(version_compare($toVersion, $this->config->version, '='))
  178. {
  179. $edition = $this->upgrade->getEditionByVersion($fromVersion);
  180. $methods = $this->upgrade->getOtherMethods($edition);
  181. foreach(array_keys($methods) as $method)
  182. {
  183. $changes[] = $this->getChangesByMethod($currentVersion, $method);
  184. }
  185. }
  186. return $changes;
  187. }
  188. /**
  189. * 获取配置文件中的变更内容列表。
  190. * Get changes by config.
  191. *
  192. * @param string $version
  193. * @access protected
  194. * @return array[]
  195. */
  196. protected function getChangesByConfig($version)
  197. {
  198. $changes = [];
  199. $functions = $this->config->upgrade->execFlow[$version]['functions'] ?? '';
  200. $xxsqls = $this->config->upgrade->execFlow[$version]['xxsqls'] ?? '';
  201. $xxfunctions = $this->config->upgrade->execFlow[$version]['xxfunctions'] ?? '';
  202. foreach(array_filter(explode(',', $functions)) as $function)
  203. {
  204. $changes[] = $this->getChangesByMethod($version, $function);
  205. }
  206. if($version == 'pro1_1_1')
  207. {
  208. $sqlFile = $this->upgrade->getUpgradeFile('pro1.1');
  209. $sqlChanges = $this->getChangesBySql($version, $sqlFile);
  210. $changes = array_merge($changes, $sqlChanges);
  211. }
  212. if($version == 'pro8_3')
  213. {
  214. $sqlFile = $this->ugprade->getUpgradeFile('pro8.2');
  215. $sqlChanges = $this->getChangesBySql($version, $sqlFile);
  216. $changes = array_merge($changes, $sqlChanges);
  217. }
  218. if(!empty($xxsqls))
  219. {
  220. foreach(array_filter(explode(',', $xxsqls)) as $sqlFile)
  221. {
  222. $sqlChanges = $this->getChangesBySql($version, $sqlFile);
  223. $changes = array_merge($changes, $sqlChanges);
  224. }
  225. }
  226. if(!empty($xxfunctions))
  227. {
  228. foreach(array_filter(explode(',', $xxfunctions)) as $function)
  229. {
  230. $changes[] = $this->getChangesByMethod($version, $function);
  231. }
  232. }
  233. return $changes;
  234. }
  235. /**
  236. * 获取 SQL 文件中的变更内容列表。
  237. * Get changes by sql file.
  238. *
  239. * @param string $version
  240. * @param string $sqlFile
  241. * @access protected
  242. * @return array[]
  243. */
  244. protected function getChangesBySql($version, $sqlFile)
  245. {
  246. if(!is_file($sqlFile)) return [];
  247. $changes = [];
  248. $sqls = $this->upgrade->parseToSqls($sqlFile);
  249. foreach($sqls as $sql)
  250. {
  251. $sqlMd5 = md5($sql);
  252. $fileMd5 = md5($sqlFile);
  253. if(isset($this->upgradeChanges[$version]['sqls'][$fileMd5][$sqlMd5])) continue;
  254. $this->upgradeChanges[$version]['sqls'][$fileMd5][$sqlMd5] = true;
  255. $items = $this->parseSqlToSemantic($sql);
  256. foreach($items as $item)
  257. {
  258. $search = ['%TABLE%', '%FIELD%', '%INDEX%', '%VIEW%', '%OLD%', '%NEW%'];
  259. $replace = [$item['table'] ?? '', $item['field'] ?? '', $item['index'] ?? '', $item['view'] ?? '', $item['old'] ?? '', $item['new'] ?? ''];
  260. $subject = $this->lang->upgrade->changeActions[$item['action']] ?? $this->lang->upgrade->changeActions['other'];
  261. $content = str_replace($search, $replace, $subject);
  262. $changes[] = ['version' => $version, 'type' => 'sql', 'mode' => $item['mode'], 'content' => $content, 'sql' => $sql, 'fileMd5' => $fileMd5, 'sqlMd5' => $sqlMd5];
  263. }
  264. }
  265. return $changes;
  266. }
  267. /**
  268. * 获取方法变更内容。
  269. * Get changes by method.
  270. *
  271. * @param string $version
  272. * @param string $rawMethod
  273. * @access protected
  274. * @return array
  275. */
  276. protected function getChangesByMethod($version, $rawMethod)
  277. {
  278. if(isset($this->upgradeChanges[$version]['methods'][$rawMethod])) return [];
  279. $this->upgradeChanges[$version]['methods'][$rawMethod] = true;
  280. $module = 'upgrade';
  281. $method = $rawMethod;
  282. if(strpos($rawMethod, '-') !== false)
  283. {
  284. list($module, $method) = explode('-', $rawMethod);
  285. }
  286. $content = str_replace(['%MODULE%', '%METHOD%'], [$module, $method], $this->lang->upgrade->changeActions['method']);
  287. return ['version' => $version, 'type' => 'method', 'mode' => 'update', 'content' => $content, 'method' => $rawMethod];
  288. }
  289. /**
  290. * 将 SQL 语句解析为语义化描述数组
  291. *
  292. * @param string $sql 单条 SQL 语句
  293. * @access protected
  294. * @return array[]
  295. */
  296. protected function parseSqlToSemantic($sql)
  297. {
  298. $sql = trim($sql);
  299. if($sql === '') return [];
  300. /* 移除末尾分号 */
  301. $sql = rtrim($sql, " \t\n\r;");
  302. if(preg_match('/^create\s+(or\s+replace\s+)?view\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'create', 'action' => 'createView', 'view' => $this->extractTableName($matches[2])]]; // CREATE VIEW / CREATE OR REPLACE VIEW
  303. if(preg_match('/^drop\s+view\s+(if\s+exists\s+)?((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'delete', 'action' => 'dropView', 'view' => $this->extractTableName($matches[2])]]; // DROP VIEW
  304. if(preg_match('/^create\s+(unique\s+)?index\s+`?([\w]+)`?\s+on\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'create', 'action' => 'addIndex', 'table' => $this->extractTableName($matches[3]), 'index' => $this->extractTableName($matches[2])]]; // CREATE INDEX idx ON table_name
  305. if(preg_match('/^drop\s+index\s+`?([\w]+)`?\s+on\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'delete', 'action' => 'dropIndex', 'table' => $this->extractTableName($matches[2]), 'index' => $this->extractTableName($matches[1])]]; // DROP INDEX idx ON table_name
  306. if(preg_match('/^create\s+table\s+(if\s+not\s+exists\s+)?((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'create', 'action' => 'createTable', 'table' => $this->extractTableName($matches[2])]]; // CREATE TABLE
  307. if(preg_match('/^drop\s+table\s+(if\s+exists\s+)?((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'delete', 'action' => 'dropTable', 'table' => $this->extractTableName($matches[2])]]; // DROP TABLE
  308. if(preg_match('/^rename\s+table\s+((?:`[^`]*`|\S)+)\s+to\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'update', 'action' => 'renameTable', 'old' => $this->extractTableName($matches[1]), 'new' => $this->extractTableName($matches[2])]]; // RENAME TABLE
  309. if(preg_match('/^(insert|replace)\s+into\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'create', 'action' => 'insertValue', 'table' => $this->extractTableName($matches[2])]]; // INSERT / REPLACE
  310. if(preg_match('/^update\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'update', 'action' => 'updateValue', 'table' => $this->extractTableName($matches[1])]]; // UPDATE
  311. if(preg_match('/^delete\s+from\s+((?:`[^`]*`|\S)+)/i', $sql, $matches)) return [['mode' => 'delete', 'action' => 'deleteValue', 'table' => $this->extractTableName($matches[1])]]; // DELETE
  312. /* ALTER TABLE */
  313. $sql = str_replace("\n", ' ', $sql); // 将换行替换为空格,方便后续处理
  314. if(preg_match('/^alter\s+table\s+((?:`[^`]*`|\S)+)\s+(.+)/i', $sql, $matches))
  315. {
  316. $tableName = $this->extractTableName($matches[1]);
  317. $alterBody = ltrim($matches[2]);
  318. /* 先检查是否是整表重命名:ALTER TABLE t RENAME TO new_t */
  319. if(preg_match('/^\s*rename\s+to\s+((?:`[^`]*`|\S)+)\s*$/i', $alterBody, $m)) return [['mode' => 'update', 'action' => 'renameTable', 'old' => $tableName, 'new' => trim($m[1], '`')]];
  320. $results = [];
  321. $clauses = $this->splitAlterClauses($alterBody);
  322. foreach($clauses as $clause)
  323. {
  324. $clause = trim($clause);
  325. if ($clause === '') continue;
  326. /* 提取关键词(忽略大小写) */
  327. $upperClause = preg_replace('/\s+/', ' ', strtoupper($clause));
  328. $words = explode(' ', $upperClause);
  329. if(empty($words)) continue;
  330. $first = $words[0];
  331. /* --- ADD [COLUMN/INDEX/KEY] --- */
  332. if($first === 'ADD')
  333. {
  334. /* 先尝试匹配 ADD INDEX / ADD KEY / ADD UNIQUE,再尝试匹配 ADD COLUMN 或 ADD field_name(即字段) */
  335. if(preg_match('/^add\s+(unique\s+)?(index|key)\s+(`[^`]*`|\w+)/i', $clause, $m))
  336. {
  337. $results[] = ['mode' => 'create', 'action' => 'addIndex', 'table' => $tableName, 'index' => trim($m[3], '`')];
  338. }
  339. else
  340. {
  341. /* 跳过 "COLUMN" */
  342. $pos = 1;
  343. if(isset($words[1]) && $words[1] === 'COLUMN') $pos = 2;
  344. /* 提取字段名(支持反引号,可能含空格) */
  345. if(isset($words[$pos]) && preg_match('/^add\s+(column\s+)?(`[^`]*`|\w+)/i', $clause, $m)) $results[] = ['mode' => 'create', 'action' => 'addField', 'table' => $tableName, 'field' => trim($m[2], '`')];
  346. }
  347. continue;
  348. }
  349. /* --- DROP [COLUMN/INDEX/KEY] --- */
  350. if($first === 'DROP')
  351. {
  352. if(isset($words[1]) && in_array($words[1], ['COLUMN', 'INDEX', 'KEY']))
  353. {
  354. /* 字段或索引 */
  355. if(preg_match('/^drop\s+(column|index|key)\s+(`[^`]*`|\w+)/i', $clause, $m))
  356. {
  357. $type = strtolower($m[1]);
  358. if($type === 'column')
  359. {
  360. $results[] = ['mode' => 'delete', 'action' => 'dropField', 'table' => $tableName, 'field' => trim($m[2], '`')];
  361. }
  362. else
  363. {
  364. $results[] = ['mode' => 'delete', 'action' => 'dropIndex', 'table' => $tableName, 'index' => trim($m[2], '`')];
  365. }
  366. }
  367. }
  368. else
  369. {
  370. /* 简写 DROP field_name */
  371. if(preg_match('/^drop\s+(`[^`]*`|\w+)/i', $clause, $m)) $results[] = ['mode' => 'delete', 'action' => 'dropField', 'table' => $tableName, 'field' => trim($m[1], '`')];
  372. }
  373. continue;
  374. }
  375. /* --- MODIFY [COLUMN] --- */
  376. if($first === 'MODIFY')
  377. {
  378. $pos = 1;
  379. if(isset($words[1]) && $words[1] === 'COLUMN') $pos = 2;
  380. if(isset($words[$pos]) && preg_match('/^modify\s+(column\s+)?(`[^`]*`|\w+)/i', $clause, $m)) $results[] = ['mode' => 'update', 'action' => 'modifyField', 'table' => $tableName, 'field' => trim($m[2], '`')];
  381. continue;
  382. }
  383. /* --- CHANGE [COLUMN] --- */
  384. if($first === 'CHANGE')
  385. {
  386. /* CHANGE [COLUMN] old_name new_name ... */
  387. $pos = 1;
  388. if(isset($words[1]) && $words[1] === 'COLUMN') $pos = 2;
  389. /* 使用原始子句提取两个标识符 */
  390. if(isset($words[$pos + 1]) && preg_match('/^change\s+(column\s+)?(`[^`]*`|\w+)\s+(`[^`]*`|\w+)/i', $clause, $m))
  391. {
  392. $old = trim($m[2], '`');
  393. $new = trim($m[3], '`');
  394. if($old == $new)
  395. {
  396. $results[] = ['mode' => 'update', 'action' => 'modifyField', 'table' => $tableName, 'field' => $old];
  397. }
  398. else
  399. {
  400. $results[] = ['mode' => 'update', 'action' => 'renameField', 'table' => $tableName, 'old' => $old, 'new' => $new];
  401. }
  402. }
  403. continue;
  404. }
  405. /* --- RENAME COLUMN old_name TO new_name (MySQL 8.0+) --- */
  406. if($first === 'RENAME' && isset($words[1]) && $words[1] === 'COLUMN' && preg_match('/^rename\s+column\s+(`[^`]*`|\w+)\s+to\s+(`[^`]*`|\w+)/i', $clause, $m)) $results[] = ['mode' => 'update', 'action' => 'renameField', 'table' => $tableName, 'old' => trim($m[1], '`'), 'new' => trim($m[2], '`')];
  407. }
  408. return $results;
  409. }
  410. return [];
  411. }
  412. /**
  413. * 从可能带数据库前缀的标识符中提取表名(如从 `db`.`table` 或 db.table 中提取 table)
  414. *
  415. * @param string $full 被反引号或不带反引号的标识符(如 "db.table" 或 "`my db`.`my-table`")
  416. * @access public
  417. * @return string 表名(不含反引号)
  418. */
  419. protected function extractTableName($full)
  420. {
  421. $full = trim($full); // 去除首尾空白
  422. // 截断第一个未被反引号包围的左括号之前的内容
  423. $trimmed = '';
  424. $inBacktick = false;
  425. for($i = 0; $i < strlen($full); $i++)
  426. {
  427. $c = $full[$i];
  428. if($c === '`')
  429. {
  430. $inBacktick = !$inBacktick;
  431. $trimmed .= $c;
  432. }
  433. elseif($c === '(' && !$inBacktick)
  434. {
  435. break; // 遇到未在反引号内的 '(',截断
  436. }
  437. else
  438. {
  439. $trimmed .= $c;
  440. }
  441. }
  442. $full = trim($trimmed);
  443. /* 按未被反引号包围的点分割 */
  444. $parts = [];
  445. $current = '';
  446. $inBacktick = false;
  447. for($i = 0; $i < strlen($full); $i++)
  448. {
  449. $c = $full[$i];
  450. if($c === '`')
  451. {
  452. $inBacktick = !$inBacktick;
  453. continue; // 反引号本身不存入
  454. }
  455. if($c === '.' && !$inBacktick)
  456. {
  457. $parts[] = $current;
  458. $current = '';
  459. }
  460. else
  461. {
  462. $current .= $c;
  463. }
  464. }
  465. $parts[] = $current;
  466. $tableName = end($parts); // 最后一个 part 就是表名
  467. return $tableName !== false ? $tableName : $full;
  468. }
  469. /**
  470. * 安全拆分 ALTER TABLE 子句,跳过字符串和括号内的逗号
  471. *
  472. * @param string $body
  473. * @access protected
  474. * @return string[]
  475. */
  476. protected function splitAlterClauses($body)
  477. {
  478. $clauses = [];
  479. $current = '';
  480. $len = strlen($body);
  481. $inSingle = false;
  482. $inDouble = false;
  483. $parenLevel = 0;
  484. for($i = 0; $i < $len; $i++)
  485. {
  486. $c = $body[$i];
  487. $next = ($i + 1 < $len) ? $body[$i + 1] : '';
  488. /* 处理转义(简化:跳过下一个字符) */
  489. if($c === '\\' && ($inSingle || $inDouble))
  490. {
  491. $current .= $c . $next;
  492. $i++;
  493. continue;
  494. }
  495. if($c === "'" && !$inDouble)
  496. {
  497. $inSingle = !$inSingle;
  498. }
  499. elseif($c === '"' && !$inSingle)
  500. {
  501. $inDouble = !$inDouble;
  502. }
  503. elseif($c === '(' && !$inSingle && !$inDouble)
  504. {
  505. $parenLevel++;
  506. }
  507. elseif($c === ')' && !$inSingle && !$inDouble)
  508. {
  509. $parenLevel--;
  510. }
  511. elseif($c === ',' && !$inSingle && !$inDouble && $parenLevel === 0)
  512. {
  513. $clauses[] = $current;
  514. $current = '';
  515. continue;
  516. }
  517. $current .= $c;
  518. }
  519. if($current !== '') $clauses[] = $current;
  520. return $clauses;
  521. }
  522. /**
  523. * 升级 sql 成功执行后的操作。
  524. * Operations after successful execution.
  525. *
  526. * @param string $fromVersion
  527. * @access protected
  528. * @return string
  529. */
  530. protected function getRedirectUrlAfterExecute($fromVersion)
  531. {
  532. /* Delete all patch actions if upgrade success. */
  533. $this->loadModel('action')->deleteByType('patch');
  534. $selectMode = true;
  535. $systemMode = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=mode');
  536. /* 如果经典管理模式。*/
  537. /* If the system mode is classic. */
  538. if($systemMode == 'classic' && $this->config->edition != 'ipd')
  539. {
  540. $this->upgradeFromClassicMode();
  541. $selectMode = false;
  542. }
  543. /* 从15 版本以后升级。*/
  544. /* when upgrade from the vesion is more than 15. */
  545. $rawFromVersion = $fromVersion;
  546. if(strpos($rawFromVersion, 'lite') !== false) $rawFromVersion = $this->config->upgrade->liteVersion[$fromVersion];
  547. $openVersion = $this->upgrade->getOpenVersion($rawFromVersion);
  548. if(version_compare($openVersion, '15_0_rc1', '>=') && $systemMode == 'new')
  549. {
  550. $this->setting->setItem('system.common.global.mode', 'ALM');
  551. $selectMode = false;
  552. }
  553. if(version_compare($openVersion, '18_0_beta1', '>=')) $selectMode = false;
  554. /* 如果是 ipd 版本,设置相关的配置。*/
  555. /* When the edition is ipd. */
  556. if($this->config->edition == 'ipd') $this->setIpdItems($openVersion);
  557. $this->setting->setItem('system.common.userview.relatedTablesUpdateTime', time());
  558. if($selectMode)
  559. {
  560. if($this->config->edition == 'ipd') return inlink('to18Guide', "fromVersion={$fromVersion}&mode=ALM");
  561. return inlink('to18Guide', "fromVersion={$fromVersion}");
  562. }
  563. return inlink('afterExec', "fromVersion={$fromVersion}");
  564. }
  565. /**
  566. * 从经典模式升级后的处理。
  567. * Process after upgrade from classic mode.
  568. *
  569. * @access private
  570. * @return void
  571. */
  572. private function upgradeFromClassicMode()
  573. {
  574. $this->loadModel('setting')->setItem('system.common.global.mode', 'light');
  575. $programID = $this->setDefaultProgram();
  576. $_POST['projectType'] = 'execution';
  577. $this->upgrade->upgradeInProjectMode($programID, 'classic');
  578. $this->upgrade->computeObjectMembers();
  579. $this->upgrade->initUserView();
  580. $this->upgrade->setDefaultPriv();
  581. $this->dao->update(TABLE_CONFIG)->set('value')->eq('0_0')->where('`key`')->eq('productProject')->exec();
  582. $hourPoint = $this->setting->getItem('owner=system&module=custom&key=hourPoint');
  583. if(empty($hourPoint)) $this->setting->setItem('system.custom.hourPoint', 0);
  584. $sprints = $this->dao->select('id')->from(TABLE_PROJECT)->where('type')->eq('sprint')->fetchAll('id');
  585. $this->dao->update(TABLE_ACTION)->set('objectType')->eq('execution')->where('objectID')->in(array_keys($sprints))->andWhere('objectType')->eq('project')->exec();
  586. $this->loadModel('custom')->disableFeaturesByMode('light');
  587. }
  588. /**
  589. * Ipd 版本升级后的处理。
  590. * Set ipd items.
  591. *
  592. * @param string $openVersion
  593. * @access private
  594. * @return void
  595. */
  596. private function setIpdItems($openVersion = '')
  597. {
  598. $this->loadModel('setting')->setItem('system.common.global.mode', 'PLM');
  599. $this->setting->setItem('system.custom.URAndSR', '1');
  600. $this->upgrade->addORPriv($openVersion);
  601. }
  602. /**
  603. * 设置迭代的概念。
  604. * Set sprint concept.
  605. *
  606. * @access protected
  607. * @return void
  608. */
  609. protected function setSprintConcept()
  610. {
  611. $sprintConcept = 0;
  612. if(isset($this->config->custom->sprintConcept))
  613. {
  614. if($this->config->custom->sprintConcept == 2) $sprintConcept = 1;
  615. }
  616. elseif(isset($this->config->custom->productProject))
  617. {
  618. $projectConcept = substr($this->config->custom->productProject, strpos($this->config->custom->productProject, '_'));
  619. if($projectConcept == 2) $sprintConcept = 1;
  620. }
  621. $this->loadModel('setting')->setItem('system.custom.sprintConcept', $sprintConcept);
  622. }
  623. /**
  624. * 创建默认项目集,并且将项目关联到默认项目集。
  625. * Set default program.
  626. *
  627. * @access protected
  628. * @return int
  629. */
  630. protected function setDefaultProgram()
  631. {
  632. $programID = $this->loadModel('program')->createDefaultProgram();
  633. $this->loadModel('setting')->setItem('system.common.global.defaultProgram', $programID);
  634. /* Set default program for product and project with no program. */
  635. $this->upgrade->relateDefaultProgram($programID);
  636. return $programID;
  637. }
  638. /**
  639. * 合并后的升级操作。
  640. * Upgrade after merged.
  641. *
  642. * @access protected
  643. * @return void
  644. */
  645. protected function upgradeAfterMerged()
  646. {
  647. $this->upgrade->computeObjectMembers();
  648. $this->upgrade->initUserView();
  649. $this->upgrade->setDefaultPriv();
  650. $this->dao->update(TABLE_CONFIG)->set('value')->eq('0_0')->where('`key`')->eq('productProject')->exec();
  651. /* Set defult hourPoint. */
  652. $hourPoint = $this->loadModel('setting')->getItem('owner=system&module=custom&key=hourPoint');
  653. if(empty($hourPoint)) $this->setting->setItem('system.custom.hourPoint', 0);
  654. /* Update sprints history. */
  655. $sprints = $this->dao->select('id')->from(TABLE_PROJECT)->where('type')->eq('sprint')->fetchAll('id');
  656. $this->dao->update(TABLE_ACTION)->set('objectType')->eq('execution')->where('objectID')->in(array_keys($sprints))->andWhere('objectType')->eq('project')->exec();
  657. $this->locate($this->createLink('upgrade', 'mergeRepo'));
  658. }
  659. /**
  660. * 获取产品线下的产品和项目。
  661. * Get products and projects group by product line.
  662. *
  663. * @param string $projectType
  664. * @access protected
  665. * @return void
  666. */
  667. protected function assignProductsAndProjectsGroupByProductline($projectType)
  668. {
  669. $productlines = $this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('line')->andWhere('root')->eq(0)->orderBy('id_desc')->fetchAll('id');
  670. $noMergedProducts = $this->dao->select('*')->from(TABLE_PRODUCT)->where('line')->in(array_keys($productlines))->andWhere('vision')->eq('rnd')->orderBy('id_desc')->fetchAll('id');
  671. if(empty($productlines) || empty($noMergedProducts)) $this->locate($this->createLink('upgrade', 'mergeProgram', "type=product&programID=0&projectType=$projectType"));
  672. /* Group product by product line. */
  673. $lineGroups = array();
  674. foreach($noMergedProducts as $product) $lineGroups[$product->line][$product->id] = $product;
  675. foreach($productlines as $line)
  676. {
  677. if(!isset($lineGroups[$line->id])) unset($productlines[$line->id]);
  678. }
  679. $noMergedSprints = $this->dao->select('t1.*')->from(TABLE_PROJECT)->alias('t1')
  680. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.project')
  681. ->where('t1.project')->eq(0)
  682. ->andWhere('t1.deleted')->eq(0)
  683. ->andWhere('t1.vision')->eq('rnd')
  684. ->andWhere('t1.type')->eq('sprint')
  685. ->andWhere('t2.product')->in(array_keys($noMergedProducts))
  686. ->orderBy('t1.id_desc')
  687. ->fetchAll('id');
  688. /* Remove sprint that linked more than two products */
  689. $sprintProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($noMergedSprints))->fetchGroup('project', 'product');
  690. foreach($sprintProducts as $sprintID => $products)
  691. {
  692. if(count($products) > 1) unset($noMergedSprints[$sprintID]);
  693. }
  694. /* Group sprint by product. */
  695. $productGroups = array();
  696. foreach($noMergedSprints as $sprint)
  697. {
  698. $sprintProduct = zget($sprintProducts, $sprint->id, array());
  699. if(empty($sprintProduct)) continue;
  700. $productID = key($sprintProduct);
  701. $productGroups[$productID][$sprint->id] = $sprint;
  702. }
  703. $this->view->productlines = $productlines;
  704. $this->view->lineGroups = $lineGroups;
  705. $this->view->productGroups = $productGroups;
  706. }
  707. /**
  708. * 获取产品下的项目。
  709. * Get projects group by product.
  710. *
  711. * @param string $projectType
  712. * @access protected
  713. * @return void
  714. */
  715. protected function assignProjectsGroupByProduct($projectType)
  716. {
  717. $noMergedSprints = $this->dao->select('t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1')
  718. ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
  719. ->where('t2.model')->eq('')
  720. ->andWhere('t2.project')->eq(0)
  721. ->andWhere('t2.vision')->eq('rnd')
  722. ->andWhere('t2.deleted')->eq(0)
  723. ->andWhere('t2.type')->eq('sprint')
  724. ->fetchAll('id');
  725. /* Remove project that linked more than two products */
  726. $sprintProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($noMergedSprints))->fetchGroup('project', 'product');
  727. foreach($sprintProducts as $sprintID => $products)
  728. {
  729. if(count($products) > 1) unset($noMergedSprints[$sprintID]);
  730. }
  731. /* Get products that are not merged by sprints. */
  732. $noMergedProducts = array();
  733. if($noMergedSprints)
  734. {
  735. $noMergedProducts = $this->dao->select('t1.*')->from(TABLE_PRODUCT)->alias('t1')
  736. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.product')
  737. ->where('t2.project')->in(array_keys($noMergedSprints))
  738. ->andWhere('t1.vision')->eq('rnd')
  739. ->fetchAll('id');
  740. }
  741. /* Add products without sprints. */
  742. $noMergedProducts += $this->dao->select('*')->from(TABLE_PRODUCT)->where('program')->eq(0)->andWhere('vision')->eq('rnd')->fetchAll('id');
  743. if(empty($noMergedProducts)) $this->locate($this->createLink('upgrade', 'mergeProgram', "type=sprint&programID=0&projectType=$projectType"));
  744. /* Group project by product. */
  745. $productGroups = array();
  746. foreach($noMergedSprints as $sprint)
  747. {
  748. $sprintProduct = zget($sprintProducts, $sprint->id, array());
  749. if(empty($sprintProduct)) continue;
  750. $productID = key($sprintProduct);
  751. $productGroups[$productID][$sprint->id] = $sprint;
  752. }
  753. $this->view->noMergedProducts = $noMergedProducts;
  754. $this->view->productGroups = $productGroups;
  755. }
  756. /**
  757. * 获取未关联产品的迭代。
  758. * Get sprints without product.
  759. *
  760. * @access protected
  761. * @return void
  762. */
  763. protected function assignSprintsWithoutProduct()
  764. {
  765. $noMergedSprints = $this->dao->select('*')->from(TABLE_PROJECT)
  766. ->where('project')->eq(0)
  767. ->andWhere('vision')->eq('rnd')
  768. ->andWhere('type')->eq('sprint')
  769. ->andWhere('deleted')->eq(0)
  770. ->orderBy('id_desc')
  771. ->fetchAll('id');
  772. $projectProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($noMergedSprints))->fetchGroup('project', 'product');
  773. foreach(array_keys($projectProducts) as $sprintID) unset($noMergedSprints[$sprintID]);
  774. if(empty($noMergedSprints)) $this->locate($this->createLink('upgrade', 'mergeProgram', "type=moreLink"));
  775. $this->view->noMergedSprints = $noMergedSprints;
  776. }
  777. /**
  778. * 获取关联了多个产品项目。
  779. * Get no merged projects that link more than two products.
  780. *
  781. * @access protected
  782. * @return void
  783. */
  784. protected function assignSprintsWithMoreProducts()
  785. {
  786. $noMergedSprints = $this->dao->select('*')->from(TABLE_PROJECT)
  787. ->where('project')->eq(0)
  788. ->andWhere('vision')->eq('rnd')
  789. ->andWhere('type')->eq('sprint')
  790. ->andWhere('deleted')->eq(0)
  791. ->orderBy('id_desc')
  792. ->fetchAll('id');
  793. $projectProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($noMergedSprints))->fetchGroup('project', 'product');
  794. $productPairs = array();
  795. foreach($projectProducts as $sprintID => $products)
  796. {
  797. foreach(array_keys($products) as $productID) $productPairs[$productID] = $productID;
  798. }
  799. $projects = $this->dao->select('t1.*, t2.product AS productID')->from(TABLE_PROJECT)->alias('t1')
  800. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id=t2.project')
  801. ->where('t2.product')->in($productPairs)
  802. ->andWhere('t1.vision')->eq('rnd')
  803. ->andWhere('t1.type')->eq('project')
  804. ->fetchAll('productID');
  805. foreach($noMergedSprints as $sprintID => $sprint)
  806. {
  807. $products = zget($projectProducts, $sprintID, array());
  808. foreach(array_keys($products) as $productID)
  809. {
  810. $project = zget($projects, $productID, '');
  811. if($project) $sprint->projects[$project->id] = $project->name;
  812. }
  813. if(!isset($sprint->projects)) $sprint->projects = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('type')->eq('project')->andWhere('vision')->eq('rnd')->fetchPairs();
  814. }
  815. $this->view->noMergedSprints = $noMergedSprints;
  816. }
  817. /**
  818. * 合并按产品线分组的产品和迭代。
  819. * Merge products and projects group by productline.
  820. *
  821. * @param string $projectType
  822. * @access protected
  823. * @return void
  824. */
  825. protected function mergeByProductline($projectType)
  826. {
  827. /* Compute checked products and sprints, unchecked products and sprints. */
  828. $linkedProducts = array();
  829. $linkedSprints = array();
  830. $unlinkSprints = array();
  831. $sprintProducts = array();
  832. foreach($_POST['products'] as $lineID => $products)
  833. {
  834. foreach($products as $productID)
  835. {
  836. $linkedProducts[$productID] = $productID;
  837. if(!isset($_POST['sprints'][$lineID][$productID])) continue;
  838. foreach($_POST['sprints'][$lineID][$productID] as $sprintID)
  839. {
  840. $linkedSprints[$sprintID] = $sprintID;
  841. $sprintProducts[$sprintID] = $productID;
  842. unset($_POST['sprintIdList'][$lineID][$productID][$sprintID]);
  843. }
  844. $unlinkSprints[$productID] = $this->post->sprintIdList[$lineID][$productID];
  845. }
  846. }
  847. /* Create Program. */
  848. $result = $this->upgrade->createProgram($linkedSprints);
  849. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  850. if(isset($result['result']) && $result['result'] == 'fail') return $this->send($result);
  851. list($programID, $projectList, $lineID) = $result;
  852. /* Process merged products and projects. */
  853. if($projectType == 'execution')
  854. {
  855. /* Use historical projects as execution upgrades. */
  856. $this->upgrade->processMergedData($programID, $projectList, $lineID, $linkedProducts, $linkedSprints);
  857. }
  858. else
  859. {
  860. /* Use historical projects as project upgrades. */
  861. foreach($linkedSprints as $sprint) $this->upgrade->processMergedData($programID, zget($projectList, $sprint, array()), $lineID, array($sprintProducts[$sprint] => $sprintProducts[$sprint]), array($sprint => $sprint));
  862. /* When upgrading historical data as a project, handle products that are not linked with the project. */
  863. $singleProducts = array_diff($linkedProducts, $sprintProducts);
  864. if(!empty($singleProducts)) $this->upgrade->computeProductAcl($singleProducts, $programID, $lineID);
  865. }
  866. /* Process unlinked sprint and product. */
  867. foreach(array_keys($linkedProducts) as $productID)
  868. {
  869. if((isset($unlinkSprints[$productID]) && empty($unlinkSprints[$productID])) || !isset($unlinkSprints[$productID])) $this->dao->update(TABLE_PRODUCT)->set('line')->eq($lineID)->where('id')->eq($productID)->exec();
  870. }
  871. }
  872. /**
  873. * 合并按产品分组的产品和迭代。
  874. * Merge products and projects group by product.
  875. *
  876. * @param string $projectType
  877. * @access protected
  878. * @return void
  879. */
  880. protected function mergeByProduct($projectType)
  881. {
  882. $linkedProducts = array();
  883. $linkedSprints = array();
  884. $unlinkSprints = array();
  885. $sprintProducts = array();
  886. foreach($_POST['products'] as $productID)
  887. {
  888. $linkedProducts[$productID] = $productID;
  889. if(isset($_POST['sprints'][$productID]))
  890. {
  891. foreach($_POST['sprints'][$productID] as $sprintID)
  892. {
  893. $linkedSprints[$sprintID] = $sprintID;
  894. $sprintProducts[$sprintID] = $productID;
  895. unset($_POST['sprintIdList'][$productID][$sprintID]);
  896. }
  897. $unlinkSprints += $this->post->sprintIdList[$productID];
  898. }
  899. }
  900. /* Create Program. */
  901. $result = $this->upgrade->createProgram($linkedSprints);
  902. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  903. if(isset($result['result']) && $result['result'] == 'fail') return $this->send($result);
  904. list($programID, $projectList, $lineID) = $result;
  905. /* Process productline. */
  906. $this->dao->delete()->from(TABLE_MODULE)->where('`root`')->eq(0)->andWhere('`type`')->eq('line')->exec();
  907. /* Process merged products and projects. */
  908. if($projectType == 'execution')
  909. {
  910. /* Use historical projects as execution upgrades. */
  911. $this->upgrade->processMergedData($programID, $projectList, $lineID, $linkedProducts, $linkedSprints);
  912. }
  913. else
  914. {
  915. /* Use historical projects as project upgrades. */
  916. foreach($linkedSprints as $sprint) $this->upgrade->processMergedData($programID, $projectList[$sprint], $lineID, array($sprintProducts[$sprint] => $sprintProducts[$sprint]), array($sprint => $sprint));
  917. /* When upgrading historical data as a project, handle products that are not linked with the project. */
  918. $singleProducts = array_diff($linkedProducts, $sprintProducts);
  919. if(!empty($singleProducts)) $this->upgrade->computeProductAcl($singleProducts, $programID, $lineID);
  920. }
  921. }
  922. /**
  923. * 合并没有关联产品的迭代。
  924. * Merge sprints without product.
  925. *
  926. * @param string $projectType
  927. * @access protected
  928. * @return void
  929. */
  930. protected function mergeBySprint($projectType)
  931. {
  932. $linkedSprints = $this->post->sprints;
  933. /* Create Program. */
  934. $result = $this->upgrade->createProgram($linkedSprints);
  935. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  936. if(isset($result['result']) && $result['result'] == 'fail') return $this->send($result);
  937. list($programID, $projectList, $lineID) = $result;
  938. if($projectType == 'execution')
  939. {
  940. /* Use historical projects as execution upgrades. */
  941. $this->upgrade->processMergedData($programID, $projectList, $lineID, array(), $linkedSprints);
  942. }
  943. else
  944. {
  945. /* Use historical projects as project upgrades. */
  946. foreach($linkedSprints as $sprint) $this->upgrade->processMergedData($programID, $projectList[$sprint], $lineID, array(), array($sprint => $sprint));
  947. }
  948. }
  949. /**
  950. * 合并关联多个产品的迭代。
  951. * Merge sprints with more than one product.
  952. *
  953. * @param string $projectType
  954. * @access protected
  955. * @return void
  956. */
  957. protected function mergeByMoreLink($projectType)
  958. {
  959. $linkedSprints = $this->post->sprints;
  960. /* Create Program. */
  961. list($programID, $projectList, $lineID) = $this->upgrade->createProgram($linkedSprints);
  962. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  963. if($projectType == 'execution')
  964. {
  965. /* Use historical projects as execution upgrades. */
  966. $this->upgrade->processMergedData($programID, $projectList, $lineID, array(), $linkedSprints);
  967. }
  968. else
  969. {
  970. /* Use historical projects as project upgrades. */
  971. foreach($linkedSprints as $sprint) $this->upgrade->processMergedData($programID, $projectList[$sprint], $lineID, array(), array($sprint => $sprint));
  972. }
  973. /* If is more-link sprints, and as project upgrade, set old relation into new project. */
  974. $projectProducts = $this->dao->select('product,project,branch,plan')->from(TABLE_PROJECTPRODUCT)->where('project')->in($linkedSprints)->fetchAll();
  975. foreach($projectProducts as $projectProduct)
  976. {
  977. $data = new stdclass();
  978. $data->project = $projectType == 'execution' ? $projectList : $projectList[$projectProduct->project];
  979. $data->product = $projectProduct->product;
  980. $data->plan = $projectProduct->plan;
  981. $data->branch = $projectProduct->branch;
  982. $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec();
  983. }
  984. }
  985. /**
  986. * 显示更改冲突的 sql。
  987. * Display consistency.
  988. *
  989. * @param string $alterSQL
  990. * @access protected
  991. * @return void
  992. */
  993. protected function displayConsistency($alterSQL)
  994. {
  995. $logFile = $this->upgrade->getConsistencyLogFile();
  996. if(file_exists($logFile)) unlink($logFile);
  997. $this->view->title = $this->lang->upgrade->consistency;
  998. $this->view->hasError = $this->upgrade->hasConsistencyError();
  999. $this->view->alterSQL = $alterSQL;
  1000. $this->view->version = $this->config->version;
  1001. $this->display('upgrade', 'consistency');
  1002. }
  1003. /**
  1004. * 显示需要执行的命令。
  1005. * Display command.
  1006. *
  1007. * @param string $command
  1008. * @param string $tips
  1009. * @access protected
  1010. * @return void
  1011. */
  1012. protected function displayCommand($command, $tips = '')
  1013. {
  1014. $this->view->title = $this->lang->upgrade->common;
  1015. $this->view->result = 'fail';
  1016. $this->view->command = $command;
  1017. $this->view->tips = $tips ?: $this->lang->upgrade->execCommand;
  1018. $this->display('upgrade', 'command');
  1019. }
  1020. /**
  1021. * 显示待处理的提示。
  1022. * Display execute process.
  1023. *
  1024. * @param string $fromVersion
  1025. * @param array $needProcess
  1026. * @access protected
  1027. * @return void
  1028. */
  1029. protected function displayExecuteProcess($fromVersion, $needProcess)
  1030. {
  1031. $showPrivTips = false;
  1032. if(is_numeric($fromVersion[0]) and version_compare($fromVersion, '18.9', '<=')) $showPrivTips = true;
  1033. if(strpos($fromVersion, 'pro') !== false) $showPrivTips = true;
  1034. if(strpos($fromVersion, 'biz') !== false and version_compare($fromVersion, 'biz8.9', '<=')) $showPrivTips = true;
  1035. if(strpos($fromVersion, 'max') !== false and version_compare($fromVersion, 'max4.9', '<=')) $showPrivTips = true;
  1036. if(strpos($fromVersion, 'ipd') !== false and version_compare($fromVersion, 'ipd1.1.1', '<=')) $showPrivTips = true;
  1037. if($showPrivTips and $this->config->edition == 'open') $showPrivTips = false;
  1038. $this->view->title = $this->lang->upgrade->result;
  1039. $this->view->needProcess = $needProcess;
  1040. $this->view->fromVersion = $fromVersion;
  1041. $this->view->showPrivTips = $showPrivTips;
  1042. $this->display();
  1043. }
  1044. /**
  1045. * 升级 sql 执行成功后的操作。
  1046. * Process after execute sql successfully.
  1047. *
  1048. * @access protected
  1049. * @return void
  1050. */
  1051. protected function processAfterExecSuccessfully()
  1052. {
  1053. $zfile = $this->app->loadClass('zfile');
  1054. $zfile->removeDir($this->app->getTmpRoot() . 'model/');
  1055. $installFile = $this->app->getAppRoot() . 'www/install.php';
  1056. $upgradeFile = $this->app->getAppRoot() . 'www/upgrade.php';
  1057. if(file_exists($installFile)) @unlink($installFile);
  1058. if(file_exists($upgradeFile)) @unlink($upgradeFile);
  1059. unset($_SESSION['upgrading']);
  1060. }
  1061. }