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::get(Tinebase_Core::USERTIMEZONE));
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
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 foreach ($resolveFields as $fieldKey => $fieldConfig) {
79 $resolveRecords[$fieldConfig['config']['recordClassName']][] = $fieldKey;
82 foreach ($resolveRecords as $foreignRecordClassName => $fields) {
83 $foreignIds = array();
84 $fields = (array) $fields;
86 foreach($fields as $field) {
87 $foreignIds = array_unique(array_merge($foreignIds, $_records->{$field}));
90 if (! Tinebase_Core::getUser()->hasRight(substr($foreignRecordClassName, 0, strpos($foreignRecordClassName, "_")), Tinebase_Acl_Rights_Abstract::RUN)) {
94 $cfg = $resolveFields[$fields[0]];
96 if ($cfg['type'] == 'user') {
97 $foreignRecords = Tinebase_User::getInstance()->getUsers();
98 } elseif ($cfg['type'] == 'container') {
99 $foreignRecords = new Tinebase_Record_RecordSet('Tinebase_Model_Container');
100 $foreignRecords->addRecord(Tinebase_Container::getInstance()->get($_id));
101 // TODO: resolve recursive records of records better in controller
102 // TODO: resolve containers
104 $controller = (isset($cfg['config']['controllerClassName']) || array_key_exists('controllerClassName', $cfg['config'])) ? $cfg['config']['controllerClassName']::getInstance() : Tinebase_Core::getApplicationInstance($foreignRecordClassName);
105 $foreignRecords = $controller->getMultiple($foreignIds);
108 $foreignRecords->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
109 $foreignRecords->convertDates = true;
110 Tinebase_Frontend_Json_Abstract::resolveContainerTagsUsers($foreignRecords);
111 $fr = $foreignRecords->getFirstRecord();
112 if ($fr && $fr->has('notes')) {
113 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
116 if ($foreignRecords->count()) {
117 foreach ($_records as $record) {
118 foreach ($fields as $field) {
119 if (is_scalar($record->{$field})) {
120 $idx = $foreignRecords->getIndexById($record->{$field});
121 if (isset($idx) && $idx !== FALSE) {
122 $record->{$field} = $foreignRecords[$idx];
133 * resolves multiple records (fallback)
135 * @deprecated use Tinebase_ModelConfiguration to configure your models, so this won't be used anymore
136 * @param Tinebase_Record_RecordSet $_records the records
137 * @param array $resolveFields
139 public static function resolveMultipleIdFields($records, $resolveFields = NULL)
141 if (! $records instanceof Tinebase_Record_RecordSet || !$records->count()) {
145 $ownRecordClass = $records->getRecordClassName();
146 if ($resolveFields === NULL) {
147 $resolveFields = $ownRecordClass::getResolveForeignIdFields();
150 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
151 . ' Resolving ' . $ownRecordClass . ' fields: ' . print_r($resolveFields, TRUE));
153 foreach ((array) $resolveFields as $foreignRecordClassName => $fields) {
154 if ($foreignRecordClassName === 'recursive') {
155 foreach ($fields as $field => $model) {
156 foreach ($records->$field as $subRecords) {
157 self::resolveMultipleIdFields($subRecords);
161 self::_resolveForeignIdFields($records, $foreignRecordClassName, (array) $fields);
167 * resolve foreign fields for records
169 * @param Tinebase_Record_RecordSet $records
170 * @param string $foreignRecordClassName
171 * @param array $fields
173 protected static function _resolveForeignIdFields($records, $foreignRecordClassName, $fields)
175 $options = (isset($fields['options']) || array_key_exists('options', $fields)) ? $fields['options'] : array();
176 $fields = (isset($fields['fields']) || array_key_exists('fields', $fields)) ? $fields['fields'] : $fields;
178 $foreignIds = array();
179 foreach ($fields as $field) {
180 $foreignIds = array_unique(array_merge($foreignIds, $records->{$field}));
184 $controller = Tinebase_Core::getApplicationInstance($foreignRecordClassName);
185 } catch (Tinebase_Exception_AccessDenied $tead) {
186 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
187 . ' Not resolving ' . $foreignRecordClassName . ' records because user has no right to run app.');
191 // 2013-07-04, ps: removing this as i don't think that this is correct here
192 // (and it breaks the tests)
193 // if (method_exists($controller, 'modlogActive')) {
194 // $modlogActive = $controller->modlogActive(FALSE);
197 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
198 . ' Fetching ' . $foreignRecordClassName . ' by id: ' . print_r($foreignIds, TRUE));
200 if ((isset($options['ignoreAcl']) || array_key_exists('ignoreAcl', $options)) && $options['ignoreAcl']) {
201 // @todo make sure that second param of getMultiple() is $ignoreAcl
202 $foreignRecords = $controller->getMultiple($foreignIds, TRUE);
204 $foreignRecords = $controller->getMultiple($foreignIds);
207 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
208 . ' Foreign records found: ' . print_r($foreignRecords->toArray(), TRUE));
210 if (count($foreignRecords) === 0) {
214 foreach ($records as $record) {
215 foreach ($fields as $field) {
216 if (is_scalar($record->{$field})) {
217 $idx = $foreignRecords->getIndexById($record->{$field});
218 if (isset($idx) && $idx !== FALSE) {
219 $record->{$field} = $foreignRecords[$idx];
221 switch ($foreignRecordClassName) {
222 case 'Tinebase_Model_User':
223 case 'Tinebase_Model_FullUser':
224 $record->{$field} = Tinebase_User::getInstance()->getNonExistentUser();
236 * resolve multiple record fields (Tinebase_ModelConfiguration._recordsFields)
238 * @param Tinebase_Record_RecordSet $_records
239 * @param Tinebase_ModelConfiguration $modelConfiguration
240 * @param boolean $multiple
242 protected function _resolveMultipleRecordFields(Tinebase_Record_RecordSet $_records, $modelConfiguration = NULL, $multiple = false)
244 if (! $modelConfiguration || (! $_records->count())) {
248 if (! ($resolveFields = $modelConfiguration->recordsFields)) {
252 $ownIds = $_records->{$modelConfiguration->idProperty};
254 // iterate fields to resolve
255 foreach ($resolveFields as $fieldKey => $c) {
256 $config = $c['config'];
258 // resolve records, if omitOnSearch is definitively set to FALSE (by default they won't be resolved on search)
259 if ($multiple && !(isset($config['omitOnSearch']) && $config['omitOnSearch'] === FALSE)) {
263 // fetch the fields by the refIfField
264 $controller = isset($config['controllerClassName']) ? $config['controllerClassName']::getInstance() : Tinebase_Core::getApplicationInstance($foreignRecordClassName);
265 $filterName = $config['filterClassName'];
267 $filterArray = array();
269 // addFilters can be added and must be added if the same model resides in more than one records fields
270 if (isset($config['addFilters']) && is_array($config['addFilters'])) {
271 $useaddFilters = true;
272 $filterArray = $config['addFilters'];
275 $filter = new $filterName($filterArray);
276 $filter->addFilter(new Tinebase_Model_Filter_Id(array('field' => $config['refIdField'], 'operator' => 'in', 'value' => $ownIds)));
279 if (isset($config['paging']) && is_array($config['paging'])) {
280 $paging = new Tinebase_Model_Pagination($config['paging']);
283 $foreignRecords = $controller->search($filter, $paging);
284 $foreignRecordClass = $foreignRecords->getRecordClassName();
285 $foreignRecordModelConfiguration = $foreignRecordClass::getConfiguration();
287 $foreignRecords->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
288 $foreignRecords->convertDates = true;
290 $fr = $foreignRecords->getFirstRecord();
292 // @todo: resolve alarms?
293 // @todo: use parts parameter?
294 if ($foreignRecordModelConfiguration->resolveRelated && $fr) {
295 if ($fr->has('notes')) {
296 Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
298 if ($fr->has('tags')) {
299 Tinebase_Tags::getInstance()->getMultipleTagsOfRecords($foreignRecords);
301 if ($fr->has('relations')) {
302 $relations = Tinebase_Relations::getInstance()->getMultipleRelations($foreignRecordClass, 'Sql', $foreignRecords->{$fr->getIdProperty()} );
303 $foreignRecords->setByIndices('relations', $relations);
305 if ($fr->has('customfields')) {
306 Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($foreignRecords);
308 if ($fr->has('attachments') && Setup_Controller::getInstance()->isFilesystemAvailable()) {
309 Tinebase_FileSystem_RecordAttachments::getInstance()->getMultipleAttachmentsOfRecords($foreignRecords);
313 if ($foreignRecords->count() > 0) {
314 foreach ($_records as $record) {
315 $filtered = $foreignRecords->filter($config['refIdField'], $record->getId())->toArray();
316 $filtered = $this->_resolveAfterToArray($filtered, $foreignRecordModelConfiguration, TRUE);
317 $record->{$fieldKey} = $filtered;
321 $_records->{$fieldKey} = NULL;
328 * resolves virtual fields, if a function has been defined in the field definition
330 * @param array $resultSet
331 * @param Tinebase_ModelConfiguration $modelConfiguration
332 * @param boolean $multiple
334 protected function _resolveVirtualFields($resultSet, $modelConfiguration = NULL, $multiple = false)
336 if (! $modelConfiguration || ! ($virtualFields = $modelConfiguration->virtualFields)) {
340 if ($modelConfiguration->resolveVFGlobally === TRUE) {
342 $controller = $modelConfiguration->getControllerInstance();
345 return $controller->resolveMultipleVirtualFields($resultSet);
347 return $controller->resolveVirtualFields($resultSet);
350 foreach($virtualFields as $field) {
351 // resolve virtual relation record from relations property
352 if (! $multiple && isset($field['type']) && $field['type'] == 'relation') {
353 $fc = $field['config'];
354 if (isset($resultSet['relations']) && (is_array($resultSet['relations']))) {
355 foreach($resultSet['relations'] as $relation) {
356 if (($relation['type'] == $fc['type']) && ($relation['related_model'] == ($fc['appName'] . '_Model_' . $fc['modelName']))) {
357 $resultSet[$field['key']] = $relation['related_record'];
361 // resolve virtual field by function
362 } elseif ((isset($field['function']) || array_key_exists('function', $field))) {
363 if (is_array($field['function'])) {
364 if (count($field['function']) > 1) { // static method call
365 $class = $field['function'][0];
366 $method = $field['function'][1];
367 $resultSet = $class::$method($resultSet);
369 } else { // use key as classname and value as method name
370 $ks = array_keys($field['function']);
371 $class = array_pop($ks);
372 $vs = array_values($field['function']);
373 $method = array_pop($vs);
374 $class = $class::getInstance();
376 $resultSet = $class->$method($resultSet);
379 // if no array has been given, this should be a function name
381 $resolveFunction = $field['function'];
382 $resultSet = $resolveFunction($resultSet);
391 * resolves child records before converting the record set to an array
393 * @param Tinebase_Record_RecordSet $records
394 * @param Tinebase_ModelConfiguration $modelConfiguration
395 * @param boolean $multiple
397 protected function _resolveBeforeToArray($records, $modelConfiguration, $multiple = false)
399 Tinebase_Frontend_Json_Abstract::resolveContainerTagsUsers($records);
401 self::resolveMultipleIdFields($records);
403 // use modern record resolving, if the model was configured using Tinebase_ModelConfiguration
404 // at first, resolve all single record fields
405 if ($modelConfiguration) {
406 $this->_resolveSingleRecordFields($records, $modelConfiguration);
408 // resolve all multiple records fields
409 $this->_resolveMultipleRecordFields($records, $modelConfiguration, $multiple);
414 * resolves child records after converting the record set to an array
416 * @param array $result
417 * @param Tinebase_ModelConfiguration $modelConfiguration
418 * @param boolean $multiple
422 protected function _resolveAfterToArray($result, $modelConfiguration, $multiple = false)
424 $result = $this->_resolveVirtualFields($result, $modelConfiguration, $multiple);
429 * converts Tinebase_Record_RecordSet to external format
431 * @param Tinebase_Record_RecordSet $_records
432 * @param Tinebase_Model_Filter_FilterGroup $_filter
433 * @param Tinebase_Model_Pagination $_pagination
437 public function fromTine20RecordSet(Tinebase_Record_RecordSet $_records = NULL, $_filter = NULL, $_pagination = NULL)
439 if (! $_records || count($_records) == 0) {
443 // find out if there is a modelConfiguration
444 $ownRecordClass = $_records->getRecordClassName();
445 $config = $ownRecordClass::getConfiguration();
447 $this->_resolveBeforeToArray($_records, $config, TRUE);
449 $_records->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
450 $_records->convertDates = true;
452 $result = $_records->toArray();
454 // resolve all virtual fields after converting to array, so we can add these properties "virtually"
455 $result = $this->_resolveAfterToArray($result, $config, TRUE);