tao.php 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. <?php
  2. class searchTao extends searchModel
  3. {
  4. /**
  5. * 设置内置字段的搜索参数。
  6. * Process buildin fields.
  7. *
  8. * @param string $module
  9. * @param array $searchConfig
  10. * @access protected
  11. * @return array
  12. */
  13. public function processBuildinFields($module, $searchConfig)
  14. {
  15. $flowModule = $module;
  16. if($module == 'projectStory' || $module == 'executionStory' || $module == 'projectstory') $flowModule = 'story';
  17. if($module == 'projectBuild' || $module == 'executionBuild') $flowModule = 'build';
  18. if($module == 'projectBug') $flowModule = 'bug';
  19. if($module == 'executionCase') $flowModule = 'testcase';
  20. $buildin = false;
  21. $this->app->loadLang('workflow');
  22. $this->app->loadConfig('workflow');
  23. if(!empty($this->config->workflow->buildin))
  24. {
  25. foreach($this->config->workflow->buildin->modules as $appModules)
  26. {
  27. if(isset($appModules->$flowModule))
  28. {
  29. $buildin = true;
  30. break;
  31. }
  32. }
  33. }
  34. if(!$buildin) return $searchConfig;
  35. $groupID = $this->loadModel('workflowgroup')->getGroupIDBySession();
  36. $fields = $this->loadModel('workflowfield')->getList($flowModule, 'searchOrder, `order`, id', $groupID);
  37. $maxCount = $this->config->maxCount;
  38. $this->config->maxCount = 0;
  39. $fieldValues = array();
  40. $formName = $module . 'Form';
  41. if($this->session->$formName)
  42. {
  43. foreach($this->session->$formName as $formField)
  44. {
  45. $field = zget($formField, 'field', '');
  46. $value = zget($formField, 'value', '');
  47. if(empty($field)) continue;
  48. if($value) $fieldValues[$field][$value] = $value;
  49. }
  50. }
  51. foreach($fields as $field)
  52. {
  53. if($field->canSearch == 0 || $field->buildin) continue;
  54. if(in_array($field->control, $this->config->workflowfield->optionControls))
  55. {
  56. $field->options = $this->workflowfield->getFieldOptions($field, true, zget($fieldValues, $field->field, ''), '', $this->config->flowLimit);
  57. }
  58. $searchConfig['fields'][$field->field] = $field->name;
  59. $searchConfig['params'][$field->field] = $this->loadModel('flow', 'sys')->processSearchParams($field->control, $field->options);
  60. }
  61. $this->config->maxCount = $maxCount;
  62. return $searchConfig;
  63. }
  64. /**
  65. * 初始化搜索表单,并且保存到 session。
  66. * Init the search session for the first time search.
  67. *
  68. * @param string $module
  69. * @param array $fields
  70. * @param array $fieldParams
  71. * @access public
  72. * @return array
  73. */
  74. public function initSession($module, $fields, $fieldParams)
  75. {
  76. $formSessionName = $module . 'Form';
  77. $queryForm = array();
  78. for($i = 1; $i <= $this->config->search->groupItems * 2; $i ++)
  79. {
  80. $currentField = key($fields);
  81. $currentParams = zget($fieldParams, $currentField, array());
  82. $operator = zget($currentParams, 'operator', '=');
  83. $queryForm[] = array('field' => $currentField, 'andOr' => 'and', 'operator' => $operator, 'value' => '');
  84. if(!next($fields)) reset($fields);
  85. }
  86. $queryForm[] = array('groupAndOr' => 'and');
  87. $this->session->set($formSessionName, $queryForm);
  88. return $queryForm;
  89. }
  90. /**
  91. * Init the old search session for the first time search.
  92. *
  93. * @param string $module
  94. * @param array $fields
  95. * @param array $fieldParams
  96. * @access public
  97. * @return void
  98. */
  99. public function initOldSession($module, $fields, $fieldParams)
  100. {
  101. $formSessionName = $module . 'Form';
  102. if(isset($_SESSION[$formSessionName]) and $_SESSION[$formSessionName] != false) return;
  103. for($i = 1; $i <= $this->config->search->groupItems * 2; $i ++)
  104. {
  105. /* Var names. */
  106. $fieldName = "field$i";
  107. $andOrName = "andOr$i";
  108. $operatorName = "operator$i";
  109. $valueName = "value$i";
  110. $currentField = key($fields);
  111. $operator = isset($fieldParams[$currentField]['operator']) ? $fieldParams[$currentField]['operator'] : '=';
  112. $queryForm[$fieldName] = key($fields);
  113. $queryForm[$andOrName] = 'and';
  114. $queryForm[$operatorName] = $operator;
  115. $queryForm[$valueName] = '';
  116. if(!next($fields)) reset($fields);
  117. }
  118. $queryForm['groupAndOr'] = 'and';
  119. $this->session->set($formSessionName, $queryForm);
  120. }
  121. /**
  122. * 处理查询表单的相关数据。
  123. * Process query form datas.
  124. *
  125. * @param array $fieldParams
  126. * @param string $field
  127. * @param string $andOrName
  128. * @param string $operatorName
  129. * @param string $valueName
  130. * @access public
  131. * @return array
  132. */
  133. public function processQueryFormDatas($fieldParams, $field, $andOrName, $operatorName, $valueName)
  134. {
  135. /* 设置分组之间的逻辑关系。*/
  136. /* Set and or. */
  137. $andOr = strtoupper($this->post->$andOrName);
  138. if($andOr != 'AND' && $andOr != 'OR') $andOr = 'AND';
  139. /* 设置操作符。*/
  140. /* Set operator. */
  141. $operator = $this->post->$operatorName;
  142. if(!isset($this->lang->search->operators[$operator])) $operator = '=';
  143. /* 设置字段的值。*/
  144. /* Skip empty values. */
  145. $value = $this->post->$valueName;
  146. if($value == 'ZERO') $this->post->set($valueName, 0); // ZERO is special, stands to 0.
  147. if(isset($fieldParams[$field]) && $fieldParams[$field]['control'] == 'select' && $value === 'null') $this->post->set($valueName, ''); // Null is special, stands to empty if control is select.
  148. $value = addcslashes(trim((string)$this->post->{$valueName}), '%');
  149. return array($andOr, $operator, $value);
  150. }
  151. /**
  152. * 根据字段,操作符和值设置查询条件。
  153. * Set condition by field, operator and value.
  154. *
  155. * @param string $field
  156. * @param string $operator
  157. * @param string $value
  158. * @param string $control
  159. * @access protected
  160. * @return string
  161. */
  162. protected function setCondition($field, $operator, $value, $control = '')
  163. {
  164. /* 替换特殊字符。 */
  165. if(is_string($value)) $value = htmlspecialchars($value, ENT_QUOTES);
  166. $condition = '';
  167. if($operator == 'include')
  168. {
  169. if($field == 'module')
  170. {
  171. $allModules = $this->loadModel('tree')->getAllChildId($value);
  172. if($allModules) $condition = helper::dbIN($allModules);
  173. }
  174. else
  175. {
  176. $condition = $control == 'select' ? " LIKE CONCAT('%,', '{$value}', ',%')" : ' LIKE ' . $this->dbh->quote("%$value%");
  177. }
  178. }
  179. elseif($operator == "notinclude")
  180. {
  181. if($field == 'module')
  182. {
  183. $allModules = $this->loadModel('tree')->getAllChildId($value);
  184. if($allModules)
  185. {
  186. if(count($allModules) > 1) $condition = " NOT " . helper::dbIN($allModules);
  187. else $condition = " !" . helper::dbIN($allModules);
  188. }
  189. }
  190. else
  191. {
  192. $condition = $control == 'select' ? " NOT LIKE CONCAT('%,', '{$value}', ',%')" : ' NOT LIKE ' . $this->dbh->quote("%$value%");
  193. }
  194. }
  195. elseif($operator == 'belong')
  196. {
  197. if($field == 'module')
  198. {
  199. $allModules = $this->loadModel('tree')->getAllChildId((int)$value);
  200. if($allModules) $condition = helper::dbIN($allModules);
  201. }
  202. elseif($field == 'dept')
  203. {
  204. $allDepts = $this->loadModel('dept')->getAllChildId((int)$value);
  205. $condition = helper::dbIN($allDepts);
  206. }
  207. elseif($field == 'scene')
  208. {
  209. $allScenes = $value === '0' ? array() : ($value === '' ? array(0) : $this->loadModel('testcase')->getAllChildId((int)$value));
  210. if(count($allScenes)) $condition = helper::dbIN($allScenes);
  211. }
  212. else
  213. {
  214. $condition = ' = ' . $this->dbh->quote($value) . ' ';
  215. }
  216. }
  217. else
  218. {
  219. if($operator == 'between' and !isset($this->config->search->dynamic[$value])) $operator = '=';
  220. $condition = $operator . ' ' . $this->dbh->quote($value) . ' ';
  221. if($operator == '=' and $field == 'id' and preg_match('/^[0-9]+(,[0-9]*)+$/', $value) and !preg_match('/[\x7f-\xff]+/', $value))
  222. {
  223. $values = array_filter(explode(',', trim($this->dbh->quote($value), "'")));
  224. foreach($values as $value) $value = "'" . $value . "'";
  225. $value = implode(',', $values);
  226. $operator = 'IN';
  227. $condition = $operator . ' (' . $value . ') ';
  228. }
  229. }
  230. return $condition;
  231. }
  232. /**
  233. * 设置 where 查询条件。
  234. * Set where condition.
  235. *
  236. * @param string $where
  237. * @param string $field
  238. * @param string $operator
  239. * @param string $value
  240. * @param string $andOr
  241. * @param string $control
  242. * @access public
  243. * @return string
  244. */
  245. public function setWhere($where, $field, $operator, $value, $andOr, $control = '')
  246. {
  247. $condition = $this->setCondition($field, $operator, $value, $control);
  248. if($operator == '=' && preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
  249. {
  250. $condition = '`' . $field . "` >= '$value' AND `" . $field . "` <= '$value 23:59:59'";
  251. $where .= " $andOr ($condition)";
  252. }
  253. elseif($operator == '!=' && preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
  254. {
  255. $condition = '`' . $field . "` < '$value' OR `" . $field . "` > '$value 23:59:59'";
  256. $where .= " $andOr ($condition)";
  257. }
  258. elseif($operator == '<=' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
  259. {
  260. $where .= " $andOr " . '`' . $field . "` <= '$value 23:59:59'";
  261. }
  262. elseif($operator == '>' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
  263. {
  264. $where .= " $andOr " . '`' . $field . "` > '$value 23:59:59'";
  265. }
  266. elseif(in_array($operator, array('include', 'notinclude')) && $control == 'select')
  267. {
  268. $where .= " $andOr CONCAT(',', `{$field}`, ',') {$condition}";
  269. }
  270. elseif($condition)
  271. {
  272. $where .= " $andOr " . '`' . $field . '` ' . $condition;
  273. }
  274. return $where;
  275. }
  276. /**
  277. * 替换日期和用户变量。
  278. * Replace dynamic account and date.
  279. *
  280. * @param string $query
  281. * @access public
  282. * @return string
  283. */
  284. public function replaceDynamic($query)
  285. {
  286. $this->app->loadClass('date');
  287. $lastWeek = date::getLastWeek();
  288. $thisWeek = date::getThisWeek();
  289. $lastMonth = date::getLastMonth();
  290. $thisMonth = date::getThisMonth();
  291. $yesterday = date::yesterday();
  292. $today = date(DT_DATE1);
  293. if(strpos($query, '$') !== false)
  294. {
  295. $query = str_replace('$@me', $this->app->user->account, $query);
  296. $query = str_replace("'\$lastMonth'", "'" . $lastMonth['begin'] . "' and '" . $lastMonth['end'] . "'", $query);
  297. $query = str_replace("'\$thisMonth'", "'" . $thisMonth['begin'] . "' and '" . $thisMonth['end'] . "'", $query);
  298. $query = str_replace("'\$lastWeek'", "'" . $lastWeek['begin'] . "' and '" . $lastWeek['end'] . "'", $query);
  299. $query = str_replace("'\$thisWeek'", "'" . $thisWeek['begin'] . "' and '" . $thisWeek['end'] . "'", $query);
  300. $query = str_replace("'\$yesterday'", "'" . $yesterday . ' 00:00:00' . "' and '" . $yesterday . ' 23:59:59' . "'", $query);
  301. $query = str_replace("'\$today'", "'" . $today . ' 00:00:00' . "' and '" . $today . ' 23:59:59' . "'", $query);
  302. }
  303. return $query;
  304. }
  305. /**
  306. * 如果搜索框的选项是users,products, executions, 获取相对应的列表。
  307. * Get user, product and execution value of the param.
  308. *
  309. * @param string $module
  310. * @param array $fields
  311. * @param array $params
  312. * @access protected
  313. * @return array
  314. */
  315. protected function getParamValues($module, $fields, $params)
  316. {
  317. $hasProduct = false;
  318. $hasExecution = false;
  319. $hasUser = false;
  320. foreach($fields as $fieldName)
  321. {
  322. if(empty($params[$fieldName])) continue;
  323. if($params[$fieldName]['values'] == 'products') $hasProduct = true;
  324. if($params[$fieldName]['values'] == 'users') $hasUser = true;
  325. if($params[$fieldName]['values'] == 'executions') $hasExecution = true;
  326. }
  327. /* 将用户的值追加到获取到的用户列表。*/
  328. $appendUsers = array();
  329. $formSessionName = $module . 'Form';
  330. if(isset($_SESSION[$formSessionName]))
  331. {
  332. for($i = 1; $i <= $this->config->search->groupItems; $i ++)
  333. {
  334. if(!isset($_SESSION[$formSessionName][$i - 1])) continue;
  335. $fieldName = $_SESSION[$formSessionName][$i - 1]['field'];
  336. if(isset($params[$fieldName]) and $params[$fieldName]['values'] == 'users')
  337. {
  338. if($_SESSION[$formSessionName][$i - 1]['value']) $appendUsers[] = $_SESSION[$formSessionName][$i - 1]['value'];
  339. }
  340. }
  341. }
  342. $users = $products = $executions = array();
  343. if($hasUser)
  344. {
  345. $users = $this->loadModel('user')->getPairs('realname|noclosed', $appendUsers);
  346. $users['$@me'] = $this->lang->search->me;
  347. }
  348. if($hasProduct) $products = $this->loadModel('product')->getPairs('', $this->session->project);
  349. if($hasExecution) $executions = $this->loadModel('execution')->getPairs($this->session->project);
  350. return array($users, $products, $executions);
  351. }
  352. /**
  353. * 获取 sql 语句的参数。
  354. * Get list sql params.
  355. *
  356. * @param string $keywords
  357. * @access protected
  358. * @return array
  359. */
  360. protected function getSqlParams($keywords)
  361. {
  362. $spliter = $this->app->loadClass('spliter');
  363. $words = explode(' ', $this->unify($keywords, ' '));
  364. $against = '';
  365. $againstCond = array();
  366. foreach($words as $word)
  367. {
  368. /* 将 utf-8 字符串拆分为单词,为每个单词计算 unicode. */
  369. $splitedWords = $spliter->utf8Split($word);
  370. $trimmedWord = trim($splitedWords['words']);
  371. $against .= '"' . $trimmedWord . '" ';
  372. if(in_array($this->config->db->driver, $this->config->pgsqlDriverList))
  373. {
  374. $splitWords = preg_split('/\s+/', trim($trimmedWord));
  375. $quotedWords = array_map(function($word) { return "''" . $word . "''"; }, $splitWords);
  376. $againstCond[] = implode(' <-> ', $quotedWords);
  377. }
  378. else
  379. {
  380. $againstCond[] = '(+"' . $trimmedWord . '")';
  381. }
  382. if(is_numeric($word) && strpos($word, '.') === false && strlen($word) == 5) $againstCond .= "(-\" $word \") ";
  383. }
  384. if(in_array($this->config->db->driver, $this->config->pgsqlDriverList))
  385. {
  386. $againstCond = implode(' | ', $againstCond);
  387. }
  388. else
  389. {
  390. $againstCond = implode(' ', $againstCond);
  391. }
  392. $likeCondition = trim($keywords) ? 'OR title LIKE ' . $this->dbh->quote("%{$keywords}%") . ' OR content LIKE ' . $this->dbh->quote("%{$keywords}%") : '';
  393. $words = str_replace('"', '', $against);
  394. $words = str_pad($words, 5, '_');
  395. return array($words, $againstCond, $likeCondition);
  396. }
  397. /**
  398. * 获取允许的模块。
  399. * Get allowed objects.
  400. *
  401. * @param string|array $type
  402. * @access protected
  403. * @return array
  404. */
  405. protected function getAllowedObjects($type)
  406. {
  407. $allowedObjects = array();
  408. if($type != 'all')
  409. {
  410. foreach($type as $module) $allowedObjects[] = $module;
  411. return $allowedObjects;
  412. }
  413. if($this->config->systemMode == 'light') unset($this->config->search->fields->program);
  414. foreach($this->config->search->fields as $objectType => $fields)
  415. {
  416. $module = $objectType;
  417. $method = 'view';
  418. if($module == 'case') $module = 'testcase';
  419. if($module == 'feedback') $method = 'adminView';
  420. if(common::hasPriv($module, $method)) $allowedObjects[] = $objectType;
  421. if($module == 'deploystep' && common::haspriv('deploy', 'viewstep')) $allowedobjects[] = $objectType;
  422. }
  423. return $allowedObjects;
  424. }
  425. /**
  426. * 检查产品的权限。
  427. * Check product prividge.
  428. *
  429. * @param array $results
  430. * @param array $objectIdList
  431. * @param string $products
  432. * @access private
  433. * @return array
  434. */
  435. private function checkProductPriv($results, $objectIdList, $products)
  436. {
  437. $shadowProducts = $this->dao->select('id')->from(TABLE_PRODUCT)->where('shadow')->eq(1)->fetchPairs('id');
  438. foreach($objectIdList as $productID => $recordID)
  439. {
  440. if(strpos(",$products,", ",$productID,") === false) unset($results[$recordID]);
  441. if(in_array($productID, $shadowProducts)) unset($results[$recordID]);
  442. }
  443. return $results;
  444. }
  445. /**
  446. * 检查项目集的权限。
  447. * Check program priviledge.
  448. *
  449. * @param array $results
  450. * @param array $objectIdList
  451. * @access private
  452. * @return array
  453. */
  454. private function checkProgramPriv($results, $objectIdList)
  455. {
  456. $programs = $this->app->user->view->programs;
  457. foreach($objectIdList as $programID => $recordID)
  458. {
  459. if(strpos(",$programs,", ",$programID,") === false) unset($results[$recordID]);
  460. }
  461. return $results;
  462. }
  463. /**
  464. * 检查项目的权限。
  465. * Check project priviledge.
  466. *
  467. * @param array $results
  468. * @param array $objectIdList
  469. * @access private
  470. * @return array
  471. */
  472. private function checkProjectPriv($results, $objectIdList)
  473. {
  474. $projects = $this->app->user->view->projects;
  475. foreach($objectIdList as $projectID => $recordID)
  476. {
  477. if(strpos(",$projects,", ",$projectID,") === false) unset($results[$recordID]);
  478. }
  479. return $results;
  480. }
  481. /**
  482. * 检查执行的权限。
  483. * Check execution priviledge.
  484. *
  485. * @param array $results
  486. * @param array $objectIdList
  487. * @param string $executions
  488. * @access private
  489. * @return array
  490. */
  491. private function checkExecutionPriv($results, $objectIdList, $executions)
  492. {
  493. foreach($objectIdList as $executionID => $recordID)
  494. {
  495. if(strpos(",$executions,", ",$executionID,") === false) unset($results[$recordID]);
  496. }
  497. return $results;
  498. }
  499. /**
  500. * 检查文档的权限。
  501. * Check doc priviledge.
  502. *
  503. * @param array $results
  504. * @param array $objectIdList
  505. * @param string $table
  506. * @access private
  507. * @return array
  508. */
  509. private function checkDocPriv($results, $objectIdList, $table)
  510. {
  511. $this->loadModel('doc');
  512. $objectDocs = $this->dao->select('*')->from($table)->where('id')->in(array_keys($objectIdList))->andWhere('deleted')->eq('0')->fetchAll('id');
  513. $privLibs = array();
  514. foreach($objectIdList as $docID => $recordID)
  515. {
  516. if(!isset($objectDocs[$docID]) || !$this->doc->checkPrivDoc($objectDocs[$docID]))
  517. {
  518. unset($results[$recordID]);
  519. continue;
  520. }
  521. $objectDoc = $objectDocs[$docID];
  522. $privLibs[$objectDoc->lib] = $objectDoc->lib;
  523. }
  524. $libs = $this->doc->getLibs('all');
  525. $objectDocLibs = $this->dao->select('id')->from(TABLE_DOCLIB)->where('id')->in($privLibs)->andWhere('id')->in(array_keys($libs))->andWhere('deleted')->eq('0')->fetchPairs();
  526. foreach($objectDocs as $docID => $doc)
  527. {
  528. if(!isset($objectDocLibs[$doc->lib]))
  529. {
  530. $recordID = $objectIdList[$docID];
  531. unset($results[$recordID]);
  532. }
  533. }
  534. return $results;
  535. }
  536. /**
  537. * 检查待办的权限。
  538. * Check todo priviledge.
  539. *
  540. * @param array $results
  541. * @param array $objectIdList
  542. * @param string $table
  543. * @access private
  544. * @return array
  545. */
  546. private function checkTodoPriv($results, $objectIdList, $table)
  547. {
  548. $objectTodos = $this->dao->select('id')->from($table)->where('id')->in(array_keys($objectIdList))->andWhere("private")->eq(1)->andWhere('account')->ne($this->app->user->account)->fetchPairs();
  549. foreach($objectTodos as $todoID)
  550. {
  551. if(isset($objectIdList[$todoID]))
  552. {
  553. $recordID = $objectIdList[$todoID];
  554. unset($results[$recordID]);
  555. }
  556. }
  557. return $results;
  558. }
  559. /**
  560. * 检查套件的权限。
  561. * Check testsuite Priviledge.
  562. *
  563. * @param array $results
  564. * @param array $objectIdList
  565. * @param string $table
  566. * @access private
  567. * @return array
  568. */
  569. private function checkTestsuitePriv($results, $objectIdList, $table)
  570. {
  571. $objectSuites = $this->dao->select('id')->from($table)->where('id')->in(array_keys($objectIdList))->andWhere('type')->eq('private')->andWhere('deleted')->eq('0')->fetchPairs();
  572. foreach($objectSuites as $suiteID)
  573. {
  574. if(isset($objectIdList[$suiteID]))
  575. {
  576. $recordID = $objectIdList[$suiteID];
  577. unset($results[$recordID]);
  578. }
  579. }
  580. return $results;
  581. }
  582. /**
  583. * 检查反馈和工单的权限。
  584. * Check feedback and ticket priviledge.
  585. *
  586. * @param string $objectType
  587. * @param array $results
  588. * @param array $objectIdList
  589. * @param string $table
  590. * @access private
  591. * @return array
  592. */
  593. private function checkFeedbackAndTicketPriv($objectType, $results, $objectIdList, $table)
  594. {
  595. $grantProducts = $this->loadModel('feedback')->getGrantProducts();
  596. $objects = $this->dao->select('*')->from($table)->where('id')->in(array_keys($objectIdList))->fetchAll('id');
  597. foreach($objects as $objectID => $object)
  598. {
  599. if($objectType == 'feedback' && $object->openedBy == $this->app->user->account) continue;
  600. if(isset($grantProducts[$object->product])) continue;
  601. if(isset($objectIdList[$objectID]))
  602. {
  603. $recordID = $objectIdList[$objectID];
  604. unset($results[$recordID]);
  605. }
  606. }
  607. return $results;
  608. }
  609. /**
  610. * 检查搜索到的模块的权限。
  611. * Check the priviledge of the object.
  612. *
  613. * @param string $objectType
  614. * @param string $table
  615. * @param array $results
  616. * @param array $objectIdList
  617. * @param string $products
  618. * @param string $executions
  619. * @access private
  620. * @return array
  621. */
  622. private function checkObjectPriv($objectType, $table, $results, $objectIdList, $products, $executions)
  623. {
  624. if($objectType == 'product') return $this->checkProductPriv($results, $objectIdList, $products);
  625. if($objectType == 'program') return $this->checkProgramPriv($results, $objectIdList);
  626. if($objectType == 'project') return $this->checkProjectPriv($results, $objectIdList);
  627. if($objectType == 'execution') return $this->checkExecutionPriv($results, $objectIdList, $executions);
  628. if($objectType == 'doc') return $this->checkDocPriv($results, $objectIdList, $table);
  629. if($objectType == 'todo') return $this->checkTodoPriv($results, $objectIdList, $table);
  630. if($objectType == 'testsuite') return $this->checkTestsuitePriv($results, $objectIdList, $table);
  631. if(strpos(',feedback,ticket,', ",$objectType,") !== false) return $this->checkFeedbackAndTicketPriv($objectType, $results, $objectIdList, $table);
  632. return $results;
  633. }
  634. /**
  635. * 检查各个模块所属的产品或者执行的权限。
  636. * Check related object priviledge.
  637. *
  638. * @param string $objectType
  639. * @param string $table
  640. * @param array $results
  641. * @param array $objectIdList
  642. * @param string $products
  643. * @param string $executions
  644. * @access private
  645. * @return array
  646. */
  647. private function checkRelatedObjectPriv($objectType, $table, $results, $objectIdList, $products, $executions)
  648. {
  649. $objectProducts = array();
  650. $objectExecutions = array();
  651. if(strpos(',bug,case,testcase,productplan,release,story,testtask,', ",$objectType,") !== false)
  652. {
  653. $objectProducts = $this->dao->select('id, product')->from($table)->where('id')->in(array_keys($objectIdList))->fetchGroup('product', 'id');
  654. }
  655. elseif(strpos(',build,task,testreport,', ",$objectType,") !== false)
  656. {
  657. $objectExecutions = $this->dao->select('id, execution')->from($table)->where('id')->in(array_keys($objectIdList))->fetchGroup('execution', 'id');
  658. }
  659. elseif($objectType == 'effort')
  660. {
  661. $efforts = $this->dao->select('id, product, execution')->from($table)->where('id')->in(array_keys($objectIdList))->fetchAll();
  662. foreach($efforts as $effort)
  663. {
  664. $objectExecutions[$effort->execution][$effort->id] = $effort;
  665. $effortProducts = explode(',', trim($effort->product, ','));
  666. foreach($effortProducts as $effortProduct) $objectProducts[$effortProduct][$effort->id] = $effort;
  667. }
  668. }
  669. foreach($objectProducts as $productID => $idList)
  670. {
  671. if(empty($productID)) continue;
  672. if(strpos(",$products,", ",$productID,") === false)
  673. {
  674. foreach($idList as $object)
  675. {
  676. $recordID = $objectIdList[$object->id];
  677. unset($results[$recordID]);
  678. }
  679. }
  680. }
  681. foreach($objectExecutions as $executionID => $idList)
  682. {
  683. if(empty($executionID)) continue;
  684. if(strpos(",$executions,", ",$executionID,") === false)
  685. {
  686. foreach($idList as $object)
  687. {
  688. $recordID = $objectIdList[$object->id];
  689. unset($results[$recordID]);
  690. }
  691. }
  692. }
  693. return $results;
  694. }
  695. /**
  696. * 将搜索到的结果分页。
  697. * Set results in pages.
  698. *
  699. * @param array $results
  700. * @param object $pager
  701. * @access protected
  702. * @return array
  703. */
  704. protected function setResultsInPage($results, $pager)
  705. {
  706. $pager->setRecTotal(count($results));
  707. $pager->setPageTotal();
  708. $pager->setPageID($pager->pageID);
  709. $results = array_chunk($results, $pager->recPerPage, true);
  710. return $results[$pager->pageID - 1];
  711. }
  712. /**
  713. * 获取搜索到的结果对应的内容。
  714. * Get object list.
  715. *
  716. * @param array $idListGroup
  717. * @access protected
  718. * @return array
  719. */
  720. protected function getObjectList($idListGroup)
  721. {
  722. $objectList = array();
  723. foreach($idListGroup as $module => $idList)
  724. {
  725. if(!isset($this->config->objectTables[$module])) continue;
  726. $fields = '';
  727. if($module == 'issue') $fields = ($this->config->edition == 'max' || $this->config->edition == 'ipd') ? 'id,project,owner,lib' : 'id,project,owner';
  728. if($module == 'project') $fields = 'id,model';
  729. if($module == 'execution') $fields = 'id,type,project';
  730. if(in_array($module, array('story', 'requirement', 'epic'))) $fields = ($this->config->edition == 'max' || $this->config->edition == 'ipd') ? 'id,type,lib' : 'id,type';
  731. if(($module == 'risk' || $module == 'opportunity') && ($this->config->edition == 'max' || $this->config->edition == 'ipd')) $fields = 'id,lib';
  732. if($module == 'doc' && ($this->config->edition == 'max' || $this->config->edition == 'ipd')) $fields = 'id,assetLib,assetLibType';
  733. if(empty($fields)) continue;
  734. $table = $this->config->objectTables[$module];
  735. $objectList[$module] = $this->dao->select($fields)->from($table)->where('id')->in($idList)->filterTpl('skip')->fetchAll('id');
  736. }
  737. return $objectList;
  738. }
  739. /**
  740. * 检查查询到的结果的权限。
  741. * Check product and project priv.
  742. *
  743. * @param array $results
  744. * @param array $objectPairs
  745. * @access protected
  746. * @return array
  747. */
  748. protected function checkPriv($results, $objectPairs = array())
  749. {
  750. if($this->app->user->admin) return $results;
  751. $products = $this->app->user->view->products;
  752. $executions = $this->app->user->view->sprints;
  753. if(empty($objectPairs)) foreach($results as $record) $objectPairs[$record->objectType][$record->objectID] = $record->id;
  754. foreach($objectPairs as $objectType => $objectIdList)
  755. {
  756. if(!isset($this->config->objectTables[$objectType])) continue;
  757. $table = $this->config->objectTables[$objectType];
  758. $results = $this->checkObjectPriv($objectType, $table, $results, $objectIdList, $products, $executions);
  759. $results = $this->checkRelatedObjectPriv($objectType, $table, $results, $objectIdList, $products, $executions);
  760. }
  761. return $results;
  762. }
  763. /**
  764. * 处理搜索结果。
  765. * Process results.
  766. *
  767. * @param array $results
  768. * @param array $objectList
  769. * @param string $words
  770. * @access protected
  771. * @return array
  772. */
  773. protected function processResults($results, $objectList, $words)
  774. {
  775. foreach($results as $record)
  776. {
  777. $record->title = str_replace('</span> ', '</span>', $this->decode($this->markKeywords($record->title, $words)));
  778. $record->title = str_replace('_', '', $record->title);
  779. $record->summary = str_replace('</span> ', '</span>', $this->getSummary($record->content, $words));
  780. $record->summary = str_replace('_', '', $record->summary);
  781. $record = $this->processRecord($record, $objectList);
  782. }
  783. return $results;
  784. }
  785. /**
  786. * 将 unicode 转换为对应的字。
  787. * Transfer unicode to words.
  788. *
  789. * @param string $string
  790. * @access private
  791. * @return string
  792. */
  793. private function decode($string)
  794. {
  795. static $dict;
  796. if(empty($dict))
  797. {
  798. $dict = $this->dao->select("concat(`key`, ' ') AS `key`, value")->from(TABLE_SEARCHDICT)->fetchPairs();
  799. $dict['|'] = '';
  800. }
  801. if(strpos($string, ' ') === false) return zget($dict, $string . ' ');
  802. return trim(str_replace(array_keys($dict), array_values($dict), $string . ' '));
  803. }
  804. /**
  805. * 获取结果显示的摘要。
  806. * Get summary of results.
  807. *
  808. * @param string $content
  809. * @param string $words
  810. * @access private
  811. * @return string
  812. */
  813. private function getSummary($content, $words)
  814. {
  815. $length = $this->config->search->summaryLength;
  816. if(strlen($content) <= $length) return $this->decode($this->markKeywords($content, $words));
  817. $content = $this->markKeywords($content, $words);
  818. preg_match_all("/\<span class='text-danger'\>.*\<\/span\>/U", $content, $matches);
  819. if(empty($matches[0])) return $this->decode($this->markKeywords(substr($content, 0, $length), $words));
  820. $matches = $matches[0];
  821. $score = 0;
  822. $needle = '';
  823. foreach($matches as $matched)
  824. {
  825. if(strlen($matched) > $score)
  826. {
  827. $content = str_replace($needle, strip_tags($needle), $content);
  828. $needle = $matched;
  829. $score = strlen($matched);
  830. }
  831. }
  832. $content = str_replace('<span class', ' <spanclass', $content);
  833. $content = explode(' ', $content);
  834. $pos = array_search(str_replace('<span class', '<spanclass', $needle), $content);
  835. $start = max(0, $pos - ($length / 2));
  836. $summary = join(' ', array_slice($content, $start, $length));
  837. $summary = str_replace(' <spanclass', '<span class', $summary);
  838. return $this->decode($summary);
  839. }
  840. /**
  841. * 在文中标记关键词。
  842. * Mark keywords in content.
  843. *
  844. * @param string $content
  845. * @param string $keywords
  846. * @access private
  847. * @return string
  848. */
  849. private function markKeywords($content, $keywords)
  850. {
  851. $words = explode(' ', trim($keywords, ' '));
  852. $leftMark = '|0000';
  853. $rightMark = '0000|';
  854. $markedWords = array();
  855. foreach($words as $key => $word)
  856. {
  857. if(preg_match('/^\|[0-9]+\|$/', $word))
  858. {
  859. $words[$key] = trim($word, '|');
  860. }
  861. elseif(is_numeric($word))
  862. {
  863. $words[$key] = $word . ' ';
  864. }
  865. else
  866. {
  867. $words[$key] = strlen($word) == 5 ? str_replace('_', '', $word) : $word;
  868. }
  869. $markedWords[] = $leftMark . $this->decode($word) . $rightMark;
  870. }
  871. $content = str_replace($words, $markedWords, $content . ' ');
  872. $content = str_replace(array($leftMark, $rightMark), array("<span class='text-danger'>", "</span > "), $content);
  873. $content = str_replace("</span > <span class='text-danger'>", '', $content);
  874. $content = str_replace("</span >", '</span>', $content);
  875. return $content;
  876. }
  877. /**
  878. * 处理搜索的记录。
  879. * Process record.
  880. *
  881. * @param object $record
  882. * @param array $objectList
  883. * @access private
  884. * @return object
  885. */
  886. private function processRecord($record, $objectList)
  887. {
  888. $module = $record->objectType == 'case' ? 'testcase' : $record->objectType;
  889. $method = 'view';
  890. if($module == 'deploystep')
  891. {
  892. $module = 'deploy';
  893. $method = 'viewstep';
  894. }
  895. $linkProjectModules = ',task,bug,testcase,build,release,testtask,testsuite,testreport,trainplan,';
  896. if(strpos($linkProjectModules, ",$module,") !== false && !isset($this->config->objectTables[$record->objectType])) return $record;
  897. if($module == 'issue') return $this->processIssueRecord($record, $objectList);
  898. if($module == 'project') return $this->processProjectRecord($record, $objectList);
  899. if($module == 'execution') return $this->processExecutionRecord($record, $objectList);
  900. if($module == 'task') return $this->processTaskRecord($record);
  901. if(in_array($module, array('story', 'requirement', 'epic'))) return $this->processStoryRecord($record, $module, $objectList);
  902. if(($module == 'risk' || $module == 'opportunity') && ($this->config->edition == 'max' || $this->config->edition == 'ipd')) return $this->processRiskRecord($record, $module, $objectList);
  903. if($module == 'doc' && ($this->config->edition == 'max' || $this->config->edition == 'ipd')) return $this->processDocRecord($record, $objectList);
  904. $record->url = helper::createLink($module, $method, "id={$record->objectID}");
  905. return $record;
  906. }
  907. /**
  908. * 处理搜索到的问题记录的链接和类型。
  909. * Process issue record url and extra type.
  910. *
  911. * @param object $record
  912. * @param array $objectList
  913. * @access private
  914. * @return object
  915. */
  916. private function processIssueRecord($record, $objectList)
  917. {
  918. $issue = $objectList['issue'][$record->objectID];
  919. $module = !empty($issue->lib) ? 'assetlib' : 'issue';
  920. $method = !empty($issue->lib) ? 'issueView' : 'view';
  921. $record->url = helper::createLink($module, $method, "id={$record->objectID}", '', false, $issue->project);
  922. $record->extraType = empty($issue->owner) ? 'commonIssue' : 'stakeholderIssue';
  923. return $record;
  924. }
  925. /**
  926. * 处理搜索到的项目的链接。
  927. * processProjectRecord
  928. *
  929. * @param object $record
  930. * @param array $objectList
  931. * @access private
  932. * @return object
  933. */
  934. private function processProjectRecord($record, $objectList)
  935. {
  936. $projectModel = $objectList['project'][$record->objectID]->model;
  937. $method = $projectModel == 'kanban' ? 'index' : 'view';
  938. $record->url = helper::createLink('project', $method, "id={$record->objectID}");
  939. return $record;
  940. }
  941. /**
  942. * 处理执行记录的链接和类型。
  943. * Process execution record url and extra type.
  944. *
  945. * @param object $record
  946. * @param array $objectList
  947. * @access private
  948. * @return object
  949. */
  950. private function processExecutionRecord($record, $objectList)
  951. {
  952. $execution = $objectList['execution'][$record->objectID];
  953. $method = $execution->type == 'kanban' ? 'kanban' : 'view';
  954. $record->url = helper::createLink('execution', $method, "id={$record->objectID}");
  955. $record->extraType = empty($execution->type) ? '' : $execution->type;
  956. return $record;
  957. }
  958. /**
  959. * 处理执行记录的链接和类型。
  960. * Process task record url and extra type.
  961. *
  962. * @param object $record
  963. * @access private
  964. * @return object
  965. */
  966. private function processTaskRecord($record)
  967. {
  968. $task = $this->dao->findById($record->objectID)->from(TABLE_TASK)->fetch();
  969. $project = $this->dao->findById($task->project)->from(TABLE_PROJECT)->fetch();
  970. $record->url = helper::createLink('task', 'view', "id={$record->objectID}");
  971. if(!$project->multiple) $record->dataApp = 'project';
  972. return $record;
  973. }
  974. /**
  975. * 处理需求记录的链接。
  976. * Process story record url.
  977. *
  978. * @param object $record
  979. * @param string $module
  980. * @param array $objectList
  981. * @access private
  982. * @return object
  983. */
  984. private function processStoryRecord($record, $module, $objectList)
  985. {
  986. $story = zget($objectList[$module], $record->objectID, null);
  987. if(empty($story))
  988. {
  989. $record->url = '';
  990. return $record;
  991. }
  992. $module = 'story';
  993. $method = 'storyView';
  994. if(!empty($story->lib))
  995. {
  996. $module = 'assetlib';
  997. $method = 'storyView';
  998. }
  999. $record->url = helper::createLink($module, $method, "id={$record->objectID}", '', false, 0, true);
  1000. if($this->config->vision == 'lite') $record->url = helper::createLink('projectstory', $method, "storyID={$record->objectID}", '', false, 0, true);
  1001. $record->extraType = zget($story, 'type', '');
  1002. return $record;
  1003. }
  1004. /**
  1005. * 处理文档记录的链接。
  1006. * Process doc record url.
  1007. *
  1008. * @param object $record
  1009. * @param array $objectList
  1010. * @access private
  1011. * @return object
  1012. */
  1013. private function processDocRecord($record, $objectList)
  1014. {
  1015. $doc = $objectList['doc'][$record->objectID];
  1016. $module = 'doc';
  1017. $method = 'view';
  1018. if(!empty($doc->assetLib))
  1019. {
  1020. $module = 'assetlib';
  1021. $method = $doc->assetLibType == 'practice' ? 'practiceView' : 'componentView';
  1022. }
  1023. $record->url = helper::createLink($module, $method, "id={$record->objectID}", '', false, 0, true);
  1024. return $record;
  1025. }
  1026. /**
  1027. * 处理风险记录的链接。
  1028. * Process risk record url.
  1029. *
  1030. * @param object $record
  1031. * @param string $module
  1032. * @param array $objectList
  1033. * @access private
  1034. * @return object
  1035. */
  1036. private function processRiskRecord($record, $module, $objectList)
  1037. {
  1038. $object = $objectList[$module][$record->objectID];
  1039. $method = 'view';
  1040. if(!empty($object->lib))
  1041. {
  1042. $method = $module == 'risk' ? 'riskView' : 'opportunityView';
  1043. $module = 'assetlib';
  1044. }
  1045. $record->url = helper::createLink($module, $method, "id={$record->objectID}", '', false, 0, true);
  1046. return $record;
  1047. }
  1048. /**
  1049. * Process data list for saving index.
  1050. * 处理要保存的索引数据。
  1051. *
  1052. * @param string $module
  1053. * @param object $field
  1054. * @param array $dataList
  1055. * @access protected
  1056. * @return array
  1057. */
  1058. protected function processDataList($module, $field, $dataList)
  1059. {
  1060. if($module == 'case') $caseStep = $this->dao->select('*')->from(TABLE_CASESTEP)->where('`case`')->in(array_keys($dataList))->fetchGroup('case', 'id');
  1061. $actions = $this->dao->select('*')->from(TABLE_ACTION)->where('objectType')->eq($module)->andWhere('objectID')->in(array_keys($dataList))->orderBy('date asc')->fetchGroup('objectID', 'id');
  1062. $files = $this->dao->select('id,objectID,title,extension')->from(TABLE_FILE)->where('objectType')->eq($module)->andWhere('objectID')->in(array_keys($dataList))->orderBy('id asc')->fetchGroup('objectID', 'id');
  1063. foreach($dataList as $id => $data)
  1064. {
  1065. $data->comment = '';
  1066. if(isset($actions[$id]))
  1067. {
  1068. foreach($actions[$id] as $action)
  1069. {
  1070. if($action->action == 'opened') $data->{$field->addedDate} = $action->date;
  1071. $data->{$field->editedDate} = $action->date;
  1072. if(!empty($action->comment)) $data->comment .= $action->comment . "\n";
  1073. }
  1074. }
  1075. if(isset($files[$id]))
  1076. {
  1077. foreach($files[$id] as $file)
  1078. {
  1079. if(!empty($file->title)) $data->comment .= $file->title . '.' . $file->extension . "\n";
  1080. }
  1081. }
  1082. if($module == 'case')
  1083. {
  1084. $data->desc = '';
  1085. $data->expect = '';
  1086. if(isset($caseStep[$id]))
  1087. {
  1088. foreach($caseStep[$id] as $step)
  1089. {
  1090. if($step->version != $data->version) continue;
  1091. $data->desc .= $step->desc . "\n";
  1092. $data->expect .= $step->expect . "\n";
  1093. }
  1094. }
  1095. }
  1096. }
  1097. return $dataList;
  1098. }
  1099. /**
  1100. * 将特殊符号替换成统一的符号。
  1101. * Unified processing of search keywords.
  1102. *
  1103. * @param string $string
  1104. * @param string $to
  1105. * @access private
  1106. * @return string
  1107. */
  1108. private static function unify($string, $to = ',')
  1109. {
  1110. $labels = array('_', '、', ' ', '-', '\n', '?', '@', '&', '%', '~', '`', '+', '*', '/', '\\', '。', ',');
  1111. $string = str_replace($labels, $to, $string);
  1112. return preg_replace("/[{$to}]+/", $to, trim($string, $to));
  1113. }
  1114. /**
  1115. * 将文档的内容追加到文档的索引备注中。
  1116. * Append document content to the document.
  1117. *
  1118. * @param object $object
  1119. * @access protected
  1120. * @return object
  1121. */
  1122. protected function appendFiles($object)
  1123. {
  1124. $docFiles = $this->dao->select('files')->from(TABLE_DOCCONTENT)->where('doc')->eq($object->id)->orderBy('version')->limit(1)->fetch('files');
  1125. if(empty($docFiles)) return $object;
  1126. if(!isset($object->comment)) $object->comment = '';
  1127. $allDocFiles = $this->loadModel('file')->getByObject('doc', $object->id);
  1128. foreach($allDocFiles as $file)
  1129. {
  1130. if(strpos(",$docFiles,", ",{$file->id},") === false) continue;
  1131. if(strpos('docx|doc', $file->extension) !== false)
  1132. {
  1133. $convertedFile = $this->file->convertOffice($file, 'txt');
  1134. if($convertedFile) $object->comment .= substr(file_get_contents($convertedFile), 0, $this->config->search->maxFileSize);
  1135. }
  1136. if($file->extension == 'txt' && file_exists($file->realPath)) $object->comment .= substr(file_get_contents($file->realPath), 0, $this->config->search->maxFileSize);
  1137. }
  1138. $object->comment = htmlspecialchars($object->comment);
  1139. return $object;
  1140. }
  1141. }