PathFilter - return right neighbours of search terms
[tine20] / tine20 / Tinebase / Model / Filter / Path.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Filter
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2016-2017 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle <p.schuele@metaways.de>
10  */
11
12 /**
13  * Tinebase_Model_Filter_Path
14  * 
15  * filters own ids match result of path search
16  * 
17  * <code>
18  *      'contact'        => array('filter' => 'Tinebase_Model_Filter_Path', 'options' => array(
19  *      )
20  * </code>     
21  * 
22  * @package     Tinebase
23  * @subpackage  Filter
24  */
25 class Tinebase_Model_Filter_Path extends Tinebase_Model_Filter_Text
26 {
27     protected $_controller = null;
28
29     /**
30      * @var array
31      */
32     protected $_pathRecordIds = null;
33
34     /**
35      * get path controller
36      * 
37      * @return Tinebase_Record_Path
38      */
39     protected function _getController()
40     {
41         if ($this->_controller === null) {
42             $this->_controller = Tinebase_Record_Path::getInstance();
43         }
44         
45         return $this->_controller;
46     }
47     
48     /**
49      * appends sql to given select statement
50      *
51      * @param Zend_Db_Select                $_select
52      * @param Tinebase_Backend_Sql_Abstract $_backend
53      */
54     public function appendFilterSql($_select, $_backend)
55     {
56         if (true !== Tinebase_Config::getInstance()->featureEnabled(Tinebase_Config::FEATURE_SEARCH_PATH) ||
57             empty($this->_value)) {
58             $_select->where('1=1');
59             return;
60         }
61
62         $modelName = $_backend->getModelName();
63         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' 
64             . 'Adding Path filter for: ' . $modelName);
65         
66         $this->_resolvePathIds($modelName);
67
68         $idField = (isset($this->_options['idProperty']) || array_key_exists('idProperty', $this->_options)) ? $this->_options['idProperty'] : 'id';
69         $db = $_backend->getAdapter();
70         $qField = $db->quoteIdentifier($_backend->getTableName() . '.' . $idField);
71         if (empty($this->_pathRecordIds)) {
72             $_select->where('1=0');
73         } else {
74             $_select->where($db->quoteInto("$qField IN (?)", $this->_pathRecordIds));
75         }
76     }
77     
78     /**
79      * resolve foreign ids
80      */
81     protected function _resolvePathIds($_model)
82     {
83         if (! is_array($this->_pathRecordIds)) {
84              $paths = $this->_getController()->search(new Tinebase_Model_PathFilter(array(
85                 array('field' => 'query', 'operator' => $this->_operator, 'value' => $this->_value)
86             )));
87
88             $this->_pathRecordIds = array();
89             if ($paths->count() > 0) {
90                 if (!is_array($this->_value)) {
91                     $this->_value = array($this->_value);
92                 }
93                 $searchTerms = array();
94                 foreach ($this->_value as $value) {
95                     //replace full text meta characters
96                     //$value = str_replace(array('+', '-', '<', '>', '~', '*', '(', ')', '"'), ' ', $value);
97                     $value = preg_replace('#[^\w\d ]|_#u', ' ', $value);
98                     // replace multiple spaces with just one
99                     $searchTerms = array_merge($searchTerms, explode(' ', preg_replace('# +#u', ' ', trim($value))));
100                 }
101
102                 if (count($searchTerms) < 1) {
103                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
104                         ' found paths, but search terms array is empty. value: ' . print_r($this->_value, true));
105                     return;
106                 }
107
108                 $searchTerms = array_filter($searchTerms, 'mb_strtolower');
109                 $hitNeighbours = array();
110                 $hitIds = array();
111
112                 /** @var Tinebase_Model_Path $path */
113                 foreach($paths as $path) {
114                     $pathParts = explode('/', trim($path->path, '/'));
115                     $shadowPathParts = explode('/', trim($path->shadow_path, '/'));
116                     $offset = 0;
117                     $hit = false;
118                     foreach($pathParts as $pathPart) {
119                         $pathPart = mb_strtolower($pathPart);
120
121                         $shadowPathPart = $shadowPathParts[$offset++];
122                         $model = substr($shadowPathPart, 1, strpos($shadowPathPart, '}') - 1);
123                         $id = substr($shadowPathPart, strpos($shadowPathPart, '}') + 1);
124                         if (false !== ($pos = strpos($id, '{'))) {
125                             $id = substr($id, 0, $pos - 1);
126                         }
127
128                         $newHit = true;
129                         foreach($searchTerms as $searchTerm) {
130                             if (false === strpos($pathPart, $searchTerm)) {
131                                 $newHit = false;
132                                 break;
133                             }
134                         }
135                         if (true === $newHit) {
136                             $hitIds[] = $id;
137                             $hit = true;
138                             continue;
139                         }
140                         if (false === $hit) {
141                             continue;
142                         }
143
144                         if ($model !== $_model) {
145                             continue;
146                         }
147
148                         $hitNeighbours[] = $id;
149                         $hit = false;
150                     }
151                 }
152
153                 if (count($hitNeighbours) > 0) {
154                     $this->_pathRecordIds = $hitNeighbours;
155                 } else {
156                     $this->_pathRecordIds = $hitIds;
157                 }
158             }
159         }
160
161         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' foreign ids: ' 
162             . print_r($this->_pathRecordIds, TRUE));
163     }
164 }