| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- <?php
- /**
- * The model file of search module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Chunsheng Wang <chunsheng@cnezsoft.com>
- * @package search
- * @version $Id: model.php 5082 2013-07-10 01:14:45Z wyd621@gmail.com $
- * @link https://www.zentao.net
- */
- ?>
- <?php
- class searchModel extends model
- {
- /**
- * 构造搜索表单时调用被搜索模块的方法处理搜索参数。
- * Call the method of the searched module to process search parameters when constructing the search form.
- *
- * @param string $module
- * @param bool $cacheSearchFunc // 是否缓存构造搜索参数的方法。默认不缓存以加载真实值。Wheater to cache the method of constructing search parameters. Default is not to cache to load real values.
- * @access public
- * @return array
- */
- public function processSearchParams($module, $cacheSearchFunc = false)
- {
- $cacheKey = $module . 'SearchFunc';
- $funcModel = $this->session->$cacheKey['funcModel'] ?? '';
- $funcName = $this->session->$cacheKey['funcName'] ?? '';
- $funcArgs = $this->session->$cacheKey['funcArgs'] ?? [];
- if(!$funcModel || !$funcName || !$funcArgs) return $_SESSION[$module . 'searchParams'] ?? [];
- $funcArgs['cacheSearchFunc'] = $cacheSearchFunc;
- return $this->loadModel($funcModel)->$funcName(...array_values($funcArgs)); // PHP 8.0以下只能展开索引数组。PHP 8.0 below can only unpack indexed arrays.
- }
- /**
- * 设置搜索参数的session。
- * Set search params to session.
- *
- * @param array $searchConfig
- * @access public
- * @return void
- */
- public function setSearchParams(&$searchConfig)
- {
- $module = $searchConfig['module'];
- if($this->config->edition != 'open') $searchConfig = $this->searchTao->processBuildinFields($module, $searchConfig);
- $searchParams['module'] = $module;
- $searchParams['actionURL'] = $searchConfig['actionURL'];
- $searchParams['style'] = zget($searchConfig, 'style', 'full');
- $searchParams['onMenuBar'] = zget($searchConfig, 'onMenuBar', 'no');
- $searchParams['queryID'] = isset($searchConfig['queryID']) ? $searchConfig['queryID'] : 0;
- if(empty($_SESSION[$module . 'SearchFunc']))
- {
- $searchParams['fields'] = $searchConfig['fields'];
- $searchParams['params'] = $searchConfig['params'];
- }
- $this->session->set($module . 'searchParams', $searchParams);
- }
- /**
- * 设置默认的搜索参数。
- * Set default params for selection.
- *
- * @param string $module
- * @param array $fields
- * @param array $params
- * @access public
- * @return array
- */
- public function setDefaultParams($module, $fields, $params)
- {
- $fields = array_keys($fields);
- list($users, $products, $executions) = $this->getParamValues($module, $fields, $params);
- foreach($fields as $fieldName)
- {
- if(!isset($params[$fieldName])) $params[$fieldName] = array('operator' => '=', 'control' => 'input', 'values' => '');
- if($params[$fieldName]['values'] == 'users') $params[$fieldName]['values'] = $users;
- if($params[$fieldName]['values'] == 'products') $params[$fieldName]['values'] = $products;
- if($params[$fieldName]['values'] == 'executions') $params[$fieldName]['values'] = $executions;
- /* 处理数组。*/
- /* Process array value. */
- if(is_array($params[$fieldName]['values']))
- {
- /* For build right sql when key is 0 and is not null. e.g. confirmed field. */
- if(isset($params[$fieldName]['values'][0]) and $params[$fieldName]['values'][0] !== '')
- {
- $params[$fieldName]['values'] = array('ZERO' => $params[$fieldName]['values'][0]) + $params[$fieldName]['values'];
- unset($params[$fieldName]['values'][0]);
- }
- elseif(empty($params[$fieldName]['values']))
- {
- $params[$fieldName]['values'] = array('' => '', 'null' => $this->lang->search->null);
- }
- elseif(empty($params[$fieldName]['nonull']))
- {
- $params[$fieldName]['values'] = $params[$fieldName]['values'] + array('null' => $this->lang->search->null);
- }
- }
- }
- return $params;
- }
- /**
- * 构建查询语句。
- * Build the query to execute.
- *
- * @access public
- * @return void
- */
- public function buildQuery()
- {
- /* Init vars. */
- $module = $this->post->module;
- $searchConfig = $this->processSearchParams($module, true);
- $searchFields = $searchConfig['fields'] ?? [] ;
- $fieldParams = $searchConfig['params'] ?? [];
- $groupItems = $this->config->search->groupItems;
- $groupAndOr = strtoupper($this->post->groupAndOr);
- if($groupAndOr != 'AND' && $groupAndOr != 'OR') $groupAndOr = 'AND';
- $queryForm = $this->searchTao->initSession($module, $searchFields, $fieldParams);
- $scoreNum = 0;
- $where = '';
- for($i = 1; $i <= $groupItems * 2; $i ++)
- {
- /* The and or between two groups. */
- $formIndex = $i - 1;
- if($i == 1) $where .= '(( 1 = 1 ';
- if($i == $groupItems + 1) $where .= " ) $groupAndOr ( 1 = 1 ";
- /* Set var names. */
- $fieldName = "field$i";
- $andOrName = "andOr$i";
- $operatorName = "operator$i";
- $valueName = "value$i";
- $field = $this->post->$fieldName;
- $value = $this->post->$valueName;
- $fieldControl = $fieldParams[$field]['control'] ?? '';
- if(empty($field) || $value === '' || $value === false) continue; // false means no exist this post item. '' means no search data. ignore it.
- if(!preg_match('/^[a-zA-Z0-9]+$/', $field)) continue; // Fix sql injection.
- /* 如果是输入框,并且输入框的值为'0',或者 id 的值为'0',将值设置为zero。*/
- if($fieldControl == 'input' && $value === '0') $this->post->set($valueName, 'ZERO');
- if($field == 'id' && $value === '0') $this->post->set($valueName, 'ZERO');
- /* set queryForm. */
- list($andOr, $operator, $value) = $this->searchTao->processQueryFormDatas($fieldParams, $field, $andOrName, $operatorName, $valueName);
- $queryForm[$formIndex] = array('field' => $field, 'andOr' => strtolower($andOr), 'operator' => $operator, 'value' => $value);
- /* Set where. */
- $where = $this->searchTao->setWhere($where, $field, $operator, $value, $andOr, $fieldControl);
- $scoreNum += 1;
- }
- foreach($queryForm as $index => $queryField)
- {
- if(!empty($queryForm[$index]['groupAndOr'])) $queryForm[$index]['groupAndOr'] = strtolower($groupAndOr);
- }
- $where .=" ))";
- $where = $this->searchTao->replaceDynamic($where);
- /* Save to session. */
- $querySessionName = $this->post->module . 'Query';
- $formSessionName = $this->post->module . 'Form';
- $this->session->set($querySessionName, $where);
- $this->session->set($formSessionName, $queryForm);
- if($scoreNum > 2 && !dao::isError()) $this->loadModel('score')->create('search', 'saveQueryAdvanced');
- }
- /**
- * Build the query to execute.
- *
- * @access public
- * @return void
- */
- public function buildOldQuery()
- {
- /* Init vars. */
- $where = '';
- $groupItems = $this->config->search->groupItems;
- $groupAndOr = strtoupper($this->post->groupAndOr);
- $module = $this->post->module;
- $searchConfig = $this->processSearchParams($module, true);
- $fieldParams = $searchConfig['params'] ?? [];
- $scoreNum = 0;
- if($groupAndOr != 'AND' and $groupAndOr != 'OR') $groupAndOr = 'AND';
- for($i = 1; $i <= $groupItems * 2; $i ++)
- {
- /* The and or between two groups. */
- if($i == 1) $where .= '(( 1 = 1 ';
- if($i == $groupItems + 1) $where .= " ) $groupAndOr ( 1 = 1 ";
- /* Set var names. */
- $fieldName = "field$i";
- $andOrName = "andOr$i";
- $operatorName = "operator$i";
- $valueName = "value$i";
- /* Fix bug #2704. */
- $field = $this->post->$fieldName;
- if(!preg_match('/^[a-zA-Z0-9]+$/', $field)) continue; // Fix sql injection.
- $fieldControl = $fieldParams[$field]['control'] ?? '';
- if($fieldControl == 'input' and $this->post->$valueName === '0') $this->post->set($valueName, 'ZERO');
- if($field == 'id' and $this->post->$valueName === '0') $this->post->set($valueName, 'ZERO');
- /* Skip empty values. */
- if($this->post->$valueName == false) continue;
- if($this->post->$valueName == 'ZERO') $this->post->$valueName = 0; // ZERO is special, stands to 0.
- if($fieldControl == 'select' and $this->post->$valueName === 'null') $this->post->set($valueName, ''); // Null is special, stands to empty if control is select. Fix bug #3279.
- $scoreNum += 1;
- /* Set and or. */
- $andOr = strtoupper($this->post->$andOrName);
- if($andOr != 'AND' and $andOr != 'OR') $andOr = 'AND';
- /* Set operator. */
- $value = addcslashes(trim($this->post->$valueName), '%');
- $operator = $this->post->$operatorName;
- if(!isset($this->lang->search->operators[$operator])) $operator = '=';
- /* Set condition. */
- $condition = '';
- if($operator == "include")
- {
- if($this->post->$fieldName == 'module')
- {
- $allModules = $this->loadModel('tree')->getAllChildId($value);
- if($allModules) $condition = helper::dbIN($allModules);
- }
- else
- {
- $condition = $fieldControl == 'select' ? " LIKE CONCAT('%,', '{$value}', ',%')" : ' LIKE ' . $this->dbh->quote("%$value%");
- }
- }
- elseif($operator == "notinclude")
- {
- if($this->post->$fieldName == 'module')
- {
- $allModules = $this->loadModel('tree')->getAllChildId($value);
- if($allModules) $condition = " NOT " . helper::dbIN($allModules);
- }
- else
- {
- $condition = $fieldControl == 'select' ? " NOT LIKE CONCAT('%,', '{$value}', ',%')" : ' NOT LIKE ' . $this->dbh->quote("%$value%");
- }
- }
- elseif($operator == 'belong')
- {
- if($this->post->$fieldName == 'module')
- {
- $allModules = $this->loadModel('tree')->getAllChildId($value);
- if($allModules) $condition = helper::dbIN($allModules);
- }
- elseif($this->post->$fieldName == 'dept')
- {
- $allDepts = $this->loadModel('dept')->getAllChildId($value);
- $condition = helper::dbIN($allDepts);
- }
- else
- {
- $condition = ' = ' . $this->dbh->quote($value) . ' ';
- }
- }
- else
- {
- if($operator == 'between' and !isset($this->config->search->dynamic[$value])) $operator = '=';
- $condition = $operator . ' ' . $this->dbh->quote($value) . ' ';
- if($operator == '=' and $this->post->$fieldName == 'id' and preg_match('/^[0-9]+(,[0-9]*)+$/', $value) and !preg_match('/[\x7f-\xff]+/', $value))
- {
- $values = array_filter(explode(',', trim($this->dbh->quote($value), "'")));
- foreach($values as $value) $value = "'" . $value . "'";
- $value = implode(',', $values);
- $operator = 'IN';
- $condition = $operator . ' (' . $value . ') ';
- }
- }
- /* Processing query criteria. */
- if($operator == '=' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
- {
- $condition = '`' . $this->post->$fieldName . "` >= '$value' AND `" . $this->post->$fieldName . "` <= '$value 23:59:59'";
- $where .= " $andOr ($condition)";
- }
- elseif($operator == '!=' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
- {
- $condition = '`' . $this->post->$fieldName . "` < '$value' OR `" . $this->post->$fieldName . "` > '$value 23:59:59'";
- $where .= " $andOr ($condition)";
- }
- elseif($operator == '<=' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
- {
- $where .= " $andOr " . '`' . $this->post->$fieldName . "` <= '$value 23:59:59'";
- }
- elseif($operator == '>' and preg_match('/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/', $value))
- {
- $where .= " $andOr " . '`' . $this->post->$fieldName . "` > '$value 23:59:59'";
- }
- elseif(in_array($operator, array('include', 'notinclude')) && $fieldControl == 'select')
- {
- $where .= " $andOr CONCAT(',', `{$this->post->$fieldName}`, ',') {$condition}";
- }
- elseif($condition)
- {
- $where .= " $andOr " . '`' . $this->post->$fieldName . '` ' . $condition;
- }
- }
- $where .=" ))";
- $where = $this->replaceDynamic($where);
- /* Save to session. */
- $querySessionName = $this->post->module . 'Query';
- $formSessionName = $this->post->module . 'Form';
- $this->session->set($querySessionName, $where);
- $this->session->set($formSessionName, $_POST);
- if($scoreNum > 2 && !dao::isError()) $this->loadModel('score')->create('search', 'saveQueryAdvanced');
- }
- /**
- * 转换 queryForm 格式以适应 buildQuery 的检查。
- * Convert queryForm to fit the buildQuery check.
- *
- * @param array $queryForm
- * @access public
- * @return array
- */
- public function convertQueryForm($queryForm)
- {
- if(isset($queryForm['field1'])) return $queryForm;
- $convertedForm = array();
- foreach($queryForm as $i => $formItem)
- {
- $i++;
- if(isset($formItem['groupAndOr']))
- {
- $convertedForm['groupAndOr'] = $formItem['groupAndOr'];
- }
- elseif(isset($formItem['field']))
- {
- $convertedForm['field' . $i] = $formItem['field'];
- $convertedForm['andOr' . $i] = $formItem['andOr'];
- $convertedForm['operator' . $i] = $formItem['operator'];
- $convertedForm['value' . $i] = $formItem['value'];
- }
- }
- return $convertedForm;
- }
- /**
- * 获取查询。
- * Get a query.
- *
- * @param int $queryID
- * @access public
- * @return object|bool
- */
- public function getQuery($queryID)
- {
- $queryID = (int)$queryID;
- $query = $this->dao->findByID($queryID)->from(TABLE_USERQUERY)->fetch();
- if(!$query) return false;
- if(in_array($query->module, $this->config->search->oldQuery)) return $this->getOldQuery($queryID);
- /* Decode html encode. */
- $query->form = htmlspecialchars_decode($query->form, ENT_QUOTES);
- /* 如果搜索表单中值有变量,把表单值放到post 表单,重新生成 query。*/
- /* If form has variable, regenerate query. */
- $hasDynamic = strpos($query->form, '$') !== false;
- $query->form = unserialize($query->form);
- if($hasDynamic)
- {
- $_POST = $this->convertQueryForm($query->form);
- $_POST['module'] = $query->module;
- $this->buildQuery();
- $querySessionName = $query->module . 'Query';
- $query->sql = $_SESSION[$querySessionName];
- }
- /* 将queryform[filed1] 转换为 queryform[1]['field']。*/
- /* Process queryform[filed1] to queryform[1]['field']. */
- $queryForm = array();
- if(isset($query->form['field1']))
- {
- foreach($query->form as $field => $value)
- {
- $index = substr($field, -1);
- if(is_numeric($index))
- {
- $field = substr($field, 0, strlen($field) - 1);
- $queryForm[$index][$field] = $value;
- }
- elseif($field == 'groupAndOr')
- {
- $queryForm[$field][$field] = $value;
- }
- }
- $query->form = array_values($queryForm);
- }
- return $query;
- }
- /**
- * Get a query.
- *
- * @param int $queryID
- * @access public
- * @return object
- */
- public function getOldQuery($queryID)
- {
- $query = $this->dao->findByID($queryID)->from(TABLE_USERQUERY)->fetch();
- if(!$query) return false;
- /* Decode html encode. */
- $query->form = htmlspecialchars_decode($query->form, ENT_QUOTES);
- $query->sql = htmlspecialchars_decode($query->sql, ENT_QUOTES);
- $hasDynamic = strpos($query->form, '$') !== false;
- $query->form = unserialize($query->form);
- if($hasDynamic)
- {
- $_POST = $query->form;
- $this->buildQuery();
- $querySessionName = $query->form['module'] . 'Query';
- $query->sql = $_SESSION[$querySessionName];
- }
- return $query;
- }
- /**
- * 将搜索条件保存到session中。
- * Save query to session.
- *
- * @param string $module
- * @param int $queryID
- * @access public
- * @return string
- */
- public function setQuery($module, $queryID = 0)
- {
- $querySessionName = $module . 'Query';
- $formSessionName = $module . 'Form';
- $queryID = (int)$queryID;
- if($queryID)
- {
- $query = $this->getQuery($queryID);
- if($query)
- {
- $this->session->set($querySessionName, $query->sql);
- $this->session->set($formSessionName, $query->form);
- }
- else
- {
- $this->session->set($querySessionName, ' 1 = 1');
- }
- }
- else
- {
- if($this->session->$querySessionName == false) $this->session->set($querySessionName, ' 1 = 1');
- }
- return $this->session->$querySessionName;
- }
- /**
- * 获取一个查询。
- * Get a query.
- *
- * @param int $queryID
- * @access public
- * @return object|false
- */
- public function getByID($queryID)
- {
- return $this->dao->findByID($queryID)->from(TABLE_USERQUERY)->fetch();
- }
- /**
- * 保存当前的查询。
- * Save current query to db.
- *
- * @access public
- * @return void
- */
- public function saveQuery()
- {
- $sqlVar = $this->post->module . 'Query';
- $formVar = $this->post->module . 'Form';
- $sql = $_SESSION[$sqlVar];
- if(!$sql) $sql = ' 1 = 1 ';
- $query = fixer::input('post')
- ->add('account', $this->app->user->account)
- ->add('form', serialize($_SESSION[$formVar]))
- ->add('sql', $sql)
- ->skipSpecial('sql,form')
- ->remove('onMenuBar')
- ->get();
- if($this->post->onMenuBar) $query->shortcut = '1';
- $this->dao->insert(TABLE_USERQUERY)->data($query)->autoCheck()->check('title', 'notempty')->exec();
- if(dao::isError()) return false;
- $queryID = $this->dao->lastInsertID();
- $this->loadModel('score')->create('search', 'saveQuery', $queryID);
- return $queryID;
- }
- /**
- * 删除保存的查询。
- * Delete current query from db.
- *
- * @param int $queryID
- * @access public
- * @return bool
- */
- public function deleteQuery($queryID)
- {
- $this->dao->delete()->from(TABLE_USERQUERY)->where('id')->eq($queryID)->andWhere('account')->eq($this->app->user->account)->exec();
- return !dao::isError();
- }
- /**
- * 获取id,title 的键值对。
- * Get title => id pairs of a user.
- *
- * @param string $module
- * @access public
- * @return array
- */
- public function getQueryPairs($module)
- {
- $queries = $this->dao->select('id, title')->from(TABLE_USERQUERY)->where('module')->eq($module)->andWhere('account', true)->eq($this->app->user->account)->orWhere('common')->eq(1)->markRight(1)->orderBy('id_desc')->fetchPairs();
- return array('' => $this->lang->search->myQuery) + $queries;
- }
- /**
- * 获取查询列表。
- * Get query list.
- *
- * @param string $module
- * @access public
- * @return array
- */
- public function getQueryList($module)
- {
- return $this->dao->select('id, account, title')
- ->from(TABLE_USERQUERY)
- ->where('module')->eq($module)
- ->andWhere('account', true)->eq($this->app->user->account)
- ->orWhere('common')->eq(1)
- ->markRight(1)
- ->orderBy('id_desc')
- ->fetchAll();
- }
- /**
- * 获取可访问的有索引的模块。
- * Get counts of keyword search results.
- *
- * @param string|array $type
- * @access public
- * @return array
- */
- public function getListCount($type = 'all')
- {
- $allowedObjects = $this->searchTao->getAllowedObjects($type);
- $filterObjects = array();
- foreach($allowedObjects as $index => $object)
- {
- if(strpos(',feedback,ticket,', ",$object,") === false) continue;
- unset($allowedObjects[$index]);
- $filterObjects[] = $object;
- }
- $typeCount = $this->dao->select("objectType, COUNT(1) AS objectCount")->from(TABLE_SEARCHINDEX)
- ->where('((vision')->eq($this->config->vision)
- ->andWhere('objectType')->in($allowedObjects)
- ->markRight(1)
- ->orWhere('(objectType')->in($filterObjects)
- ->markRight(2)
- ->andWhere('addedDate')->le(helper::now())
- ->groupBy('objectType')
- ->fetchPairs();
- arsort($typeCount);
- return $typeCount;
- }
- /**
- * 获取搜索结果。
- * get search results of keywords.
- *
- * @param string $keywords
- * @param string $type
- * @param object $pager
- * @access public
- * @return array
- */
- public function getList($keywords, $type, $pager = null)
- {
- $filter = $this->app->loadClass('sqlfilter');
- $keywords = $filter->getRecommendedFilter($keywords, 'medium');
- list($words, $againstCond, $likeCondition) = $this->searchTao->getSqlParams($keywords);
- $allowedObjects = $this->searchTao->getAllowedObjects($type);
- $filterObjects = array();
- foreach($allowedObjects as $index => $object)
- {
- if(strpos(',feedback,ticket,', ",$object,") === false) continue;
- unset($allowedObjects[$index]);
- $filterObjects[] = $object;
- }
- if(in_array($this->config->db->driver, $this->config->pgsqlDriverList))
- {
- $table = "SELECT *, ts_rank(to_tsvector('pg_catalog.english', coalesce(title, '') || ' ' || coalesce(content, '')), to_tsquery('pg_catalog.english', '{$againstCond}')) AS score FROM " . TABLE_SEARCHINDEX;
- $score = '0.02';
- }
- elseif($this->config->db->driver == 'dm')
- {
- $spliter = $this->app->loadClass('spliter');
- $labels = array('_', '、', ' ', '-', '\n', '?', '@', '&', '%', '~', '`', '+', '*', '/', '\\', '。', ',');
- $words = str_replace($labels, ' ', $words);
- $words = explode(' ', trim($words));
- $conditions = [];
- foreach($words as $word)
- {
- $trimmedWord = trim($word);
- $conditions[] = "title LIKE '%" . $trimmedWord . "%' OR content LIKE '%" . $trimmedWord . "%'";
- }
- $table = "SELECT *, (CASE WHEN (" . implode(' OR ', $conditions) . ") THEN 1 ELSE 0 END) AS score FROM " . TABLE_SEARCHINDEX;
- $score = '1';
- }
- else
- {
- $table = "SELECT *, (MATCH(title, content) AGAINST('{$againstCond}' IN BOOLEAN MODE)) AS score FROM " . TABLE_SEARCHINDEX;
- $score = '1';
- }
- $stmt = $this->dao->select('*')->from("({$table})")->alias('t1')
- ->where("(score >= {$score} {$likeCondition})")
- ->andWhere('((vision')->eq($this->config->vision)
- ->andWhere('objectType')->in($allowedObjects)
- ->markRight(1)
- ->orWhere('(objectType')->in($filterObjects)
- ->markRight(2)
- ->andWhere('addedDate')->le(helper::now())
- ->orderBy('score_desc, editedDate_desc')
- ->query();
- $results = array();
- $idListGroup = array();
- while($record = $stmt->fetch())
- {
- $results[$record->id] = $record;
- $module = $record->objectType == 'case' ? 'testcase' : $record->objectType;
- $idListGroup[$module][$record->objectID] = $record->objectID;
- }
- $results = $this->searchTao->checkPriv($results, $idListGroup);
- if(empty($results)) return $results;
- /* Reset pager total and get this page data. */
- if($pager) $results = $this->searchTao->setResultsInPage($results, $pager);
- $objectList = $this->searchTao->getobjectList($idListGroup);
- return $this->processResults($results, $objectList, $words);
- }
- /**
- * 保存一个索引项。
- * Save an index item.
- *
- * @param string $objectType article|blog|page|product|thread|reply|
- * @param object $object
- * @access public
- * @return bool
- */
- public function saveIndex($objectType, $object)
- {
- $fields = $this->config->search->fields->{$objectType};
- if(empty($fields)) return true;
- /* 如果是文档,将文档的内容追加上。*/
- /* If the objectType is doc, append the content of doc. */
- if($objectType == 'doc' && $this->config->edition != 'open') $object = $this->searchTao->appendFiles($object);
- $index = new stdclass();
- $index->objectType = $objectType;
- $index->objectID = $object->{$fields->id};
- $index->title = $object->{$fields->title};
- $index->addedDate = !empty($object->{$fields->addedDate}) ? (!helper::isZeroDate($object->{$fields->addedDate}) ? $object->{$fields->addedDate} : NULL) : NULL;
- $index->editedDate = !empty($object->{$fields->editedDate}) ? (!helper::isZeroDate($object->{$fields->editedDate}) ? $object->{$fields->editedDate} : NULL) : NULL;
- $index->vision = isset($object->vision) ? $object->vision : 'rnd';
- $index->content = '';
- $contentFields = explode(',', $fields->content . ',comment');
- foreach($contentFields as $field)
- {
- if(empty($field)) continue;
- $index->content .= $object->$field;
- }
- $spliter = $this->app->loadClass('spliter');
- $titleSplited = $spliter->utf8Split($index->title);
- $contentSplited = $spliter->utf8Split(strip_tags($index->content));
- $index->title = $titleSplited['words'];
- $index->content = $contentSplited['words'];
- $this->saveDict($titleSplited['dict'] + $contentSplited['dict']);
- $this->dao->delete()->from(TABLE_SEARCHINDEX)->where('objectType')->eq($index->objectType)->andWhere('objectID')->eq($index->objectID)->exec();
- $this->dao->insert(TABLE_SEARCHINDEX)->data($index)->exec();
- return !dao::isError();
- }
- /**
- * 保存搜索字典。
- * Save dict info.
- *
- * @param array $dict
- * @access public
- * @return bool
- */
- public function saveDict($dict)
- {
- static $savedDict;
- if(empty($savedDict)) $savedDict = $this->dao->select("`key`")->from(TABLE_SEARCHDICT)->fetchPairs();
- foreach($dict as $key => $value)
- {
- if(!is_numeric($key) || empty($value) || strlen($key) != 5 || $key < 0 || $key > 65535) continue;
- if(isset($savedDict[$key])) continue;
- $this->dao->replace(TABLE_SEARCHDICT)->data(array('key' => $key, 'value' => $value))->exec();
- $savedDict[$key] = $key;
- }
- return !dao::isError();
- }
- /**
- * 构建索引查询。
- * Build index query.
- *
- * @param string $type
- * @param bool $testDelete
- * @access public
- * @return object
- * @param bool $testDeleted
- */
- public function buildIndexQuery($type, $testDeleted = true)
- {
- $table = $this->config->objectTables[$type];
- if($type == 'story' || $type == 'requirement' || $type == 'epic')
- {
- $query = $this->dao->select('DISTINCT t1.*, t2.spec, t2.verify')->from($table)->alias('t1')
- ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story')
- ->where('t1.deleted')->eq('0')
- ->andWhere('type')->eq($type)
- ->andWhere('t1.version=t2.version');
- }
- elseif($type == 'doc')
- {
- $query = $this->dao->select('DISTINCT t1.*, t2.content, t2.digest')->from($table)->alias('t1')->leftJoin(TABLE_DOCCONTENT)->alias('t2')->on('t1.id=t2.doc')->where('t1.deleted')->eq('0')->andWhere('t1.version=t2.version');
- }
- else
- {
- $data = '';
- if($testDeleted) $data = $this->dao->select('*')->from($table)->limit(1)->fetch();
- $query = $this->dao->select('t1.*')->from($table)->alias('t1')
- ->where('1=1')
- ->beginIF($type == 'program')->andWhere('type')->eq('program')->fi()
- ->beginIF($type == 'project')->andWhere('type')->eq('project')->fi()
- ->beginIF($type == 'execution')->andWhere('type')->in('stage,sprint,kanban')->fi()
- ->beginIF(isset($data->deleted))->andWhere('t1.deleted')->eq('0')->fi();
- }
- return $query;
- }
- /**
- * 构建索引。
- * Build all search index.
- *
- * @param string $type
- * @param int $lastID
- * @access public
- * @return array
- */
- public function buildAllIndex($type = '', $lastID = 0)
- {
- /* 如果类型是空的,获取第一个类型,从第一个类型开始创建索引。*/
- /* If the type is empty, get the first type and create index starting from the first type. */
- if(empty($type))
- {
- $this->dao->delete()->from(TABLE_SEARCHINDEX)->exec();
- $this->dao->delete()->from(TABLE_SEARCHDICT)->exec();
- try
- {
- $this->dbh->exec('ALTER TABLE ' . TABLE_SEARCHINDEX . ' auto_increment=1');
- }
- catch(Exception $e){}
- $type = key((array)$this->config->search->fields);
- }
- $limit = 100;
- $nextObject = false;
- /* 获取某些字段值并且将其保存到索引。*/
- /* Get some field value and save it to index. */
- foreach($this->config->search->fields as $module => $field)
- {
- if($module != $type && !$nextObject) continue;
- if(!isset($this->config->objectTables[$module])) continue;
- if($module == $type) $nextObject = true;
- while(true)
- {
- $query = $this->buildIndexQuery($module);
- $dataList = $query->beginIF($lastID)->andWhere('t1.id')->gt($lastID)->fi()->orderBy('t1.id')->limit($limit)->fetchAll('id', false);
- if(empty($dataList))
- {
- $lastID = 0;
- break;
- }
- $dataList = $this->searchTao->processDataList($module, $field, $dataList);
- foreach($dataList as $data) $this->saveIndex($module, $data);
- return array('type' => $module, 'count' => count($dataList), 'lastID' => max(array_keys($dataList)));
- }
- }
- return array('finished' => true);
- }
- /**
- * 删除索引。
- * Delete index of an object.
- *
- * @param string $objectType
- * @param int $objectID
- * @access public
- * @return bool
- */
- public function deleteIndex($objectType, $objectID)
- {
- $this->dao->delete()->from(TABLE_SEARCHINDEX)->where('objectType')->eq($objectType)->andWhere('objectID')->eq($objectID)->exec();
- return !dao::isError();
- }
- }
|