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 $fields = (array) $fields;
86 foreach ($fields as $field) {
87 $idsForField = $_records->{$field};
88 foreach ($idsForField as $key => $value) {
89 if ($value && ! in_array($value, $foreignIds)) {
90 $foreignIds[] = $value;
95 if (empty($foreignIds)) {
99 $cfg = $resolveFields[$fields[0]];
101 if ($cfg['type'] == 'user') {
102 $foreignRecords = Tinebase_User::getInstance()->getMultiple($foreignIds);
103 } else if ($cfg['type'] == 'container') {
104 // TODO: resolve recursive records of records better in controller
105 // TODO: resolve containers
106 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
107 . ' No handling for container foreign records implemented');
109 $foreignRecords = new Tinebase_Record_RecordSet('Tinebase_Model_Container');
110 // $foreignRecords->addRecord(Tinebase_Container::getInstance()->get(XXX));
113 $controller = Tinebase_Core::getApplicationInstance($foreignRecordClassName);
114 $foreignRecords = $controller->getMultiple($foreignIds);
115 } catch (Tinebase_Exception_AccessDenied $tead) {
116 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
117 . ' No right to access application of record ' . $foreignRecordClassName);
122 if ($foreignRecords->count() === 0) {
123 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
124 . ' No matching foreign records found (' . $foreignRecordClassName . ')');
127 $foreignRecords->setTimezone(Tinebase_Core::getUserTimezone());
128 $foreignRecords->convertDates = true;
129 Tinebase_Frontend_Json_Abstract::resolveContainersAndTags($foreignRecords);
130 $fr = $foreignRecords->getFirstRecord();
131 if ($fr && $fr->has('notes')) {
132 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
135 foreach ($_records as $record) {
136 foreach ($fields as $field) {
137 $foreignId = $record->{$field};
138 if (is_scalar($foreignId)) {
139 $idx = $foreignRecords->getIndexById($foreignId);
140 if (isset($idx) && $idx !== FALSE) {
141 $record->{$field} = $foreignRecords[$idx];
143 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
144 . ' No matching foreign record found for id: ' . $foreignId);
154 * resolves multiple records (fallback)
156 * @deprecated use Tinebase_ModelConfiguration to configure your models, so this won't be used anymore
157 * @param Tinebase_Record_RecordSet $_records the records
158 * @param array $resolveFields
160 public static function resolveMultipleIdFields($records, $resolveFields = NULL)
162 if (! $records instanceof Tinebase_Record_RecordSet || !$records->count()) {
166 $ownRecordClass = $records->getRecordClassName();
167 if ($resolveFields === NULL) {
168 $resolveFields = $ownRecordClass::getResolveForeignIdFields();
171 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
172 . ' Resolving ' . $ownRecordClass . ' fields: ' . print_r($resolveFields, TRUE));
174 foreach ((array) $resolveFields as $foreignRecordClassName => $fields) {
175 if ($foreignRecordClassName === 'recursive') {
176 foreach ($fields as $field => $model) {
177 foreach ($records->$field as $subRecords) {
178 self::resolveMultipleIdFields($subRecords);
182 self::_resolveForeignIdFields($records, $foreignRecordClassName, (array) $fields);
188 * resolve foreign fields for records like user ids to users, etc.
190 * @param Tinebase_Record_RecordSet $records
191 * @param string $foreignRecordClassName
192 * @param array $fields
194 protected static function _resolveForeignIdFields($records, $foreignRecordClassName, $fields)
196 $options = (isset($fields['options']) || array_key_exists('options', $fields)) ? $fields['options'] : array();
197 $fields = (isset($fields['fields']) || array_key_exists('fields', $fields)) ? $fields['fields'] : $fields;
199 $foreignIds = array();
200 foreach ($fields as $field) {
201 $foreignIds = array_unique(array_merge($foreignIds, $records->{$field}));
205 $controller = Tinebase_Core::getApplicationInstance($foreignRecordClassName);
206 } catch (Tinebase_Exception_AccessDenied $tead) {
207 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
208 . ' No right to access application of record ' . $foreignRecordClassName);
212 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
213 . ' Fetching ' . $foreignRecordClassName . ' by id: ' . print_r($foreignIds, TRUE));
215 if ((isset($options['ignoreAcl']) || array_key_exists('ignoreAcl', $options)) && $options['ignoreAcl']) {
216 // @todo make sure that second param of getMultiple() is $ignoreAcl
217 $foreignRecords = $controller->getMultiple($foreignIds, TRUE);
219 $foreignRecords = $controller->getMultiple($foreignIds);
222 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
223 . ' Foreign records found: ' . print_r($foreignRecords->toArray(), TRUE));
225 if (count($foreignRecords) === 0) {
229 foreach ($records as $record) {
230 foreach ($fields as $field) {
231 if (is_scalar($record->{$field})) {
232 $idx = $foreignRecords->getIndexById($record->{$field});
233 if (isset($idx) && $idx !== FALSE) {
234 $record->{$field} = $foreignRecords[$idx];
236 switch ($foreignRecordClassName) {
237 case 'Tinebase_Model_User':
238 case 'Tinebase_Model_FullUser':
239 $record->{$field} = Tinebase_User::getInstance()->getNonExistentUser();
251 * resolve multiple record fields (Tinebase_ModelConfiguration._recordsFields)
253 * @param Tinebase_Record_RecordSet $_records
254 * @param Tinebase_ModelConfiguration $modelConfiguration
255 * @param boolean $multiple
256 * @throws Tinebase_Exception_UnexpectedValue
258 protected function _resolveMultipleRecordFields(Tinebase_Record_RecordSet $_records, $modelConfiguration = NULL, $multiple = false)
260 if (! $modelConfiguration || (! $_records->count())) {
264 if (! ($resolveFields = $modelConfiguration->recordsFields)) {
268 $ownIds = $_records->{$modelConfiguration->idProperty};
270 // iterate fields to resolve
271 foreach ($resolveFields as $fieldKey => $c) {
272 $config = $c['config'];
274 // resolve records, if omitOnSearch is definitively set to FALSE (by default they won't be resolved on search)
275 if ($multiple && !(isset($config['omitOnSearch']) && $config['omitOnSearch'] === FALSE)) {
279 if (! isset($config['controllerClassName'])) {
280 throw new Tinebase_Exception_UnexpectedValue('Controller class name needed');
283 // fetch the fields by the refIfField
284 $controller = $config['controllerClassName']::getInstance();
285 $filterName = $config['filterClassName'];
287 $filterArray = array();
289 // addFilters can be added and must be added if the same model resides in more than one records fields
290 if (isset($config['addFilters']) && is_array($config['addFilters'])) {
291 $filterArray = $config['addFilters'];
294 $filter = new $filterName($filterArray);
295 $filter->addFilter(new Tinebase_Model_Filter_Id(array('field' => $config['refIdField'], 'operator' => 'in', 'value' => $ownIds)));
298 if (isset($config['paging']) && is_array($config['paging'])) {
299 $paging = new Tinebase_Model_Pagination($config['paging']);
302 $foreignRecords = $controller->search($filter, $paging);
303 $foreignRecordClass = $foreignRecords->getRecordClassName();
304 $foreignRecordModelConfiguration = $foreignRecordClass::getConfiguration();
306 $foreignRecords->setTimezone(Tinebase_Core::getUserTimezone());
307 $foreignRecords->convertDates = true;
309 $fr = $foreignRecords->getFirstRecord();
311 // @todo: resolve alarms?
312 // @todo: use parts parameter?
313 if ($foreignRecordModelConfiguration->resolveRelated && $fr) {
314 if ($fr->has('notes')) {
315 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
317 if ($fr->has('tags')) {
318 Tinebase_Tags::getInstance()->getMultipleTagsOfRecords($foreignRecords);
320 if ($fr->has('relations')) {
321 $relations = Tinebase_Relations::getInstance()->getMultipleRelations($foreignRecordClass, 'Sql', $foreignRecords->{$fr->getIdProperty()} );
322 $foreignRecords->setByIndices('relations', $relations);
324 if ($fr->has('customfields')) {
325 Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($foreignRecords);
327 if ($fr->has('attachments') && Tinebase_Core::isFilesystemAvailable()) {
328 Tinebase_FileSystem_RecordAttachments::getInstance()->getMultipleAttachmentsOfRecords($foreignRecords);
332 if ($foreignRecords->count() > 0) {
333 foreach ($_records as $record) {
334 $filtered = $foreignRecords->filter($config['refIdField'], $record->getId())->toArray();
335 $filtered = $this->_resolveAfterToArray($filtered, $foreignRecordModelConfiguration, TRUE);
336 $record->{$fieldKey} = $filtered;
340 $_records->{$fieldKey} = NULL;
347 * resolves virtual fields, if a function has been defined in the field definition
349 * @param array $resultSet
350 * @param Tinebase_ModelConfiguration $modelConfiguration
351 * @param boolean $multiple
353 protected function _resolveVirtualFields($resultSet, $modelConfiguration = NULL, $multiple = false)
355 if (! $modelConfiguration || ! ($virtualFields = $modelConfiguration->virtualFields)) {
359 if ($modelConfiguration->resolveVFGlobally === TRUE) {
361 $controller = $modelConfiguration->getControllerInstance();
364 return $controller->resolveMultipleVirtualFields($resultSet);
366 return $controller->resolveVirtualFields($resultSet);
369 foreach ($virtualFields as $field) {
370 // resolve virtual relation record from relations property
371 if (! $multiple && isset($field['type']) && $field['type'] == 'relation') {
372 $fc = $field['config'];
373 if (isset($resultSet['relations']) && (is_array($resultSet['relations']))) {
374 foreach($resultSet['relations'] as $relation) {
375 if (($relation['type'] == $fc['type']) && ($relation['related_model'] == ($fc['appName'] . '_Model_' . $fc['modelName']))) {
376 $resultSet[$field['key']] = $relation['related_record'];
380 // resolve virtual field by function
381 } else if ((isset($field['function']) || array_key_exists('function', $field))) {
382 if (is_array($field['function'])) {
383 if (count($field['function']) > 1) { // static method call
384 $class = $field['function'][0];
385 $method = $field['function'][1];
386 $resultSet = $class::$method($resultSet);
388 } else { // use key as classname and value as method name
389 $ks = array_keys($field['function']);
390 $class = array_pop($ks);
391 $vs = array_values($field['function']);
392 $method = array_pop($vs);
393 $class = $class::getInstance();
395 $resultSet = $class->$method($resultSet);
398 // if no array has been given, this should be a function name
400 $resolveFunction = $field['function'];
401 $resultSet = $resolveFunction($resultSet);
410 * resolves child records before converting the record set to an array
412 * @param Tinebase_Record_RecordSet $records
413 * @param Tinebase_ModelConfiguration $modelConfiguration
414 * @param boolean $multiple
416 protected function _resolveBeforeToArray($records, $modelConfiguration, $multiple = false)
418 Tinebase_Frontend_Json_Abstract::resolveContainersAndTags($records);
420 self::resolveMultipleIdFields($records);
422 // use modern record resolving, if the model was configured using Tinebase_ModelConfiguration
423 // at first, resolve all single record fields
424 if ($modelConfiguration) {
425 $this->_resolveSingleRecordFields($records, $modelConfiguration);
427 // resolve all multiple records fields
428 $this->_resolveMultipleRecordFields($records, $modelConfiguration, $multiple);
433 * resolves child records after converting the record set to an array
435 * @param array $result
436 * @param Tinebase_ModelConfiguration $modelConfiguration
437 * @param boolean $multiple
441 protected function _resolveAfterToArray($result, $modelConfiguration, $multiple = false)
443 $result = $this->_resolveVirtualFields($result, $modelConfiguration, $multiple);
448 * converts Tinebase_Record_RecordSet to external format
450 * @param Tinebase_Record_RecordSet $_records
451 * @param Tinebase_Model_Filter_FilterGroup $_filter
452 * @param Tinebase_Model_Pagination $_pagination
456 public function fromTine20RecordSet(Tinebase_Record_RecordSet $_records = NULL, $_filter = NULL, $_pagination = NULL)
458 if (! $_records || count($_records) == 0) {
462 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
463 . ' Processing ' . count($_records) . ' records ...');
465 // find out if there is a modelConfiguration
466 $ownRecordClass = $_records->getRecordClassName();
467 $config = $ownRecordClass::getConfiguration();
469 $this->_resolveBeforeToArray($_records, $config, TRUE);
471 $_records->setTimezone(Tinebase_Core::getUserTimezone());
472 $_records->convertDates = true;
474 $result = $_records->toArray();
476 // resolve all virtual fields after converting to array, so we can add these properties "virtually"
477 $result = $this->_resolveAfterToArray($result, $config, TRUE);