3 * convert functions for records from/to json (array) format
7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @author Philipp Schüle <p.schuele@metaways.de>
9 * @copyright Copyright (c) 2011-2014 Metaways Infosystems GmbH (http://www.metaways.de)
13 * convert functions for records from/to json (array) format
18 class Tinebase_Convert_Json implements Tinebase_Convert_Interface
21 * converts external format to Tinebase_Record_Abstract
23 * @param mixed $_blob the input data to parse
24 * @param Tinebase_Record_Abstract $_record update existing record
25 * @return Tinebase_Record_Abstract
27 public function toTine20Model($_blob, Tinebase_Record_Abstract $_record = NULL)
29 throw new Tinebase_Exception_NotImplemented('From json to record is not implemented yet');
33 * converts Tinebase_Record_Abstract to external format
35 * @param Tinebase_Record_Abstract $_record
38 public function fromTine20Model(Tinebase_Record_Abstract $_record)
44 // for resolving we'll use recordset
45 $recordClassName = get_class($_record);
46 $records = new Tinebase_Record_RecordSet($recordClassName, array($_record));
47 $modelConfiguration = $recordClassName::getConfiguration();
49 $this->_resolveBeforeToArray($records, $modelConfiguration, FALSE);
51 $_record = $records->getFirstRecord();
52 $_record->setTimezone(Tinebase_Core::getUserTimezone());
53 $_record->bypassFilters = true;
55 $result = $_record->toArray();
57 $result = $this->_resolveAfterToArray($result, $modelConfiguration, FALSE);
63 * resolves single record fields (Tinebase_ModelConfiguration._recordsFields)
65 * @param Tinebase_Record_RecordSet $_records the records
66 * @param Tinebase_ModelConfiguration $modelConfig
68 protected function _resolveSingleRecordFields(Tinebase_Record_RecordSet $_records, $modelConfig = NULL)
74 $resolveFields = $modelConfig->recordFields;
76 if ($resolveFields && is_array($resolveFields)) {
77 // don't search twice if the same recordClass gets resolved on multiple fields
78 $resolveRecords = array();
79 foreach ($resolveFields as $fieldKey => $fieldConfig) {
80 $resolveRecords[$fieldConfig['config']['recordClassName']][] = $fieldKey;
83 foreach ($resolveRecords as $foreignRecordClassName => $fields) {
84 $foreignIds = array();
85 $foreignRecordsArray = array();
86 $fields = (array) $fields;
87 foreach ($fields as $field) {
88 $idsForField = $_records->{$field};
89 foreach ($idsForField as $key => $value) {
90 if ($value instanceof Tinebase_Record_Abstract) {
91 $foreignRecordsArray[$value->getId()] = $value;
93 if ($value && !isset($foreignRecordsArray[$value])) {
94 $foreignIds[$value] = $value;
100 if (empty($foreignIds) && empty($foreignRecordsArray)) {
104 $cfg = $resolveFields[$fields[0]];
106 if ($cfg['type'] == 'user') {
107 $foreignRecords = Tinebase_User::getInstance()->getMultiple($foreignIds);
108 } else if ($cfg['type'] == 'container') {
109 // TODO: resolve recursive records of records better in controller
110 // TODO: resolve containers
111 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
112 . ' No handling for container foreign records implemented');
114 //$foreignRecords = new Tinebase_Record_RecordSet('Tinebase_Model_Container');
115 // $foreignRecords->addRecord(Tinebase_Container::getInstance()->get(XXX));
119 $controller = Tinebase_Core::getApplicationInstance($foreignRecordClassName);
120 $foreignRecords = $controller->getMultiple($foreignIds);
121 } catch (Tinebase_Exception_AccessDenied $tead) {
122 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
123 . ' No right to access application of record ' . $foreignRecordClassName);
128 foreach($foreignRecordsArray as $id => $rec) {
129 if ($foreignRecords->getById($id) === false) {
130 $foreignRecords->addRecord($rec);
134 if ($foreignRecords->count() === 0) {
135 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
136 . ' No matching foreign records found (' . $foreignRecordClassName . ')');
139 $foreignRecords->setTimezone(Tinebase_Core::getUserTimezone());
140 $foreignRecords->convertDates = true;
141 Tinebase_Frontend_Json_Abstract::resolveContainersAndTags($foreignRecords);
142 $fr = $foreignRecords->getFirstRecord();
143 if ($fr && $fr->has('notes')) {
144 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
147 foreach ($_records as $record) {
148 foreach ($fields as $field) {
149 $foreignId = $record->{$field};
150 if (is_scalar($foreignId)) {
151 $idx = $foreignRecords->getIndexById($foreignId);
152 if (isset($idx) && $idx !== FALSE) {
153 $record->{$field} = $foreignRecords[$idx];
155 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
156 . ' No matching foreign record found for id: ' . $foreignId);
166 * resolves multiple records (fallback)
168 * @deprecated use Tinebase_ModelConfiguration to configure your models, so this won't be used anymore
169 * @param Tinebase_Record_RecordSet $_records the records
170 * @param array $resolveFields
172 public static function resolveMultipleIdFields($records, $resolveFields = NULL)
174 if (! $records instanceof Tinebase_Record_RecordSet || !$records->count()) {
178 $ownRecordClass = $records->getRecordClassName();
179 if ($resolveFields === NULL) {
180 $resolveFields = $ownRecordClass::getResolveForeignIdFields();
183 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
184 . ' Resolving ' . $ownRecordClass . ' fields: ' . print_r($resolveFields, TRUE));
186 foreach ((array) $resolveFields as $foreignRecordClassName => $fields) {
187 if ($foreignRecordClassName === 'recursive') {
188 foreach ($fields as $field => $model) {
189 foreach ($records->$field as $subRecords) {
190 self::resolveMultipleIdFields($subRecords);
194 self::_resolveForeignIdFields($records, $foreignRecordClassName, (array) $fields);
200 * resolve foreign fields for records like user ids to users, etc.
202 * @param Tinebase_Record_RecordSet $records
203 * @param string $foreignRecordClassName
204 * @param array $fields
206 protected static function _resolveForeignIdFields($records, $foreignRecordClassName, $fields)
208 $options = (isset($fields['options']) || array_key_exists('options', $fields)) ? $fields['options'] : array();
209 $fields = (isset($fields['fields']) || array_key_exists('fields', $fields)) ? $fields['fields'] : $fields;
211 $foreignIds = array();
212 foreach ($fields as $field) {
213 $foreignIds = array_unique(array_merge($foreignIds, $records->{$field}));
217 $controller = Tinebase_Core::getApplicationInstance($foreignRecordClassName);
218 } catch (Tinebase_Exception_AccessDenied $tead) {
219 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
220 . ' No right to access application of record ' . $foreignRecordClassName);
224 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
225 . ' Fetching ' . $foreignRecordClassName . ' by id: ' . print_r($foreignIds, TRUE));
227 if ((isset($options['ignoreAcl']) || array_key_exists('ignoreAcl', $options)) && $options['ignoreAcl']) {
228 // @todo make sure that second param of getMultiple() is $ignoreAcl
229 $foreignRecords = $controller->getMultiple($foreignIds, TRUE);
231 $foreignRecords = $controller->getMultiple($foreignIds);
234 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
235 . ' Foreign records found: ' . print_r($foreignRecords->toArray(), TRUE));
237 if (count($foreignRecords) === 0) {
241 foreach ($records as $record) {
242 foreach ($fields as $field) {
243 if (is_scalar($record->{$field})) {
244 $idx = $foreignRecords->getIndexById($record->{$field});
245 if (isset($idx) && $idx !== FALSE) {
246 $record->{$field} = $foreignRecords[$idx];
248 switch ($foreignRecordClassName) {
249 case 'Tinebase_Model_User':
250 case 'Tinebase_Model_FullUser':
251 $record->{$field} = Tinebase_User::getInstance()->getNonExistentUser();
263 * resolve multiple record fields (Tinebase_ModelConfiguration._recordsFields)
265 * @param Tinebase_Record_RecordSet $_records
266 * @param Tinebase_ModelConfiguration $modelConfiguration
267 * @param boolean $multiple
268 * @throws Tinebase_Exception_UnexpectedValue
270 protected function _resolveMultipleRecordFields(Tinebase_Record_RecordSet $_records, $modelConfiguration = NULL, $multiple = false)
272 if (! $modelConfiguration || (! $_records->count())) {
276 if (! ($resolveFields = $modelConfiguration->recordsFields)) {
280 $ownIds = $_records->{$modelConfiguration->idProperty};
282 // iterate fields to resolve
283 foreach ($resolveFields as $fieldKey => $c) {
284 $config = $c['config'];
286 // resolve records, if omitOnSearch is definitively set to FALSE (by default they won't be resolved on search)
287 if ($multiple && !(isset($config['omitOnSearch']) && $config['omitOnSearch'] === FALSE)) {
291 if (! isset($config['controllerClassName'])) {
292 throw new Tinebase_Exception_UnexpectedValue('Controller class name needed');
295 // fetch the fields by the refIfField
296 $controller = $config['controllerClassName']::getInstance();
297 $filterName = $config['filterClassName'];
299 $filterArray = array();
301 // addFilters can be added and must be added if the same model resides in more than one records fields
302 if (isset($config['addFilters']) && is_array($config['addFilters'])) {
303 $filterArray = $config['addFilters'];
306 $filter = new $filterName($filterArray);
307 $filter->addFilter(new Tinebase_Model_Filter_Id(array('field' => $config['refIdField'], 'operator' => 'in', 'value' => $ownIds)));
310 if (isset($config['paging']) && is_array($config['paging'])) {
311 $paging = new Tinebase_Model_Pagination($config['paging']);
314 $foreignRecords = $controller->search($filter, $paging);
315 $foreignRecordClass = $foreignRecords->getRecordClassName();
316 $foreignRecordModelConfiguration = $foreignRecordClass::getConfiguration();
318 $foreignRecords->setTimezone(Tinebase_Core::getUserTimezone());
319 $foreignRecords->convertDates = true;
321 $fr = $foreignRecords->getFirstRecord();
323 // @todo: resolve alarms?
324 // @todo: use parts parameter?
325 if ($foreignRecordModelConfiguration->resolveRelated && $fr) {
326 if ($fr->has('notes')) {
327 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
329 if ($fr->has('tags')) {
330 Tinebase_Tags::getInstance()->getMultipleTagsOfRecords($foreignRecords);
332 if ($fr->has('relations')) {
333 $relations = Tinebase_Relations::getInstance()->getMultipleRelations($foreignRecordClass, 'Sql', $foreignRecords->{$fr->getIdProperty()} );
334 $foreignRecords->setByIndices('relations', $relations);
336 if ($fr->has('customfields')) {
337 Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($foreignRecords);
339 if ($fr->has('attachments') && Tinebase_Core::isFilesystemAvailable()) {
340 Tinebase_FileSystem_RecordAttachments::getInstance()->getMultipleAttachmentsOfRecords($foreignRecords);
344 if ($foreignRecords->count() > 0) {
345 foreach ($_records as $record) {
346 $filtered = $foreignRecords->filter($config['refIdField'], $record->getId())->toArray();
347 $filtered = $this->_resolveAfterToArray($filtered, $foreignRecordModelConfiguration, TRUE);
348 $record->{$fieldKey} = $filtered;
352 $_records->{$fieldKey} = NULL;
359 * resolves virtual fields, if a function has been defined in the field definition
361 * @param array $resultSet
362 * @param Tinebase_ModelConfiguration $modelConfiguration
363 * @param boolean $multiple
365 protected function _resolveVirtualFields($resultSet, $modelConfiguration = NULL, $multiple = false)
367 if (! $modelConfiguration || ! ($virtualFields = $modelConfiguration->virtualFields)) {
371 if ($modelConfiguration->resolveVFGlobally === TRUE) {
373 $controller = $modelConfiguration->getControllerInstance();
376 return $controller->resolveMultipleVirtualFields($resultSet);
378 return $controller->resolveVirtualFields($resultSet);
381 foreach ($virtualFields as $field) {
382 // resolve virtual relation record from relations property
383 if (! $multiple && isset($field['type']) && $field['type'] == 'relation') {
384 $fc = $field['config'];
385 if (isset($resultSet['relations']) && (is_array($resultSet['relations']))) {
386 foreach($resultSet['relations'] as $relation) {
387 if (($relation['type'] == $fc['type']) && ($relation['related_model'] == ($fc['appName'] . '_Model_' . $fc['modelName']))) {
388 $resultSet[$field['key']] = $relation['related_record'];
392 // resolve virtual field by function
393 } else if ((isset($field['function']) || array_key_exists('function', $field))) {
394 if (is_array($field['function'])) {
395 if (count($field['function']) > 1) { // static method call
396 $class = $field['function'][0];
397 $method = $field['function'][1];
398 $resultSet = $class::$method($resultSet);
400 } else { // use key as classname and value as method name
401 $ks = array_keys($field['function']);
402 $class = array_pop($ks);
403 $vs = array_values($field['function']);
404 $method = array_pop($vs);
405 $class = $class::getInstance();
407 $resultSet = $class->$method($resultSet);
410 // if no array has been given, this should be a function name
412 $resolveFunction = $field['function'];
413 $resultSet = $resolveFunction($resultSet);
422 * resolves child records before converting the record set to an array
424 * @param Tinebase_Record_RecordSet $records
425 * @param Tinebase_ModelConfiguration $modelConfiguration
426 * @param boolean $multiple
428 protected function _resolveBeforeToArray($records, $modelConfiguration, $multiple = false)
430 Tinebase_Frontend_Json_Abstract::resolveContainersAndTags($records);
432 self::resolveMultipleIdFields($records);
434 // use modern record resolving, if the model was configured using Tinebase_ModelConfiguration
435 // at first, resolve all single record fields
436 if ($modelConfiguration) {
437 $this->_resolveSingleRecordFields($records, $modelConfiguration);
439 // resolve all multiple records fields
440 $this->_resolveMultipleRecordFields($records, $modelConfiguration, $multiple);
445 * resolves child records after converting the record set to an array
447 * @param array $result
448 * @param Tinebase_ModelConfiguration $modelConfiguration
449 * @param boolean $multiple
453 protected function _resolveAfterToArray($result, $modelConfiguration, $multiple = false)
455 $result = $this->_resolveVirtualFields($result, $modelConfiguration, $multiple);
460 * converts Tinebase_Record_RecordSet to external format
462 * @param Tinebase_Record_RecordSet $_records
463 * @param Tinebase_Model_Filter_FilterGroup $_filter
464 * @param Tinebase_Model_Pagination $_pagination
468 public function fromTine20RecordSet(Tinebase_Record_RecordSet $_records = NULL, $_filter = NULL, $_pagination = NULL)
470 if (! $_records || count($_records) == 0) {
474 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
475 . ' Processing ' . count($_records) . ' records ...');
477 // find out if there is a modelConfiguration
478 $ownRecordClass = $_records->getRecordClassName();
479 $config = $ownRecordClass::getConfiguration();
481 $this->_resolveBeforeToArray($_records, $config, TRUE);
483 $_records->setTimezone(Tinebase_Core::getUserTimezone());
484 $_records->convertDates = true;
486 $result = $_records->toArray();
488 // resolve all virtual fields after converting to array, so we can add these properties "virtually"
489 $result = $this->_resolveAfterToArray($result, $config, TRUE);