Core  3.2
PHP API documentation
 All Data Structures Namespaces Files Functions Variables Pages
Class.SearchDoc.php
Go to the documentation of this file.
1 <?php
2 /*
3  * @author Anakeen
4  * @package FDL
5 */
6 /**
7  * Search Document
8  *
9  * @author Anakeen
10  * @version $Id: Class.SearchDoc.php,v 1.8 2008/08/14 14:20:25 eric Exp $
11  * @package FDL
12  */
13 /**
14  */
15 
16 include_once ("FDL/Lib.Dir.php");
17 /**
18  * document searches
19  * @code
20  * $s=new SearchDoc($db,"IUSER");
21  $s->setObjectReturn(); // document object returns
22  $s->addFilter('us_extmail is not null'); // simple filter
23  $s->search(); // send search query
24  $c=$s->count();
25  print "count $c\n";
26  $k=0;
27  while ($doc=$s->nextDoc()) {
28  // iterate document by document
29  print "$k)".$doc->getTitle()."(".$doc->getRawValue("US_MAIL","nomail").")\n";clam
30  $k+
31  * @endcode
32  * @class SearchDoc.
33  */
34 class SearchDoc
35 {
36  /**
37  * family identifier filter
38  * @public string
39  */
40  public $fromid;
41  /**
42  * folder identifier filter
43  * @public int
44  */
45  public $dirid = 0;
46  /**
47  * recursive search for folders
48  * @public boolean
49  */
50  public $recursiveSearch = false;
51  /**
52  * max recursive level
53  * @public int
54  */
56  /**
57  * number of results : set "ALL" if no limit
58  * @public int
59  */
60  public $slice = "ALL";
61  /**
62  * index of results begins
63  * @public int
64  */
65  public $start = 0;
66  /**
67  * sql filters
68  * @public array
69  */
70  public $filters = array();
71  /**
72  * search in sub-families set false if restriction to top family
73  * @public bool
74  */
75  public $only = false;
76  /**
77  *
78  * @public bool
79  */
80  public $distinct = false;
81  /**
82  * order of result : like sql order
83  * @public string
84  */
85  public $orderby = 'title';
86  /**
87  * order result by this attribute label/title
88  * @public string
89  */
90  public $orderbyLabel = '';
91  /**
92  * to search in trash : [no|also|only]
93  * @public string
94  */
95  public $trash = "";
96  /**
97  * restriction to latest revision
98  * @public bool
99  */
100  public $latest = true;
101  /**
102  * user identifier : set to current user by default
103  * @public int
104  */
105  public $userid = 0;
106  /**
107  * debug mode : to view query and delay
108  * @public bool
109  */
110  private $debug = false;
111  private $debuginfo = [];
112  private $join = "";
113  /**
114  * sql filter not return confidential document if current user cannot see it
115  * @var string
116  */
117  private $excludeFilter = "";
118  /**
119  *
120  * Iterator document
121  * @var Doc
122  */
123  private $iDoc = null;
124  /**
125  *
126  * Iterator document
127  * @var Doc[]
128  */
129  private $cacheDocuments = array();
130  /**
131  * result type [ITEM|TABLE]
132  * @private string
133  */
134  private $mode = "TABLE";
135  private $count = - 1;
136  private $index = 0;
137  /**
138  * @var bool|array
139  */
140  private $result = false;
141  private $searchmode;
142  /**
143  * @var string pertinence order in case of full searches
144  */
145  private $pertinenceOrder = '';
146  /**
147  * @var string words used by SearchHighlight class
148  */
149  private $highlightWords = '';
150  private $resultPos = 0;
151  /**
152  * @var int query number (in ITEM mode)
153  */
154 
155  private $resultQPos = 0;
156  protected $originalDirId = 0;
157 
158  protected $returnsFields = array();
159  /**
160  * initialize with family
161  *
162  * @param string $dbaccess database coordinate
163  * @param int|string $fromid family identifier to filter
164  */
165  public function __construct($dbaccess = '', $fromid = 0)
166  {
167  if ($dbaccess == "") $dbaccess = getDbAccess();
168  $this->dbaccess = $dbaccess;
169  $this->fromid = trim($fromid);
170  $this->setOrder('title');
171  $this->userid = getUserId();
172  }
173  /**
174  * Normalize supported forms of fromid
175  *
176  * @param int|string $id the fromid to normalize
177  * @return bool|int normalized integer or bool(false) on normalization failure
178  */
179  private function normalizeFromId($id)
180  {
181  $id = trim($id);
182  // "0" or "" (empty srting) = search on all documents (cross-family)
183  if ($id === "0" || $id === "") {
184  return 0;
185  }
186  // "-1" = search on docfam
187  if ($id === "-1") {
188  return -1;
189  }
190  if (is_numeric($id)) {
191  // 123 or -123 = search on family with id 123
192  $sign = 1;
193  if ($id < 0) {
194  // -123 = search on family with id 123 without sub-families
195  $sign = - 1;
196  $id = abs($id);
197  }
198  $fam = new_Doc($this->dbaccess, $id);
199  if ($fam->isAlive() && $fam->defDoctype === 'C') {
200  return $sign * (int)$fam->id;
201  }
202  } else {
203  // "ABC" or "-ABC" = search on family with name ABC
204  $sign = 1;
205  if (substr($id, 0, 1) == '-') {
206  // "-ABC" = search on family with name 123 without sub-families
207  $sign = - 1;
208  $id = substr($id, 1);
209  }
210  $fam = new_Doc($this->dbaccess, $id);
211  if ($fam->isAlive() && $fam->defDoctype === 'C') {
212  return $sign * (int)$fam->id;
213  }
214  }
215  return false;
216  }
217  /**
218  * Count results without returning data.
219  *
220  * Note:
221  * - The setStart() and setSlice() parameters are not used when counting with this method.
222  *
223  * @api send query search and only count results
224  *
225  * @return int the number of results
226  * @throws Dcp\SearchDoc\Exception
227  * @throws Dcp\Db\Exception
228  */
229  public function onlyCount()
230  {
231  /* @var Dir $fld */
232  $fld = new_Doc($this->dbaccess, $this->dirid);
234  if ($fld->fromid != getFamIdFromName($this->dbaccess, "SSEARCH")) {
235  $this->recursiveSearchInit();
236  $tqsql = $this->getQueries();
237  $this->debuginfo["query"] = $tqsql[0];
238  $count = 0;
239  if (!is_array($tqsql)) {
240  if (!isset($this->debuginfo["error"]) || $this->debuginfo["error"] == "") {
241  $this->debuginfo["error"] = _("cannot produce sql request");
242  }
243  return -1;
244  }
245  foreach ($tqsql as $sql) {
246  if ($sql) {
247  if (preg_match('/from\s+(?:only\s+)?([a-z0-9_\-]*)/', $sql, $reg)) $maintable = $reg[1];
248  else $maintable = '';
249  $maintabledot = ($maintable) ? $maintable . '.' : '';
250 
251  $mainid = ($maintable) ? "$maintable.id" : "id";
252  $distinct = "";
253  if (preg_match('/^\s*select\s+distinct(\s+|\(.*?\))/iu', $sql, $m)) {
254  $distinct = "distinct ";
255  }
256  $sql = preg_replace('/^\s*select\s+(.*?)\s+from\s/iu', "select count($distinct$mainid) from ", $sql, 1);
257  if ($userid != 1) {
258  $sql.= sprintf(" and (%sviews && '%s')", $maintabledot, $this->getUserViewVector($userid));
259  }
260  $dbid = getDbid($this->dbaccess);
261  $mb = microtime(true);
262  try {
263  simpleQuery($this->dbaccess, $sql, $result, false, true, true);
264  }
265  catch(\Dcp\Db\Exception $e) {
266  $this->debuginfo["query"] = $sql;
267  $this->debuginfo["error"] = pg_last_error($dbid);
268  $this->count = - 1;
269  throw $e;
270  }
271  $count+= $result["count"];
272  $this->debuginfo["query"] = $sql;
273  $this->debuginfo["delay"] = sprintf("%.03fs", microtime(true) - $mb);
274  }
275  }
276  $this->count = $count;
277  return $count;
278  } else {
279  $this->count = count($fld->getContent());
280  }
281 
282  return $this->count;
283  }
284  /**
285  * return memberof to be used in profile filters
286  * @static
287  * @param $uid
288  * @return string
289  */
290  public static function getUserViewVector($uid)
291  {
293  if ($memberOf === null) {
294  return '';
295  }
296  $memberOf[] = 0;
297  $memberOf[] = $uid;
298  return '{' . implode(',', $memberOf) . '}';
299  }
300  /**
301  * return original sql query before test permissions
302  *
303  *
304  * @return string
305  */
306  public function getOriginalQuery()
307  {
308  return _internalGetDocCollection(true, $this->dbaccess, $this->dirid, $this->start, $this->slice, $this->getFilters() , $this->userid, $this->searchmode, $this->fromid, $this->distinct, $this->orderby, $this->latest, $this->trash, $debuginfo, $this->folderRecursiveLevel, $this->join, $this);
309  }
310  /**
311  * add join condition
312  *
313  * @api Add join condition
314  * @code
315  $s=new searchDoc();
316  $s->trash='only';
317  $s->join("id = dochisto(id)");
318  $s->addFilter("dochisto.uid = %d",$this->getSystemUserId());
319  // search all document which has been deleted by search DELETE code in history
320  $s->addFilter("dochisto.code = 'DELETE'");
321  $s->distinct=true;
322  $result= $s->search();
323  * @endcode
324  * @param string $jointure
325  * @throws Dcp\Exception
326  */
327  public function join($jointure)
328  {
329  if (empty($jointure)) {
330  $this->join = '';
331  } elseif (preg_match('/([a-z0-9_\-:]+)\s*(=|<|>|<=|>=)\s*([a-z0-9_\-:]+)\(([^\)]*)\)/', $jointure, $reg)) {
332  $this->join = $jointure;
333  } else {
334  throw new \Dcp\SearchDoc\Exception("SD0001", $jointure);
335  }
336  }
337  /**
338  * count results
339  * ::search must be call before
340  * @see SearchDoc::search()
341  * @api count results after query search is sended
342  *
343  * @return int
344  *
345  */
346  public function count()
347  {
348  if ($this->isExecuted()) {
349  if ($this->count == - 1) {
350  if ($this->searchmode == "ITEM") {
351  $this->count = $this->countDocs();
352  } else {
353  $this->count = count($this->result);
354  }
355  }
356  }
357  return $this->count;
358  }
359  /**
360  * count returned document in sql select ressources
361  * @return int
362  */
363  protected function countDocs()
364  {
365  $n = 0;
366  foreach ($this->result as $res) $n+= pg_num_rows($res);
367  reset($this->result);
368  return $n;
369  }
370  /**
371  * reset results to use another search
372  *
373  *
374  * @return void
375  */
376  public function reset()
377  {
378  $this->result = false;
379  $this->resultPos = 0;
380  $this->resultQPos = 0;
381  $this->debuginfo = [];
382  $this->count = - 1;
383  }
384  /**
385  * reset result offset
386  * use it to redo a document's iteration
387  *
388  */
389  public function rewind()
390  {
391 
392  $this->resultPos = 0;
393  $this->resultQPos = 0;
394  }
395  /**
396  * Verify if query is already sended to database
397  *
398  * @return boolean
399  */
400  public function isExecuted()
401  {
402  return ($this->result !== false);
403  }
404  /**
405  * Return sql filters used for request
406  *
407  * @return array of string
408  */
409  public function getFilters()
410  {
411  if (!$this->excludeFilter) {
412  return $this->filters;
413  } else {
414  return array_merge(array(
415  $this->excludeFilter
416  ) , $this->filters);
417  }
418  }
419  /**
420  * send search
421  * the query is sent to database
422  * @api send query
423  * @return array|null|SearchDoc array of documents if no setObjectReturn else itself
424  * @throws Dcp\SearchDoc\Exception
425  * @throws Dcp\Db\Exception
426  */
427  public function search()
428  {
429  if (count($this->filters) > 0 && $this->dirid > 0) {
430  $dir = new_Doc($this->dbaccess, $this->dirid);
431  if (is_object($dir) && $dir->isAlive() && is_a($dir, '\Dcp\Family\Ssearch')) {
432  // Searching on a "Specialized search" collection and specifying additional filters is not supported
433  throw new \Dcp\SearchDoc\Exception("SD0008");
434  }
435  }
436  if ($this->getError()) {
437  if ($this->mode == "ITEM") {
438  return null;
439  } else {
440  return array();
441  }
442  }
443  if ($this->fromid) {
444  if (!is_numeric($this->fromid)) {
445  $fromid = getFamIdFromName($this->dbaccess, $this->fromid);
446  } else {
447  if ($this->fromid != - 1) {
448  // test if it is a family
449  if ($this->fromid < - 1) {
450  $this->only = true;
451  }
452  simpleQuery($this->dbaccess, sprintf("select doctype from docfam where id=%d", abs($this->fromid)) , $doctype, true, true);
453  if ($doctype != 'C') $fromid = 0;
454  else $fromid = $this->fromid;
455  } else $fromid = $this->fromid;
456  }
457  if ($fromid == 0) {
458  $error = sprintf(_("%s is not a family") , $this->fromid);
459  $this->debuginfo["error"] = $error;
460  error_log("ERROR SearchDoc: " . $error);
461  if ($this->mode == "ITEM") return null;
462  else return array();
463  }
464  if ($this->only) $this->fromid = - (abs($fromid));
465  else $this->fromid = $fromid;
466  }
467  $this->recursiveSearchInit();
468  $this->index = 0;
469  $this->searchmode = $this->mode;
470  if ($this->mode == "ITEM") {
471  if ($this->dirid) {
472  // change search mode because ITEM mode not supported for Specailized searches
473  $fld = new_Doc($this->dbaccess, $this->dirid);
474  if ($fld->fromid == getFamIdFromName($this->dbaccess, "SSEARCH")) {
475  $this->searchmode = "TABLE";
476  }
477  }
478  }
479  $debuginfo = array();
480  $this->count = - 1;
481  $this->result = internalGetDocCollection($this->dbaccess, $this->dirid, $this->start, $this->slice, $this->getFilters() , $this->userid, $this->searchmode, $this->fromid, $this->distinct, $this->orderby, $this->latest, $this->trash, $debuginfo, $this->folderRecursiveLevel, $this->join, $this);
482  if ($this->searchmode == "TABLE") $this->count = count($this->result); // memo cause array is unset by shift
483  $this->debuginfo = $debuginfo;
484  if (($this->searchmode == "TABLE") && ($this->mode == "ITEM")) $this->mode = "TABLEITEM";
485  $this->resultPos = 0;
486  $this->resultQPos = 0;
487  if ($this->mode == "ITEM") return $this;
488 
489  return $this->result;
490  }
491  /**
492  * return document iterator to be used in loop
493  * @code
494  * $s=new \SearchDoc($dbaccess, $famName);
495  $s->setObjectReturn();
496  $s->search();
497  $dl=$s->getDocumentList();
498  foreach ($dl as $docId=>$doc) {
499  print $doc->getTitle();
500  }
501  * @endcode
502  * @api get document iterator
503  * @return DocumentList
504  */
505  public function getDocumentList()
506  {
507  include_once ("FDL/Class.DocumentList.php");
508  return new DocumentList($this);
509  }
510  /**
511  * limit query to a subset of somes attributes
512  * @param array $returns
513  */
514  public function returnsOnly(array $returns)
515  {
516  if ($this->fromid) {
517  $fdoc = createTmpDoc($this->dbaccess, $this->fromid, false);
518  $fields = array_merge($fdoc->fields, $fdoc->sup_fields);
519  } else {
520  $fdoc = new Doc();
521  $fields = array_merge($fdoc->fields, $fdoc->sup_fields);
522  }
523  foreach ($returns as $k => $r) {
524  if (empty($r)) unset($returns[$k]);
525  $returns[$k] = strtolower($r);
526  // delete unknow fields
527  if (!in_array($r, $fields)) {
528  unset($returns[$k]);
529  }
530  }
531  $this->returnsFields = array_unique(array_merge(array(
532  "id",
533  "title",
534  "fromid",
535  "doctype"
536  ) , $returns));
537  }
538  public function getReturnsFields()
539  {
540  if ($this->returnsFields) return $this->returnsFields;
541  if ($this->fromid) {
542  $fdoc = createTmpDoc($this->dbaccess, $this->fromid, false);
543  if ($fdoc->isAlive()) return array_merge($fdoc->fields, $fdoc->sup_fields);
544  }
545  return null;
546  }
547  /**
548  * return error message
549  * @return string empty if no errors
550  */
551  public function searchError()
552  {
553  return $this->getError();
554  }
555  /**
556  * Return error message
557  * @api get error message
558  * @return string
559  */
560  public function getError()
561  {
562  if ($this->debuginfo && isset($this->debuginfo["error"])) return $this->debuginfo["error"];
563  return "";
564  }
565  /**
566  * do the search in debug mode, you can after the search get infrrmation with getDebugIndo()
567  * @param boolean $debug set to true search in debug mode
568  * @deprecated no debug mode setting are necessary
569  * @return void
570  */
571  public function setDebugMode($debug = true)
572  {
574  $this->debug = $debug;
575  }
576  /**
577  * set recursive mode for folder searches
578  * can be use only if collection set if a static folder
579  * @param bool $recursiveMode set to true to use search in sub folders when collection is folder
580  * @param int $level Indicate depth to inspect subfolders
581  * @throws Dcp\SearchDoc\Exception
582  * @api set recursive mode for folder searches
583  * @see SearchDoc::useCollection
584  * @return void
585  */
586  public function setRecursiveSearch($recursiveMode = true, $level = 2)
587  {
588  $this->recursiveSearch = $recursiveMode;
589  if (!is_int($level) || $level < 0) {
590  throw new \Dcp\SearchDoc\Exception("SD0006", $level);
591  }
592  $this->folderRecursiveLevel = $level;
593  }
594  /**
595  * return debug info if debug mode enabled
596  * @deprecated use getSearchInfo instead
597  *
598  * @return array of info
599  */
600  public function getDebugInfo()
601  {
603  return $this->debuginfo;
604  }
605  /**
606  * return informations about query after search has been sent
607  * array indexes are : query, err, count, delay
608  * @api get informations about query results
609  * @return array of info
610  */
611  public function getSearchInfo()
612  {
613  return $this->debuginfo;
614  }
615  /**
616  * set maximum number of document to return
617  * @api set maximum number of document to return
618  * @param int $slice the limit ('ALL' means no limit)
619  *
620  * @return Boolean
621  */
622  public function setSlice($slice)
623  {
624  if ((!is_numeric($slice)) && ($slice != 'ALL')) return false;
625  $this->slice = $slice;
626  return true;
627  }
628  /**
629  * use different order , default is title
630  * @api set order to sort results
631  * @param string $order the new order, empty means no order
632  * @param string $orderbyLabel string of comma separated columns names on which the order should be performed on their label instead of their value (e.g. order enum by their label instead of their key)
633  * @return void
634  */
635  public function setOrder($order, $orderbyLabel = '')
636  {
637  $this->orderby = $order;
638  $this->orderbyLabel = $orderbyLabel;
639  /* Rewrite "-<column_name>" to "<column_name> desc" */
640  $this->orderby = preg_replace('/(^\s*|,\s*)-([A-Z_0-9]{1,63})\b/i', '$1$2 desc', $this->orderby);
641  }
642  /**
643  * use folder or search document to search within it
644  * @api use folder or search document
645  * @param int $dirid identifier of the collection
646  *
647  * @return Boolean true if set
648  */
649  public function useCollection($dirid)
650  {
651  $dir = new_doc($this->dbaccess, $dirid);
652  if ($dir->isAlive()) {
653  $this->dirid = $dir->initid;
654  $this->originalDirId = $this->dirid;
655  return true;
656  }
657  $this->debuginfo["error"] = sprintf(_("collection %s not exists") , $dirid);
658 
659  return false;
660  }
661  /**
662  * set offset where start the result window
663  * @api set offset where start the result window
664  * @param int $start the offset (0 is the begin)
665  *
666  * @return Boolean true if set
667  */
668  public function setStart($start)
669  {
670  if (!(is_numeric($start))) return false;
671  $this->start = intval($start);
672  return true;
673  }
674  /**
675  * can, be use in loop
676  * ::search must be call before
677  *
678  * @see Application::getNextDoc
679  *
680  * @deprecated use { @link Application::getNextDoc } instead
681  *
682  * @see SearchDoc::search
683  *
684  * @return Doc|array or null if this is the end
685  */
686  public function nextDoc()
687  {
689  return $this->getNextDoc();
690  }
691  /**
692  * can, be use in loop
693  * ::search must be call before
694  *
695  * @see SearchDoc::search
696  *
697  * @api get next document results
698  *
699  * @return Doc|array|bool false if this is the end
700  */
701  public function getNextDoc()
702  {
703  if ($this->mode == "ITEM") {
704  $n = empty($this->result[$this->resultQPos]) ? null : $this->result[$this->resultQPos];
705  if (!$n) return false;
706  $tdoc = @pg_fetch_array($n, $this->resultPos, PGSQL_ASSOC);
707  if ($tdoc === false) {
708  $this->resultQPos++;
709  $n = empty($this->result[$this->resultQPos]) ? null : $this->result[$this->resultQPos];
710  if (!$n) return false;
711  $this->resultPos = 0;
712  $tdoc = @pg_fetch_array($n, $this->resultPos, PGSQL_ASSOC);
713  if ($tdoc === false) return false;
714  }
715  $this->resultPos++;
716  return $this->iDoc = $this->getNextDocument($tdoc);
717  } elseif ($this->mode == "TABLEITEM") {
718  $tdoc = current(array_slice($this->result, $this->resultPos, 1));
719  if (!is_array($tdoc)) return false;
720  $this->resultPos++;
721  return $this->iDoc = $this->getNextDocument($tdoc);
722  } else {
723  return current(array_slice($this->result, $this->resultPos++, 1));
724  }
725  }
726  /**
727  * after search return only document identifiers instead of complete document
728  * @api get only document identifiers
729  * @return int[] document identifiers
730  */
731  public function getIds()
732  {
733  $ids = array();
734  if ($this->mode == "ITEM") {
735  foreach ($this->result as $n) {
736  $c = pg_num_rows($n);
737  for ($i = 0; $i < $c; $i++) {
738  $ids[] = pg_fetch_result($n, $i, "id");
739  }
740  }
741  } else {
742 
743  foreach ($this->result as $raw) {
744  $ids[] = $raw["id"];
745  }
746  }
747  return $ids;
748  }
749  /**
750  * Return an object document from array of values
751  *
752  * @param array $v the values of documents
753  * @return Doc the document object
754  */
755  protected function getNextDocument(Array $v)
756  {
757  $fromid = $v["fromid"];
758  if ($v["doctype"] == "C") {
759  if (!isset($this->cacheDocuments["family"])) $this->cacheDocuments["family"] = new DocFam($this->dbaccess);
760  $this->cacheDocuments["family"]->Affect($v, true);
761  $fromid = "family";
762  } else {
763  if (!isset($this->cacheDocuments[$fromid])) {
764  $this->cacheDocuments[$fromid] = createDoc($this->dbaccess, $fromid, false, false);
765  if (empty($this->cacheDocuments[$fromid])) {
766  throw new Exception(sprintf('Document "%s" has an unknow family "%s"', $v["id"], $fromid));
767  }
768  }
769  }
770 
771  $this->cacheDocuments[$fromid]->Affect($v, true);
772  $this->cacheDocuments[$fromid]->nocache = true;
773  if ((!empty($this->returnsFields))) $this->cacheDocuments[$fromid]->doctype = "I"; // incomplete document
774  return $this->cacheDocuments[$fromid];
775  }
776  /**
777  * add a condition in filters
778  * @api add a new condition in filters
779  * @param string $filter the filter string
780  * @param string $args arguments of the filter string (arguments are escaped to avoid sql injection)
781  * @return void
782  */
783  public function addFilter($filter, $args = '')
784  {
785 
786  if ($filter != "") {
787  $args = func_get_args();
788  if (count($args) > 1) {
789  $fs[0] = $args[0];
790  for ($i = 1; $i < count($args); $i++) {
791  $fs[] = pg_escape_string($args[$i]);
792  }
793  $filter = call_user_func_array("sprintf", $fs);
794  }
795  if (preg_match('/(\s|^|\()(?P<relname>[a-z0-9_\-]+)\./', $filter, $reg)) {
796  // when use join filter like "zoo_espece.es_classe='Boo'"
797  $famid = getFamIdFromName($this->dbaccess, $reg['relname']);
798  if ($famid > 0) $filter = preg_replace('/(\s|^|\()(?P<relname>[a-z0-9_\-]+)\./', '${1}doc' . $famid . '.', $filter);
799  }
800  $this->filters[] = $filter;
801  }
802  }
803  /**
804  * add global filter based on keyword to match any attribute value
805  * available example :
806  * foo : filter all values with has the word foo
807  * foo bar : the word foo and the word bar are set in document attributes
808  * foo OR bar : the word foo or the word bar are set in a document attributes
809  * foo OR (bar AND zou) : more complex logical expression
810  * @api add global filter based on keyword
811  * @param string $keywords
812  * @param bool $useSpell use spell french checker
813  * @param bool $usePartial if true each words are defined as partial characters
814  * @throws \Dcp\SearchDoc\Exception SD0004 SD0003 SD0002
815  */
816  public function addGeneralFilter($keywords, $useSpell = false, $usePartial = false)
817  {
818  if (!$this->checkGeneralFilter($keywords)) {
819  throw new \Dcp\SearchDoc\Exception("SD0004", $keywords);
820  } else {
821  $filter = $this->getGeneralFilter(trim($keywords) , $useSpell, $this->pertinenceOrder, $this->highlightWords, $usePartial);
822  $this->addFilter($filter);
823  }
824  }
825  /**
826  * Verify if $keywords syntax is comptatible with a part of query
827  * for the moment verify only parenthesis balancing
828  * @param string $keyword
829  * @return bool
830  */
831  public static function checkGeneralFilter($keyword)
832  {
833  // no symbol allowed
834  if (preg_match('/\(\s*\)/u', $keyword)) return false;
835  // test parenthensis count
836  $keyword = str_replace('\(', '-', $keyword);
837  $keyword = str_replace('\)', '-', $keyword);
838  if (substr_count($keyword, '(') != substr_count($keyword, ')')) return false;
839  $si = strlen($keyword); // be carrefyl no use mb_strlen here : it is wanted
840  $pb = 0;
841  for ($i = 0; $i < $si; $i++) {
842  if ($keyword[$i] == '(') $pb++;
843  if ($keyword[$i] == ')') $pb--;
844  if ($pb < 0) return false;
845  }
846  return true;
847  }
848  /**
849  * add a order based on keyword
850  * consider how often the keyword terms appear in the document
851  * @api add a order based on keyword
852  * @param string $keyword
853  */
854  public function setPertinenceOrder($keyword = '')
855  {
856  if ($keyword != '') {
857  $rank = preg_replace('/\s+(OR)\s+/u', '|', $keyword);
858  $rank = preg_replace('/\s+(AND)\s+/u', '&', $rank);
859  $rank = preg_replace('/\s+/u', '&', $rank);
860  $this->pertinenceOrder = sprintf("ts_rank(fulltext,to_tsquery('french', E'%s')) desc, id desc", pg_escape_string(unaccent($rank)));
861  }
862  if ($this->pertinenceOrder) $this->setOrder($this->pertinenceOrder);
863  }
864  /**
865  * get global filter
866  * @see SearchDoc::addGeneralFilter
867  * @static
868  * @param string $keywords
869  * @param bool $useSpell
870  * @param string $pertinenceOrder return pertinence order
871  * @param string $highlightWords return words to be use by SearchHighlight class
872  * @param bool $usePartial if true each words are defined as partial characters
873  * @return string
874  * @throws \Dcp\Lex\LexException
875  * @throws \Dcp\SearchDoc\Exception
876  */
877  public static function getGeneralFilter($keywords, $useSpell = false, &$pertinenceOrder = '', &$highlightWords = '', $usePartial = false)
878  {
879  $filter = "";
880  $rank = "";
881  $words = array();
882  $currentOperator = "and";
883  $parenthesisBalanced = 0;
884  $filterElement = "";
885  $parenthesis = "";
886  $rankElement = "";
887  $stringWords = array();
888 
889  $convertOperatorToTs = function ($operator)
890  {
891  if ($operator === "") {
892  return "";
893  }
894  if ($operator === "and") {
895  return "&";
896  } else if ($operator === "or") {
897  return "|";
898  } else {
899  throw new \Dcp\SearchDoc\Exception("SD0002", $operator);
900  }
901  };
902 
903  $filterElements = \Dcp\Lex\GeneralFilter::analyze($keywords);
904  if ($usePartial) {
905  $isOnlyWord = false;
906  } else {
907  $isOnlyWord = true;
908  foreach ($filterElements as $currentFilter) {
909  if ($usePartial && $currentFilter["mode"] === \Dcp\Lex\GeneralFilter::MODE_WORD) {
910  $isOnlyWord = false;
911  $currentFilter["mode"] = \Dcp\Lex\GeneralFilter::MODE_PARTIAL_BOTH;
912  }
913  if (!in_array($currentFilter["mode"], array(
914  \Dcp\Lex\GeneralFilter::MODE_OR,
915  \Dcp\Lex\GeneralFilter::MODE_AND,
916  \Dcp\Lex\GeneralFilter::MODE_OPEN_PARENTHESIS,
917  \Dcp\Lex\GeneralFilter::MODE_CLOSE_PARENTHESIS,
918  \Dcp\Lex\GeneralFilter::MODE_WORD,
919  ))) {
920  $isOnlyWord = false;
921  break;
922  }
923  }
924  }
925  foreach ($filterElements as $currentElement) {
926  if ($usePartial && ($currentElement["mode"] === \Dcp\Lex\GeneralFilter::MODE_WORD || $currentElement["mode"] === \Dcp\Lex\GeneralFilter::MODE_STRING)) {
927  $currentElement["mode"] = \Dcp\Lex\GeneralFilter::MODE_PARTIAL_BOTH;
928  }
929  switch ($currentElement["mode"]) {
930  case \Dcp\Lex\GeneralFilter::MODE_OR:
931  $currentOperator = "or";
932  break;
933 
934  case \Dcp\Lex\GeneralFilter::MODE_AND:
935  $currentOperator = "and";
936  break;
937 
938  case \Dcp\Lex\GeneralFilter::MODE_OPEN_PARENTHESIS:
939  $parenthesis = "(";
940  $parenthesisBalanced+= 1;
941  break;
942 
943  case \Dcp\Lex\GeneralFilter::MODE_CLOSE_PARENTHESIS:
944  $parenthesis = ")";
945  $parenthesisBalanced-= 1;
946  break;
947 
948  case \Dcp\Lex\GeneralFilter::MODE_WORD:
949  $filterElement = $currentElement["word"];
950  if ($useSpell) {
951  $filterElement = self::testSpell($currentElement["word"]);
952  }
953  $rankElement = unaccent($filterElement);
954  if (is_numeric($filterElement)) {
955  $filterElement = sprintf("(%s|-%s)", $filterElement, $filterElement);
956  }
957 
958  $words[] = $filterElement;
959  if ($isOnlyWord) {
960  $filterElement = pg_escape_string(unaccent($filterElement));
961  } else {
962  $to_tsquery = sprintf("to_tsquery('french', E'%s')", pg_escape_string(unaccent($filterElement)));
963  $dbObj = new DbObj('');
964  $point = sprintf('dcp:%s', uniqid(__METHOD__));
965  $dbObj->savePoint($point);
966  try {
967  simpleQuery('', sprintf("select %s", $to_tsquery) , $indexedWord, true, true);
968  $dbObj->rollbackPoint($point);
969  }
970  catch(Dcp\Db\Exception $e) {
971  $dbObj->rollbackPoint($point);
972  throw new \Dcp\SearchDoc\Exception("SD0007", unaccent($filterElement));
973  }
974  if ($indexedWord) {
975  $filterElement = sprintf("(fulltext @@ E'%s')", pg_escape_string($indexedWord));
976  } else {
977  //ignore stop words
978  $filterElement = "";
979  }
980  }
981  break;
982 
983  case \Dcp\Lex\GeneralFilter::MODE_STRING:
984  $rankElement = unaccent($currentElement["word"]);
985  if (!preg_match('/\p{L}|\p{N}/u', mb_substr($rankElement, 0, 1))) {
986  $begin = '[£|\\\\s]';
987  } else {
988  $begin = '\\\\y';
989  }
990  if (!preg_match('/\p{L}|\p{N}/u', mb_substr($rankElement, -1))) {
991  $end = '[£|\\\\s]';
992  } else {
993  $end = '\\\\y';
994  }
995  /* Strip non-word chars to prevent errors with to_tsquery() */
996  $rankElement = trim(preg_replace('/[^\w]+/', ' ', $rankElement));
997  $stringWords[] = $rankElement;
998 
999  $filterElement = sprintf("svalues ~* E'%s%s%s'", $begin, pg_escape_string(preg_quote($currentElement["word"])) , $end);
1000  break;
1001 
1002  case \Dcp\Lex\GeneralFilter::MODE_PARTIAL_END:
1003  $rankElement = unaccent($currentElement["word"]);
1004 
1005  if (!preg_match('/\p{L}|\p{N}/u', mb_substr($rankElement, 0, 1))) {
1006  $begin = '[£|\\\\s]';
1007  } else {
1008  $begin = '\\\\y';
1009  }
1010  $filterElement = sprintf("svalues ~* E'%s%s'", $begin, pg_escape_string(preg_quote($currentElement["word"])));
1011  break;
1012 
1013  case \Dcp\Lex\GeneralFilter::MODE_PARTIAL_BEGIN:
1014  $rankElement = unaccent($currentElement["word"]);
1015 
1016  if (!preg_match('/\p{L}|\p{N}/u', mb_substr($rankElement, -1))) {
1017  $end = '[£|\\\\s]';
1018  } else {
1019  $end = '\\\\y';
1020  }
1021  $filterElement = sprintf("svalues ~* E'%s%s'", pg_escape_string(preg_quote($currentElement["word"])) , $end);
1022  break;
1023 
1024  case \Dcp\Lex\GeneralFilter::MODE_PARTIAL_BOTH:
1025  $rankElement = unaccent($currentElement["word"]);
1026  if ($usePartial) {
1027  $stringWords[] = $currentElement["word"];
1028  }
1029  $filterElement = sprintf("svalues ~* E'%s'", pg_escape_string(preg_quote($currentElement["word"])));
1030  break;
1031  }
1032  if ($filterElement) {
1033  if ($isOnlyWord) {
1034  $filter.= $filter ? $convertOperatorToTs($currentOperator) . $filterElement : $filterElement;
1035  } else {
1036  $filter.= $filter ? " " . $currentOperator . " " . $filterElement : $filterElement;
1037  }
1038  $rank.= $rank ? $convertOperatorToTs($currentOperator) . $rankElement : $rankElement;
1039  $filterElement = "";
1040  $currentOperator = "and";
1041  } else if ($parenthesis) {
1042  if ($isOnlyWord) {
1043 
1044  $filter.= $filter && $parenthesis === "(" ? $convertOperatorToTs($currentOperator) . $parenthesis : $parenthesis;
1045  } else {
1046  $filter.= $filter && $parenthesis === "(" ? " " . $currentOperator . " " . $parenthesis : $parenthesis;
1047  }
1048  $rank.= $rank && $parenthesis === "(" ? $convertOperatorToTs($currentOperator) . $parenthesis : $parenthesis;
1049  $currentOperator = $parenthesis === "(" ? "" : "and";
1050  $parenthesis = "";
1051  }
1052  }
1053  if ($parenthesisBalanced !== 0) {
1054  throw new \Dcp\SearchDoc\Exception("SD0003", $keywords);
1055  }
1056  if ($isOnlyWord) {
1057  $filter = str_replace(')(', ')&(', $filter);
1058  $filter = sprintf("fulltext @@ to_tsquery('french', E'%s')", pg_escape_string($filter));
1059  }
1060 
1061  $pertinenceOrder = sprintf("ts_rank(fulltext,to_tsquery('french', E'%s')) desc, id desc", pg_escape_string(preg_replace('/\s+/u', '&', $rank)));
1062 
1063  $highlightWords = implode("|", array_merge($words, $stringWords));
1064 
1065  return $filter;
1066  }
1067  /**
1068  * return a document part where general filter term is found
1069  *
1070  * @see SearchDoc::addGeneralFilter
1071  * @param Doc $doc document to analyze
1072  * @param string $beginTag delimiter begin tag
1073  * @param string $endTag delimiter end tag
1074  * @param int $limit file size limit to analyze
1075  * @return mixed
1076  */
1077  public function getHighLightText(Doc & $doc, $beginTag = '<b>', $endTag = '</b>', $limit = 200, $wordMode = true)
1078  {
1079  static $oh = null;
1080  if (!$oh) {
1081  $oh = new SearchHighlight();
1082  }
1083  if ($beginTag) $oh->beginTag = $beginTag;
1084  if ($endTag) $oh->endTag = $endTag;
1085  if ($limit > 0) $oh->setLimit($limit);
1086  simpleQuery($this->dbaccess, sprintf("select svalues from docread where id=%d", $doc->id) , $text, true, true);
1087 
1088  if ($wordMode) {
1089  $h = $oh->highlight($text, $this->highlightWords);
1090  } else {
1091  $h = $oh->rawHighlight($text, $this->highlightWords);
1092  }
1093 
1094  return $h;
1095  }
1096  /**
1097  * detect if word is a word of language
1098  * if not the near word is set to do an OR condition
1099  * @static
1100  * @param string $word word to analyze
1101  * @param string $language
1102  * @return string word with its correction if it is not correct
1103  */
1104  protected static function testSpell($word, $language = "fr")
1105  {
1106  static $pspell_link = null;
1107  if (function_exists('pspell_new')) {
1108  if (!$pspell_link) $pspell_link = pspell_new($language, "", "", "utf-8", PSPELL_FAST);
1109  if ((!is_numeric($word)) && (!pspell_check($pspell_link, $word))) {
1110  $suggestions = pspell_suggest($pspell_link, $word);
1111  $sug = false;
1112  if (isset($suggestions[0])) {
1113  $sug = unaccent($suggestions[0]);
1114  }
1115  if ($sug && ($sug != unaccent($word)) && (!strstr($sug, ' '))) {
1116  $word = sprintf("(%s|%s)", $word, $sug);
1117  }
1118  }
1119  }
1120  return $word;
1121  }
1122  /**
1123  * return where condition like : foo in ('x','y','z')
1124  *
1125  * @static
1126  * @param array $values set of values
1127  * @param string $column database column name
1128  * @param bool $integer set to true if database column is numeric type
1129  * @return string
1130  */
1131  public static function sqlcond(array $values, $column, $integer = false)
1132  {
1133  $sql_cond = "true";
1134  if (count($values) > 0) {
1135  if ($integer) { // for integer type
1136  $sql_cond = "$column in (";
1137  $sql_cond.= implode(",", $values);
1138  $sql_cond.= ")";
1139  } else { // for text type
1140  foreach ($values as & $v) $v = pg_escape_string($v);
1141  $sql_cond = "$column in ('";
1142  $sql_cond.= implode("','", $values);
1143  $sql_cond.= "')";
1144  }
1145  }
1146 
1147  return $sql_cond;
1148  }
1149  /**
1150  * no use access view control in filters
1151  * @see SearchDoc::overrideViewControl
1152  *
1153  * @deprecated use { @link SearchDoc::overrideViewControl } instead
1154  * @return void
1155  */
1156  public function noViewControl()
1157  {
1159  $this->overrideViewControl();
1160  }
1161  /**
1162  * no use access view control in filters
1163  * @api no add view access criteria in final query
1164  * @return void
1165  */
1166  public function overrideViewControl()
1167  {
1168  $this->userid = 1;
1169  }
1170  /**
1171  * the return of ::search will be array of document's object
1172  *
1173  * @api set return type : document object or document array
1174  * @param bool $returnobject set to true to return object, false to return raw data
1175  * @return void
1176  */
1177  public function setObjectReturn($returnobject = true)
1178  {
1179  if ($returnobject) $this->mode = "ITEM";
1180  else $this->mode = "TABLE";
1181  }
1182 
1183  public function isObjectReturn()
1184  {
1185  return ($this->mode == "ITEM");
1186  }
1187  /**
1188  * the return of ::search will be array of values
1189  * @deprecated use setObjectReturn(false) instead
1190  * @return void
1191  */
1192  public function setValueReturn()
1193  {
1195  $this->mode = "TABLE";
1196  }
1197  /**
1198  * add a filter to not return confidential document if current user cannot see it
1199  * @api add a filter to not return confidential
1200  * @param boolean $exclude set to true to exclude confidential
1201  * @return void
1202  */
1203  public function excludeConfidential($exclude = true)
1204  {
1205  if ($exclude) {
1206  if ($this->userid != 1) {
1207  $this->excludeFilter = sprintf("confidential is null or hasaprivilege('%s', profid,%d)", DocPerm::getMemberOfVector($this->userid) , 1 << POS_CONF);
1208  }
1209  } else {
1210  $this->excludeFilter = '';
1211  }
1212  }
1213 
1214  protected function recursiveSearchInit()
1215  {
1216  if ($this->recursiveSearch && $this->dirid) {
1217  if (!$this->originalDirId) {
1218  $this->originalDirId = $this->dirid;
1219  }
1220  /*
1221  * @var DocSearch $tmps
1222  */
1223  $tmps = createTmpDoc($this->dbaccess, "SEARCH");
1224  $tmps->setValue(\Dcp\AttributeIdentifiers\Search::se_famid, $this->fromid);
1225  $tmps->setValue(\Dcp\AttributeIdentifiers\Search::se_idfld, $this->originalDirId);
1226  $tmps->setValue(\Dcp\AttributeIdentifiers\Search::se_latest, "yes");
1227  $err = $tmps->add();
1228  if ($err == "") {
1229  $tmps->addQuery($tmps->getQuery()); // compute internal sql query
1230  $this->dirid = $tmps->id;
1231  } else {
1232  throw new \Dcp\SearchDoc\Exception("SD0005", $err);
1233  }
1234  }
1235  }
1236  /**
1237  * Get the SQL queries that will be executed by the search() method
1238  * @return array|bool boolean false on error, or array() of queries on success.
1239  */
1240  public function getQueries()
1241  {
1243  $dirid = $this->dirid;
1245  $sqlfilters = $this->getFilters();
1248  $trash = $this->trash;
1250  $join = $this->join;
1251 
1252  $normFromId = $this->normalizeFromId($fromid);
1253  if ($normFromId === false) {
1254  $this->debuginfo["error"] = sprintf(_("%s is not a family") , $fromid);
1255  return false;
1256  }
1257  $fromid = $normFromId;
1258  if (($fromid != "") && (!is_numeric($fromid))) {
1259  preg_match('/^(?P<sign>-?)(?P<fromid>.+)$/', trim($fromid) , $m);
1260  $fromid = $m['sign'] . getFamIdFromName($dbaccess, $m['fromid']);
1261  }
1262  if ($this->only && strpos($fromid, '-') !== 0) {
1263  $fromid = '-' . $fromid;
1264  }
1265  $table = "doc";
1266  $only = "";
1267 
1268  if ($fromid == - 1) {
1269  $table = "docfam";
1270  } elseif ($fromid < 0) {
1271  $only = "only";
1272  $fromid = - $fromid;
1273  $table = "doc$fromid";
1274  } else {
1275  if ($fromid != 0) {
1276  if (isSimpleFilter($sqlfilters) && (familyNeedDocread($dbaccess, $fromid))) {
1277  $table = "docread";
1278  $fdoc = new_doc($dbaccess, $fromid);
1279  $sqlfilters[-4] = GetSqlCond(array_merge(array(
1280  $fromid
1281  ) , array_keys($fdoc->GetChildFam())) , "fromid", true);
1282  } else {
1283  $table = "doc$fromid";
1284  }
1285  } elseif ($fromid == 0) {
1286  if (isSimpleFilter($sqlfilters)) $table = "docread";
1287  }
1288  }
1289  $maintable = $table; // can use join only on search
1290  if ($join) {
1291  if (preg_match('/(?P<attr>[a-z0-9_\-:]+)\s*(?P<operator>=|<|>|<=|>=)\s*(?P<family>[a-z0-9_\-:]+)\((?P<family_attr>[^\)]*)\)/', $join, $reg)) {
1292  $joinid = getFamIdFromName($dbaccess, $reg['family']);
1293  $jointable = ($joinid) ? "doc" . $joinid : $reg['family'];
1294 
1295  $sqlfilters[] = sprintf("%s.%s %s %s.%s", $table, $reg['attr'], $reg['operator'], $jointable, $reg['family_attr']); // "id = dochisto(id)";
1296  $maintable = $table;
1297  $table.= ", " . $jointable;
1298  } else {
1299  addWarningMsg(sprintf(_("search join syntax error : %s") , $join));
1300  return false;
1301  }
1302  }
1303  $maintabledot = ($maintable && $dirid == 0) ? $maintable . '.' : '';
1304 
1305  if ($distinct) {
1306  $selectfields = "distinct on ($maintable.initid) $maintable.*";
1307  } else {
1308  $selectfields = "$maintable.*";
1309  $sqlfilters[-2] = $maintabledot . "doctype != 'T'";
1310  ksort($sqlfilters);
1311  }
1312  $sqlcond = "true";
1313  ksort($sqlfilters);
1314  if (count($sqlfilters) > 0) $sqlcond = " (" . implode(") and (", $sqlfilters) . ")";
1315 
1316  $qsql = '';
1317  if ($dirid == 0) {
1318  //-------------------------------------------
1319  // search in all Db
1320  //-------------------------------------------
1321  if (strpos(implode(",", $sqlfilters) , "archiveid") === false) $sqlfilters[-4] = $maintabledot . "archiveid is null";
1322 
1323  if ($trash === "only") {
1324  $sqlfilters[-3] = $maintabledot . "doctype = 'Z'";
1325  } elseif ($trash !== "also") {
1326  $sqlfilters[-3] = $maintabledot . "doctype != 'Z'";
1327  }
1328 
1329  if (($latest) && (($trash == "no") || (!$trash))) {
1330  $sqlfilters[-1] = $maintabledot . "locked != -1";
1331  }
1332  ksort($sqlfilters);
1333  if (count($sqlfilters) > 0) {
1334  $sqlcond = " (" . implode(") and (", $sqlfilters) . ")";
1335  }
1336  $qsql = "select $selectfields " . "from $only $table " . "where " . $sqlcond;
1337  $qsql = $this->injectFromClauseForOrderByLabel($fromid, $this->orderbyLabel, $qsql);
1338  } else {
1339  //-------------------------------------------
1340  // in a specific folder
1341  //-------------------------------------------
1342  $fld = new_Doc($dbaccess, $dirid);
1343  if ($fld->defDoctype != 'S') {
1344  /*
1345  * @var Dir $fld
1346  */
1347  $hasFilters = false;
1348  if ($fld && method_exists($fld, "getSpecificFilters")) {
1349  $specFilters = $fld->getSpecificFilters();
1350  if (is_array($specFilters) && (count($specFilters) > 0)) {
1351  $sqlfilters = array_merge($sqlfilters, $specFilters);
1352  $hasFilters = true;
1353  }
1354  }
1355  if (strpos(implode(",", $sqlfilters) , "archiveid") === false) $sqlfilters[-4] = $maintabledot . "archiveid is null";
1356  //if ($fld->getRawValue("se_trash")!="yes") $sqlfilters[-3] = "doctype != 'Z'";
1357  if ($trash == "only") $sqlfilters[-1] = "locked = -1";
1358  elseif ($latest) $sqlfilters[-1] = "locked != -1";
1359  ksort($sqlfilters);
1360  if (count($sqlfilters) > 0) $sqlcond = " (" . implode(") and (", $sqlfilters) . ")";
1361 
1362  $sqlfld = "dirid=$dirid and qtype='S'";
1363  if ($fromid == 2) $sqlfld.= " and doctype='D'";
1364  if ($fromid == 5) $sqlfld.= " and doctype='S'";
1365  if ($hasFilters) {
1366  $sqlcond = " (" . implode(") and (", $sqlfilters) . ")";
1367  $qsql = "select $selectfields from $only $table where $sqlcond ";
1368  } else {
1369  $q = new QueryDb($dbaccess, "QueryDir");
1370  $q->AddQuery($sqlfld);
1371  $tfld = $q->Query(0, 0, "TABLE");
1372  if ($q->nb > 0) {
1373  $tfldid = array();
1374  foreach ($tfld as $onefld) {
1375  $tfldid[] = $onefld["childid"];
1376  }
1377  if (count($tfldid) > 1000) {
1378  $qsql = "select $selectfields " . "from $table where initid in (select childid from fld where $sqlfld) " . "and $sqlcond ";
1379  } else {
1380  $sfldids = implode(",", $tfldid);
1381  if ($table == "docread") {
1382  /*$qsql= "select $selectfields ".
1383  "from $table where initid in (select childid from fld where $sqlfld) ".
1384  "and $sqlcond "; */
1385  $qsql = "select $selectfields " . "from $table where initid in ($sfldids) " . "and $sqlcond ";
1386  } else {
1387  /*$qsql= "select $selectfields ".
1388  "from (select childid from fld where $sqlfld) as fld2 inner join $table on (initid=childid) ".
1389  "where $sqlcond ";*/
1390  $qsql = "select $selectfields " . "from $only $table where initid in ($sfldids) " . "and $sqlcond ";
1391  }
1392  }
1393  }
1394  }
1395  } else {
1396  //-------------------------------------------
1397  // search familly
1398  //-------------------------------------------
1399  $docsearch = new QueryDb($dbaccess, "QueryDir");
1400  $docsearch->AddQuery("dirid=$dirid");
1401  $docsearch->AddQuery("qtype = 'M'");
1402  $ldocsearch = $docsearch->Query(0, 0, "TABLE");
1403  // for the moment only one query search
1404  if (($docsearch->nb) > 0) {
1405  switch ($ldocsearch[0]["qtype"]) {
1406  case "M": // complex query
1407  // $sqlM=$ldocsearch[0]["query"];
1408  $fld = new_Doc($dbaccess, $dirid);
1409  /*
1410  * @var DocSearch $fld
1411  */
1412  if ($trash) {
1413  $fld->setValue("se_trash", $trash);
1414  } else {
1415  $trash = $fld->getRawValue("se_trash");
1416  }
1417  $fld->folderRecursiveLevel = $folderRecursiveLevel;
1418  $tsqlM = $fld->getQuery();
1419  $qsql=[];
1420  foreach ($tsqlM as $sqlM) {
1421  if ($sqlM != false) {
1422  if (!preg_match("/doctype[ ]*=[ ]*'Z'/", $sqlM, $reg)) {
1423  if (($trash != "also") && ($trash != "only")) {
1424  $sqlfilters[-3] = "doctype != 'Z'"; // no zombie if no trash
1425 
1426  }
1427  ksort($sqlfilters);
1428  foreach ($sqlfilters as $kf => $sf) { // suppress doubles
1429  if (strstr($sqlM, $sf)) {
1430  unset($sqlfilters[$kf]);
1431  }
1432  }
1433  if (count($sqlfilters) > 0) {
1434  $sqlcond = " (" . implode(") and (", $sqlfilters) . ")";
1435  } else {
1436  $sqlcond = "";
1437  }
1438  }
1439  if ($fromid > 0) {
1440  $sqlM = str_replace("from doc ", "from $only $table ", $sqlM);
1441  }
1442  $fldFromId = ($fromid == 0) ? $fld->getRawValue('se_famid', 0) : $fromid;
1443  $sqlM = $this->injectFromClauseForOrderByLabel($fldFromId, $this->orderbyLabel, $sqlM);
1444  if ($sqlcond) {
1445  $qsql[] = $sqlM . " and " . $sqlcond;
1446  } else {
1447  $qsql[] = $sqlM;
1448  }
1449  }
1450  }
1451  break;
1452  }
1453  } else {
1454  return false; // no query avalaible
1455 
1456  }
1457  }
1458  }
1459  if (is_array($qsql)) return $qsql;
1460  return array(
1461  $qsql
1462  );
1463  }
1464  /**
1465  * Insert an additional relation in the FROM clause of the given query
1466  * to perform a sort on a label/title instead of a key/id.
1467  *
1468  * After rewriting the query, the new column name which will serve for
1469  * the ordering is stored into the private _orderbyLabelMaps struct
1470  * which will be used later when the "ORDER BY" directive will be
1471  * constructed.
1472  *
1473  * @param int $fromid The identifier of the family which the query is based on
1474  * @param string $column The name of the column on which the result is supposed to be be ordered
1475  * @param string $sqlM The SQL query in which an additional FROM relation should be injected
1476  * @return string The modified query
1477  */
1478  private function injectFromClauseForOrderByLabel($fromid, $column, $sqlM)
1479  {
1480  if ($column == '') {
1481  return $sqlM;
1482  }
1483  $attr = $this->_getAttributeFromColumn($fromid, $column);
1484  if ($attr === false || $attr->isMultiple()) {
1485  return $sqlM;
1486  }
1487  switch ($attr->type) {
1488  case 'enum':
1489  $enumKeyLabelList = $attr->getEnum();
1490  $mapValues = array(
1491  "('', NULL)"
1492  );
1493  foreach ($enumKeyLabelList as $key => $label) {
1494  $mapValues[] = sprintf("('%s', '%s')", pg_escape_string($key) , pg_escape_string($label));
1495  }
1496  $map = sprintf('(VALUES %s) AS map_%s(key, label)', join(', ', $mapValues) , $attr->id);
1497  $where = sprintf("map_%s.key = coalesce(doc%s.%s, '')", $attr->id, $fromid, $attr->id);
1498 
1499  $sqlM = preg_replace('/ where /i', ", $map where ($where) and ", $sqlM);
1500  $this->orderby = preg_replace(sprintf('/\b%s\b/', preg_quote($column, "/")) , sprintf("map_%s.label", $attr->id) , $this->orderby);
1501  break;
1502 
1503  case 'docid':
1504  /*
1505  * No need to inject anything, just remap the docid attribute
1506  * to the one holding the title.
1507  */
1508  $opt_doctitle = $attr->getOption('doctitle');
1509  if ($opt_doctitle != '') {
1510  if ($opt_doctitle == 'auto') {
1511  $opt_doctitle = sprintf('%s_title', $attr->id);
1512  }
1513  $this->orderby = preg_replace(sprintf('/\b%s\b/', preg_quote($column, "/")) , $opt_doctitle, $this->orderby);
1514  }
1515  }
1516  return $sqlM;
1517  }
1518  /**
1519  * Get the NormalAttribute object corresponding to the column of the given family
1520  *
1521  * @param $fromid
1522  * @param $column
1523  * @return NormalAttribute|bool
1524  */
1525  private function _getAttributeFromColumn($fromid, $column)
1526  {
1527  $fam = new_Doc($this->dbaccess, $fromid);
1528  if (!$fam->isAlive()) {
1529  return false;
1530  }
1531  $attrList = $fam->getNormalAttributes();
1532  foreach ($attrList as $attr) {
1533  if ($attr->id == $column) {
1534  return $attr;
1535  }
1536  }
1537  return false;
1538  }
1539 }
getHighLightText(Doc &$doc, $beginTag= '< b >', $endTag= '</b >', $limit=200, $wordMode=true)
if(substr($wsh, 0, 1)!= '/') $args
static sqlcond(array $values, $column, $integer=false)
static getMemberOfVector($uid=0, $strict=false)
$tdoc
join($jointure)
excludeConfidential($exclude=true)
$s slice
$memberOf
static checkGeneralFilter($keyword)
static getUserMemberOf($uid, $strict=false)
setDebugMode($debug=true)
familyNeedDocread($dbaccess, $id)
Definition: Lib.Dir.php:1082
static getGeneralFilter($keywords, $useSpell=false, &$pertinenceOrder= '', &$highlightWords= '', $usePartial=false)
useCollection($dirid)
addWarningMsg($msg)
Definition: Lib.Common.php:95
static analyze($source, $onlyToken=false)
setObjectReturn($returnobject=true)
returnsOnly(array $returns)
setSlice($slice)
const POS_CONF
setOrder($order, $orderbyLabel= '')
getDbid($dbaccess)
Definition: Lib.Common.php:353
getNextDocument(Array $v)
createDoc($dbaccess, $fromid, $control=true, $defaultvalues=true, $temporary=false)
setPertinenceOrder($keyword= '')
isSimpleFilter($sqlfilters)
Definition: Lib.Dir.php:68
if($updateExistingTable) $point
Definition: updateclass.php:88
internalGetDocCollection($dbaccess, $dirid, $start="0", $slice="ALL", $sqlfilters=array(), $userid=1, $qtype="LIST", $fromid="", $distinct=false, $orderby="title", $latest=true, $trash="", &$debug=null, $folderRecursiveLevel=2, $join= '',\SearchDoc &$searchDoc=null)
Definition: Lib.Dir.php:428
getFamIdFromName($dbaccess, $name)
if(is_numeric($parms['famid'])) $attrList
deprecatedFunction($msg= '')
Definition: Lib.Common.php:86
$s start
getDbAccess()
Definition: Lib.Common.php:368
new_Doc($dbaccess, $id= '', $latest=false)
$dir
Definition: resizeimg.php:144
GetSqlCond($Table, $column, $integer=false)
setRecursiveSearch($recursiveMode=true, $level=2)
addFilter($filter, $args= '')
static testSpell($word, $language="fr")
$keyword
static getUserViewVector($uid)
$dbaccess
Definition: checkVault.php:17
unaccent($s)
Definition: Lib.Util.php:569
getUserId()
Definition: Lib.Common.php:608
simpleQuery($dbaccess, $query, &$result=array(), $singlecolumn=false, $singleresult=false, $useStrict=null)
Definition: Lib.Common.php:484
__construct($dbaccess= '', $fromid=0)
createTmpDoc($dbaccess, $fromid, $defaultvalue=true)
if($file) if($subject==""&&$file) if($subject=="") $err
$s orderby
addGeneralFilter($keywords, $useSpell=false, $usePartial=false)
setStart($start)
← centre documentaire © anakeen