From 4c6d047bb81e76e0c6d181c19a63ac631babc72b Mon Sep 17 00:00:00 2001 From: Uwe Steinmann Date: Fri, 16 May 2025 07:46:49 +0200 Subject: [PATCH] copy most of the search functions into own class --- inc/inc.Utils.php | 601 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 598 insertions(+), 3 deletions(-) diff --git a/inc/inc.Utils.php b/inc/inc.Utils.php index 049035396..fbf1b6098 100644 --- a/inc/inc.Utils.php +++ b/inc/inc.Utils.php @@ -1399,16 +1399,611 @@ class SeedDMS_FolderTree { /* {{{ */ class SeedDMS_Search { /* {{{ */ protected $dms; + protected $user; + + protected $fulltextservice; + protected $settings; - public function __construct($dms, $settings) { + public $searchparams; + + protected $dcount; + + protected $fcount; + + protected $totalPages; + + protected $entries; + + protected $terms; + + protected $searchTime; + + public function __construct($dms, $user, $fulltextservice, $settings) { $this->dms = $dms; + $this->user = $user; + $this->fulltextservice = $fulltextservice; $this->settings = $settings; + $this->searchparams = []; + $this->dcount = 0; + $this->fcount = 0; + $this->totalPages = 0; + $this->entries = array(); + $this->terms = array(); + $this->searchTime = 0; } - public function createSearchFromQuery($urlquery) { /* {{{ */ + protected function getTime() { /* {{{ */ + if (function_exists('microtime')) { + $tm = microtime(); + $tm = explode(' ', $tm); + return (float) sprintf('%f', $tm[1] + $tm[0]); + } + return time(); } /* }}} */ - public function search() { /* {{{ */ + public function createSearchFromQuery($get) { /* {{{ */ + + /* Creation date {{{ */ + $createstartts = null; + $createstartdate = null; + $createendts = null; + $createenddate = null; + $created['from'] = null; + $created['to'] = null; + if(!empty($get["created"]["from"])) { + $createstartts = makeTsFromDate($get["created"]["from"]); + $createstartdate = array('year'=>(int)date('Y', $createstartts), 'month'=>(int)date('m', $createstartts), 'day'=>(int)date('d', $createstartts), 'hour'=>0, 'minute'=>0, 'second'=>0); + if (!checkdate($createstartdate['month'], $createstartdate['day'], $createstartdate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_create_date_end")); + } + $created['from'] = $createstartts; + } + if(!empty($get["created"]["to"])) { + $createendts = makeTsFromDate($get["created"]["to"]); + $createenddate = array('year'=>(int)date('Y', $createendts), 'month'=>(int)date('m', $createendts), 'day'=>(int)date('d', $createendts), 'hour'=>23, 'minute'=>59, 'second'=>59); + if (!checkdate($createenddate['month'], $createenddate['day'], $createenddate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_create_date_end")); + } + $created['to'] = $createendts; + } + $this->searchparams['created'] = $created; + /* }}} */ + + /* Modification date {{{ */ + $modifystartts = null; + $modifystartdate = null; + $modifyendts = null; + $modifyenddate = null; + $modified['from'] = null; + $modified['to'] = null; + if(!empty($get["modified"]["from"])) { + $modifystartts = makeTsFromDate($get["modified"]["from"]); + $modifystartdate = array('year'=>(int)date('Y', $modifystartts), 'month'=>(int)date('m', $modifystartts), 'day'=>(int)date('d', $modifystartts), 'hour'=>0, 'minute'=>0, 'second'=>0); + if (!checkdate($modifystartdate['month'], $modifystartdate['day'], $modifystartdate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_modification_date_end")); + } + $modified['from'] = $modifystartts; + } + if(!empty($get["modified"]["to"])) { + $modifyendts = makeTsFromDate($get["modified"]["to"]); + $modifyenddate = array('year'=>(int)date('Y', $modifyendts), 'month'=>(int)date('m', $modifyendts), 'day'=>(int)date('d', $modifyendts), 'hour'=>23, 'minute'=>59, 'second'=>59); + if (!checkdate($modifyenddate['month'], $modifyenddate['day'], $modifyenddate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_modification_date_end")); + } + $modified['to'] = $modifyendts; + } + $this->searchparams['modified'] = $modified; + /* }}} */ + + /* Filesize {{{ */ + $filesizestart = 0; + $filesizeend = 0; + $filesize['from'] = null; + $filesize['to'] = null; + if(!empty($get["filesize"]["from"])) { + $filesizestart = $get["filesize"]["from"]; + $filesize['from'] = $get["filesize"]["from"]; + } + if(!empty($get["filesize"]["to"])) { + $filesizeend = $get["filesize"]["to"]; + $filesize['to'] = $get["filesize"]["to"]; + } + $this->searchparams['filesize'] = $filesize; + /* }}} */ + + // Check to see if the search has been restricted to a particular + // document owner. + // $get['owner'] can be a name of an array of names or ids {{{ + $owner = []; + $ownernames = []; // Needed by fulltext search + $ownerobjs = []; // Needed by database search + if(!empty($get["owner"])) { + $owner = $get['owner']; + if (!is_array($get['owner'])) { + if(is_numeric($get['owner'])) + $o = $dms->getUser($get['owner']); + else + $o = $dms->getUserByLogin($get['owner']); + if($o) { + $ownernames[] = $o->getLogin(); + $ownerobjs[] = $o; + } + } else { + foreach($get["owner"] as $l) { + if($l) { + if(is_numeric($l)) + $o = $dms->getUser($l); + else + $o = $dms->getUserByLogin($l); + if($o) { + $ownernames[] = $o->getLogin(); + $ownerobjs[] = $o; + } + } + } + } + } + $this->searchparams['ownernames'] = $ownernames; + $this->searchparams['ownerobjs'] = $ownerobjs; + /* }}} */ + + // category {{{ + $categories = array(); + $categorynames = array(); + $category = array(); + if(isset($get['category']) && $get['category']) { + $category = $get['category']; + foreach($get['category'] as $catid) { + if($catid) { + if(is_numeric($catid)) { + if($cat = $dms->getDocumentCategory($catid)) { + $categories[] = $cat; + $categorynames[] = $cat->getName(); + } + } else { + $categorynames[] = $catid; + } + } + } + } + $this->searchparams['categorynames'] = $categorynames; + $this->searchparams['categories'] = $categories; + /* }}} */ + + if (isset($get["orderby"]) && is_string($get["orderby"])) { + $orderby = $get["orderby"]; + } else { + $orderby = ""; + } + $this->searchparams['orderby'] = $orderby; + + $limit = (isset($get["limit"]) && is_numeric($get["limit"])) ? (int) $get['limit'] : 20; + $this->searchparams['limit'] = $limit; + $fullsearch = ((!isset($get["fullsearch"]) && $this->settings->_defaultSearchMethod == 'fulltext') || !empty($get["fullsearch"])) && $this->settings->_enableFullSearch; + $this->searchparams['fullsearch'] = $fullsearch; + $facetsearch = !empty($get["facetsearch"]) && $this->settings->_enableFullSearch; + $this->searchparams['facetsearch'] = $facetsearch; + + if (isset($get["query"]) && is_string($get["query"])) { + $query = $get["query"]; + } else { + $query = ""; + } + $this->searchparams['query'] = $query; + + // Check to see if the search has been restricted to a particular + // mimetype. {{{ + $mimetype = []; + if (isset($get["mimetype"])) { + if (!is_array($get['mimetype'])) { + if(!empty($get['mimetype'])) + $mimetype[] = $get['mimetype']; + } else { + foreach($get["mimetype"] as $l) { + if($l) + $mimetype[] = $l; + } + } + } + $this->searchparams['mimetype'] = $mimetype; + /* }}} */ + + // status + $status = isset($get['status']) ? $get['status'] : array(); + $this->searchparams['status'] = $status; + + // Get the page number to display. If the result set contains more than + // 25 entries, it is displayed across multiple pages. + // + // This requires that a page number variable be used to track which page the + // user is interested in, and an extra clause on the select statement. + // + // Default page to display is always one. + $pageNumber=1; + if (isset($get["pg"])) { + if (is_numeric($get["pg"]) && $get["pg"]>0) { + $pageNumber = (int) $get["pg"]; + } + elseif (!strcasecmp($get["pg"], "all")) { + $pageNumber = "all"; + } + } + $this->searchparams['pageNumber'] = $pageNumber; + + if($fullsearch) { + // Search in Fulltext {{{ + + // record_type + if(isset($get['record_type'])) + $record_type = $get['record_type']; + else + $record_type = array(); + $this->searchparams['record_type'] = $record_type; + + if (isset($get["attributes"])) + $attributes = $get["attributes"]; + else + $attributes = array(); + + foreach($attributes as $an=>&$av) { + if(substr($an, 0, 5) == 'attr_') { + $tmp = explode('_', $an); + if($attrdef = $dms->getAttributeDefinition($tmp[1])) { + switch($attrdef->getType()) { + /* Turn dates into timestamps */ + case SeedDMS_Core_AttributeDefinition::type_date: + foreach(['from', 'to'] as $kk) + if(!empty($av[$kk])) { + if(!is_numeric($av[$kk])) { + $av[$kk] = makeTsFromDate($av[$kk]); + } + } + break; + } + } + } + } + $this->searchparams['attributes'] = $attributes; + + /* Create $order array for fulltext search */ + $order = ['by'=>'', 'dir'=>'']; + switch($orderby) { + case 'dd': + $order = ['by'=>'created', 'dir'=>'desc']; + break; + case 'd': + $order = ['by'=>'created', 'dir'=>'asc']; + break; + case 'nd': + $order = ['by'=>'title', 'dir'=>'desc']; + break; + case 'n': + $order = ['by'=>'title', 'dir'=>'asc']; + break; + case 'id': + $order = ['by'=>'id', 'dir'=>'desc']; + break; + case 'i': + $order = ['by'=>'id', 'dir'=>'asc']; + break; + default: + $order = ['by'=>'', 'dir'=>'']; + } + $this->searchparams['order'] = $order; + + // Check to see if the search has been restricted to a particular sub-tree in + // the folder hierarchy. + $startFolder = null; + if (isset($get["folderfullsearchid"]) && is_numeric($get["folderfullsearchid"]) && $get["folderfullsearchid"]>0) { + $targetid = $get["folderfullsearchid"]; + $startFolder = $this->dms->getFolder($targetid); + if (!is_object($startFolder)) { + UI::exitError(getMLText("search"),getMLText("invalid_folder_id")); + } + } + $this->searchparams['startFolder'] = $startFolder; + + $rootFolder = $this->dms->getFolder($this->settings->_rootFolderID); + $this->searchparams['rootFolder'] = $rootFolder; + + // }}} + } else { + // Search in Database {{{ + + /* Select if only documents (0x01), only folders (0x02) or both (0x03) + * are found + */ + $resultmode = 0x03; + if (isset($get["resultmode"]) && is_numeric($get["resultmode"])) { + $resultmode = $get['resultmode']; + } + $this->searchparams['resultmode'] = $resultmode; + + $mode = "AND"; + if (isset($get["mode"]) && is_numeric($get["mode"]) && $get["mode"]==0) { + $mode = "OR"; + } + $this->searchparams['mode'] = $mode; + + $searchin = array(); + if (isset($get['searchin']) && is_array($get["searchin"])) { + foreach ($get["searchin"] as $si) { + if (isset($si) && is_numeric($si)) { + switch ($si) { + case 1: // keywords + case 2: // name + case 3: // comment + case 4: // attributes + case 5: // id + $searchin[$si] = $si; + break; + } + } + } + } + + // if none is checkd search all + if (count($searchin)==0) $searchin=array(1, 2, 3, 4, 5); + $this->searchparams['searchin'] = $searchin; + + // Check to see if the search has been restricted to a particular sub-tree in + // the folder hierarchy. + if (isset($get["targetid"]) && is_numeric($get["targetid"]) && $get["targetid"]>0) { + $targetid = $get["targetid"]; + $startFolder = $this->dms->getFolder($targetid); + } + else { + $startFolder = $this->dms->getRootFolder(); + } + if (!is_object($startFolder)) { + UI::exitError(getMLText("search"),getMLText("invalid_folder_id")); + } + $this->searchparams['startFolder'] = $startFolder; + + /* Status date {{{ */ + $statusstartdate = array(); + $statusenddate = array(); + if(!empty($get["statusdatestart"])) { + $statusstartts = makeTsFromDate($get["statusdatestart"]); + $statusstartdate = array('year'=>(int)date('Y', $statusstartts), 'month'=>(int)date('m', $statusstartts), 'day'=>(int)date('d', $statusstartts), 'hour'=>0, 'minute'=>0, 'second'=>0); + } + $this->searchparams['statusstartdate'] = $statusstartdate; + if ($statusstartdate && !checkdate($statusstartdate['month'], $statusstartdate['day'], $statusstartdate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_status_date_start")); + } + + if(!empty($get["statusdateend"])) { + $statusendts = makeTsFromDate($get["statusdateend"]); + $statusenddate = array('year'=>(int)date('Y', $statusendts), 'month'=>(int)date('m', $statusendts), 'day'=>(int)date('d', $statusendts), 'hour'=>23, 'minute'=>59, 'second'=>59); + } + if ($statusenddate && !checkdate($statusenddate['month'], $statusenddate['day'], $statusenddate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_status_date_end")); + } + $this->searchparams['statusenddate'] = $statusenddate; + /* }}} */ + + /* Expiration date {{{ */ + $expstartdate = array(); + $expenddate = array(); + if(!empty($get["expirationstart"])) { + $expstartts = makeTsFromDate($get["expirationstart"]); + $expstartdate = array('year'=>(int)date('Y', $expstartts), 'month'=>(int)date('m', $expstartts), 'day'=>(int)date('d', $expstartts), 'hour'=>0, 'minute'=>0, 'second'=>0); + if (!checkdate($expstartdate['month'], $expstartdate['day'], $expstartdate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_expiration_date_start")); + } + } + if(!empty($get["expirationend"])) { + $expendts = makeTsFromDate($get["expirationend"]); + $expenddate = array('year'=>(int)date('Y', $expendts), 'month'=>(int)date('m', $expendts), 'day'=>(int)date('d', $expendts), 'hour'=>23, 'minute'=>59, 'second'=>59); + if (!checkdate($expenddate['month'], $expenddate['day'], $expenddate['year'])) { + UI::exitError(getMLText("search"),getMLText("invalid_expiration_date_end")); + } + } + /* }}} */ + + /* Do not search for folders if result shall be filtered by status. + * If this is not done, unexplainable results will be delivered. + * e.g. a search for expired documents of a given user will list + * also all folders of that user because the status doesn't apply + * to folders. + */ + // if($status) + // $resultmode = 0x01; + + if (isset($get["attributes"])) + $attributes = $get["attributes"]; + else + $attributes = array(); + + foreach($attributes as $attrdefid=>$attribute) { + $attrdef = $this->dms->getAttributeDefinition($attrdefid); + if($attribute) { + if($attrdef->getType() == SeedDMS_Core_AttributeDefinition::type_date) { + if(is_array($attribute)) { + if(!empty($attributes[$attrdefid]['from'])) + $attributes[$attrdefid]['from'] = date('Y-m-d', makeTsFromDate($attribute['from'])); + if(!empty($attributes[$attrdefid]['to'])) + $attributes[$attrdefid]['to'] = date('Y-m-d', makeTsFromDate($attribute['to'])); + } else { + $attributes[$attrdefid] = date('Y-m-d', makeTsFromDate($attribute)); + } + } + } + } + $this->searchparams['attributes'] = $attributes; + + // }}} + } + return $this->searchparams; } /* }}} */ + + public function setSearchParam($name, $value) { + $this->searchparams[$name] = $value; + } + + public function search() { /* {{{ */ + if($this->searchparams['fullsearch']) { + if($this->settings->_fullSearchEngine == 'lucene') { + Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8'); + } + + $startTime = $this->getTime(); + $total = 0; + $index = $this->fulltextservice->Indexer(); + if($index) { + if(!empty($this->settings->_suggestTerms) && !empty($get['query'])) { + $st = preg_split("/[\s,]+/", trim($get['query'])); + if($lastterm = end($st)) + $this->terms = $index->terms($lastterm, $this->settings->_suggestTerms); + } + $limit = $this->searchparams['limit']; + $lucenesearch = $this->fulltextservice->Search(); + $searchresult = $lucenesearch->search($this->searchparams['query'], + array( + 'record_type'=>$this->searchparams['record_type'], + 'owner'=>$this->searchparams['ownernames'], + 'status'=>$this->searchparams['status'], + 'category'=>$this->searchparams['categorynames'], + 'user'=>$this->user->isAdmin() ? [] : [$this->user->getLogin()], + 'mimetype'=>$this->searchparams['mimetype'], + 'startFolder'=>$this->searchparams['startFolder'], + 'rootFolder'=>$this->searchparams['rootFolder'], + 'created_start'=>$this->searchparams['created']['from'], + 'created_end'=>$this->searchparams['created']['to'], + 'modified_start'=>$this->searchparams['modified']['from'], + 'modified_end'=>$this->searchparams['modified']['to'], + 'filesize_start'=>$this->searchparams['filesize']['from'], + 'filesize_end'=>$this->searchparams['filesize']['to'], + 'attributes'=>$this->searchparams['attributes'] + ), ($this->searchparams['pageNumber'] == 'all' ? array() : array('limit'=>$limit, 'offset'=>$limit * ($this->searchparams['pageNumber']-1))), $this->searchparams['order']); + if($searchresult !== false) { + $entries = array(); + $facets = $searchresult['facets']; + $stats = $searchresult['stats'] ?? null; + $dcount = 0; + $fcount = 0; + if($searchresult['hits']) { + foreach($searchresult['hits'] as $hit) { + if($hit['document_id'][0] == 'D') { + if($tmp = $this->dms->getDocument(substr($hit['document_id'], 1))) { + // if($tmp->getAccessMode($user) >= M_READ) { + $tmp->verifyLastestContentExpriry(); + $entries[] = $tmp; + $dcount++; + // } + } + } elseif($hit['document_id'][0] == 'F') { + if($tmp = $this->dms->getFolder(substr($hit['document_id'], 1))) { + // if($tmp->getAccessMode($user) >= M_READ) { + $entries[] = $tmp; + $fcount++; + // } + } + } + } + if(isset($facets['record_type'])) { + $fcount = isset($facets['record_type']['folder']) ? $facets['record_type']['folder'] : 0; + $dcount = isset($facets['record_type']['document']) ? $facets['record_type']['document'] : 0 ; + } + } + $this->fcount = $fcount; + $this->dcount = $dcount; + $this->stats = $stats; + $this->entries = $entries; + $this->facets = $facets; + $totalPages = 0; + if($limit > 0) { + if($searchresult['count'] > $limit) { + $totalPages = (int) ($searchresult['count']/$limit); + if($searchresult['count']%$limit) + $totalPages++; + } else { + $totalPages = 1; + } + } + $this->total = $searchresult['count']; + $this->totalPages = $totalPages; + } + $searchTime = $this->getTime() - $startTime; + $this->searchTime = round($searchTime, 2); + } + } else { + // ---------------- Start searching ----------------------------------------- + $startTime = $this->getTime(); + $resArr = $this->dms->search(array( + 'query'=>$query, + 'limit'=>0, + 'offset'=>0, + 'logicalmode'=>$mode, + 'searchin'=>$searchin, + 'startFolder'=>$startFolder, + 'owner'=>$ownerobjs, + 'status'=>$status, + 'mimetype'=>$mimetype, + 'creationstartdate'=>$created['from'], + 'creationenddate'=>$created['to'], + 'modificationstartdate'=>$modified['from'], + 'modificationenddate'=>$modified['to'], + 'filesizestart'=>$filesize['from'], + 'filesizeend'=>$filesize['to'], + 'categories'=>$categories, + 'attributes'=>$attributes, + 'mode'=>$resultmode, + 'expirationstartdate'=>$expstartdate ? $expstartdate : array(), + 'expirationenddate'=>$expenddate ? $expenddate : array(), + 'statusstartdate'=>$statusstartdate ? $statusstartdate : array(), + 'statusenddate'=>$statusenddate ? $statusenddate : array(), + 'orderby'=>$orderby + )); + $this->total = $resArr['totalDocs'] + $resArr['totalFolders']; + $searchTime = $this->getTime() - $startTime; + $this->searchTime = round($searchTime, 2); + + $entries = array(); + $fcount = 0; + if($resArr['folders']) { + foreach ($resArr['folders'] as $entry) { + if ($entry->getAccessMode($user) >= M_READ) { + $entries[] = $entry; + $fcount++; + } + } + } + $this->fcount = $fcount; + $dcount = 0; + if($resArr['docs']) { + foreach ($resArr['docs'] as $entry) { + if ($entry->getAccessMode($user) >= M_READ) { + if($entry->getLatestContent()) { + $entry->verifyLastestContentExpriry(); + $entries[] = $entry; + $dcount++; + } + } + } + } + $this->dcount = $dcount; + $totalPages = (int) (count($entries)/$limit); + if(count($entries)%$limit) + $totalPages++; + if($pageNumber != 'all') + $this->entries = array_slice($entries, ($pageNumber-1)*$limit, $limit); + $this->totalPages = $totalPages; + $this->facets = array(); + $this->stats = array(); + } + } /* }}} */ + + public function getFolderCount() { + return $this->fcount; + } + + public function getDocumentCount() { + return $this->dcount; + } + + public function getTotal() { + return $this->total; + } } /* }}} */