diff --git a/lib/output/json.php b/lib/output/json.php index 5ac03a6..5e38623 100644 --- a/lib/output/json.php +++ b/lib/output/json.php @@ -1,309 +1,313 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class kolab_api_output_json extends kolab_api_output { /** * Send successful response * * @param mixed Response data * @param string Data type * @param array Context (folder_uid, object_uid, object) * @param array Optional attributes filter */ public function send($data, $type, $context = null, $attrs_filter = array()) { // Set output type $this->headers(array('Content-Type' => "application/json; charset=utf-8")); list($type, $mode) = explode('-', $type); if ($mode != 'list') { $data = array($data); } $class = "kolab_api_output_json_$type"; $model = new $class($this); $result = array(); $debug = $this->api->config->get('kolab_api_debug'); foreach ($data as $idx => $item) { if ($element = $model->element($item, $attrs_filter)) { $result[] = $element; } else { unset($data[$idx]); } } // apply output filter if ($this->api->filter) { $this->api->filter->output($result, $type, $context, $attrs_filter); } // generate JSON output - $opts = $debug && defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; + $opts = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if ($debug && defined('JSON_PRETTY_PRINT')) { + $opts |= JSON_PRETTY_PRINT; + } + $result = json_encode($result, $opts); if ($mode != 'list') { $result = trim($result, '[]'); } if ($debug) { rcube::console($result); } $this->send_status(kolab_api_output::STATUS_OK, false); // send JSON output echo $result; exit; } /** * Convert object data into JSON API format * * @param array Object data * @param string Object type * * @return array Object data in JSON API format */ public function convert($data, $type) { $class = "kolab_api_output_json_$type"; $model = new $class($this); return $model->element($data); } /** * Convert (part of) kolab_format object into an array * * @param array Kolab object * @param string Object type * @param string Data element name * @param array Optional list of return properties * @param array List of array properties (to force their type) * * @return array Object data */ public function object_to_array($object, $type, $element, $properties = array(), $array_elements = array()) { // load old object to preserve data we don't understand/process if (is_object($object['_formatobj'])) { $format = $object['_formatobj']; } // create new kolab_format instance if (!$format) { $ftype = $object['_type'] ?: $type; $format = kolab_format::factory($ftype, kolab_storage::$version); if (PEAR::isError($format)) { return; } $format->set($object); } $xml = $format->write(kolab_storage::$version); if (empty($xml) || !$format->is_valid() || !$format->uid) { return; } // Modify which has been set to current date-time by write() call above // Workaround for #5026 if ($object['changed'] instanceof DateTime && $type == 'contact') { $xml = preg_replace( '/([\r\n\s]*)[TZ0-9]+(<\/timestamp>[\r\n\s]*<\/rev>)/m', '' . $object['changed']->format('Ymd\THis\Z') . '', $xml, 1); } // The simplest way of "normalizing object properties // is to use its XML representation $doc = new DOMDocument(); // LIBXML_NOBLANKS is required for xml_to_array() below $doc->loadXML($xml, LIBXML_NOBLANKS); $result = array(); foreach ($doc->getElementsByTagName($element) as $node) { $result[] = $this->node_to_array($node, $properties, $array_elements); } // faked 'categories' property (we need this for unit-tests // @TODO: find a better way if (array_key_exists('categories', $object) && (empty($properties) || in_array('categories', $properties)) ) { if ($result[0]['properties']) { $result[0]['properties']['categories'] = $object['categories']; } else { $result[0]['categories'] = $object['categories']; } } return $result; } /** * Convert XML element into an array * This is intended to use with Kolab XML format * * @param DOMElement XML element * * @return mixed Conversion result */ protected function node_to_array($node, $properties, $array_elements) { $node = $this->xml_to_array($node); $node = array_filter($node); unset($node['prodid']); if (!empty($properties)) { $node = array_intersect_key($node, array_combine($properties, $properties)); } // force some elements to be arrays if (!empty($array_elements)) { self::parse_array_result($node, $array_elements); } return $node; } /** * Convert XML element into an array * This is intended to use with Kolab XML format * * @param DOMElement XML element * * @return mixed Conversion result */ public function xml_to_array($node) { $children = $node->childNodes; if (!$children->length) { return; } if ($children->length == 1) { if ($node->firstChild->nodeType == XML_TEXT_NODE || !$node->firstChild->childNodes->length ) { return (string) $node->textContent; } if ($node->firstChild->nodeType == XML_ELEMENT_NODE && $node->firstChild->childNodes->length == 1 && $node->firstChild->firstChild->nodeType == XML_TEXT_NODE ) { switch ($node->firstChild->nodeName) { case 'integer': return (int) $node->textContent; case 'boolean': return strtoupper($node->textContent) == 'TRUE'; case 'date-time': case 'timestamp': case 'date': case 'text': case 'uri': case 'sex': return (string) $node->textContent; } } } $result = array(); foreach ($children as $child) { $value = $child->nodeType == XML_TEXT_NODE ? $child->nodeValue : $this->xml_to_array($child); if (!isset($result[$child->nodeName])) { $result[$child->nodeName] = $value; } else { if (!is_array($result[$child->nodeName]) || !isset($result[$child->nodeName][0])) { $result[$child->nodeName] = array($result[$child->nodeName]); } $result[$child->nodeName][] = $value; } } if (is_array($result['text']) && count($result) == 1) { $result = $result['text']; } return $result; } public static function parse_array_result(&$data, $array_elements = array()) { foreach ($array_elements as $key) { $items = explode('/', $key); if (count($items) > 1 && !empty($data[$items[0]])) { $key = array_shift($items); self::parse_array_result($data[$key], array(implode('/', $items))); } else if (!empty($data[$key]) && (!is_array($data[$key]) || !array_key_exists(0, $data[$key]))) { $data[$key] = array($data[$key]); } else if (empty($data[$key])) { unset($data[$key]); } } } /** * Makes sure exdate/rdate output is consistent/unified */ public static function parse_recurrence(&$data) { foreach (array('exdate', 'rdate') as $key) { if ($data[$key]) { if (is_string($data[$key])) { $idx = strlen($data[$key]) > 10 ? 'date-time' : 'date'; $data[$key] = array($idx => array($data[$key])); } else if (array_key_exists('date', $data[$key]) && !is_array($data[$key]['date'])) { $data[$key]['date'] = (array) $data[$key]['date']; } else if (array_key_exists('date-time', $data[$key]) && !is_array($data[$key]['date-time'])) { $data[$key]['date-time'] = (array) $data[$key]['date-time']; } } } } }