Tinebase_Export - add customfield, keyfield, virtual field resolving
[tine20] / tine20 / Tinebase / CustomField.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  CustomField
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle <p.schuele@metaways.de>
10  * 
11  * @todo        add join to cf config to value backend to get name
12  * @todo        use recordset instead of array to store cfs of record
13  * @todo        move custom field handling from sql backend to abstract record controller
14  * @todo        remove the memory logging
15  */
16
17 /**
18  * the class provides functions to handle custom fields and custom field configs
19  * 
20  * @package     Tinebase
21  * @subpackage  CustomField
22  */
23 class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
24 {
25     /**************************** protected vars *********************/
26     
27     /**
28      * custom field config backend
29      * 
30      * @var Tinebase_CustomField_Config
31      */
32     protected $_backendConfig;
33     
34     /**
35      * custom field acl backend
36      * 
37      * @var Tinebase_Backend_Sql
38      */
39     protected $_backendACL;
40     
41     /**
42      * custom field values backend
43      * 
44      * @var Tinebase_Backend_Sql
45      */
46     protected $_backendValue;
47     
48     /**
49      * custom fields by application cache
50      * 
51      * @var array (app id + modelname => Tinebase_Record_RecordSet with cfs)
52      */
53     protected $_cfByApplicationCache = array();
54     
55     /**
56      * holds the instance of the singleton
57      *
58      * @var Tinebase_CustomField
59      */
60     private static $_instance = NULL;
61     
62     /**
63      * the constructor
64      *
65      * don't use the constructor. use the singleton 
66      */    
67     private function __construct() 
68     {
69         $this->_backendConfig = new Tinebase_CustomField_Config();
70         $this->_backendValue = new Tinebase_Backend_Sql(array(
71             'modelName' => 'Tinebase_Model_CustomField_Value', 
72             'tableName' => 'customfield',
73         ));
74         $this->_backendACL = new Tinebase_Backend_Sql(array(
75             'modelName' => 'Tinebase_Model_CustomField_Grant', 
76             'tableName' => 'customfield_acl',
77         ));
78     }
79
80     /**
81      * don't clone. Use the singleton.
82      *
83      */
84     private function __clone() 
85     {
86     }
87
88     /**
89      * Returns instance of Tinebase_CustomField
90      *
91      * @return Tinebase_CustomField
92      */
93     public static function getInstance() 
94     {
95         if (static::$_instance === NULL) {
96             static::$_instance = new Tinebase_CustomField();
97         }
98         
99         return static::$_instance;
100     }
101     
102     /**
103      * add new custom field
104      *
105      * @param Tinebase_Model_CustomField_Config $_record
106      * @return Tinebase_Model_CustomField_Config
107      */
108     public function addCustomField(Tinebase_Model_CustomField_Config $_record)
109     {
110         $result = $this->_backendConfig->create($_record);
111         Tinebase_CustomField::getInstance()->setGrants($result, Tinebase_Model_CustomField_Grant::getAllGrants());
112         
113         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
114             . ' Created new custom field ' . $_record->name . ' for application ' . $_record->application_id);
115         
116         return $result;
117     }
118     
119     /**
120      * update custom field
121      *
122      * @param Tinebase_Model_CustomField_Config $_record
123      * @return Tinebase_Model_CustomField_Config
124      */
125     public function updateCustomField(Tinebase_Model_CustomField_Config $_record)
126     {
127         $this->_clearCache();
128         $result = $this->_backendConfig->update($_record);
129         Tinebase_CustomField::getInstance()->setGrants($result, Tinebase_Model_CustomField_Grant::getAllGrants());
130         return $result;
131     }
132
133     /**
134      * get custom field by id
135      *
136      * @param string $_customFieldId
137      * @return Tinebase_Model_CustomField_Config
138      */
139     public function getCustomField($_customFieldId)
140     {
141         /** @noinspection PhpIncompatibleReturnTypeInspection */
142         return $this->_backendConfig->get($_customFieldId);
143     }
144
145     /**
146      * get custom field by name and app
147      *
148      * @param string|Tinebase_Model_Application $applicationId application object, id or name
149      * @param string $customFieldName
150      * @param string $modelName
151      * @return Tinebase_Model_CustomField_Config|null
152      */
153     public function getCustomFieldByNameAndApplication($applicationId, $customFieldName, $modelName = null)
154     {
155         $allAppCustomfields = $this->getCustomFieldsForApplication($applicationId, $modelName);
156         return $allAppCustomfields->find('name', $customFieldName);
157     }
158     
159     /**
160      * get custom fields for an application
161      * - results are cached in class cache $_cfByApplicationCache
162      * - results are cached if caching is active (with cache tag 'customfields')
163      *
164      * @param string|Tinebase_Model_Application $_applicationId application object, id or name
165      * @param string                            $_modelName
166      * @param string                            $_requiredGrant (read grant by default)
167      * @return Tinebase_Record_RecordSet|Tinebase_Model_CustomField_Config of Tinebase_Model_CustomField_Config records
168      */
169     public function getCustomFieldsForApplication($_applicationId, $_modelName = NULL, $_requiredGrant = Tinebase_Model_CustomField_Grant::GRANT_READ)
170     {
171         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
172         
173         $userId = (is_object(Tinebase_Core::getUser())) ? Tinebase_Core::getUser()->getId() : 'nouser';
174         $cfIndex = $applicationId . (($_modelName !== NULL) ? $_modelName : '') . $_requiredGrant . $userId;
175         
176         if (isset($this->_cfByApplicationCache[$cfIndex])) {
177             return $this->_cfByApplicationCache[$cfIndex];
178         } 
179         
180         $cache = Tinebase_Core::getCache();
181         $cacheId = Tinebase_Helper::convertCacheId('getCustomFieldsForApplication' . $cfIndex);
182         $result = $cache->load($cacheId);
183         
184         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
185             . ' Before - MEMORY: ' . memory_get_usage(TRUE)/1024/1024 . ' MBytes');
186         
187         if (! $result) {
188             $filterValues = array(array(
189                 'field'     => 'application_id', 
190                 'operator'  => 'equals', 
191                 'value'     => $applicationId
192             ));
193             
194             if ($_modelName !== NULL) {
195                 $filterValues[] = array(
196                     'field'     => 'model', 
197                     'operator'  => 'equals', 
198                     'value'     => $_modelName
199                 );
200             }
201             
202             $filter = new Tinebase_Model_CustomField_ConfigFilter($filterValues);
203             $filter->setRequiredGrants((array)$_requiredGrant);
204             $result = $this->_backendConfig->search($filter);
205         
206             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
207                 . ' Got ' . count($result) . ' uncached custom fields for app id ' . $applicationId . ' (cacheid: ' . $cacheId . ')');
208             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE) && (count($result) > 0)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
209                 . print_r($result->toArray(), TRUE));
210             
211             $cache->save($result, $cacheId, array('customfields'));
212         }
213         
214         $this->_cfByApplicationCache[$cfIndex] = $result;
215         
216         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
217             . ' After - MEMORY: ' . memory_get_usage(TRUE)/1024/1024 . ' MBytes');
218         
219         return $result;
220     }
221     
222     /**
223      * check if app has customfield configs
224      * 
225      * @param string $applicationName
226      * @param string $modelName
227      * @return boolean 
228      */
229     public function appHasCustomFields($applicationName, $modelName = NULL)
230     {
231         if (empty($applicationName)) {
232             return FALSE;
233         }
234         $app = Tinebase_Application::getInstance()->getApplicationByName($applicationName);
235         $result = $this->getCustomFieldsForApplication($app, $modelName);
236         return (count($result) > 0);
237     }
238     
239     /**
240      * resolve config grants
241      * 
242      * @param Tinebase_Record_RecordSet $_cfConfigs
243      */
244     public function resolveConfigGrants($_cfConfigs)
245     {
246         $user = Tinebase_Core::getUser();
247         if (! is_object($user)) {
248             return; // do nothing
249         }
250         
251         $cfAcl = $this->_backendConfig->getAclForIds($user->getId(), $_cfConfigs->getArrayOfIds());
252         
253         foreach ($_cfConfigs as $config) {
254             $config->account_grants = ((isset($cfAcl[$config->getId()]) || array_key_exists($config->getId(), $cfAcl))) ? explode(',', $cfAcl[$config->getId()]) : array();
255         }
256     }
257     
258     /**
259      * delete a custom field
260      *
261      * @param string|Tinebase_Model_CustomField_Config $_customField
262      */
263     public function deleteCustomField($_customField)
264     {
265         $cfId = ($_customField instanceof Tinebase_Model_CustomField_Config) ? $_customField->getId() : $_customField;
266         
267         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
268             . ' Deleting custom field config ' . $cfId . ' and values.');
269         
270         $this->_clearCache();
271         $this->_backendValue->deleteByProperty($cfId, 'customfield_id');
272         $this->_backendACL->deleteByProperty($cfId, 'customfield_id');
273         $this->_backendConfig->delete($cfId);
274     }
275     
276     /**
277      * delete custom fields for an application
278      *
279      * @param string|Tinebase_Model_Application $_applicationId
280      * @return integer numer of deleted custom fields
281      */
282     public function deleteCustomFieldsForApplication($_applicationId)
283     {
284         $this->_clearCache();
285         $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($_applicationId);
286  
287         $filterValues = array(array(
288             'field'     => 'application_id', 
289             'operator'  => 'equals',
290             'value'     => $applicationId
291         ));
292             
293           $filter = new Tinebase_Model_CustomField_ConfigFilter($filterValues);
294           $filter->customfieldACLChecks(FALSE);
295         $customFields = $this->_backendConfig->search($filter);
296             
297         $deletedCount = 0;
298         foreach ($customFields as $customField) {
299                $this->deleteCustomField($customField);
300                $deletedCount++;
301         }
302         
303         return $deletedCount;
304     }
305     
306     /**
307      * saves multiple Custom Fields
308      * @param String $_modelName
309      * @param array $_recordIds
310      * @param array $_customFieldValues
311      */
312     
313     public function saveMultipleCustomFields($_modelName, $_recordIds, $_customFieldValues) 
314     {
315         $expModelName = explode('_', $_modelName);
316         $app = array_shift($expModelName);
317         $app = Tinebase_Application::getInstance()->getApplicationByName($app);
318         
319         $cF = $this->getCustomFieldsForApplication($app->getId(), $_modelName, Tinebase_Model_CustomField_Grant::GRANT_WRITE);
320         $fA = array();
321          
322         foreach($cF as $field) {
323             $fA[$field->__get('name')] = $field->__get('id');
324         }
325         
326         unset($cF);
327         
328         foreach($_recordIds as $recId) {
329             foreach($_customFieldValues as $cfKey => $cfValue) {
330                 $filterValues = array(
331                     array(
332                         'field'     => 'record_id',
333                         'operator'  => 'in',
334                         'value'     => (array) $recId
335                         ),
336                     array(
337                         'field'     => 'customfield_id',
338                         'operator'  => 'in',
339                         'value'     => (array) $fA[$cfKey]
340                         )
341                     );
342                 
343                 $filter = new Tinebase_Model_CustomField_ValueFilter($filterValues);
344                 
345                 $record = $this->_backendValue->search($filter)->getFirstRecord();
346                 
347                 if($record) {
348                     // DELETE
349                     if(empty($_customFieldValues[$cfKey])) {
350                         $this->_backendValue->delete($record);
351                     } else { // UPDATE
352                         $record->value = $_customFieldValues[$cfKey];
353                         $this->_backendValue->update($record);
354                     }
355                 } else {
356                     if(!empty($_customFieldValues[$cfKey])) {
357                         $record = new Tinebase_Model_CustomField_Value(array(
358                                 'record_id'         => $recId,
359                                 'customfield_id'    => $fA[$cfKey],
360                                 'value'             =>  $_customFieldValues[$cfKey]
361                             ));
362                         $this->_backendValue->create($record);
363                     }
364                 }
365             }
366         }
367         $this->_clearCache();
368       }
369
370     /**
371      * save custom fields of record in its custom fields table
372      *
373      * @param Tinebase_Record_Interface $_record
374      * @throws Tinebase_Exception_Record_Validation
375      */
376     public function saveRecordCustomFields(Tinebase_Record_Interface $_record)
377     {
378         $applicationId = Tinebase_Application::getInstance()->getApplicationByName($_record->getApplication())->getId();
379         $appCustomFields = $this->getCustomFieldsForApplication($applicationId, get_class($_record), Tinebase_Model_CustomField_Grant::GRANT_WRITE);
380         $this->resolveConfigGrants($appCustomFields);
381         
382         $existingCustomFields = $this->_getCustomFields($_record->getId());
383         $existingCustomFields->addIndices(array('customfield_id'));
384         
385         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
386             . ' Updating custom fields for record of class ' . get_class($_record));
387         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
388             . ' Record cf values: ' . print_r($_record->customfields, TRUE));
389         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
390             . ' App cf names: ' . print_r($appCustomFields->name, TRUE));
391         
392         foreach ($appCustomFields as $customField) {
393             if (is_array($_record->customfields) && (isset($_record->customfields[$customField->name]) || array_key_exists($customField->name, $_record->customfields))) {
394                 $value = $_record->customfields[$customField->name];
395                 $filtered = $existingCustomFields->filter('customfield_id', $customField->id);
396                 
397                 // we need to resolve the modelName and the record value if array is given (e.g. on updating customfield)
398                 if (strtolower($customField->definition['type']) == 'record' || strtolower($customField->definition['type']) == 'recordlist') {
399                     $value = $this->_getValueForRecordOrListCf($_record, $customField, $value);
400                 }
401
402                 switch (count($filtered)) {
403                     case 1:
404                         $cf = $filtered->getFirstRecord();
405                         if ($customField->valueIsEmpty($value)) {
406                             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting cf value for ' . $customField->name);
407                             $this->_backendValue->delete($cf);
408                         } else {
409                             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Updating value for ' . $customField->name . ' to ' . $value);
410                             $cf->value = $value;
411                             $this->_backendValue->update($cf);
412                         }
413                         break;
414                     case 0:
415                         if (! $customField->valueIsEmpty($value)) {
416                             $cf = new Tinebase_Model_CustomField_Value(array(
417                                 'record_id'         => $_record->getId(),
418                                 'customfield_id'    => $customField->getId(),
419                                 'value'             => $value
420                             ));
421                             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Creating value for ' . $customField->name . ' -> ' . $value);
422                             $this->_backendValue->create($cf);
423                         }
424                         break;
425                     default:
426                         throw new Tinebase_Exception_UnexpectedValue('Oops, there should be only one custom field value here!');
427                 }
428             }
429         }
430     }
431
432     public static function getModelNameFromDefinition($definition)
433     {
434         $modelParts = explode('.', $definition[$definition['type'] . 'Config']['value']['records']);
435         return $modelParts[1] . '_Model_' . $modelParts[3];
436     }
437
438     /**
439      * @param $_record
440      * @param $_customField
441      * @param $_value
442      * @return string
443      * @throws Tinebase_Exception_Record_Validation
444      */
445     protected function _getValueForRecordOrListCf($_record, $_customField, $_value)
446     {
447         // get model parts from saved record class e.g. Tine.Admin.Model.Group
448         $modelName = self::getModelNameFromDefinition($_customField->definition);
449         $model = new $modelName(array(), true);
450         $idProperty = $model->getIdProperty();
451         if (is_array($_value)) {
452             if (strtolower($_customField->definition['type']) == 'record') {
453                 /** @var Tinebase_Record_Interface $model */
454                 $value = $_value[$idProperty];
455
456             } else {
457                 // recordlist
458                 $values = array();
459                 foreach ($_value as $record) {
460                     if (is_array($record) || $record instanceof Tinebase_Record_Abstract) {
461                         $values[] = $record[$idProperty];
462                     } else {
463                         $values[] = $record;
464                     }
465                 }
466
467                 // remove own record if in list
468                 sort($values);
469                 Tinebase_Helper::array_remove_by_value($_record->getId(), $values);
470                 $value = json_encode($values);
471             }
472         } else {
473             $value = $_value;
474         }
475
476         // check if customfield value is the record itself
477         if (get_class($_record) == $modelName && strpos($value, $_record->getId()) !== false) {
478             throw new Tinebase_Exception_Record_Validation('It is not allowed to add the same record as customfield record!');
479         }
480
481         return $value;
482     }
483     
484     /**
485      * get custom fields and add them to $_record->customfields array
486      *
487      * @param Tinebase_Record_Interface $_record
488      * @param Tinebase_Record_RecordSet $_customFields
489      * @param Tinebase_Record_RecordSet $_configs
490      */
491     public function resolveRecordCustomFields(Tinebase_Record_Interface $_record, $_customFields = NULL, $_configs = NULL)
492     {
493         $customFields = ($_customFields === NULL) ? $this->_getCustomFields($_record->getId()) : $_customFields;
494         
495         if (count($customFields) == 0) {
496             return;
497         }
498         
499         if ($_configs === NULL) {
500             $_configs = $this->getCustomFieldsForApplication(Tinebase_Application::getInstance()->getApplicationByName($_record->getApplication()));
501         };
502         
503         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
504             . ' Adding ' . count($customFields) . ' customfields to record  ' . $_record->getId());
505
506         foreach ($customFields as $customField) {
507             $this->_setCfValueInRecord($_record, $customField, $_configs);
508         }
509     }
510     
511     /**
512      * set customfield value in record
513      * 
514      * @param Tinebase_Record_Interface $record
515      * @param Tinebase_Model_CustomField_Value $customField
516      * @param Tinebase_Record_RecordSet $configs
517      * @param bool $extendedResolving
518      */
519     protected function _setCfValueInRecord(Tinebase_Record_Interface $record, Tinebase_Model_CustomField_Value $customField, Tinebase_Record_RecordSet $configs, $extendedResolving = false)
520     {
521         $recordCfs = $record->customfields;
522         $idx = $configs->getIndexById($customField->customfield_id);
523         if ($idx !== FALSE) {
524             /** @var Tinebase_Model_CustomField_Config $config */
525             $config = $configs[$idx];
526             if (strtolower($config->definition->type) == 'record' || strtolower($config->definition->type) == 'recordlist') {
527                 $value = $this->_getRecordTypeCfValue($config, $customField->value, strtolower($config->definition['type']));
528             } else {
529                 $value = $customField->value;
530             }
531             if (true === $extendedResolving) {
532                 $definition = is_object($config->definition) ? $config->definition->toArray() : (array)$config->definition;
533                 $definition['value'] = $value;
534                 $recordCfs[$config->name] = $definition;
535             } else {
536                 $recordCfs[$config->name] = $value;
537             }
538         }
539
540         // sort customfields by key
541         if (is_array($recordCfs)) {
542             ksort($recordCfs);
543         }
544         
545         $record->customfields = $recordCfs;
546     }
547     
548     /**
549      * get record cf value
550      * 
551      * @param Tinebase_Model_CustomField_Config $config
552      * @param string $value
553      * @return string
554      */
555     protected function _getRecordTypeCfValue($config, $value, $type = 'record')
556     {
557         try {
558             $recordConfigIndex = $type === 'record' ? 'recordConfig' : 'recordListConfig';
559             $model = $config->definition[$recordConfigIndex]['value']['records'];
560             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
561                 . ' Fetching ' . $type . ' customfield of type ' . $model);
562             
563             $controller = Tinebase_Core::getApplicationInstance($model);
564             // TODO why do we already convert to array here? should be done in converter!
565             if ($type === 'record') {
566                 $result = $controller->get($value)->toArray();
567             } else {
568                 $result = $controller->getMultiple(Tinebase_Helper::jsonDecode($value))->toArray();
569             }
570         } catch (Exception $e) {
571             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
572                 . ' Error resolving custom field record: ' . $e->getMessage());
573             $result = $value;
574         }
575         
576         return $result;
577     }
578
579     /**
580      * get all customfields of all given records
581      * 
582      * @param  Tinebase_Record_RecordSet $_records     records to get customfields for
583      * @param  bool                      $_extendedResolving
584      */
585     public function resolveMultipleCustomfields(Tinebase_Record_RecordSet $_records, $_extendedResolving = false)
586     {
587         if (count($_records) == 0) {
588             return;
589         }
590         
591         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
592             . ' Before resolving - MEMORY: ' . memory_get_usage(TRUE)/1024/1024 . ' MBytes');
593         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
594             . ' Resolving custom fields for ' . count($_records) . ' ' . $_records->getRecordClassName() . ' records.');
595         
596         $configs = $this->getCustomFieldsForApplication(Tinebase_Application::getInstance()->getApplicationByName($_records->getFirstRecord()->getApplication()));
597         
598         $customFields = $this->_getCustomFields($_records->getArrayOfIdsAsString(), $configs->getArrayOfIds());
599         $customFields->sort('record_id');
600         
601         // NOTE: as filtering is currently very slow, we have to loop the customfields and add the value to the record.
602         // @see 0007496: timeout when opening multiedit dlg and assigning records to events/projects/email
603         // @see 0007558: reactivate indices in Tinebase_Record_RecordSet
604         /** @var Tinebase_Record_Interface $record */
605         $record = NULL;
606         foreach ($customFields as $customField) {
607             if (! $record || $record->getId() !== $customField->record_id) {
608                 $record = $_records->getById($customField->record_id);
609             }
610             $this->_setCfValueInRecord($record, $customField, $configs, $_extendedResolving);
611         }
612         
613         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
614             . ' After resolving - MEMORY: ' . memory_get_usage(TRUE)/1024/1024 . ' MBytes');
615     }
616     
617     /**
618      * get custom fields of record(s)
619      *
620      * @param string|array $_recordId
621      * @param array|null $_configIds
622      * @return Tinebase_Record_RecordSet of Tinebase_Model_CustomField_Value
623      */
624     protected function _getCustomFields($_recordId, $_configIds = NULL)
625     {
626         $recordIds = is_array($_recordId) ? $_recordId : array((string) $_recordId);
627         
628         $filterValues = array(array(
629             'field'     => 'record_id', 
630             'operator'  => 'in', 
631             'value'     => $recordIds
632         ));
633         if ($_configIds) {
634             $filterValues[] = array(
635                 'field'     => 'customfield_id', 
636                 'operator'  => 'in', 
637                 'value'     => (array) $_configIds
638             );
639         }
640         $filter = new Tinebase_Model_CustomField_ValueFilter($filterValues);
641         
642         $result = $this->_backendValue->search($filter);
643         
644         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
645             . ' Fetched ' . count($result) . ' customfield values.');
646         
647         return $result;
648     }
649     
650     /**
651      * set grants for custom field
652      *
653      * @param   string|Tinebase_Model_CustomField_Config $_customfieldId
654      * @param   array $_grants list of grants to add
655      * @param   string $_accountType
656      * @param   int $_accountId
657      * @return  void
658      * @throws Tinebase_Exception_Backend
659      */
660     public function setGrants($_customfieldId, $_grants = array(), $_accountType = NULL, $_accountId = NULL)
661     {
662         $cfId = ($_customfieldId instanceof Tinebase_Model_CustomField_Config) ? $_customfieldId->getId() : $_customfieldId;
663         
664         try {
665             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
666                 . ' Setting grants for custom field ' . $cfId . ' -> ' . implode(',', $_grants) . ' for '
667                 . ($_accountType !== NULL ? $_accountType : Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE) . ' (' . $_accountId . ')');
668             
669             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
670             $this->_backendACL->deleteByProperty($cfId, 'customfield_id');
671             
672             foreach ($_grants as $grant) {
673                 if (in_array($grant, Tinebase_Model_CustomField_Grant::getAllGrants())) {
674                     $newGrant = new Tinebase_Model_CustomField_Grant(array(
675                         'customfield_id'=> $cfId,
676                         'account_type'  => ($_accountType !== NULL) ? $_accountType : Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
677                         'account_id'    => ($_accountId !== NULL) ? $_accountId : 0,
678                         'account_grant' => $grant
679                     ));
680                     $this->_backendACL->create($newGrant);
681                 }
682             }
683             
684             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
685             $this->_clearCache();
686             
687         } catch (Exception $e) {
688             Tinebase_TransactionManager::getInstance()->rollBack();
689             throw new Tinebase_Exception_Backend($e->getMessage());
690         }
691     }
692     
693     /**
694      * get customfield config ids by grant
695      * 
696      * @param string $_grant
697      * @return array of ids
698      */
699     public function getCustomfieldConfigIdsByAcl($_grant)
700     {
701         $user = Tinebase_Core::getUser();
702         if (is_object($user)) {
703             $result = $this->_backendConfig->getByAcl($_grant, $user->getId());
704         } else {
705             $result = array();
706         }
707         
708         return $result;
709     }
710     
711     /**
712      * remove all customfield related entries from cache
713      * 
714      * @todo this needs to clear in a more efficient way
715      */
716     protected function _clearCache() 
717     {
718         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
719             . ' Clearing custom field cache.');
720         
721         $this->_cfByApplicationCache = array();
722         
723         $cache = Tinebase_Core::getCache();
724         $cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('customfields'));
725     }
726
727     /**
728     * remove related entries from cache for given cf config record
729     * 
730     * @param Tinebase_Model_CustomField_Config $record
731     * 
732     * @todo this needs to clear in a more efficient way
733     */
734     public function clearCacheForConfig(/** @noinspection PhpUnusedParameterInspection */
735         Tinebase_Model_CustomField_Config $record)
736     {
737         $this->_clearCache();
738         
739         // NOTE: this does not work as we need the user id in the cacheId
740         /*
741         $cfIndexRead  = Tinebase_Model_CustomField_Grant::GRANT_READ;
742         $cfIndexWrite = Tinebase_Model_CustomField_Grant::GRANT_WRITE;
743         $cfIndexModelRead  = $record->model . Tinebase_Model_CustomField_Grant::GRANT_READ;
744         $cfIndexModelWrite = $record->model . Tinebase_Model_CustomField_Grant::GRANT_WRITE;
745         $idsToClear = array($cfIndexRead, $cfIndexModelRead, $cfIndexWrite, $cfIndexModelWrite);
746         
747         $cache = Tinebase_Core::getCache();
748         foreach ($idsToClear as $id) {
749             $cacheId = 'getCustomFieldsForApplication' . $record->application_id . $id;
750             unset($this->_cfByApplicationCache[$record->application_id . $id]);
751             $cache->remove($cacheId);
752         }
753         */
754     }
755     
756     /******************** functions for Tinebase_Controller_SearchInterface / get custom field values ***************************/
757     
758     /**
759      * get list of custom field values
760      *
761      * @param Tinebase_Model_Filter_FilterGroup $_filter
762      * @param Tinebase_Model_Pagination $_pagination
763      * @param bool $_getRelations (unused)
764      * @param boolean $_onlyIds (unused)
765      * @param string $_action
766      * @return Tinebase_Record_RecordSet
767      */
768     public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
769     {
770         $result = $this->_backendValue->search($_filter, $_pagination);
771         return $result;
772     }
773     
774     /**
775      * Gets total count of search with $_filter
776      * 
777      * @param Tinebase_Model_Filter_FilterGroup $_filter
778      * @param string $_action
779      * @return int
780      */
781     public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
782     {
783         $count = $this->_backendValue->searchCount($_filter);
784         return $count;
785     }
786
787     /**
788      * get list of custom field values
789      *
790      * @param Tinebase_Model_Filter_FilterGroup $_filter
791      * @param Tinebase_Record_Interface $_pagination
792      * @param bool $_getRelations (unused)
793      * @param bool $_onlyIds (unused)
794      * @return Tinebase_Record_RecordSet
795      */
796     public function searchConfig(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, /** @noinspection PhpUnusedParameterInspection */ $_getRelations = FALSE, /** @noinspection PhpUnusedParameterInspection */ $_onlyIds = FALSE)
797     {
798         $result = $this->_backendConfig->search($_filter, $_pagination);
799         return $result;
800     }
801
802     public function deleteCustomFieldValue($_ids)
803     {
804         return $this->_backendValue->delete($_ids);
805     }
806
807     public function saveCustomFieldValue(Tinebase_Model_CustomField_Value $_record)
808     {
809         $recordId = $_record->getId();
810         if (! empty($recordId)) {
811             return $this->_backendValue->update($_record);
812         }
813         return $this->_backendValue->create($_record);
814     }
815 }