0012312: pgsql text filters should be case insensitive
[tine20] / tine20 / Tinebase / Model / Filter / Query.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) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Cornelius Weiss <c.weiss@metaways.de>
10  */
11
12 /**
13  * Tinebase_Model_Filter_Query
14  * 
15  * filters for all of the given filterstrings if it is contained in at least 
16  * one of the defined fields
17  * 
18  * -> allow search for all Müllers who live in Munich but not all Müllers and all people who live in Munich
19  * 
20  * The fields to query in _must_ be defined in the options key 'fields'
21  * The value string is space-exploded into multiple filterstrings
22  * 
23  * @package     Tinebase
24  * @subpackage  Filter
25  */
26 class Tinebase_Model_Filter_Query extends Tinebase_Model_Filter_Abstract
27 {
28     /**
29      * @var array list of allowed operators
30      */
31     protected $_operators = array(
32         0 => 'contains',
33         1 => 'in',
34         2 => 'equals',
35         3 => 'startswith',
36         4 => 'endswith',
37         5 => 'not',
38         6 => 'notcontains',
39         7 => 'notin',
40     );
41
42     /**
43      * set options 
44      *
45      * @param  array $_options
46      * @throws Tinebase_Exception_Record_NotDefined
47      */
48     protected function _setOptions(array $_options)
49     {
50         if (empty($_options['fields'])) {
51             throw new Tinebase_Exception_Record_NotDefined('Fields must be defined in the options of a query filter');
52         }
53         
54         $this->_options = $_options;
55     }
56
57     /**
58      * appends sql to given select statement
59      *
60      * @param Zend_Db_Select $_select
61      * @param Tinebase_Backend_Sql_Abstract $_backend
62      * @throws Tinebase_Exception_InvalidArgument
63      */
64     public function appendFilterSql($_select, $_backend)
65     {
66         if (empty($this->_value)) {
67             $_select->where('1=1/* empty query */');
68             return;
69         }
70
71         $db = $_backend->getAdapter();
72
73         $sqlCommand = Tinebase_Backend_Sql_Command::factory($db);
74         if (0 === strpos($this->_operator, 'not')) {
75             $not = true;
76         } else {
77             $not = false;
78         }
79
80         switch ($this->_operator) {
81             case 'contains':
82             case 'notcontains':
83             case 'equals':
84             case 'not':
85             case 'startswith':
86             case 'endswith':
87                 $queries = explode(' ', $this->_value);
88
89                 foreach ($queries as $query) {
90                     $whereParts = array();
91                     foreach ($this->_options['fields'] as $qField) {
92                         // if field has . in name, then we already have tablename
93                         if (strpos($qField, '.') !== FALSE) {
94                             $whereParts[] = $sqlCommand->prepareForILike($sqlCommand->getUnaccent($db->quoteIdentifier($qField))) . ' ' . ($not?'NOT ':'') . $sqlCommand->getLike() . $sqlCommand->prepareForILike($sqlCommand->getUnaccent('(?)'));
95                         }
96                         else {
97                             $whereParts[] = $sqlCommand->prepareForILike($sqlCommand->getUnaccent($db->quoteIdentifier($_backend->getTableName() . '.' . $qField))) . ' ' . ($not?'NOT ':'') . $sqlCommand->getLike() . $sqlCommand->prepareForILike($sqlCommand->getUnaccent('(?)'));
98                         }
99                     }
100                     $whereClause = '';
101                     if (!empty($whereParts)) {
102                         if ($not) {
103                             $whereClause = implode(' AND ', $whereParts);
104                         } else {
105                             $whereClause = implode(' OR ', $whereParts);
106                         }
107                     }
108
109                     if (!empty($whereClause)) {
110                         $query = trim($query);
111                         if ($this->_operator === 'startswith') {
112                             $query .= '%';
113                         } else if ($this->_operator === 'contains' || $this->_operator === 'notcontains'){
114                             $query = '%' . $query . '%';
115                         } else if ($this->_operator === 'endswith') {
116                             $query = '%' . $query;
117                         }
118
119                         $_select->where($db->quoteInto($whereClause, $query));
120                     }
121                 }
122                 break;
123
124             case 'notin':
125             case 'in':
126                 foreach ($this->_options['fields'] as $qField) {
127                     // if field has . in name, then we already have tablename
128                     if (strpos($qField, '.') !== FALSE) {
129                         $whereParts[] = $db->quoteInto($db->quoteIdentifier($qField) . ($not?' NOT':'') . ' IN (?)', (array) $this->_value);
130                     }
131                     else {
132                          $whereParts[] = $db->quoteInto($db->quoteIdentifier($_backend->getTableName() . '.' . $qField) . ($not?' NOT':'') . ' IN (?)', (array) $this->_value);
133                     }
134                 }
135                 if (! empty($whereParts)) {
136                     if ($not) {
137                         $whereClause = implode(' AND ', $whereParts);
138                     } else {
139                         $whereClause = implode(' OR ', $whereParts);
140                     }
141                 }
142                 if (! empty($whereClause)) {
143                     $_select->where($whereClause);
144                 }
145                 break;
146             default:
147                 throw new Tinebase_Exception_InvalidArgument('Operator not defined: ' . $this->_operator);
148         }
149
150         // append advanced search filter if configured
151         if (isset($this->_options['relatedModels']) && isset($this->_options['modelName'])) {
152             $relationFilter = $this->_getAdvancedSearchFilter($this->_options['modelName'], $this->_options['relatedModels']);
153             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
154                 . ' Got relation filter: '
155                 . ($relationFilter instanceof Tinebase_Model_Filter_Abstract ? print_r($relationFilter->toArray(), true) : ''));
156             if ($relationFilter) {
157                 $relationSelect = new Tinebase_Backend_Sql_Filter_GroupSelect($_select);
158                 $relationFilter->appendFilterSql($relationSelect, $_backend);
159                 $relationSelect->appendWhere($not?Zend_Db_Select::SQL_AND:Zend_Db_Select::SQL_OR);
160             }
161         }
162     }
163 }