Merge branch '2013.03'
[tine20] / tine20 / Tinebase / Model / Filter / CustomField.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) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schuele <p.schuele@metaways.de>
10  */
11
12 /**
13  * Tinebase_Model_Filter_CustomField
14  * 
15  * @package     Tinebase
16  * @subpackage  Filter
17  * 
18  * filters by given customfield name/value
19  * 
20  * a custom field filter is constructed like this:
21  * 
22  *  array(
23  *     'field' => 'customfield', 
24  *     'operator' => 'contains', 
25  *     'value' => array('cfId' => '1234', 'value' => 'searchstring')
26  *  ),
27  */
28 class Tinebase_Model_Filter_CustomField extends Tinebase_Model_Filter_Abstract
29 {
30     /**
31      * the filter used for querying the customfields table
32      * 
33      * @var Tinebase_Model_Filter_Abstract
34      */
35     protected $_subFilter = NULL;
36     
37     /**
38      * possible operators
39      * 
40      * @var array
41      */
42     protected $_operators = NULL;
43     
44     /**
45      * the customfield record
46      * 
47      * @var Tinebase_Model_CustomField_Config
48      */
49     protected $_cfRecord  = NULL;
50     
51     /**
52      * get a new single filter action
53      *
54      * @param string|array $_fieldOrData
55      * @param string $_operator
56      * @param mixed  $_value    
57      * @param array  $_options
58      */
59     public function __construct($_fieldOrData, $_operator = NULL, $_value = NULL, array $_options = array())
60     {
61         // no legacy handling
62         if(!is_array($_fieldOrData)) {
63             throw new Tinebase_Exception_InvalidArgument('$_fieldOrDatamust be an array!');
64         }
65         
66         $be = new Tinebase_CustomField_Config();
67         $this->_cfRecord = $be->get($_fieldOrData['value']['cfId']);
68         $type = $this->_cfRecord->definition['type'];
69         if ($type == 'date' || $type == 'datetime') {
70             $this->_subFilter = new Tinebase_Model_CustomField_ValueFilter(array());
71             $this->_subFilter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'customfield_id', 'operator' => 'equals', 'value' => $_fieldOrData['value']['cfId'])));
72             $valueFilter = new Tinebase_Model_Filter_Date(array('field' => 'value', 'operator' => $_fieldOrData['operator'], 'value' => $_fieldOrData['value']['value']));
73             $this->_subFilter->addFilter($valueFilter);
74         } elseif ($type == 'integer') {
75             $valueFilter = new Tinebase_Model_Filter_Int($_fieldOrData, $_operator, $_value, $_options);
76         } else {
77             $valueFilter = new Tinebase_Model_Filter_Text($_fieldOrData, $_operator, $_value, $_options);
78         }
79         
80         $this->_valueFilter = $valueFilter;
81         $this->_operators = $valueFilter->getOperators();
82         $this->_opSqlMap = $valueFilter->getOpSqlMap();
83         
84         parent::__construct($_fieldOrData, $_operator, $_value, $_options);
85     }
86     
87     /**
88      * set options 
89      *
90      * @param  array $_options
91      */
92     protected function _setOptions(array $_options)
93     {
94         $_options['idProperty'] = isset($_options['idProperty']) ? $_options['idProperty'] : 'id';
95         
96         $this->_options = $_options;
97     }
98     
99     /**
100      * appends sql to given select statement
101      *
102      * @param  Zend_Db_Select                $_select
103      * @param  Tinebase_Backend_Sql_Abstract $_backend
104      * @throws Tinebase_Exception_UnexpectedValue
105      */
106     public function appendFilterSql($_select, $_backend)
107     {
108         // don't take empty filter into account
109         if (     empty($this->_value)          || ! is_array($this->_value)    || ! isset($this->_value['cfId'])  || empty($this->_value['cfId']) 
110             || ! isset($this->_value['value'])) 
111         {
112             return;
113         } else if ($this->_operator == 'in') {
114             throw new Tinebase_Exception_UnexpectedValue('Operator "in" not supported.');
115         }
116         
117         // make sure $correlationName is a string
118         $correlationName = Tinebase_Record_Abstract::generateUID(30);
119         
120         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Adding custom field filter: ' . print_r($this->_value, true));
121         
122         $db = Tinebase_Core::getDb();
123         $idProperty = $db->quoteIdentifier($this->_options['idProperty']);
124         
125         // per left join we add a customfield column named as the customfield and filter this joined column
126         // NOTE: we name the column we join like the customfield, to be able to join multiple customfield criteria (multiple invocations of this function)
127         $what = array($correlationName => SQL_TABLE_PREFIX . 'customfield');
128         $on = $db->quoteIdentifier("{$correlationName}.record_id")      . " = $idProperty AND " 
129             . $db->quoteIdentifier("{$correlationName}.customfield_id") . " = " . $db->quote($this->_value['cfId']);
130         $_select->joinLeft($what, $on, array());
131
132         $valueIdentifier = $db->quoteIdentifier("{$correlationName}.value");
133         
134         switch($this->_cfRecord->definition['type']) {
135             case 'date':
136             case 'datetime':
137                 $customfields = Tinebase_CustomField::getInstance()->search($this->_subFilter);
138                 if($customfields->count()) {
139                     $where = $db->quoteInto($idProperty . ' IN (?) ', $customfields->record_id);
140                 } else {
141                     $where = '1=2';
142                 }
143                 break;
144             default:
145                 if (!$this->_value['value']) {
146                     $where = $db->quoteInto($valueIdentifier. ' IS NULL OR ' . $valueIdentifier . ' = ?', $this->_value['value']);
147                 } else {
148                     $value = $this->_replaceWildcards($this->_value['value']);
149                     if (($this->_cfRecord->definition['type'] == 'keyField' || $this->_cfRecord->definition['type'] == 'record') && $this->_operator == 'not') {
150                         $where = $db->quoteInto($valueIdentifier . ' IS NULL OR ' . $valueIdentifier . $this->_opSqlMap[$this->_operator]['sqlop'], $value);
151                     } else {
152                         $where = $db->quoteInto($valueIdentifier . $this->_opSqlMap[$this->_operator]['sqlop'], $value);
153                     }
154                 }
155         }
156         $_select->where($where);
157     }
158     
159     /**
160      * returns array with the filter settings of this filter
161      *
162      * @param  bool $valueToJson resolve value for json api?
163      * @return array
164      */
165     public function toArray($valueToJson = false)
166     {
167         $result = parent::toArray($valueToJson);
168         if (strtolower($this->_cfRecord->definition['type']) == 'record') {
169             try {
170                 $modelParts = explode('.', $this->_cfRecord->definition['recordConfig']['value']['records']); // get model parts from saved record class e.g. Tine.Admin.Model.Group
171                 $controller = Tinebase_Core::getApplicationInstance($modelParts[1], $modelParts[3]);
172                 $result['value']['value'] = $controller->get($result['value']['value'])->toArray();
173             } catch (Exception $e) {
174                 if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Error resolving custom field record: ' . $e->getMessage());
175                 $result['value']['value'] = $customField->value;
176             }
177         }
178         
179         return $result;
180     }
181 }