diff --git a/lib/api/file_info.php b/lib/api/file_info.php index 5f38c89..f5ca11d 100644 --- a/lib/api/file_info.php +++ b/lib/api/file_info.php @@ -1,164 +1,169 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class file_api_file_info extends file_api_common { /** * Request handler */ public function handle() { parent::handle(); // check Manticore support. Note: we don't use config->get('fileapi_manticore') // here as it may be not properly set if backend driver wasn't initialized yet $capabilities = $this->api->capabilities(false); $manticore = $capabilities['MANTICORE']; $wopi = $capabilities['WOPI']; // support file_info by session ID if (!isset($this->args['file']) || $this->args['file'] === '') { if (($manticore || $wopi) && !empty($this->args['session'])) { $this->args['file'] = $this->file_document_file($this->args['session']); } else { throw new Exception("Missing file name", file_api_core::ERROR_CODE); } } if ($this->args['file'] !== null) { list($driver, $path) = $this->api->get_driver($this->args['file']); $info = $driver->file_info($path); $info['file'] = $this->args['file']; } // Possible 'viewer' types are defined in files_api.js:file_type_supported() // 1 - Native browser support // 2 - Chwala viewer exists // 4 - Editor exists (manticore/wopi) if (rcube_utils::get_boolean((string) $this->args['viewer'])) { if ($this->args['file'] !== null) { $this->file_viewer_info($info); } - // check if file type is supported by manticore/wopi editor? - if ($manticore) { - if (strtolower($info['type']) == 'application/vnd.oasis.opendocument.text') { - $info['viewer']['manticore'] = true; - } - } - if ($wopi) { - if (preg_match('/^application\/vnd\.oasis\.opendocument\./', $info['type'])) { - $info['viewer']['wopi'] = true; - } - } - if ((intval($this->args['viewer']) & 4)) { // @TODO: Chwala client should have a possibility to select // between wopi and manticore? - if ($info['viewer']['wopi']) { - $this->file_wopi_handler($info); - } - else if ($info['viewer']['manticore']) { - $this->file_manticore_handler($info); + if (!$wopi || !$this->file_wopi_handler($info)) { + if ($manticore) { + $this->file_manticore_handler($info); + } } } } // check writable flag if ($this->args['file'] !== null) { $path = explode(file_storage::SEPARATOR, $path); array_pop($path); $path = implode(file_storage::SEPARATOR, $path); $acl = $driver->folder_rights($path); $info['writable'] = ($acl & file_storage::ACL_WRITE) != 0; } return $info; } /** * Merge file viewer data into file info */ protected function file_viewer_info(&$info) { $file = $this->args['file']; $viewer = $this->find_viewer($info['type']); if ($viewer) { $info['viewer'] = array(); if ($frame = $viewer->frame($file, $info['type'])) { $info['viewer']['frame'] = $frame; } else if ($href = $viewer->href($file, $info['type'])) { $info['viewer']['href'] = $href; } } } /** * Get file from manticore/wopi session */ protected function file_document_file($session_id) { $document = new file_document($this->api); return $document->session_file($session_id, true); } /** * Merge manticore session data into file info */ protected function file_manticore_handler(&$info) { $manticore = new file_manticore($this->api); $file = $this->args['file']; $session = $this->args['session']; + if (in_array_nocase($info['type'], $manticore->supported_filetypes(true))) { + $info['viewer']['manticore'] = true; + } + else { + return false; + } + if ($uri = $manticore->session_start($file, $info['type'], $session)) { $info['viewer']['href'] = $uri; $info['viewer']['post'] = $manticore->editor_post_params($info); $info['session'] = $manticore->session_info($session, true); } + + return true; } /** * Merge WOPI session data into file info */ protected function file_wopi_handler(&$info) { $wopi = new file_wopi($this->api); $file = $this->args['file']; $session = $this->args['session']; + if (in_array_nocase($info['type'], $wopi->supported_filetypes(true))) { + $info['viewer']['wopi'] = true; + } + else { + return false; + } + if ($uri = $wopi->session_start($file, $info['type'], $session)) { $info['viewer']['href'] = $uri; $info['viewer']['post'] = $wopi->editor_post_params($info); $info['session'] = $wopi->session_info($session, true); } + + return true; } } diff --git a/lib/file_api_core.php b/lib/file_api_core.php index e51552d..36fb3b2 100644 --- a/lib/file_api_core.php +++ b/lib/file_api_core.php @@ -1,401 +1,411 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ class file_api_core extends file_locale { const API_VERSION = 2; const ERROR_CODE = 500; const ERROR_INVALID = 501; const OUTPUT_JSON = 'application/json'; const OUTPUT_HTML = 'text/html'; public $env = array( 'date_format' => 'Y-m-d H:i', 'language' => 'en_US', ); protected $app_name = 'Kolab File API'; protected $drivers = array(); protected $icache = array(); protected $backend; /** * Returns API version */ public function client_version() { return self::API_VERSION; } /** * Initialise authentication/configuration backend class * * @return file_storage Main storage driver */ public function get_backend() { if ($this->backend) { return $this->backend; } $rcube = rcube::get_instance(); $driver = $rcube->config->get('fileapi_backend', 'kolab'); $this->backend = $this->load_driver_object($driver); // configure api $this->backend->configure($this->env); return $this->backend; } /** * Return supported/enabled external storage instances * * @param bool $as_objects Return drivers as objects not config data * * @return array List of storage drivers */ public function get_drivers($as_objects = false) { $rcube = rcube::get_instance(); $enabled = $rcube->config->get('fileapi_drivers'); $preconf = $rcube->config->get('fileapi_sources'); $result = array(); $all = array(); $iRony = defined('KOLAB_DAV_ROOT'); if (!empty($enabled)) { $backend = $this->get_backend(); $drivers = $backend->driver_list(); foreach ($drivers as $item) { // Disable webdav sources/drivers in iRony that point to the // same host to prevent infinite recursion if ($iRony && $item['driver'] == 'webdav') { $self_url = parse_url($_SERVER['SCRIPT_URI']); $item_url = parse_url($item['host']); if ($self_url['host'] == $item_url['host']) { continue; } } $all[] = $item['title']; if ($item['enabled'] && in_array($item['driver'], (array) $enabled)) { $result[] = $as_objects ? $this->get_driver_object($item) : $item; } } } if (empty($result) && !empty($preconf)) { foreach ((array) $preconf as $title => $item) { if (!in_array($title, $all)) { $item['title'] = $title; $item['admin'] = true; $result[] = $as_objects ? $this->get_driver_object($item) : $item; } } } return $result; } /** * Return driver for specified file/folder path * * @param string $path Folder/file path * * @return array Storage driver object, modified path, driver config */ public function get_driver($path) { $drivers = $this->get_drivers(); foreach ($drivers as $item) { $prefix = $item['title'] . file_storage::SEPARATOR; if ($path == $item['title'] || strpos($path, $prefix) === 0) { $selected = $item; break; } } if (empty($selected)) { return array($this->get_backend(), $path); } $path = substr($path, strlen($selected['title']) + 1); return array($this->get_driver_object($selected), $path, $selected); } /** * Initialize driver instance * * @param array $config Driver config * * @return file_storage Storage driver instance */ public function get_driver_object($config) { $key = $config['title']; if (empty($this->drivers[$key])) { $this->drivers[$key] = $driver = $this->load_driver_object($config['driver']); if ($config['username'] == '%u') { $backend = $this->get_backend(); $auth_info = $backend->auth_info(); $config['username'] = $auth_info['username']; $config['password'] = $auth_info['password']; } else if (!empty($config['password']) && empty($config['admin']) && !empty($key)) { $config['password'] = $this->decrypt($config['password']); } // configure api $driver->configure(array_merge($config, $this->env), $key); } return $this->drivers[$key]; } /** * Loads a driver */ public function load_driver_object($name) { $class = $name . '_file_storage'; if (!class_exists($class, false)) { $include_path = __DIR__ . "/drivers/$name" . PATH_SEPARATOR; $include_path .= ini_get('include_path'); set_include_path($include_path); } return new $class; } /** * Returns storage(s) capabilities * * @param bool $full Return all drivers' capabilities * * @return array Capabilities */ public function capabilities($full = true) { $rcube = rcube::get_instance(); $backend = $this->get_backend(); $caps = array(); // check support for upload progress if (($progress_sec = $rcube->config->get('upload_progress')) && ini_get('apc.rfc1867') && function_exists('apc_fetch') ) { $caps[file_storage::CAPS_PROGRESS_NAME] = ini_get('apc.rfc1867_name'); $caps[file_storage::CAPS_PROGRESS_TIME] = $progress_sec; } // get capabilities of main storage module foreach ($backend->capabilities() as $name => $value) { // skip disabled capabilities if ($value !== false) { $caps[$name] = $value; } } // Manticore support if ($rcube->config->get('fileapi_manticore')) { $caps['MANTICORE'] = true; } // WOPI support if ($rcube->config->get('fileapi_wopi_office')) { $caps['WOPI'] = true; } if (!$full) { return $caps; } + if ($caps['MANTICORE']) { + $manticore = new file_manticore($this); + $caps['MANTICORE_EDITABLE'] = $manticore->supported_filetypes(true); + } + + if ($caps['WOPI']) { + $wopi = new file_wopi($this); + $caps['WOPI_EDITABLE'] = $wopi->supported_filetypes(true); + } + // get capabilities of other drivers $drivers = $this->get_drivers(true); foreach ($drivers as $driver) { if ($driver != $backend) { $title = $driver->title(); foreach ($driver->capabilities() as $name => $value) { // skip disabled capabilities if ($value !== false) { $caps['MOUNTPOINTS'][$title][$name] = $value; } } } } return $caps; } /** * Get user name from user identifier (email address) using LDAP lookup * * @param string $email User identifier * * @return string User name */ public function resolve_user($email) { $key = "user:$email"; // make sure Kolab backend is initialized so kolab_storage can be found $this->get_backend(); // @todo: Move this into drivers if ($this->icache[$key] === null && class_exists('kolab_storage') && ($ldap = kolab_storage::ldap()) ) { $user = $ldap->get_user_record($email, $_SESSION['imap_host']); $this->icache[$key] = $user ?: false; } if ($this->icache[$key]) { return $this->icache[$key]['displayname'] ?: $this->icache[$key]['name']; } } /** * Return mimetypes list supported by built-in viewers * * @return array List of mimetypes */ protected function supported_mimetypes() { $rcube = rcube::get_instance(); $mimetypes = array(); $mimetypes_c = array(); $dir = __DIR__ . '/viewers'; if ($handle = opendir($dir)) { while (false !== ($file = readdir($handle))) { if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) { include_once $dir . '/' . $file; $class = 'file_viewer_' . $matches[1]; $viewer = new $class($this); if ($supported = $viewer->supported_mimetypes()) { $mimetypes = array_merge($mimetypes, $supported); } } } closedir($handle); } // Here we return mimetypes supported for editing and creation of files // @TODO: maybe move this to viewers if ($rcube->config->get('fileapi_wopi_office')) { $mimetypes_c['application/vnd.oasis.opendocument.text'] = array('ext' => 'odt'); $mimetypes_c['application/vnd.oasis.opendocument.presentation'] = array('ext' => 'odp'); $mimetypes_c['application/vnd.oasis.opendocument.spreadsheet'] = array('ext' => 'ods'); } else if ($rcube->config->get('fileapi_manticore')) { $mimetypes_c['application/vnd.oasis.opendocument.text'] = array('ext' => 'odt'); } $mimetypes_c['text/plain'] = array('ext' => 'txt'); $mimetypes_c['text/html'] = array('ext' => 'html'); foreach (array_keys($mimetypes_c) as $type) { list ($app, $label) = explode('/', $type); $label = preg_replace('/[^a-z]/', '', $label); $mimetypes_c[$type]['label'] = $this->translate('type.' . $label); } return array( 'view' => $mimetypes, 'edit' => $mimetypes_c, ); } /** * Encrypts data with current user password * * @param string $str A string to encrypt * * @return string Encrypted string (and base64-encoded) */ public function encrypt($str) { $rcube = rcube::get_instance(); $key = $this->get_crypto_key(); return $rcube->encrypt($str, $key, true); } /** * Decrypts data encrypted with encrypt() method * * @param string $str Encrypted string (base64-encoded) * * @return string Decrypted string */ public function decrypt($str) { $rcube = rcube::get_instance(); $key = $this->get_crypto_key(); return $rcube->decrypt($str, $key, true); } /** * Set encryption password */ protected function get_crypto_key() { $key = 'chwala_crypto_key'; $rcube = rcube::get_instance(); $backend = $this->get_backend(); $user = $backend->auth_info(); $password = $user['password'] . $user['username']; // encryption password must be 24 characters, no less, no more if (($len = strlen($password)) > 24) { $password = substr($password, 0, 24); } else { $password = $password . substr($rcube->config->get('des_key'), 0, 24 - $len); } $rcube->config->set($key, $password); return $key; } } diff --git a/lib/file_manticore.php b/lib/file_manticore.php index 55552a5..278d6f9 100644 --- a/lib/file_manticore.php +++ b/lib/file_manticore.php @@ -1,224 +1,238 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ /** * Document editing sessions handling (Manticore) */ class file_manticore extends file_document { protected $request; /** * Return viewer URI for specified file/session. This creates * a new collaborative editing session when needed. * * @param string $file File path * @param string &$mimetype File type * @param string &$session_id Optional session ID to join to * @param string $readonly Create readonly (one-time) session * * @return string Manticore URI * @throws Exception */ public function session_start($file, &$mimetype, &$session_id = null, $readonly = false) { parent::session_start($file, $mimetype, $session_id, $readonly); // authenticate to Manticore, we need auth token for frame_uri if (empty($_SESSION['manticore_token'])) { $this->get_request(); } // @TODO: make sure the session exists in Manticore? return $this->frame_uri($session_id); } /** * Delete editing session (only owner can do that) * * @param string $id Session identifier * @param bool $local Remove session only from local database */ public function session_delete($id, $local = false) { $success = parent::session_delete($id, $local); // Send document delete to Manticore if ($success && !$local) { $req = $this->get_request(); $res = $req->document_delete($id); } return $success; } /** * Create editing session */ protected function session_create($id, $uri, $owner, $data, $readonly = false) { $success = parent::session_create($id, $uri, $owner, $data, $readonly); // create the session in Manticore if ($success) { $req = $this->get_request(); $res = $req->document_create(array( 'id' => $id, 'title' => '', // @TODO: maybe set to a file path without extension? 'access' => array( array( 'identity' => $owner, 'permission' => file_manticore_api::ACCESS_WRITE, ), ), )); if (!$res) { $this->session_delete($id, true); return false; } } return $success; } /** * Create an invitation * * @param string $session_id Document session identifier * @param string $user User identifier (use null for current user) * @param string $status Invitation status (invited, requested) * @param string $comment Invitation description/comment * @param string &$user_name Optional user name * * @throws Exception */ public function invitation_create($session_id, $user, $status = 'invited', $comment = '', &$user_name = '') { parent::invitation_create($session_id, $user, $status, $comment, $user_name); // Update Manticore 'access' array if ($status == file_document::STATUS_INVITED) { $req = $this->get_request(); $res = $req->editor_add($session_id, $user, file_manticore_api::ACCESS_WRITE); if (!$res) { $this->invitation_delete($session_id, $user, true); throw new Exception("Failed to create an invitation.", file_api_core::ERROR_CODE); } } } /** * Delete an invitation (only session owner can do that) * * @param string $session_id Session identifier * @param string $user User identifier * @param bool $local Remove invitation only from local database * * @throws Exception */ public function invitation_delete($session_id, $user, $local = false) { parent::invitation_delete($session_id, $user, $local); // Update Manticore 'access' array if (!$local) { $req = $this->get_request(); $res = $req->editor_delete($session_id, $user); if (!$res) { throw new Exception("Failed to remove an invitation.", file_api_core::ERROR_CODE); } } } /** * Update an invitation status * * @param string $session_id Session identifier * @param string $user User identifier (use null for current user) * @param string $status Invitation status (accepted, declined) * @param string $comment Invitation description/comment * * @throws Exception */ public function invitation_update($session_id, $user, $status, $comment = '') { parent::invitation_update($session_id, $user, $status, $comment); // Update Manticore 'access' array if an owner accepted an invitation request if ($status == file_document::STATUS_ACCEPTED_OWNER) { $req = $this->get_request(); $res = $req->editor_add($session_id, $user, file_manticore_api::ACCESS_WRITE); if (!$res) { throw new Exception("Failed to update an invitation status.", file_api_core::ERROR_CODE); } } } + /** + * List supported mimetypes + * + * @param bool $editable Return only editable mimetypes + * + * @return array List of supported mimetypes + */ + public function supported_filetypes($editable = false) + { + return array( + 'application/vnd.oasis.opendocument.text', + ); + } + /** * Generate URI of Manticore editing session */ protected function frame_uri($id) { $base_url = rtrim($this->rc->config->get('fileapi_manticore'), ' /'); return $base_url . '/document/' . $id . '/' . $_SESSION['manticore_token']; } /** * Initialize Manticore API request handler */ protected function get_request() { if (!$this->request) { $uri = rcube_utils::resolve_url($this->rc->config->get('fileapi_manticore')); $this->request = new file_manticore_api($uri); // Use stored session token, check if it's still valid if ($_SESSION['manticore_token']) { $is_valid = $this->request->set_session_token($_SESSION['manticore_token'], true); if ($is_valid) { return $this->request; } } $backend = $this->api->get_backend(); $auth = $backend->auth_info(); $_SESSION['manticore_token'] = $this->request->login($auth['username'], $auth['password']); if (empty($_SESSION['manticore_token'])) { throw new Exception("Unable to login to Manticore server.", file_api_core::ERROR_CODE); } } return $this->request; } } diff --git a/lib/file_wopi.php b/lib/file_wopi.php index 7ac6f3d..a416d5a 100644 --- a/lib/file_wopi.php +++ b/lib/file_wopi.php @@ -1,272 +1,311 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ /** * Document editing sessions handling (WOPI) */ class file_wopi extends file_document { protected $cache; + // Mimetypes supported by CODE, but not advertised by all possible names + protected $aliases = array( + 'application/vnd.corel-draw' => 'image/x-coreldraw', + ); + /** * Return viewer URI for specified file/session. This creates * a new collaborative editing session when needed. * * @param string $file File path * @param string &$mimetype File type * @param string &$session_id Optional session ID to join to * @param string $readonly Create readonly (one-time) session * * @return string WOPI URI for specified document * @throws Exception */ public function session_start($file, &$mimetype, &$session_id = null, $readonly = false) { parent::session_start($file, $mimetype, $session_id, $readonly); if ($session_id) { // Create Chwala session for use as WOPI access_token // This session will have access to this one document session only $keys = array('language', 'user_id', 'user', 'username', 'password', 'storage_host', 'storage_port', 'storage_ssl'); $data = array_intersect_key($_SESSION, array_flip($keys)); $data['document_session'] = $session_id; $this->token = $this->api->session->create($data); } return $this->frame_uri($session_id, $mimetype); } /** * Generate URI of WOPI editing session (WOPIsrc) */ protected function frame_uri($id, $mimetype) { $capabilities = $this->capabilities(); if (empty($capabilities) || empty($mimetype)) { return; } $metadata = $capabilities[strtolower($mimetype)]; if (empty($metadata)) { return; } $office_url = rtrim($metadata['urlsrc'], ' /?'); // collabora $service_url = rtrim($this->rc->config->get('fileapi_wopi_service'), ' /'); // kolab-wopi $service_url .= '/wopi/files/' . $id; // @TODO: Parsing and replacing placeholder values // https://wopi.readthedocs.io/en/latest/discovery.html#action-urls return $office_url . '?WOPISrc=' . urlencode($service_url); } /** * Retern extra viewer parameters to post the the viewer iframe * * @param array $info File info * * @return array POST parameters */ public function editor_post_params($info) { // Access token TTL (number of milliseconds since January 1, 1970 UTC) if ($ttl = $this->rc->config->get('session_lifetime', 0) * 60) { $now = new DateTime('now', new DateTimeZone('UTC')); $ttl = ($ttl + $now->format('U')) . '000'; } $params = array( 'access_token' => $this->token, 'access_token_ttl' => $ttl ?: 0, ); if (!empty($info['readonly'])) { $params['permission'] = 'readonly'; } // @TODO: we should/could also add: // lang, title, timestamp, closebutton, revisionhistory - return $params; } /** * List supported mimetypes + * + * @param bool $editable Return only editable mimetypes + * + * @return array List of supported mimetypes */ - public function supported_filetypes() + public function supported_filetypes($editable = false) { $caps = $this->capabilities(); + if ($editable) { + $editable = array(); + foreach ($caps as $mimetype => $c) { + if ($c['name'] == 'edit') { + $editable[] = $mimetype; + } + } + + return $editable; + } + return array_keys($caps); } /** * Uses WOPI discovery to get Office capabilities * https://wopi.readthedocs.io/en/latest/discovery.html */ protected function capabilities() { $cache_key = 'wopi.capabilities'; if ($result = $this->get_from_cache($cache_key)) { - return $result; + return $this->apply_aliases($result); } $office_url = rtrim($this->rc->config->get('fileapi_wopi_office'), ' /'); $office_url .= '/hosting/discovery'; try { $request = $this->http_request(); $request->setMethod(HTTP_Request2::METHOD_GET); $request->setBody(''); $request->setUrl($office_url); $response = $request->send(); $body = $response->getBody(); $code = $response->getStatus(); if (empty($body) || $code != 200) { throw new Exception("Unexpected WOPI discovery response"); } } catch (Exception $e) { rcube::raise_error($e, true, true); } // parse XML output // // // // // // ... $node = new DOMDocument('1.0', 'UTF-8'); $node->loadXML($body); $result = array(); foreach ($node->getElementsByTagName('app') as $app) { if ($mimetype = $app->getAttribute('name')) { if ($action = $app->getElementsByTagName('action')->item(0)) { foreach ($action->attributes as $attr) { $result[$mimetype][$attr->name] = $attr->value; } } } } if (empty($result)) { rcube::raise_error("Failed to parse WOPI discovery response: $body", true, true); } $this->save_in_cache($cache_key, $result); - return $result; + return $this->apply_aliases($result); } /** * Initializes HTTP request object */ protected function http_request() { require_once 'HTTP/Request2.php'; $request = new HTTP_Request2(); // Configure connection options $config = $this->rc->config; $http_config = (array) $config->get('http_request', $config->get('kolab_http_request')); // Deprecated config, all options are separated variables if (empty($http_config)) { $options = array( 'ssl_verify_peer', 'ssl_verify_host', 'ssl_cafile', 'ssl_capath', 'ssl_local_cert', 'ssl_passphrase', 'follow_redirects', ); foreach ($options as $optname) { if (($optvalue = $config->get($optname)) !== null || ($optvalue = $config->get('kolab_' . $optname)) !== null ) { $http_config[$optname] = $optvalue; } } } if (!empty($http_config)) { try { $request->setConfig($http_config); } catch (Exception $e) { rcube::log_error("HTTP: " . $e->getMessage()); } } // proxy User-Agent $request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']); // some HTTP server configurations require this header $request->setHeader('accept', "application/json,text/javascript,*/*"); return $request; } + /** + * Get cached data + */ protected function get_from_cache($key) { if ($cache = $this->get_cache) { return $cache->get($key); } } + /** + * Store data in cache + */ protected function save_in_cache($key, $value) { if ($cache = $this->get_cache) { $cache->set($key, $value); } } /** * Getter for the shared cache engine object */ protected function get_cache() { if ($this->cache === null) { $cache = $this->rc->get_cache_shared('chwala'); $this->cache = $cache ?: false; } return $this->cache; } + + /** + * Support more mimetypes in CODE capabilities + */ + protected function apply_aliases($caps) + { + foreach ($this->aliases as $type => $alias) { + if (isset($caps[$type]) && !isset($caps[$alias])) { + $caps[$alias] = $caps[$type]; + } + } + + return $caps; + } } diff --git a/lib/viewers/odf.php b/lib/viewers/odf.php index 3ff5d2b..c9d1b63 100644 --- a/lib/viewers/odf.php +++ b/lib/viewers/odf.php @@ -1,147 +1,136 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ /** * Class integrating ODF documents viewer from http://webodf.org */ class file_viewer_odf extends file_viewer { protected $mimetypes = array( 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.chart', // 'application/vnd.oasis.opendocument.formula', 'application/vnd.oasis.opendocument.image', 'application/vnd.oasis.opendocument.text-master', // 'application/vnd.sun.xml.base', // 'application/vnd.oasis.opendocument.base', // 'application/vnd.oasis.opendocument.database', 'application/vnd.oasis.opendocument.text-template', 'application/vnd.oasis.opendocument.spreadsheet-template', 'application/vnd.oasis.opendocument.presentation-template', 'application/vnd.oasis.opendocument.graphics-template', 'application/vnd.oasis.opendocument.chart-template', // 'application/vnd.oasis.opendocument.formula-template', 'application/vnd.oasis.opendocument.image-template', ); /** * Class constructor * * @param file_api File API object */ public function __construct($api) { $this->api = $api; $browser = $api->get_browser(); // disable viewer in unsupported browsers if ($browser->ie && $browser->ver < 9) { $this->mimetypes = array(); } } /** * Returns list of supported mimetype * * @return array List of mimetypes */ public function supported_mimetypes() { // @TODO: check supported browsers return $this->mimetypes; } /** * Check if mimetype is supported by the viewer * * @param string $mimetype File type * * @return bool */ public function supports($mimetype) { return in_array($mimetype, $this->mimetypes); } - /** - * Return output of file content area - * - * @param string $file File name - * @param string $mimetype File type - */ - public function frame($file, $mimetype = null) - { - // we use iframe method, see output() - } - /** * Return file viewer URL * * @param string $file File name * @param string $mimetype File type */ public function href($file, $mimetype = null) { return file_utils::script_uri() . '?method=file_get' . '&viewer=odf' . '&file=' . urlencode($file) . '&token=' . urlencode(session_id()); } /** * Print output and exit * * @param string $file File name * @param string $mimetype File type */ public function output($file, $mimetype = null) { $file_uri = $this->api->file_url($file); echo <<
EOT; } } diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js index 4dd5296..56509b6 100644 --- a/public_html/js/files_api.js +++ b/public_html/js/files_api.js @@ -1,1078 +1,1091 @@ /** +--------------------------------------------------------------------------+ | This file is part of the Kolab File API | | | | Copyright (C) 2012-2015, Kolab Systems AG | | | | This program is free software: you can redistribute it and/or modify | | it under the terms of the GNU Affero General Public License as published | | by the Free Software Foundation, either version 3 of the License, or | | (at your option) any later version. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | You should have received a copy of the GNU Affero General Public License | | along with this program. If not, see | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ function files_api() { var ref = this; // default config this.sessions = {}; this.translations = {}; this.env = { url: 'api/', directory_separator: '/', resources_dir: 'resources' }; /*********************************************************/ /********* Basic utilities *********/ /*********************************************************/ // set environment variable(s) this.set_env = function(p, value) { if (p != null && typeof p === 'object' && !value) for (var n in p) this.env[n] = p[n]; else this.env[p] = value; }; // add a localized label(s) to the client environment this.tdef = function(p, value) { if (typeof p == 'string') this.translations[p] = value; else if (typeof p == 'object') $.extend(this.translations, p); }; // return a localized string this.t = function(label) { if (this.translations[label]) return this.translations[label]; else return label; }; /********************************************************/ /********* Remote request methods *********/ /********************************************************/ // send a http POST request to the API service this.post = function(action, data, func) { var url = this.env.url + '?method=' + action; if (!func) func = 'response'; this.set_request_time(); return $.ajax({ type: 'POST', url: url, data: JSON.stringify(data), dataType: 'json', contentType: 'application/json; charset=utf-8', success: function(response) { if (typeof func == 'function') func(response); else ref[func](response); }, error: function(o, status, err) { ref.http_error(o, status, err, data); }, cache: false, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); } }); }; // send a http GET request to the API service this.get = function(action, data, func) { var url = this.env.url; if (!func) func = 'response'; this.set_request_time(); data.method = action; return $.ajax({ type: 'GET', url: url, data: data, dataType: 'json', success: function(response) { if (typeof func == 'function') func(response); else ref[func](response); }, error: function(o, status, err) { ref.http_error(o, status, err, data); }, cache: false, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); } }); }; // send request with auto-selection of POST/GET method this.request = function(action, data, func) { // Use POST for modification actions with probable big request size var method = /_(create|delete|move|copy|update|auth|subscribe|unsubscribe|invite|decline|request|accept|remove)$/.test(action) ? 'post' : 'get'; return this[method](action, data, func); }; // handle HTTP request errors this.http_error = function(request, status, err, data) { var errmsg = request.statusText; this.set_busy(false); request.abort(); if (request.status && errmsg) this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error'); }; this.response = function(response) { this.update_request_time(); this.set_busy(false); return this.response_parse(response); }; this.response_parse = function(response) { if (!response || response.status != 'OK') { // Logout on invalid-session error if (response && response.code == 403) this.logout(response); else this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error'); return false; } return true; }; /*********************************************************/ /********* Utilities *********/ /*********************************************************/ // Called on "session expired" session this.logout = function(response) {}; // set state this.set_busy = function(state, message) {}; // displays error message this.display_message = function(label, type) {}; // called when a request timed out this.request_timed_out = function() {}; // called on start of the request this.set_request_time = function() {}; // called on request response this.update_request_time = function() {}; /*********************************************************/ /********* Helpers *********/ /*********************************************************/ // compose a valid url with the given parameters this.url = function(action, query) { var k, param = {}, querystring = typeof query === 'string' ? '&' + query : ''; if (typeof action !== 'string') query = action; else if (!query || typeof query !== 'object') query = {}; // overwrite task name if (action) query.method = action; // remove undefined values for (k in query) { if (query[k] !== undefined && query[k] !== null) param[k] = query[k]; } return '?' + $.param(param) + querystring; }; // fill folder selector with options this.folder_select_element = function(select, params) { var options = [], selected = params && params.selected ? params.selected : this.env.folder; if (params && params.empty) options.push($('