checkTmp() === false) { $canModifyDIR = false; $folderPath = $this->app->tmpRoot; } elseif(!$this->checkDataRoot()) { $canModifyDIR = false; $folderPath = $this->app->dataRoot; } if(!$canModifyDIR) { $lang = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? $this->lang->user->mkdirWin : $this->lang->user->mkdirLinux; $message = sprintf($lang, $folderPath, $folderPath, $folderPath, $folderPath); if($_POST) $this->send(array('result' => 'fail', 'message' => array('size' => 'md', 'message' => array('html' => str_replace("\n", '
', strip_tags($message)))))); helper::end($message); } } /** * 判断附件目录(dataRoot)对当前 PHP 进程是否可读写。 * Check whether the upload directory is readable and writable by the current PHP process. * * @access protected * @return bool */ protected function checkDataRoot(): bool { $dataRoot = $this->app->dataRoot; if(!is_dir($dataRoot)) return false; $isWin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; if($isWin) return is_readable($dataRoot) && is_writable($dataRoot); if(is_readable($dataRoot) && is_writable($dataRoot)) return true; $perms = @fileperms($dataRoot); if($perms === false) return false; $mode = $perms & 07777; $ownerId = @fileowner($dataRoot); if($ownerId === false) return false; if($ownerId === posix_geteuid()) return ($mode & 0700) === 0700; return ($mode & 0007) === 0007; } /** * 检查缓存目录是否有写权限。 * Check if the tmp directory is writable. * * @access public * @return bool */ public function checkTmp(): bool { if(!is_dir($this->app->tmpRoot)) mkdir($this->app->tmpRoot, 0755, true); if(!is_dir($this->app->cacheRoot)) mkdir($this->app->cacheRoot, 0755, true); if(!is_dir($this->app->logRoot)) mkdir($this->app->logRoot, 0755, true); if(!is_dir($this->app->logRoot)) return false; $file = $this->app->logRoot . DS . 'demo.txt'; if($fp = @fopen($file, 'a+')) { @fclose($fp); @unlink($file); return true; } return false; } /** * 获取一个用户用于 json 格式返回给前台。 * Get a user for json format to return to the front end. * * @param object $user * @access public * @return object */ public function getUserForJSON(object $user): object { unset($user->password); unset($user->deleted); $user->token = session_id(); // App client will use session id as token. $user->company = $this->app->company->name; return $user; } /** * 获取 FeatureBar 导航。 * Get featureBar menus. * * @param object $user * @access public * @return array */ public function getFeatureBarMenus(object $user): array { $moduleName = $this->app->moduleName; $methodName = $this->app->methodName; $storyType = zget($this->app->params, 'storyType', ''); $params = "userID={$user->id}"; $featureBarMenus = array(); if(common::hasPriv($moduleName, 'todo')) $featureBarMenus['todo'] = array('active' => false, 'url' => $this->createLink($moduleName, 'view', $params), 'text' => $this->lang->user->schedule); if(common::hasPriv($moduleName, 'task')) $featureBarMenus['task'] = array('active' => false, 'url' => $this->createLink($moduleName, 'task', $params), 'text' => $this->lang->user->task); 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); if(common::hasPriv($moduleName, 'story')) $featureBarMenus['story'] = array('active' => false, 'url' => $this->createLink($moduleName, 'story', "$params&storyType=story"), 'text' => $this->lang->SRCommon); if(common::hasPriv($moduleName, 'bug')) $featureBarMenus['bug'] = array('active' => false, 'url' => $this->createLink($moduleName, 'bug', $params), 'text' => $this->lang->user->bug); if(common::hasPriv($moduleName, 'testtask')) $featureBarMenus['testtask'] = array('active' => false, 'url' => $this->createLink($moduleName, 'testtask', $params), 'text' => $this->lang->user->testTask); if(common::hasPriv($moduleName, 'testcase')) $featureBarMenus['testcase'] = array('active' => false, 'url' => $this->createLink($moduleName, 'testcase', $params), 'text' => $this->lang->user->testCase); 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); 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); 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); if(common::hasPriv($moduleName, 'dynamic')) $featureBarMenus['dynamic'] = array('active' => false, 'url' => $this->createLink($moduleName, 'dynamic', "$params&type=today"), 'text' => $this->lang->user->dynamic); if(common::hasPriv($moduleName, 'profile')) $featureBarMenus['profile'] = array('active' => false, 'url' => $this->createLink($moduleName, 'profile', $params), 'text' => $this->lang->user->profile); if($methodName != 'story') $featureBarMenus[$methodName]['active'] = true; if($methodName == 'story' && $storyType == 'story') $featureBarMenus['story']['active'] = true; if($methodName == 'story' && $storyType == 'requirement') $featureBarMenus['requirement']['active'] = true; return $featureBarMenus; } /** * 登录。 * Login. * * @param string $referer * @param string $viewType * @param string $loginLink * @param string $denyLink * @param string $locateReferer * @param string $locateWebRoot * @access public * @return array */ public function login(string $referer = '', string $viewType = '', string $loginLink = '', string $denyLink = '', string $locateReferer = '', string $locateWebRoot = ''): array { if(empty($_POST) && (!isset($_GET['account']) || !isset($_GET['password']))) return array(); /* 预处理账号和密码。*/ /* Preprocess account and password. */ $account = ''; $password = ''; if($this->post->account) $account = trim($this->post->account); if($this->post->password) $password = trim($this->post->password); if($this->get->account) $account = trim($this->get->account); if($this->get->password) $password = trim($this->get->password); if(!$account) return $this->responseForLoginFail($viewType); /* 如果用户被锁定返回相关信息。*/ /* Return related information if the user is locked. */ if($this->user->checkLocked($account)) return $this->responseForLocked($viewType); /* 如果开启了登录验证码检查验证码是否正确。*/ /* Check if the login captcha is correct if the login captcha is enabled. */ 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); /* 验证账号和密码。*/ /* Verify account and password. */ $passwordStrength = $viewType == 'json' ? 1 : (int)$this->post->passwordStrength; $user = $this->user->identify($account, $password, $passwordStrength); /* 登录失败返回错误信息。*/ /* Return error message if login failed. */ if(!$user) return $this->responseForLoginfail($viewType, $account); /* 获取用户所属权限组、权限和视图,记录日志并发放登录积分。*/ /* Get user's group, priv and view, save log and give login score. */ helper::setcookie('logout', false, 0); $user = $this->user->login($user, true, $this->post->keepLogin); /* 以 json 格式返回用户数据。*/ /* Return user data in json format. */ if($viewType == 'json') return array('status' => 'success', 'token' => session_id(), 'user' => $this->getUserForJSON($user)); /* 来源网址不满足条件时跳转到首页。*/ /* Jump to home page if the referer does not meet the conditions. */ if(!$referer || strpos($referer, $loginLink) !== false || strpos($referer, $denyLink) !== false || strpos($referer, 'ajax') !== false || strpos($referer, 'block') !== false) return array('result' => 'success', 'locate' => $locateWebRoot); /* 解析来源网址包含的模块和方法。*/ /* Parse the module and method contained in the referer. */ list($module, $method) = $this->parseLoginModuleAndMethod($referer); /* 如果模块和方法为空或者不合法则跳转到首页。*/ /* Jump to home page if the module and method are empty or illegal. */ if(empty($module) || empty($method) || !$this->app->checkModuleName($module, false) || !$this->app->checkMethodName($module, false)) return array('result' => 'success', 'locate' => $locateWebRoot); /* 如果有模块和方法的访问权限则跳转到来源网址。*/ /* Jump to the referer if there is access to the module and method. */ if(common::hasPriv($module, $method)) return array('result' => 'success', 'locate' => $locateReferer); /* 跳转到首页。*/ /* Jump to home page. */ return array('result' => 'success', 'locate' => $locateWebRoot); } /** * 解析来源网址包含的模块和方法。 * Parse the module and method contained in the referer. * * @param string $referer * @access public * @return array */ public function parseLoginModuleAndMethod(string $referer): array { $module = ''; $method = ''; /* Get the module and method of the referer. */ if($this->config->requestType == 'PATH_INFO') { $requestFix = $this->config->requestFix; $path = substr($referer, strrpos($referer, '/') + 1); $path = rtrim($path, '.html'); if($path && strpos($path, $requestFix) !== false) list($module, $method) = explode($requestFix, $path); } else { $url = html_entity_decode($referer); $param = substr($url, strrpos($url, '?') + 1); if(strpos($param, '&') !== false) list($module, $method) = explode('&', $param); $module = str_replace('m=', '', $module); $method = str_replace('f=', '', $method); } return array($module, $method); } /** * 构建职位和权限组数据。 * Prepare roles and groups data. * * @access public * @return void */ public function prepareRolesAndGroups(): void { $groupList = array(); $roleGroup = array(); $groups = $this->dao->select('id, name, role, vision')->from(TABLE_GROUP)->where('vision')->eq($this->config->vision)->andWhere('project')->eq('0')->fetchAll(); foreach($groups as $group) { $groupList[$group->id] = $group->name; if($group->role) $roleGroup[$group->role] = $group->id; } $this->view->groupList = $groupList; $this->view->roleGroup = $roleGroup; } /** * 构建自定义字段。 * Prepare custom fields. * * @param string $method * @param string $requiredMethod * @access public * @return void */ public function prepareCustomFields(string $method, string $requiredMethod): void { $customFields = 'custom' . ucfirst($method) . 'Fields'; $showField = $method . 'Fields'; /* 获取所有的联系方式字段。*/ /* Get all contact fields. */ $allContactFields = array_keys($this->lang->user->contactFieldList); /* 获取自定义的联系方式字段,转为数组并去空、去重。*/ /* Get custom contact fields, convert to array and remove empty and duplicate. */ $customContactFields = array_unique(array_filter(explode(',', trim($this->config->user->contactField, ',')))); /* 获取不可用的联系方式字段。*/ /* Get unavailable contact fields. */ $unAvailableContactFields = array_diff($allContactFields, $customContactFields); /* 从配置文件获取所有自定义字段,转为数组并去空、去重。*/ /* Get all custom fields from config file, convert to array and remove empty and duplicate. */ $customFields = array_unique(array_filter(explode(',', trim($this->config->user->list->$customFields, ',')))); /* 从自定义字段中去除不可用的联系方式字段。*/ /* Remove unavailable contact fields from custom fields. */ $customFields = array_diff($customFields, $unAvailableContactFields); /* 获取可以显示的字段。*/ /* Get fields that can be displayed. */ $listFields = array(); foreach($customFields as $field) $listFields[$field] = $this->lang->user->$field; /* 从配置文件获取必填项字段,转为数组并去空、去重。*/ /* Get required fields from config file, convert to array and remove empty and duplicate. */ $requiredFields = array_unique(array_filter(explode(',', trim($this->config->user->$requiredMethod->requiredFields, ',')))); /* 从数据库中获取应该显示的字段。*/ /* Get fields that should be displayed from database. */ $showFields = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=user§ion=custom&key={$showField}"); /* 从配置文件中获取应该显示的字段。*/ /* Get fields that should be displayed from config file. */ if(!$showFields) $showFields = $this->config->user->custom->$showField; /* 把应该显示的字段转为数组并去空、去重。*/ /* Convert fields that should be displayed to array and remove empty and duplicate. */ $showFields = array_unique(array_filter(explode(',', trim($showFields, ',')))); /* 把应该显示的字段和必填项字段合并,确保自定义字段面板中必填项字段是被勾选中的。*/ /* Merge fields that should be displayed and required fields to ensure that required fields are checked in the custom field panel. */ $showFields = array_merge($showFields, $requiredFields); /* 把应该显示的字段和可用字段取交集。*/ /* Get the intersection of fields that should be displayed and available fields. */ $showFields = array_intersect($showFields, $customFields); $this->view->listFields = $listFields; $this->view->showFields = $showFields; } /** * 重新加载语言项。 * Reload language items. * * @param string $lang * @access public * @return void */ public function reloadLang(string $lang): void { $this->app->setClientLang($lang); $this->app->loadLang('user'); } /** * 用户已登录时的响应。 * Response when user has logged in. * * @param string $referer * @param string $viewType * @param string $loginLink * @param string $denyLink * @param string $locateReferer * @param string $locateWebRoot * @access public * @return array */ public function responseForLogon(string $referer, string $viewType, string $loginLink, string $denyLink, string $locateReferer, string $locateWebRoot): array { helper::setcookie('logout', false, 0); /* 以 json 格式返回用户数据。*/ /* Return user data in json format. */ if($viewType == 'json') return array('status' => 'success', 'token' => session_id(), 'user' => $this->getUserForJSON($this->app->user)); /* 来源网址不满足条件时跳转到首页。*/ /* Jump to home page if the referer does not meet the conditions. */ if(!$referer || strpos($referer, $loginLink) !== false || strpos($referer, $denyLink) !== false || strpos($referer, 'ajax') !== false || strpos($referer, 'block') !== false) return array('result' => 'success', 'locate' => $locateWebRoot); /* 跳转到来源网址。*/ /* Jump to the referer. */ return array('result' => 'success', 'locate' => $locateReferer); } /** * 用户被锁定时的响应。 * Response when user is locked. * * @param string $viewType * @access public * @return array */ public function responseForLocked(string $viewType): array { $message = sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes); if($viewType == 'json') return array('status' => 'failed', 'reason' => $message); return array('result' => 'fail', 'message' => $message); } /** * 登录失败时的响应。 * Response when login failed. * * @param string $viewType * @param string $account * @access public * @return array */ public function responseForLoginFail(string $viewType, string $account = ''): array { if($viewType == 'json') return array('status' => 'failed', 'reason' => $this->lang->user->loginFailed); if($account) { $remainTimes = $this->config->user->failTimes - $this->user->failPlus($account); if($remainTimes <= 0) return array('result' => 'fail', 'message' => sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes)); if($remainTimes <= 3) return array('result' => 'fail', 'message' => sprintf($this->lang->user->lockWarning, $remainTimes)); if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError()); } return array('result' => 'fail', 'message' => $this->lang->user->loginFailed); } /** * 设置来源地址。 * Set referer. * * @param string $referer * @access public * @return string */ public function setReferer(string $referer = ''): string { $this->referer = $this->server->http_referer ? $this->server->http_referer: ''; if(!empty($referer)) $this->referer = helper::safe64Decode($referer); if($this->post->referer) $this->referer = $this->post->referer; /* 构建禅道链接的正则表达式。*/ /* Build zentao link regular expression. */ $webRoot = $this->config->webRoot; $linkReg = $webRoot . 'index.php?' . $this->config->moduleVar . '=\w+&' . $this->config->methodVar . '=\w+'; if($this->config->requestType == 'PATH_INFO') $linkReg = $webRoot . '\w+' . $this->config->requestFix . '\w+'; $linkReg = str_replace(array('/', '.', '?', '-'), array('\/', '\.', '\?', '\-'), $linkReg); /* 检查来源地址是否为禅道链接。*/ /* Check zentao link by regular expression. */ return preg_match('/^' . $linkReg . '/', $this->referer) ? $this->referer : $webRoot; } }