zen.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. class userZen extends user
  3. {
  4. /**
  5. * 检查缓存目录和数据目录访问权限。如果不能访问,终止程序并输出提示信息。
  6. * Check the access permissions of the cache directory and data directory. If you cannot access, terminate the program and output the prompt message.
  7. *
  8. * @access public
  9. * @return void
  10. */
  11. public function checkDirPermission()
  12. {
  13. $canModifyDIR = true;
  14. if($this->checkTmp() === false)
  15. {
  16. $canModifyDIR = false;
  17. $folderPath = $this->app->tmpRoot;
  18. }
  19. elseif(!$this->checkDataRoot())
  20. {
  21. $canModifyDIR = false;
  22. $folderPath = $this->app->dataRoot;
  23. }
  24. if(!$canModifyDIR)
  25. {
  26. $lang = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? $this->lang->user->mkdirWin : $this->lang->user->mkdirLinux;
  27. $message = sprintf($lang, $folderPath, $folderPath, $folderPath, $folderPath);
  28. if($_POST) $this->send(array('result' => 'fail', 'message' => array('size' => 'md', 'message' => array('html' => str_replace("\n", '<br>', strip_tags($message))))));
  29. helper::end($message);
  30. }
  31. }
  32. /**
  33. * 判断附件目录(dataRoot)对当前 PHP 进程是否可读写。
  34. * Check whether the upload directory is readable and writable by the current PHP process.
  35. *
  36. * @access protected
  37. * @return bool
  38. */
  39. protected function checkDataRoot()
  40. {
  41. $dataRoot = $this->app->dataRoot;
  42. if(!is_dir($dataRoot)) return false;
  43. $isWin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
  44. if($isWin) return is_readable($dataRoot) && is_writable($dataRoot);
  45. if(is_readable($dataRoot) && is_writable($dataRoot)) return true;
  46. $perms = @fileperms($dataRoot);
  47. if($perms === false) return false;
  48. $mode = $perms & 07777;
  49. $ownerId = @fileowner($dataRoot);
  50. if($ownerId === false) return false;
  51. if($ownerId === posix_geteuid()) return ($mode & 0700) === 0700;
  52. return ($mode & 0007) === 0007;
  53. }
  54. /**
  55. * 检查缓存目录是否有写权限。
  56. * Check if the tmp directory is writable.
  57. *
  58. * @access public
  59. * @return bool
  60. */
  61. public function checkTmp()
  62. {
  63. if(!is_dir($this->app->tmpRoot)) mkdir($this->app->tmpRoot, 0755, true);
  64. if(!is_dir($this->app->cacheRoot)) mkdir($this->app->cacheRoot, 0755, true);
  65. if(!is_dir($this->app->logRoot)) mkdir($this->app->logRoot, 0755, true);
  66. if(!is_dir($this->app->logRoot)) return false;
  67. $file = $this->app->logRoot . DS . 'demo.txt';
  68. if($fp = @fopen($file, 'a+'))
  69. {
  70. @fclose($fp);
  71. @unlink($file);
  72. return true;
  73. }
  74. return false;
  75. }
  76. /**
  77. * 获取一个用户用于 json 格式返回给前台。
  78. * Get a user for json format to return to the front end.
  79. *
  80. * @param object $user
  81. * @access public
  82. * @return object
  83. */
  84. public function getUserForJSON($user)
  85. {
  86. unset($user->password);
  87. unset($user->deleted);
  88. $user->token = session_id(); // App client will use session id as token.
  89. $user->company = $this->app->company->name;
  90. return $user;
  91. }
  92. /**
  93. * 获取 FeatureBar 导航。
  94. * Get featureBar menus.
  95. *
  96. * @param object $user
  97. * @access public
  98. * @return array
  99. */
  100. public function getFeatureBarMenus($user)
  101. {
  102. $moduleName = $this->app->moduleName;
  103. $methodName = $this->app->methodName;
  104. $storyType = zget($this->app->params, 'storyType', '');
  105. $params = "userID={$user->id}";
  106. $featureBarMenus = array();
  107. if(common::hasPriv($moduleName, 'todo')) $featureBarMenus['todo'] = array('active' => false, 'url' => $this->createLink($moduleName, 'view', $params), 'text' => $this->lang->user->schedule);
  108. if(common::hasPriv($moduleName, 'task')) $featureBarMenus['task'] = array('active' => false, 'url' => $this->createLink($moduleName, 'task', $params), 'text' => $this->lang->user->task);
  109. if($this->config->URAndSR && $this->config->vision == 'rnd') $featureBarMenus['requirement'] = array('active' => false, 'url' => $this->createLink($moduleName, 'story', "$params&storyType=requirement"), 'text' => $this->lang->URCommon);
  110. if(common::hasPriv($moduleName, 'story')) $featureBarMenus['story'] = array('active' => false, 'url' => $this->createLink($moduleName, 'story', "$params&storyType=story"), 'text' => $this->lang->SRCommon);
  111. if(common::hasPriv($moduleName, 'bug')) $featureBarMenus['bug'] = array('active' => false, 'url' => $this->createLink($moduleName, 'bug', $params), 'text' => $this->lang->user->bug);
  112. if(common::hasPriv($moduleName, 'testtask')) $featureBarMenus['testtask'] = array('active' => false, 'url' => $this->createLink($moduleName, 'testtask', $params), 'text' => $this->lang->user->testTask);
  113. if(common::hasPriv($moduleName, 'testcase')) $featureBarMenus['testcase'] = array('active' => false, 'url' => $this->createLink($moduleName, 'testcase', $params), 'text' => $this->lang->user->testCase);
  114. if(common::hasPriv($moduleName, 'execution') && in_array($this->config->systemMode, array('ALM', 'PLM'))) $featureBarMenus['execution'] = array('active' => false, 'url' => $this->createLink($moduleName, 'execution', $params), 'text' => $this->lang->user->execution);
  115. if(common::hasPriv($moduleName, 'issue') && in_array($this->config->edition, array('max', 'ipd')) && helper::hasFeature('issue')) $featureBarMenus['issue'] = array('active' => false, 'url' => $this->createLink($moduleName, 'issue', $params), 'text' => $this->lang->user->issue);
  116. if(common::hasPriv($moduleName, 'risk') && in_array($this->config->edition, array('max', 'ipd')) && helper::hasFeature('risk')) $featureBarMenus['risk'] = array('active' => false, 'url' => $this->createLink($moduleName, 'risk', $params), 'text' => $this->lang->user->risk);
  117. if(common::hasPriv($moduleName, 'dynamic')) $featureBarMenus['dynamic'] = array('active' => false, 'url' => $this->createLink($moduleName, 'dynamic', "$params&type=today"), 'text' => $this->lang->user->dynamic);
  118. if(common::hasPriv($moduleName, 'profile')) $featureBarMenus['profile'] = array('active' => false, 'url' => $this->createLink($moduleName, 'profile', $params), 'text' => $this->lang->user->profile);
  119. if($methodName != 'story') $featureBarMenus[$methodName]['active'] = true;
  120. if($methodName == 'story' && $storyType == 'story') $featureBarMenus['story']['active'] = true;
  121. if($methodName == 'story' && $storyType == 'requirement') $featureBarMenus['requirement']['active'] = true;
  122. return $featureBarMenus;
  123. }
  124. /**
  125. * 登录。
  126. * Login.
  127. *
  128. * @param string $referer
  129. * @param string $viewType
  130. * @param string $loginLink
  131. * @param string $denyLink
  132. * @param string $locateReferer
  133. * @param string $locateWebRoot
  134. * @access public
  135. * @return array
  136. */
  137. public function login($referer = '', $viewType = '', $loginLink = '', $denyLink = '', $locateReferer = '', $locateWebRoot = '')
  138. {
  139. if(empty($_POST) && (!isset($_GET['account']) || !isset($_GET['password']))) return array();
  140. /* 预处理账号和密码。*/
  141. /* Preprocess account and password. */
  142. $account = '';
  143. $password = '';
  144. if($this->post->account) $account = trim($this->post->account);
  145. if($this->post->password) $password = trim($this->post->password);
  146. if($this->get->account) $account = trim($this->get->account);
  147. if($this->get->password) $password = trim($this->get->password);
  148. if(!$account) return $this->responseForLoginFail($viewType);
  149. /* 如果用户被锁定返回相关信息。*/
  150. /* Return related information if the user is locked. */
  151. if($this->user->checkLocked($account)) return $this->responseForLocked($viewType);
  152. /* 如果开启了登录验证码检查验证码是否正确。*/
  153. /* Check if the login captcha is correct if the login captcha is enabled. */
  154. if((!empty($this->config->safe->loginCaptcha) && strtolower($this->post->captcha) != strtolower($this->session->captcha) && $viewType != 'json')) return array('result' => 'fail', 'message' => $this->lang->user->errorCaptcha);
  155. /* 验证账号和密码。*/
  156. /* Verify account and password. */
  157. $passwordStrength = $viewType == 'json' ? 1 : (int)$this->post->passwordStrength;
  158. $user = $this->user->identify($account, $password, $passwordStrength);
  159. /* 登录失败返回错误信息。*/
  160. /* Return error message if login failed. */
  161. if(!$user) return $this->responseForLoginfail($viewType, $account);
  162. /* 获取用户所属权限组、权限和视图,记录日志并发放登录积分。*/
  163. /* Get user's group, priv and view, save log and give login score. */
  164. helper::setcookie('logout', false, 0);
  165. $user = $this->user->login($user, true, $this->post->keepLogin);
  166. /* 以 json 格式返回用户数据。*/
  167. /* Return user data in json format. */
  168. if($viewType == 'json') return array('status' => 'success', 'token' => session_id(), 'user' => $this->getUserForJSON($user));
  169. /* 来源网址不满足条件时跳转到首页。*/
  170. /* Jump to home page if the referer does not meet the conditions. */
  171. if(!$referer || strpos($referer, $loginLink) !== false || strpos($referer, $denyLink) !== false || strpos($referer, 'ajax') !== false || strpos($referer, 'block') !== false) return array('result' => 'success', 'locate' => $locateWebRoot);
  172. /* 解析来源网址包含的模块和方法。*/
  173. /* Parse the module and method contained in the referer. */
  174. list($module, $method) = $this->parseLoginModuleAndMethod($referer);
  175. /* 如果模块和方法为空或者不合法则跳转到首页。*/
  176. /* Jump to home page if the module and method are empty or illegal. */
  177. if(empty($module) || empty($method) || !$this->app->checkModuleName($module, false) || !$this->app->checkMethodName($module, false)) return array('result' => 'success', 'locate' => $locateWebRoot);
  178. /* 如果有模块和方法的访问权限则跳转到来源网址。*/
  179. /* Jump to the referer if there is access to the module and method. */
  180. if(common::hasPriv($module, $method)) return array('result' => 'success', 'locate' => $locateReferer);
  181. /* 跳转到首页。*/
  182. /* Jump to home page. */
  183. return array('result' => 'success', 'locate' => $locateWebRoot);
  184. }
  185. /**
  186. * 解析来源网址包含的模块和方法。
  187. * Parse the module and method contained in the referer.
  188. *
  189. * @param string $referer
  190. * @access public
  191. * @return array
  192. */
  193. public function parseLoginModuleAndMethod($referer)
  194. {
  195. $module = '';
  196. $method = '';
  197. /* Get the module and method of the referer. */
  198. if($this->config->requestType == 'PATH_INFO')
  199. {
  200. $requestFix = $this->config->requestFix;
  201. $path = substr($referer, strrpos($referer, '/') + 1);
  202. $path = rtrim($path, '.html');
  203. if($path && strpos($path, $requestFix) !== false) list($module, $method) = explode($requestFix, $path);
  204. }
  205. else
  206. {
  207. $url = html_entity_decode($referer);
  208. $param = substr($url, strrpos($url, '?') + 1);
  209. if(strpos($param, '&') !== false) list($module, $method) = explode('&', $param);
  210. $module = str_replace('m=', '', $module);
  211. $method = str_replace('f=', '', $method);
  212. }
  213. return array($module, $method);
  214. }
  215. /**
  216. * 构建职位和权限组数据。
  217. * Prepare roles and groups data.
  218. *
  219. * @access public
  220. * @return void
  221. */
  222. public function prepareRolesAndGroups()
  223. {
  224. $groupList = array();
  225. $roleGroup = array();
  226. $groups = $this->dao->select('id, name, role, vision')->from(TABLE_GROUP)->where('vision')->eq($this->config->vision)->andWhere('project')->eq('0')->fetchAll();
  227. foreach($groups as $group)
  228. {
  229. $groupList[$group->id] = $group->name;
  230. if($group->role) $roleGroup[$group->role] = $group->id;
  231. }
  232. $this->view->groupList = $groupList;
  233. $this->view->roleGroup = $roleGroup;
  234. }
  235. /**
  236. * 构建自定义字段。
  237. * Prepare custom fields.
  238. *
  239. * @param string $method
  240. * @param string $requiredMethod
  241. * @access public
  242. * @return void
  243. */
  244. public function prepareCustomFields($method, $requiredMethod)
  245. {
  246. $customFields = 'custom' . ucfirst($method) . 'Fields';
  247. $showField = $method . 'Fields';
  248. /* 获取所有的联系方式字段。*/
  249. /* Get all contact fields. */
  250. $allContactFields = array_keys($this->lang->user->contactFieldList);
  251. /* 获取自定义的联系方式字段,转为数组并去空、去重。*/
  252. /* Get custom contact fields, convert to array and remove empty and duplicate. */
  253. $customContactFields = array_unique(array_filter(explode(',', trim($this->config->user->contactField, ','))));
  254. /* 获取不可用的联系方式字段。*/
  255. /* Get unavailable contact fields. */
  256. $unAvailableContactFields = array_diff($allContactFields, $customContactFields);
  257. /* 从配置文件获取所有自定义字段,转为数组并去空、去重。*/
  258. /* Get all custom fields from config file, convert to array and remove empty and duplicate. */
  259. $customFields = array_unique(array_filter(explode(',', trim($this->config->user->list->$customFields, ','))));
  260. /* 从自定义字段中去除不可用的联系方式字段。*/
  261. /* Remove unavailable contact fields from custom fields. */
  262. $customFields = array_diff($customFields, $unAvailableContactFields);
  263. /* 获取可以显示的字段。*/
  264. /* Get fields that can be displayed. */
  265. $listFields = array();
  266. foreach($customFields as $field) $listFields[$field] = $this->lang->user->$field;
  267. /* 从配置文件获取必填项字段,转为数组并去空、去重。*/
  268. /* Get required fields from config file, convert to array and remove empty and duplicate. */
  269. $requiredFields = array_unique(array_filter(explode(',', trim($this->config->user->$requiredMethod->requiredFields, ','))));
  270. /* 从数据库中获取应该显示的字段。*/
  271. /* Get fields that should be displayed from database. */
  272. $showFields = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=user&section=custom&key={$showField}");
  273. /* 从配置文件中获取应该显示的字段。*/
  274. /* Get fields that should be displayed from config file. */
  275. if(!$showFields) $showFields = $this->config->user->custom->$showField;
  276. /* 把应该显示的字段转为数组并去空、去重。*/
  277. /* Convert fields that should be displayed to array and remove empty and duplicate. */
  278. $showFields = array_unique(array_filter(explode(',', trim($showFields, ','))));
  279. /* 把应该显示的字段和必填项字段合并,确保自定义字段面板中必填项字段是被勾选中的。*/
  280. /* Merge fields that should be displayed and required fields to ensure that required fields are checked in the custom field panel. */
  281. $showFields = array_merge($showFields, $requiredFields);
  282. /* 把应该显示的字段和可用字段取交集。*/
  283. /* Get the intersection of fields that should be displayed and available fields. */
  284. $showFields = array_intersect($showFields, $customFields);
  285. $this->view->listFields = $listFields;
  286. $this->view->showFields = $showFields;
  287. }
  288. /**
  289. * 重新加载语言项。
  290. * Reload language items.
  291. *
  292. * @param string $lang
  293. * @access public
  294. * @return void
  295. */
  296. public function reloadLang($lang)
  297. {
  298. $this->app->setClientLang($lang);
  299. $this->app->loadLang('user');
  300. }
  301. /**
  302. * 用户已登录时的响应。
  303. * Response when user has logged in.
  304. *
  305. * @param string $referer
  306. * @param string $viewType
  307. * @param string $loginLink
  308. * @param string $denyLink
  309. * @param string $locateReferer
  310. * @param string $locateWebRoot
  311. * @access public
  312. * @return array
  313. */
  314. public function responseForLogon($referer, $viewType, $loginLink, $denyLink, $locateReferer, $locateWebRoot)
  315. {
  316. helper::setcookie('logout', false, 0);
  317. /* 以 json 格式返回用户数据。*/
  318. /* Return user data in json format. */
  319. if($viewType == 'json') return array('status' => 'success', 'token' => session_id(), 'user' => $this->getUserForJSON($this->app->user));
  320. /* 来源网址不满足条件时跳转到首页。*/
  321. /* Jump to home page if the referer does not meet the conditions. */
  322. if(!$referer || strpos($referer, $loginLink) !== false || strpos($referer, $denyLink) !== false || strpos($referer, 'ajax') !== false || strpos($referer, 'block') !== false) return array('result' => 'success', 'locate' => $locateWebRoot);
  323. /* 跳转到来源网址。*/
  324. /* Jump to the referer. */
  325. return array('result' => 'success', 'locate' => $locateReferer);
  326. }
  327. /**
  328. * 用户被锁定时的响应。
  329. * Response when user is locked.
  330. *
  331. * @param string $viewType
  332. * @access public
  333. * @return array
  334. */
  335. public function responseForLocked($viewType)
  336. {
  337. $message = sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes);
  338. if($viewType == 'json') return array('status' => 'failed', 'reason' => $message);
  339. return array('result' => 'fail', 'message' => $message);
  340. }
  341. /**
  342. * 登录失败时的响应。
  343. * Response when login failed.
  344. *
  345. * @param string $viewType
  346. * @param string $account
  347. * @access public
  348. * @return array
  349. */
  350. public function responseForLoginFail($viewType, $account = '')
  351. {
  352. if($viewType == 'json') return array('status' => 'failed', 'reason' => $this->lang->user->loginFailed);
  353. if($account)
  354. {
  355. $remainTimes = $this->config->user->failTimes - $this->user->failPlus($account);
  356. if($remainTimes <= 0) return array('result' => 'fail', 'message' => sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes));
  357. if($remainTimes <= 3) return array('result' => 'fail', 'message' => sprintf($this->lang->user->lockWarning, $remainTimes));
  358. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
  359. }
  360. return array('result' => 'fail', 'message' => $this->lang->user->loginFailed);
  361. }
  362. /**
  363. * 设置来源地址。
  364. * Set referer.
  365. *
  366. * @param string $referer
  367. * @access public
  368. * @return string
  369. */
  370. public function setReferer($referer = '')
  371. {
  372. $this->referer = $this->server->http_referer ? $this->server->http_referer: '';
  373. if(!empty($referer)) $this->referer = helper::safe64Decode($referer);
  374. if($this->post->referer) $this->referer = $this->post->referer;
  375. /* 构建禅道链接的正则表达式。*/
  376. /* Build zentao link regular expression. */
  377. $webRoot = $this->config->webRoot;
  378. $linkReg = $webRoot . 'index.php?' . $this->config->moduleVar . '=\w+&' . $this->config->methodVar . '=\w+';
  379. if($this->config->requestType == 'PATH_INFO') $linkReg = $webRoot . '\w+' . $this->config->requestFix . '\w+';
  380. $linkReg = str_replace(array('/', '.', '?', '-'), array('\/', '\.', '\?', '\-'), $linkReg);
  381. /* 检查来源地址是否为禅道链接。*/
  382. /* Check zentao link by regular expression. */
  383. return preg_match('/^' . $linkReg . '/', $this->referer) ? $this->referer : $webRoot;
  384. }
  385. }