router.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. <?php /**
  2. * 此文件包括ZenTaoPHP框架的三个类:router, config, lang。
  3. * The router, config and lang class file of ZenTaoPHP framework.
  4. *
  5. * The author disclaims copyright to this source code. In place of
  6. * a legal notice, here is a blessing:
  7. *
  8. * May you do good and not evil.
  9. * May you find forgiveness for yourself and forgive others.
  10. * May you share freely, never taking more than you give.
  11. */
  12. /**
  13. * router类。
  14. * The router class.
  15. *
  16. * @package framework
  17. */
  18. include __DIR__ . '/base/router.class.php';
  19. class router extends baseRouter
  20. {
  21. /**
  22. * 系统是否正在安装中。
  23. * Whether is installing.
  24. *
  25. * @var array
  26. * @access public
  27. */
  28. public $installing = false;
  29. /**
  30. * 系统是否正在升级中。
  31. * Whether is upgrading.
  32. *
  33. * @var array
  34. * @access public
  35. */
  36. public $upgrading = false;
  37. /**
  38. * 请求的原始参数。
  39. * The requested params parsed from a URL.
  40. *
  41. * @var array
  42. * @access public
  43. */
  44. public $rawParams;
  45. /**
  46. * 原始URI
  47. *
  48. * @var string
  49. * @access public
  50. */
  51. public $rawURI;
  52. /**
  53. * 标记是否是工作流
  54. * Whether the tag is a workflow
  55. *
  56. * @var bool
  57. * @access public
  58. */
  59. public $isFlow = false;
  60. /**
  61. * Fetch的模块名。
  62. * The fetched module name.
  63. *
  64. * @var string
  65. * @access public
  66. */
  67. public $fetchModule;
  68. /**
  69. * Check whether app is serving.
  70. * @access public
  71. * @return bool
  72. */
  73. public function isServing()
  74. {
  75. return !$this->installing && !$this->upgrading;
  76. }
  77. /**
  78. * Get the $moduleRoot var.
  79. *
  80. * @param string $appName
  81. * @access public
  82. * @return string
  83. */
  84. public function getModuleRoot($appName = '')
  85. {
  86. return $this->moduleRoot;
  87. }
  88. /**
  89. * Merge system and translated langs.
  90. *
  91. * @param string $lang zh-cn|zh-tw|en
  92. * @access public
  93. * @return void
  94. */
  95. public function setClientLang($lang = '')
  96. {
  97. if($this->dbh)
  98. {
  99. $langs = $this->dbQuery('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='global' AND `key`='langs'")->fetch();
  100. $langs = empty($langs) ? array() : json_decode($langs->value, true);
  101. foreach($langs as $langKey => $langData) $this->config->langs[$langKey] = $langData['name'];
  102. }
  103. return parent::setClientLang($lang);
  104. }
  105. /**
  106. * 企业版部分功能是从然之合并过来的。然之代码中调用loadLang方法时传递了一个非空的appName,在禅道中会导致错误。
  107. * 把appName设置为空来避免这个错误。
  108. * Some codes merged from ranzhi called the function loadLang with a non-empty appName which causes an error in zentao.
  109. * Set the value of appName to empty to avoid this error.
  110. *
  111. * @param string $moduleName the module name
  112. * @param string $appName the app name
  113. * @access public
  114. * @return object the lang object or false.
  115. */
  116. public function loadLang($moduleName, $appName = '')
  117. {
  118. global $lang;
  119. if(!is_object($lang)) $lang = new language();
  120. if(isset(static::$loadedLangs[$moduleName])) return $lang;
  121. /* Set productCommon and projectCommon for flow. */
  122. if($moduleName == 'common') $this->setCommonLang();
  123. parent::loadLang($moduleName, $appName);
  124. /* Replace main nav lang. */
  125. if($moduleName == 'common' and $this->dbh and !empty($this->config->db->name))
  126. {
  127. $customMenus = array();
  128. try
  129. {
  130. $customMenus = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_LANG . " WHERE `module`='common' AND `section`='mainNav' AND `lang`='{$this->clientLang}' AND `vision`='{$this->config->vision}'")->fetchAll();
  131. }
  132. catch(PDOException $exception){}
  133. foreach($customMenus as $menu)
  134. {
  135. $menuKey = $menu->key;
  136. if(isset($lang->mainNav->$menuKey)) $lang->mainNav->$menuKey = zget($lang->navIcons, $menuKey, '') . " {$menu->value}" . substr((string) $lang->mainNav->$menuKey, strpos((string) $lang->mainNav->$menuKey, '|'));
  137. }
  138. }
  139. if($moduleName == 'common' && $this->config->edition != 'open') $this->mergeFlowLang();
  140. /* Merge from the db lang. */
  141. if($moduleName != 'common' and isset($lang->db->custom[$moduleName]))
  142. {
  143. foreach($lang->db->custom[$moduleName] as $section => $fields)
  144. {
  145. if(in_array($section, array('featureBar', 'moreSelects')))
  146. {
  147. foreach($fields as $featureBarMethod => $featureBarValues)
  148. {
  149. foreach($featureBarValues as $featureBarKey => $featureBarValue)
  150. {
  151. if(is_array($featureBarValue))
  152. {
  153. foreach($featureBarValue as $key => $value) $lang->{$moduleName}->{$section}[$featureBarMethod][$featureBarKey][$key] = $value;
  154. }
  155. else
  156. {
  157. $lang->{$moduleName}->{$section}[$featureBarMethod][$featureBarKey] = $featureBarValue;
  158. }
  159. }
  160. }
  161. }
  162. else
  163. {
  164. if(isset($lang->{$moduleName}->{$section}['']))
  165. {
  166. $nullKey = '';
  167. $nullValue = $lang->{$moduleName}->{$section}[$nullKey];
  168. }
  169. elseif(isset($lang->{$moduleName}->{$section}[0]))
  170. {
  171. $nullKey = 0;
  172. $nullValue = $lang->{$moduleName}->{$section}[0];
  173. }
  174. unset($lang->{$moduleName}->{$section});
  175. if(isset($nullKey))$lang->{$moduleName}->{$section}[$nullKey] = $nullValue;
  176. foreach($fields as $key => $value)
  177. {
  178. if($section == 'priList' and $key > 0 and trim((string) $value) === '') continue; // Fix bug #23538.
  179. if(!isset($lang->{$moduleName})) $lang->{$moduleName} = new stdclass();
  180. if(!isset($lang->{$moduleName}->{$section})) $lang->{$moduleName}->{$section} = array();
  181. $lang->{$moduleName}->{$section}[$key] = $value;
  182. }
  183. unset($nullKey);
  184. unset($nullValue);
  185. }
  186. }
  187. }
  188. return $lang;
  189. }
  190. /**
  191. * Merge workflow mainNav Lang.
  192. *
  193. * @access public
  194. * @return void
  195. */
  196. public function mergeFlowLang()
  197. {
  198. if(!$this->checkInstalled() || !$this->isServing()) return;
  199. /* 12版本升级到付费版时,workflow表不存在。 */
  200. /* When upgrading from version 12 to the paid version, the workflow table does not exist. */
  201. try
  202. {
  203. $flows = $this->dbQuery('SELECT `module`, `name`, `navigator` FROM ' . TABLE_WORKFLOW . " WHERE `buildin` = 0 AND `vision` = '{$this->config->vision}' AND status = 'normal' AND type = 'flow'")->fetchAll();
  204. foreach($flows as $flow)
  205. {
  206. if($flow->navigator == 'primary') $this->lang->mainNav->{$flow->module} = "{$this->lang->navIcons['workflow']} {$flow->name}|{$flow->module}|browse|";
  207. }
  208. }
  209. catch(PDOException $exception){}
  210. }
  211. /**
  212. * Set common lang.
  213. *
  214. * @access public
  215. * @return void
  216. */
  217. public function setCommonLang()
  218. {
  219. if(defined('COMMONLANGSETTED')) return true;
  220. define('COMMONLANGSETTED', true);
  221. if(!defined('ITERATION_KEY')) define('ITERATION_KEY', 0);
  222. if(!defined('SPRINT_KEY')) define('SPRINT_KEY', 1);
  223. if(!defined('PRODUCT_KEY')) define('PRODUCT_KEY', 0);
  224. if(!defined('PROJECT_KEY')) define('PROJECT_KEY', 0);
  225. if(!defined('STORYPOINT_KEY')) define('STORYPOINT_KEY', 1);
  226. if(!defined('FUNCTIONPOINT_KEY')) define('FUNCTIONPOINT_KEY', 2);
  227. global $lang, $app, $config;
  228. $sprintConcept = $hourPoint = false;
  229. $commonSettings = array();
  230. /* Get config from DB. */
  231. if($this->dbh and !empty($this->config->db->name))
  232. {
  233. if(!isset($config->global)) $config->global = new stdclass();
  234. $config->global->flow = 'full';
  235. try
  236. {
  237. $commonSettings = $this->dbQuery('SELECT `section`, `key`, `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND (`module`='custom' or `module`='common') and `key` in ('sprintConcept', 'hourPoint', 'URSR', 'mode', 'scoreStatus', 'disabledFeatures', 'closedFeatures')")->fetchAll();
  238. }
  239. catch (PDOException $exception)
  240. {
  241. helper::checkDB2Repair($exception);
  242. }
  243. }
  244. $hourKey = $URSR = 0;
  245. $mode = 'ALM';
  246. $score = '0';
  247. $projectKey = ITERATION_KEY;
  248. $disabledFeatures = '';
  249. $closedFeatures = '';
  250. foreach($commonSettings as $setting)
  251. {
  252. if($setting->key == 'sprintConcept') $projectKey = $setting->value;
  253. if($setting->key == 'hourPoint') $hourKey = $setting->value;
  254. if($setting->key == 'URSR') $URSR = $setting->value;
  255. if($setting->key == 'mode' and $setting->section == 'global') $mode = $setting->value;
  256. if($setting->key == 'scoreStatus' and $setting->section == 'global') $score = $setting->value;
  257. if($setting->key == 'disabledFeatures') $disabledFeatures = $setting->value;
  258. if($setting->key == 'closedFeatures') $closedFeatures = $setting->value;
  259. }
  260. /* Lite Version is compatible with classic modes */
  261. if($config->vision == 'lite') $mode = 'ALM';
  262. /* Record system mode. */
  263. $config->systemMode = $mode;
  264. $config->disabledFeatures = $disabledFeatures . ',' . $closedFeatures;
  265. /* Sanplex hide features. */
  266. $this->loadModuleConfig('common');
  267. if(!empty($config->hiddenFeature)) $config->disabledFeatures .= ',' . implode(',', $config->hiddenFeature);
  268. /* Record system score.*/
  269. $config->systemScore = $score;
  270. /* Record hour unit. */
  271. $config->hourUnit = 'h';
  272. if($hourKey == STORYPOINT_KEY) $config->hourUnit = 'sp';
  273. if($hourKey == FUNCTIONPOINT_KEY) $config->hourUnit = 'fp';
  274. $iterationKey = $projectKey;
  275. /* Set productCommon, projectCommon and hourCommon. Default english lang. */
  276. $lang->productCommon = $this->config->productCommonList[$this->clientLang][PRODUCT_KEY];
  277. $lang->projectCommon = $this->config->projectCommonList[$this->clientLang][PROJECT_KEY];
  278. $lang->iterationCommon = $this->config->executionCommonList[$this->clientLang][(int)$iterationKey] ?? $this->config->executionCommonList['en'][(int)$iterationKey];
  279. $lang->executionCommon = $this->config->executionCommonList[$this->clientLang][(int)$projectKey] ?? $this->config->executionCommonList['en'][(int)$projectKey];
  280. $lang->hourCommon = $this->config->hourPointCommonList[$this->clientLang][(int)$hourKey] ?? $this->config->hourPointCommonList['en'][(int)$hourKey];
  281. /* User preference init. */
  282. $config->URSR = $URSR;
  283. $config->URAndSR = strpos(",{$config->disabledFeatures},", ',productUR,') === false;
  284. $config->enableER = strpos(",{$config->disabledFeatures},", ',productER,') === false;
  285. $config->programLink = 'program-browse';
  286. $config->productLink = 'product-all';
  287. $config->projectLink = 'project-browse';
  288. $config->executionLink = 'execution-task';
  289. $config->docLink = 'doc-lastViewedSpace';
  290. /* Get user preference. */
  291. $account = $_SESSION['user']->account ?? '';
  292. $userSetting = array();
  293. if($this->dbh and !empty($this->config->db->name) and $account)
  294. {
  295. $sql = new sql();
  296. $account = $sql->quote($account);
  297. $userSetting = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_CONFIG . " WHERE `owner`= $account AND `module`='common' and `key` in ('programLink', 'productLink', 'projectLink', 'executionLink', 'URSR', 'docLink')")->fetchAll();
  298. }
  299. foreach($userSetting as $setting)
  300. {
  301. if($setting->key == 'URSR') $config->URSR = $setting->value;
  302. if($setting->key == 'programLink') $config->programLink = $setting->value;
  303. if($setting->key == 'productLink') $config->productLink = $setting->value;
  304. if($setting->key == 'projectLink') $config->projectLink = $setting->value;
  305. if($setting->key == 'executionLink') $config->executionLink = $setting->value;
  306. if($setting->key == 'docLink') $config->docLink = $setting->value;
  307. }
  308. $lang->ERCommon = $config->storyCommonList[$this->clientLang]['epic'];
  309. $lang->URCommon = $config->storyCommonList[$this->clientLang]['requirement'];
  310. $lang->SRCommon = $config->storyCommonList[$this->clientLang]['story'];
  311. if($this->dbh and !empty($this->config->db->name))
  312. {
  313. if(!$this->upgrading)
  314. {
  315. /* Get story concept in project and product. */
  316. $clientLang = $this->clientLang == 'zh-tw' ? 'zh-cn' : $this->clientLang;
  317. $URSRList = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_LANG . " WHERE `module` = 'custom' and `section` = 'URSRList' and `lang` = '{$clientLang}'")->fetchAll();
  318. if(empty($URSRList)) $URSRList = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_LANG . " WHERE module = 'custom' and `section` = 'URSRList' and `key` = '{$config->URSR}'")->fetchAll();
  319. /* Get UR pairs and SR pairs. */
  320. $ERPairs = array();
  321. $URPairs = array();
  322. $SRPairs = array();
  323. foreach($URSRList as $id => $value)
  324. {
  325. $URSRObj = json_decode((string) $value->value);
  326. $ERPairs[$value->key] = $URSRObj->ERName;
  327. $URPairs[$value->key] = $URSRObj->URName;
  328. $SRPairs[$value->key] = $URSRObj->SRName;
  329. }
  330. /* Set default story concept and init UR and SR concept. */
  331. $lang->ERCommon = $ERPairs[$config->URSR] ?? reset($ERPairs);
  332. $lang->URCommon = $URPairs[$config->URSR] ?? reset($URPairs);
  333. $lang->SRCommon = $SRPairs[$config->URSR] ?? reset($SRPairs);
  334. /* Replace common lang. */
  335. $customMenus = array();
  336. try
  337. {
  338. $customMenus = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_LANG . " WHERE `module`='common' AND `lang`='{$this->clientLang}' AND `section`='' AND `vision`='{$config->vision}'")->fetchAll();
  339. }
  340. catch(PDOException $exception){}
  341. foreach($customMenus as $menu)
  342. {
  343. /* Modified the language item of dev story-related navigation, which will only affect users whose personalized value is the same as the story concept default value.*/
  344. if(in_array($menu->key, array('ERCommon', 'URCommon', 'SRCommon')) && $URSR != $config->URSR) continue;
  345. if(isset($lang->{$menu->key})) $lang->{$menu->key} = $menu->value;
  346. }
  347. }
  348. }
  349. }
  350. /**
  351. * 企业版部分功能是从然之合并过来的。然之代码中调用loadModuleConfig方法时传递了一个非空的appName,在禅道中会导致错误。
  352. * 把appName设置为空来避免这个错误。
  353. * Some codes merged from ranzhi called the function loadModuleConfig with a non-empty appName which causes an error in zentao.
  354. * Set the value of appName to empty to avoid this error.
  355. *
  356. * @param string $moduleName module name
  357. * @param string $appName app name
  358. * @param bool $force force to load
  359. * @access public
  360. * @return void
  361. */
  362. public function loadModuleConfig($moduleName, $appName = '', $force = false)
  363. {
  364. if(isset(static::$loadedConfigs[$moduleName]) && !$force) return false;
  365. static::$loadedConfigs[$moduleName] = $moduleName;
  366. global $config;
  367. if($config and (!isset($config->$moduleName) or !is_object($config->$moduleName))) $config->$moduleName = new stdclass();
  368. /* 初始化数组。Init the variables. */
  369. $extConfigFiles = array();
  370. $commonExtConfigFiles = array();
  371. $visionExtConfigFiles = array();
  372. $siteExtConfigFiles = array();
  373. /* 先获得模块的主配置文件。Get the main config file for current module first. */
  374. $mainConfigFile = $this->getModulePath($appName, $moduleName) . 'config.php';
  375. /* 获取 config 目录的配置文件。Get config files from config directory. */
  376. $configDirFiles = helper::ls($this->getModulePath($appName, $moduleName) . DS . 'config', '.php');
  377. /* 查找扩展配置文件。Get extension config files. */
  378. $extConfigPath = array();
  379. if($config->framework->extensionLevel > 0) $extConfigPath = $this->getModuleExtPath($moduleName, 'config');
  380. if($config->framework->extensionLevel >= 1)
  381. {
  382. if(!empty($extConfigPath['common'])) $commonExtConfigFiles = helper::ls($extConfigPath['common'], '.php');
  383. if(!empty($extConfigPath['xuan'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['xuan'], '.php'));
  384. if(!empty($extConfigPath['vision'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['vision'], '.php'));
  385. if(!empty($extConfigPath['saas'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['saas'], '.php'));
  386. if(!empty($extConfigPath['custom'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['custom'], '.php'));
  387. }
  388. if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php');
  389. $extConfigFiles = array_merge($commonExtConfigFiles, $configDirFiles, $siteExtConfigFiles);
  390. /* 将主配置文件和扩展配置文件合并在一起。Put the main config file and extension config files together. */
  391. $configFiles = array_merge(array($mainConfigFile), $configDirFiles, $extConfigFiles);
  392. /* 加载每一个配置文件。Load every config file. */
  393. foreach($configFiles as $configFile)
  394. {
  395. if(isset(static::$loadedConfigs[$configFile]) && !$force) continue;
  396. if(file_exists($configFile)) include $configFile;
  397. static::$loadedConfigs[$configFile] = $configFile;
  398. }
  399. /* 加载数据库中与本模块相关的配置项。Merge from the db configs. */
  400. if($moduleName != 'common')
  401. {
  402. if(isset($config->systemDB->$moduleName)) $this->mergeConfig($config->systemDB->$moduleName, $moduleName);
  403. if(isset($config->personalDB->$moduleName)) $this->mergeConfig($config->personalDB->$moduleName, $moduleName);
  404. }
  405. }
  406. /**
  407. * The alias for loadModuleConfig.
  408. *
  409. * @param string $moduleName
  410. * @param string $appName
  411. * @param bool $force
  412. * @access public
  413. * @return void
  414. */
  415. public function loadConfig($moduleName, $appName = '', $force = false)
  416. {
  417. return $this->loadModuleConfig($moduleName, $appName, $force);
  418. }
  419. /**
  420. * Get sql driver instance.
  421. *
  422. * @param string $driverName mysql|duckdb
  423. * @access public
  424. * @return object
  425. */
  426. public function loadDriver($driverName = 'mysql')
  427. {
  428. if($driverName == 'duckdb')
  429. {
  430. $this->loadClass('duckdb');
  431. return new duckdb();
  432. }
  433. return $this->dbh;
  434. }
  435. /**
  436. * Export config.
  437. *
  438. * @access public
  439. * @return string
  440. */
  441. public function exportConfig()
  442. {
  443. $view = json_decode(parent::exportConfig());
  444. $view->rand = $view->random;
  445. $this->session->set('rand', $view->rand);
  446. return json_encode($view);
  447. }
  448. /**
  449. * 检查请求的模块和方法是否应该调用工作流引擎进行处理。
  450. * Check if the requested module and method should call the workflow engine for processing.
  451. *
  452. * 处理逻辑:
  453. * Processing logic:
  454. * 1、如果当前版本不是企业版,或者当前请求处于安装模式或升级模式,调用父类方法并返回。
  455. * 1. If the current version is not the enterprise version, or if the current request is in install mode or upgrade mode, call the parent class method and return.
  456. *
  457. * 2、如果当前请求的模块在TABLE_WORKFLOW表中不存在,调用父类方法并返回。
  458. * 2. If the currently requested module does not exist in the TABLE_WORKFLOW table, call the parent class method and return.
  459. *
  460. * 3、如果当前请求的模块在TABLE_WORKFLOW表中存在并且是内置模块,并且请求的方法名是browselabel,则修改请求的模块名为flow,修改请求的方法名为browse,重新设置URI参数,调用父类方法并返回。
  461. * 3. If the currently requested module exists in the TABLE_WORKFLOW table and is a built-in module, and the requested method name is
  462. * browselabel, rename the module of the request to flow and the method of the request to browse, and reset the URI, call the parent class method and return.
  463. *
  464. * 4、如果不满足3中的条件但当前请求的方法在TABLE_WORKFLOWACTION表中存在,且方法扩展类型为重写,则修改请求的模块名为flow,方法名根据5中的规则修改,重新设置URI参数,调用父类方法并返回。
  465. * 4. If the condition of 3 is not satisfied but the currently requested method exists in the TABLE_WORKFLOWACTION table, and the method
  466. * extension type is overwrite, rename the module of the request to flow, and rename the method of the request according to the rule in 5.
  467. * Then reset the URI, call the parent class method and return.
  468. *
  469. * 5、如果当前请求的方法名为browse、create、edit、view、delete、export中任意一个,则方法名不变,否则方法名改为operate。
  470. * 5. If the currently requested method is named any one of browse, create, edit, view, delete, or export, the method name is unchanged, otherwise the method name is changed to operate.
  471. *
  472. * @param bool $exitIfNone 没有找到该控制器文件的情况:如果该参数为true,则终止程序;如果为false,则打印错误日志
  473. * The controller file was not found: if the parameter is true, the program is terminated;
  474. * if false, the error log is printed.
  475. * @access public
  476. * @return bool
  477. */
  478. public function setControlFile($exitIfNone = true)
  479. {
  480. /* Set raw module and method name for fetch control. */
  481. if(empty($this->rawModule)) $this->rawModule = $this->moduleName;
  482. if(empty($this->rawMethod)) $this->rawMethod = $this->methodName;
  483. /* If is not a biz version or is in install mode or in in upgrade mode, call parent method. */
  484. if($this->config->edition == 'open' or $this->installing or $this->upgrading) return parent::setControlFile($exitIfNone);
  485. /* Check if the requested module is defined in workflow. */
  486. $flow = $this->dbQuery("SELECT * FROM " . TABLE_WORKFLOW . " WHERE `module` = '$this->moduleName'")->fetch();
  487. if(!$flow) return parent::setControlFile($exitIfNone);
  488. if($flow->status != 'normal') helper::end("<html><head><meta charset='utf-8'></head><body>{$this->lang->flowNotRelease}</body></html>");
  489. /**
  490. * 工作流中配置的标签应该请求browse方法,而某些内置流程本身包含browse方法。在这里处理请求的时候会无法区分是内置的browse方法还是工作
  491. * 流标签的browse方法,为了避免此类冲突,在工作流中配置出的标签请求的方法改为browseLabel,在设置控制器文件时需要将其重设为browse。
  492. * Tags configured in the workflow should request the browse method, and some built-in processes themselves contain the browse
  493. * method. When processing a request here, it is impossible to distinguish between the built-in browse method and the browse
  494. * method of the workflow tag. In order to avoid such conflicts, the method of configuring the label request in the workflow
  495. * is changed to browseLabel, which needs to be reset to browse when setting the controller file.
  496. */
  497. if($flow->buildin && $this->methodName == 'browselabel')
  498. {
  499. $this->rawModule = $this->moduleName;
  500. $this->rawMethod = 'browse';
  501. $this->isFlow = true;
  502. $moduleName = 'flow';
  503. $methodName = 'browse';
  504. $this->setFlowURI($moduleName, $methodName);
  505. }
  506. else
  507. {
  508. $action = $this->dbQuery("SELECT * FROM " . TABLE_WORKFLOWACTION . " WHERE `module` = '$this->moduleName' AND `action` = '$this->methodName' AND `vision` = '{$this->config->vision}'")->fetch();
  509. if(zget($action, 'extensionType') == 'override')
  510. {
  511. $this->rawModule = $this->moduleName;
  512. $this->rawMethod = $this->methodName;
  513. $this->isFlow = true;
  514. $this->loadModuleConfig('workflowaction');
  515. $moduleName = 'flow';
  516. $methodName = $this->methodName;
  517. /*
  518. * 工作流中除了内置方法外的方法,如果是批量操作调用batchOperate方法,其它操作调用operate方法来执行。
  519. * In addition to the built-in methods in the workflow, if the batch operation calls the batchOperate method, other operations call the operate method to execute.
  520. */
  521. if(!in_array($this->methodName, $this->config->workflowaction->default->actions))
  522. {
  523. if($action->type == 'single') $methodName = 'operate';
  524. if($action->type == 'batch') $methodName = 'batchOperate';
  525. }
  526. $this->setFlowURI($moduleName, $methodName);
  527. }
  528. }
  529. /* Call method of parent. */
  530. return parent::setControlFile($exitIfNone);
  531. }
  532. /**
  533. * 把请求的URI重设成工作流引擎可以解析的URI。
  534. * Reset the requested URI to a URI that the workflow engine can resolve.
  535. *
  536. * e.g. /$module-browse-search-1.html => /flow-browse-$module-search-1.html
  537. * /$module-create.html => /flow-create-$module.html
  538. * /$module-edit-1.html => /flow-edit-$module-1.html
  539. * /$module-view-1.html => /flow-view-$module-1.html
  540. * /$module-delete-1.html => /flow-delete-$module-1.html
  541. * /$module-close-1.html => /flow-operate-$module-close-1.html
  542. *
  543. * /index.php?m=$module&f=browse&mode=search&label=1 => /index.php?m=flow&f=browse&module=$module&mode=search&label=1
  544. * /index.php?m=$module&f=create&id=1 => /index.php?m=flow&f=create&module=$module&$id=1
  545. * /index.php?m=$module&f=edit&id=1 => /index.php?m=flow&f=edit&module=$module&$id=1
  546. * /index.php?m=$module&f=view&id=1 => /index.php?m=flow&f=view&module=$module&$id=1
  547. * /index.php?m=$module&f=delete&id=1 => /index.php?m=flow&f=delete&module=$module&$id=1
  548. * /index.php?m=$module&f=close&id=1 => /index.php?m=flow&f=operate&module=$module&action=close&$id=1
  549. *
  550. * @param string $moduleName
  551. * @param string $methodName
  552. * @access public
  553. * @return void
  554. */
  555. public function setFlowURI($moduleName, $methodName)
  556. {
  557. $query = null;
  558. $path = null;
  559. $this->rawURI = $this->uri;
  560. $this->setModuleName($moduleName);
  561. $this->setMethodName($methodName);
  562. if($this->config->requestType != 'GET')
  563. {
  564. /* e.g. $this->uri = /$module-close-1.html. */
  565. $params = explode($this->config->requestFix, (string) $this->uri); // $params = array($module, 'close', 1);
  566. /* Remove module and method. */
  567. $params = array_slice($params, 2); // $params = array(1);
  568. if($moduleName == 'flow' and $methodName == 'browse')
  569. {
  570. $mode = 'browse';
  571. if(count($params) > 0 and !is_numeric($params[0])) $mode = array_shift($params);
  572. array_unshift($params, $mode);
  573. }
  574. array_unshift($params, $methodName); // $params = array('operate', 1);
  575. array_unshift($params, $moduleName); // $params = array('flow', 'operate', 1);
  576. $this->uri = implode($this->config->requestFix, $params); // $this->uri = flow-operate-1.html;
  577. }
  578. else
  579. {
  580. /* Extract $path and $query from $params. */
  581. /* e.g. $tshi->URI = /index.php?m=$module&f=close&id=1. */
  582. $params = parse_url((string) $this->uri); // $params = array('path' => '/index.php', 'query' => m=$module&f=close&id=1;
  583. extract($params); // $path = '/index.php'; $query = 'm=$module&f=close&id=1';
  584. parse_str((string) $query, $params); // $params = array('m' => $module, 'f' => 'close', 'id' => 1);
  585. /* Remove module and method. */
  586. unset($params[$this->config->moduleVar]); // $params = array('f' => 'close', 'id' => 1);
  587. unset($params[$this->config->methodVar]); // $params = array('id' => 1);
  588. $params = array_reverse($params); // $params = array('id' => 1);
  589. if($moduleName == 'flow' and $methodName == 'browse')
  590. {
  591. $mode = zget($params, 'mode', 'browse');
  592. if(is_numeric($mode)) $mode = 'browse';
  593. $params['mode'] = $mode;
  594. $get = array_reverse($_GET);
  595. $get['mode'] = $mode;
  596. $_GET = array_reverse($get);
  597. }
  598. $params[$this->config->methodVar] = $methodName; // $param = array('id' => 1, 'f' => 'operate');
  599. $params[$this->config->moduleVar] = $moduleName; // $param = array('id' => 1, 'f' => 'operate', 'm' => 'flow');
  600. $params = array_reverse($params); // $params = array('m' => 'flow', 'f' => 'operate', 'id' => 1);
  601. $this->uri = $path . '?' . http_build_query($params); // $this->uri = '/index.php?m=flow&f=operate&id=1';
  602. }
  603. }
  604. /**
  605. * PATH_INFO方式解析,获取$URI和$viewType。
  606. * Parse PATH_INFO, get the $URI and $viewType.
  607. *
  608. * @access public
  609. * @return void
  610. */
  611. public function parsePathInfo()
  612. {
  613. parent::parsePathInfo();
  614. if($this->get->display == 'card') $this->viewType = 'xhtml';
  615. }
  616. /**
  617. * GET请求方式解析,获取$URI和$viewType。
  618. * Parse GET, get $URI and $viewType.
  619. *
  620. * @access public
  621. * @return void
  622. */
  623. public function parseGET()
  624. {
  625. parent::parseGET();
  626. if($this->get->display == 'card') $this->viewType = 'xhtml';
  627. }
  628. /**
  629. * 获取$URL。
  630. * Get the $URL.
  631. *
  632. * @param bool $full The URI contains the webRoot if $full is true else only the URI will be return.
  633. * @access public
  634. * @return string
  635. */
  636. public function getURI($full = false)
  637. {
  638. $theURI = !empty($this->rawURI) ? $this->rawURI : $this->uri;
  639. $tidParam = ($this->config->requestType == 'PATH_INFO' and helper::isWithTID()) ? "?tid={$_GET['tid']}" : '';
  640. if($full and $this->config->requestType == 'PATH_INFO')
  641. {
  642. if($theURI) return $this->config->webRoot . $theURI . '.' . $this->viewType . $tidParam;
  643. return $this->config->webRoot . $tidParam;
  644. }
  645. return $theURI . $tidParam;
  646. }
  647. /**
  648. * 如果$this->isFlow的值为true,说明这个请求需要工作流引擎来处理,则要根据工作流引擎的需要重新设置参数。
  649. * If the values of $this->isFlow is true, indicating that the request needs to be processed
  650. * by the workflow engine, the parameters are reset according to the needs of the workflow engine.
  651. *
  652. * @param array $defaultParams the default params defined by the method.
  653. * @param array $passedParams the params passed in through url.
  654. * @access public
  655. * @return array the merged params.
  656. */
  657. public function mergeParams($defaultParams, $passedParams)
  658. {
  659. if(isset($_GET['project'])) $this->session->set('project', $_GET['project']);
  660. /* If the isFlow is true, reset the passed params. */
  661. /* display参数用来标记请求是否来自禅道客户端的卡片展示页面,此处应该删掉以避免对方法调用产生影响。 */
  662. /* The display parameter is used to mark whether the request comes from the card display page of the ZenTao client. It should be deleted here to avoid affecting the method call. */
  663. unset($passedParams['display']);
  664. return parent::mergeParams($defaultParams, $passedParams);
  665. }
  666. /**
  667. * 加载一个模块:
  668. *
  669. * Load a module.
  670. *
  671. * @access public
  672. * @return bool|object if the module object of die.
  673. */
  674. public function loadModule()
  675. {
  676. /* 不能直接请求基类的方法 Cannot call methods of base control class. */
  677. if(method_exists('Control', $this->methodName))
  678. {
  679. echo 'Cannot call methods of base control class.';
  680. return false;
  681. }
  682. return parent::loadModule();
  683. }
  684. /**
  685. * 获取禅道版本在不同语言下的显示名称。
  686. * Get the display name of the ZenTaoPMS version in different language.
  687. *
  688. * @param string $version 20.0|biz9.0|max5.0|ipd2.0
  689. * @return string
  690. */
  691. public function getVersionName($version = '')
  692. {
  693. global $config, $lang;
  694. $editionName = $config->edition === 'open' ? $lang->pmsName : $lang->{$config->edition . 'Name'};
  695. $versionName = empty($version) ? $config->version : $version;
  696. $versionName = $editionName . str_replace(array('max', 'biz', 'ipd'), '', $versionName);
  697. $versionName = ($config->inQuickon ? $lang->devopsPrefix : '') . $versionName;
  698. return $versionName;
  699. }
  700. /**
  701. * 当找不到control文件时,尝试去其他界面下寻找,如果有则重设视图。
  702. * When the control file cannot be found, try to find it under other interfaces, and if there is one, reset the view.
  703. *
  704. * @return bool
  705. */
  706. public function tryResetVision()
  707. {
  708. $vision = $this->config->vision;
  709. if($vision == 'rnd')
  710. {
  711. if($this->resetVision('or')) return true;
  712. if($this->resetVision('lite')) return true;
  713. }
  714. if($vision == 'or') return $this->resetVision('lite');
  715. if($vision == 'lite') return $this->resetVision('or');
  716. return false;
  717. }
  718. /**
  719. * 当找不到control文件时,尝试去其他界面下寻找,如果有则重设视图。
  720. * When the control file cannot be found, try to find it under other interfaces, and if there is one, reset the view.
  721. *
  722. * @param $vision string 界面名称
  723. * @return bool
  724. */
  725. public function resetVision($vision)
  726. {
  727. $controlFile = $this->getExtensionRoot() . $vision . DS . $this->moduleName . DS . 'control.php';
  728. if(file_exists($controlFile))
  729. {
  730. helper::import($controlFile);
  731. $module = new $this->moduleName();
  732. if(method_exists($module, $this->methodName))
  733. {
  734. helper::setcookie('vision', $vision);
  735. $this->config->vision = $vision;
  736. return true;
  737. }
  738. }
  739. return false;
  740. }
  741. }