55cabdf6d5f82f24e2560d2c6533c785eab0ad34
[tine20] / tine20 / Tinebase / Model / Filter / Abstract.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-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Cornelius Weiss <c.weiss@metaways.de>
10  */
11
12 /**
13  * Tinebase_Model_Filter_Abstract
14  * 
15  * Abstract filter
16  * 
17  * @todo validate value!
18  * @package     Tinebase
19  * @subpackage  Filter
20  */
21 abstract class Tinebase_Model_Filter_Abstract
22 {
23     /**
24      * @var array list of allowed operators
25      */
26     protected $_operators = array();
27     
28     /**
29      * @var string property this filter is applied to
30      */
31     protected $_field = NULL;
32     
33     /**
34      * @var string operator
35      */
36     protected $_operator = NULL;
37     
38     /**
39      * @var mixed value to filter with
40      */
41     protected $_value = NULL;
42     
43     /**
44      * @var string filter id [optional]
45      */
46     protected $_id = NULL;
47     
48     /**
49      * @var string filter label [optional]
50      */
51     protected $_label = NULL;
52     
53     /**
54      * @var array special options
55      */
56     protected $_options = NULL;
57
58     /**
59      * @var Tinebase_Backend_Sql_Command_Interface
60      */
61     protected $_dbCommand;
62
63     /**
64      * filter is implicit, this is returned in toArray
65      * - this is only needed to detect acl filters that have been added by a controller
66      * 
67      * @var boolean
68      * @todo move this to acl filter?
69      */
70     protected $_isImplicit = FALSE;
71     
72     /**
73      * get a new single filter action
74      *
75      * @param string|array $_fieldOrData
76      * @param string $_operator
77      * @param mixed  $_value    
78      * @param array  $_options
79      * 
80      * @todo remove legacy code + obsolete params sometimes
81      */
82     public function __construct($_fieldOrData, $_operator = NULL, $_value = NULL, array $_options = array())
83     {
84         $this->_db = Tinebase_Core::getDb();
85         $this->_dbCommand = Tinebase_Backend_Sql_Command::factory($this->_db);
86
87         if (is_array($_fieldOrData)) {
88             $data = $_fieldOrData;
89         } else {
90             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
91                 . ' Using deprecated constructor syntax. Please pass all filter data in one array (filter field: ' . $_fieldOrData . ').');
92             
93             $data = array(
94                 'field'     => $_fieldOrData,
95                 'operator'  => $_operator,
96                 'value'     => $_value,
97                 'options'   => $_options,
98             );
99         }
100
101         foreach (array('field', 'operator', 'value') as $requiredKey) {
102             if (! (isset($data[$requiredKey]) || array_key_exists($requiredKey, $data))) {
103                 throw new Tinebase_Exception_InvalidArgument('Filter object needs ' . $requiredKey);
104             }
105         }
106         
107         $this->_setOptions((isset($data['options'])) ? $data['options'] : array());
108         $this->setField($data['field']);
109         $this->setOperator($data['operator']);
110         $this->setValue($data['value']);
111         
112         if (isset($data['id'])) {
113             $this->setId($data['id']);
114         }
115         if (isset($data['label'])) {
116             $this->setLabel($data['label']);
117         }
118     }
119     
120     /**
121      * returns the id of the filter
122      * 
123      * @return string
124      */
125     public function getId()
126     {
127         return $this->_id;
128     }
129     
130     /**
131      * returns operators of this filter model
132      * @return array
133      */
134     public function getOperators()
135     {
136         return $this->_operators;
137     }
138     
139     /**
140      * returns operator sql mapping
141      * @return array
142      */
143     public function getOpSqlMap()
144     {
145         if($this->_opSqlMap) {
146             return $this->_opSqlMap;
147         }
148         return NULL;
149     }
150     
151     /**
152      * set options 
153      *
154      * @param array $_options
155      */
156     protected function _setOptions(array $_options)
157     {
158         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
159             . ' ' . print_r($_options, TRUE));
160         
161         $this->_options = $_options;
162     }
163     
164     /**
165      * set field 
166      *
167      * @param string $_field
168      */
169     public function setField($_field)
170     {
171         $this->_field = $_field;
172     }
173     
174     /**
175      * returns fieldname of this filter
176      *
177      * @return string
178      */
179     public function getField()
180     {
181         return $this->_field;
182     }
183     
184     /**
185      * sets operator
186      *
187      * @param string $_operator
188      */
189     public function setOperator($_operator)
190     {
191         if (empty($_operator) && isset($this->_operators[0])) {
192             // try to use default/first operator
193             $_operator = $this->_operators[0];
194         }
195         
196         if (! in_array($_operator, $this->_operators)) {
197             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' 
198                 . ' Allowed operators: ' . print_r($this->_operators, TRUE));
199             throw new Tinebase_Exception_UnexpectedValue("operator $_operator is not defined");
200         }
201         
202         $this->_operator = $_operator;
203     }
204     
205     /**
206      * gets operator
207      *
208      * @return string
209      */
210     public function getOperator()
211     {
212         return $this->_operator;
213     }
214     
215     /**
216      * sets value
217      *
218      * @param string $_value
219      */
220     public function setValue($_value)
221     {
222         // cope with resolved records
223         if (is_array($_value) && (isset($_value['id']) || array_key_exists('id', $_value))) {
224             $_value = $_value['id'];
225         }
226         
227         //@todo validate value before setting it!
228         $this->_value = $_value;
229     }
230
231     /**
232      * sets id
233      *
234      * @param string $_id
235      */
236     public function setId($_id)
237     {
238         $this->_id = $_id;
239     }
240     
241     /**
242      * remove id of filter object
243      */
244     public function removeId()
245     {
246         $this->_id = NULL;
247     }
248
249     /**
250      * set label
251      *
252      * @param string $_label
253      */
254     public function setLabel($_label)
255     {
256         $this->_label = $_label;
257     }
258     
259     /**
260      * gets value
261      *
262      * @return  mixed 
263      */
264     public function getValue()
265     {
266         return $this->_value;
267     }
268     
269     /**
270      * set implicit
271      * @deprecated use isImplicit()
272      *
273      * @param boolean $_isImplicit
274      */
275     public function setIsImplicit($_isImplicit)
276     {
277         $this->_isImplicit = ($_isImplicit === TRUE);
278     }
279     
280     /**
281      * set implicit
282      *
283      * @param  boolean optional
284      * @return boolean
285      */
286     public function isImplicit()
287     {
288         $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
289         $currValue = $this->_isImplicit;
290         if ($value !== NULL) {
291             $this->_isImplicit = $value;
292         }
293         
294         return $currValue;
295     }
296     
297     /**
298      * appends sql to given select statement
299      *
300      * @param Zend_Db_Select                $_select
301      * @param Tinebase_Backend_Sql_Abstract $_backend
302      * 
303      * @todo to be removed once we split filter model / backend
304      */
305     abstract public function appendFilterSql($_select, $_backend);
306     
307     /**
308      * returns quoted column name for sql backend
309      *
310      * @param  Tinebase_Backend_Sql_Interface $_backend
311      * @return string
312      * 
313      * @todo to be removed once we split filter model / backend
314      */
315     protected function _getQuotedFieldName($_backend) {
316         $tablename = (isset($this->_options['tablename'])) ? $this->_options['tablename'] : $_backend->getTableName();
317         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
318             . 'Using tablename: ' . $tablename);
319         
320         return $_backend->getAdapter()->quoteIdentifier(
321             $tablename . '.' . $this->_field
322         );
323     }
324     
325     /**
326      * returns array with the filter settings of this filter
327      *
328      * @param  bool $_valueToJson resolve value for json api?
329      * @return array
330      */
331     public function toArray($_valueToJson = false)
332     {
333         $result = array(
334             'field'     => $this->_field,
335             'operator'  => $this->_operator,
336             'value'     => $this->_value
337         );
338         
339         if ($this->_isImplicit) {
340             $result['implicit'] = TRUE;
341         }
342
343         if ($this->_id) {
344             $result['id'] = $this->_id;
345         }
346         if ($this->_label) {
347             $result['label'] = $this->_label;
348         }
349         
350         return $result;
351     }
352
353     /**
354      * convert string in user time to UTC
355      *
356      * @param string $_string
357      * @return string
358      */
359     protected function _convertStringToUTC($_string)
360     {
361         if (empty($_string)) {
362             $date = new Tinebase_DateTime();
363             $result = $date->toString(Tinebase_Record_Abstract::ISO8601LONG);
364         } elseif (isset($this->_options['timezone']) && $this->_options['timezone'] !== 'UTC') {
365             $date = new Tinebase_DateTime($_string, $this->_options['timezone']);
366             $date->setTimezone('UTC');
367             $result = $date->toString(Tinebase_Record_Abstract::ISO8601LONG);
368         } else {
369             $result = $_string;
370         }
371         
372         return $result;
373     }
374
375     /**
376      * replaces wildcards
377      *
378      * @param  string $value
379      * @return string
380      */
381     protected function _replaceWildcards($value)
382     {
383         if (is_array($value)) {
384             $returnValue = array();
385             foreach ($value as $idx => $val) {
386                 $returnValue[$idx] = $this->_replaceWildcardsSingleValue($val);
387             }
388         } else {
389             $returnValue = $this->_replaceWildcardsSingleValue($value);
390         }
391
392         return $returnValue;
393     }
394
395     /**
396      * replaces wildcards of a single value
397      *
398      * @param  string $value
399      * @return string
400      */
401     protected function _replaceWildcardsSingleValue($value)
402     {
403         $action = $this->_opSqlMap[$this->_operator];
404
405         // replace wildcards from user ()
406         $returnValue = str_replace(array('*', '_'),  $this->_dbCommand->setDatabaseJokerCharacters(), $value);
407
408         // add wildcard to value according to operator
409         $returnValue = str_replace('?', $returnValue, $action['wildcards']);
410
411         return $returnValue;
412     }
413
414     /**
415      * append relation filter
416      *
417      * @param string $ownModel
418      * @param array $relationsToSearchIn
419      * @return Tinebase_Model_Filter_Id
420      */
421     protected function _getAdvancedSearchFilter($ownModel = null, $relationsToSearchIn = null)
422     {
423         if (  Tinebase_Core::get('ADVANCED_SEARCHING') ||
424             ! Tinebase_Core::getPreference()->getValue(Tinebase_Preference::ADVANCED_SEARCH, false) ||
425               empty($relationsToSearchIn))
426         {
427             return null;
428         }
429
430         if (0 === strpos($this->_operator, 'not')) {
431             $not = true;
432             $operator = substr($this->_operator, 3);
433         } else {
434             $not = false;
435             $operator = $this->_operator;
436         }
437         $ownIds = array();
438         foreach ((array) $relationsToSearchIn as $relatedModel) {
439             $filterModel = $relatedModel . 'Filter';
440             // prevent recursion here
441             // TODO find a better way for this, maybe we could pass this an option to all filters in filter model
442             Tinebase_Core::set('ADVANCED_SEARCHING', true);
443             $relatedFilter = new $filterModel(array(
444                 array('field' => 'query',   'operator' => $operator, 'value' => $this->_value),
445             ));
446             $relatedIds = Tinebase_Core::getApplicationInstance($relatedModel)->search($relatedFilter, NULL, FALSE, TRUE);
447             Tinebase_Core::set('ADVANCED_SEARCHING', false);
448
449             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
450                 . ' Found ' . count($relatedIds) . ' related ids');
451
452             $relationFilter = new Tinebase_Model_RelationFilter(array(
453                 array('field' => 'own_model',     'operator' => 'equals', 'value' => $ownModel),
454                 array('field' => 'related_model', 'operator' => 'equals', 'value' => $relatedModel),
455                 array('field' => 'related_id',    'operator' => 'in'    , 'value' => $relatedIds)
456             ));
457             $ownIds = array_merge($ownIds, Tinebase_Relations::getInstance()->search($relationFilter, NULL)->own_id);
458         }
459
460         return new Tinebase_Model_Filter_Id('id', $not?'notin':'in', $ownIds);
461     }
462 }