diff --git a/lib/drivers/kolabfiles/kolabfiles_file_storage.php b/lib/drivers/kolabfiles/kolabfiles_file_storage.php index 19810ef..369ce66 100644 --- a/lib/drivers/kolabfiles/kolabfiles_file_storage.php +++ b/lib/drivers/kolabfiles/kolabfiles_file_storage.php @@ -1,1177 +1,1177 @@ | +--------------------------------------------------------------------------+ | Author: Christian Mollekopf | +--------------------------------------------------------------------------+ */ // FIXME // * Support non-toplevel folders for collection creation/moves/... // * Paging (we don't display beyond the first page (100 items)) // * Searching is not implemented class kolabfiles_file_storage implements file_storage { /** * @var rcube */ protected $rc; protected $client = null; /** * @var array */ protected $config = array(); /** * List of Kolabfiles collections * * @var array */ protected $collections; /** * Instance title (mount point) * * @var string */ protected $title; /** * Class constructor */ public function __construct() { $this->rc = rcube::get_instance(); $this->config = [ - 'baseuri' => $this->rc->config->get('fileapi_kolabfiles_server', 'https://' . $_SERVER["HTTP_HOST"] . '/api/') + 'baseuri' => $this->rc->config->get('fileapi_kolabfiles_baseuri', 'https://' . $_SERVER["HTTP_HOST"] . '/api/') ]; } protected function init() { if ($this->client !== null) { return true; } $stack = new \GuzzleHttp\HandlerStack(); $stack->setHandler(\GuzzleHttp\choose_handler()); $stack->push(\GuzzleHttp\Middleware::retry( function ( $retries, \GuzzleHttp\Psr7\Request $request, \GuzzleHttp\Psr7\Response $response = null, \GuzzleHttp\Exception\RequestException $exception = null ) { $maxRetries = 2; if ($retries >= $maxRetries) { return false; } if ($response && $response->getStatusCode() === 401) { $this->refreshClientAccessToken(); return true; } return false; } )); $stack->push(\GuzzleHttp\Middleware::mapRequest(function (\GuzzleHttp\Psr7\Request $request) { //TODO Just forward the bearer token, once we manage to make sure it get's sent to roundcube // 'Authorization' => rcube_utils::request_header('Authorization') if ($accessToken = $this->getClientAccessToken()) { return $request->withHeader('Authorization', 'Bearer ' . $accessToken); } return $request; })); $this->client = new \GuzzleHttp\Client( [ 'http_errors' => false, // No exceptions from Guzzle 'base_uri' => rtrim($this->config['baseuri'], '/') . '/', 'handler' => $stack, 'verify' => false, 'connect_timeout' => 10, 'timeout' => 10, // 'on_stats' => function (\GuzzleHttp\TransferStats $stats) { // $threshold = \config('logging.slow_log'); // if ($threshold && ($sec = $stats->getTransferTime()) > $threshold) { // $url = $stats->getEffectiveUri(); // $method = $stats->getRequest()->getMethod(); // \Log::warning(sprintf("[STATS] %s %s: %.4f sec.", $method, $url, $sec)); // } // }, ] ); } private function getClientAccessToken() { if (!empty($_SESSION['access_token'])) { return $this->rc->decrypt($_SESSION['access_token']); } return null; } private function refreshClientAccessToken() { //TODO use the refresh token if available instead of refreshing from scratch always. rcube::write_log('kolabfiles', "Refreshing the access token"); $username = $_SESSION['username']; $password = $this->rc->decrypt($_SESSION['password']); $client = new \GuzzleHttp\Client([ 'http_errors' => false, // No exceptions from Guzzle 'base_uri' => rtrim($this->config['baseuri'], '/') . '/', 'verify' => false, 'connect_timeout' => 10, 'timeout' => 10, ]); $response = $client->request('POST', "auth/login?email=$username&password=$password"); if ($response->getStatusCode() != 200) { throw new Exception("Failed to authenticate $username:$password"); } $json = json_decode($response->getBody(), true); $accessToken = $json['access_token']; $_SESSION['access_token'] = $this->rc->encrypt($accessToken); } protected function client() { $this->init(); return $this->client; } /** * Authenticates a user * * @param string $username User name * @param string $password User password * * @param bool True on success, False on failure */ public function authenticate($username, $password) { $_SESSION['username'] = $username; $_SESSION['password'] = $this->rc->encrypt($password); $this->init(); return true; } /** * Get password and name of authenticated user * * @return array Authenticated user data */ public function auth_info() { return array( 'username' => $_SESSION['username'], 'password' => $this->rc->decrypt($_SESSION['password']), ); } /** * Configures environment * * @param array $config Configuration * @param string $title Source identifier */ public function configure($config, $title = null) { $this->config = array_merge($this->config, $config); $this->title = $title; } /** * Returns current instance title * * @return string Instance title (mount point) */ public function title() { return $this->title; } /** * Storage driver capabilities * * @return array List of capabilities */ public function capabilities() { // find max filesize value $max_filesize = parse_bytes(ini_get('upload_max_filesize')); $max_postsize = parse_bytes(ini_get('post_max_size')); if ($max_postsize && $max_postsize < $max_filesize) { $max_filesize = $max_postsize; } return array( file_storage::CAPS_MAX_UPLOAD => $max_filesize, file_storage::CAPS_QUOTA => false, file_storage::CAPS_LOCKS => true, file_storage::CAPS_ACL => true, ); } /** * Save configuration of external driver (mount point) * * @param array $driver Driver data * * @throws Exception */ public function driver_create($driver) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Delete configuration of external driver (mount point) * * @param string $title Driver instance name * * @throws Exception */ public function driver_delete($title) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Return list of registered drivers (mount points) * * @return array List of drivers data * @throws Exception */ public function driver_list() { return []; } /** * Update configuration of external driver (mount point) * * @param string $title Driver instance name * @param array $driver Driver data * * @throws Exception */ public function driver_update($title, $driver) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Returns metadata of the driver * * @return array Driver meta data (image, name, form) */ public function driver_metadata() { $image_content = file_get_contents(__DIR__ . '/kolab.png'); $metadata = array( 'image' => 'data:image/png;base64,' . base64_encode($image_content), 'name' => 'Kolab Files', 'ref' => 'http://kolab.org', 'description' => 'Kolab File Storage', 'form' => array( 'host' => 'hostname', 'username' => 'username', 'password' => 'password', ), ); return $metadata; } /** * Validate metadata (config) of the driver * * @param array $metadata Driver metadata * * @return array Driver meta data to be stored in configuration * @throws Exception */ public function driver_validate($metadata) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Create a file. * * @param string $file_name Name of a file (with folder path) * @param array $file File data (path, type) * * @throws Exception */ public function file_create($file_name, $file) { list($fn, $repo_id) = $this->find_collection($file_name); if (empty($repo_id)) { throw new Exception("Storage error. Folder not found.", file_storage::ERROR); } $data = null; $fp = null; if ($file['path']) { $fp = fopen($file['path'], 'r'); $data = $fp; } else { $data = $file['content']; } $response = $this->client->request("POST", "v4/fs?name=$fn&parent=$repo_id", ['body' => $data]); $created = $response->getStatusCode() == 200; if (!$created) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error saving file to Kolab server"), true, false); throw new Exception("Storage error. Saving file failed.", file_storage::ERROR); } } /** * Update a file. * * @param string $file_name Name of a file (with folder path) * @param array $file File data (path, type) * * @throws Exception */ public function file_update($file_name, $file) { list($fn, $repo_id) = $this->find_collection($file_name); $file_id = $this->find_file_id($fn, $repo_id); if (empty($repo_id)) { throw new Exception("Storage error. Folder not found.", file_storage::ERROR); } $data = null; $fp = null; if ($file['path']) { $fp = fopen($file['path'], 'r'); $data = $fp; } else { $data = $file['content']; } $response = $this->client->request("PATCH", "v4/fs/$file_id?media=content", ['body' => $data]); if ($response->getStatusCode() != 200) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error saving file to Kolab server"), true, false); throw new Exception("Storage error. Saving file failed.", file_storage::ERROR); } } /** * Delete a file. * * @param string $file_name Name of a file (with folder path) * * @throws Exception */ public function file_delete($file_name) { list($file_name, $repo_id) = $this->find_collection($file_name); if ($repo_id && $file_name != '/') { $file_id = $this->find_file_id($file_name, $repo_id); $response = $this->client->request("DELETE", "v4/fs/$file_id"); $deleted = $response->getStatusCode() == 200; } if (!$deleted) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error deleting object from Kolab server"), true, false); throw new Exception("Storage error. Deleting file failed.", file_storage::ERROR); } } /** * Return file body. * * @param string $file_name Name of a file (with folder path) * @param array $params Parameters (force-download, force-type, head) * @param resource $fp Print to file pointer instead (send no headers) * * @throws Exception */ public function file_get($file_name, $params = array(), $fp = null) { list($fn, $repo_id) = $this->find_collection($file_name); $file_id = $this->find_file_id($fn, $repo_id); $response = $this->client->request("GET", "v4/fs/{$file_id}"); $file = json_decode($response->getBody(), true); // write to file pointer, send no headers if ($fp) { $response = $this->client->request("GET", "v4/fs/{$file_id}?download=1"); fwrite(fp, $request->getBody()); return; } if (!empty($params['force-download'])) { $disposition = 'attachment'; header("Content-Type: application/octet-stream"); // @TODO // if ($browser->ie) // header("Content-Type: application/force-download"); } else { $mimetype = file_utils::real_mimetype($params['force-type'] ? $params['force-type'] : $file['mimetype']); $disposition = 'inline'; header("Content-Transfer-Encoding: binary"); header("Content-Type: $mimetype"); } $filename = addcslashes($file['name'], '"'); // Workaround for nasty IE bug (#1488844) // If Content-Disposition header contains string "attachment" e.g. in filename // IE handles data as attachment not inline /* @TODO if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) { $filename = str_ireplace('attachment', 'attach', $filename); } */ header("Content-Length: " . $file['size']); header("Content-Disposition: $disposition; filename=\"$filename\""); // just send redirect to Kolab server if ($file['size'] && empty($params['head'])) { $allow_redirects = $this->rc->config->get('fileapi_kolabfiles_allow_redirects'); // In view-mode we can't redirect to Kolab server because: // - it responds with Content-Disposition: attachment, which causes that // e.g. previewing images is not possible // - pdf/odf viewers can't follow redirects for some reason (#4590) if ($allow_redirects && !empty($params['force-download'])) { $response = $this->client->request("GET", "v4/fs/{$file_id}?downloadUrl=1"); $json = json_decode($response->getBody(), true); $link = $json['downloadUrl']; header("Location: $link"); } else if ($fp = fopen('php://output', 'wb')) { $response = $this->client->request("GET", "v4/fs/{$file_id}?download=1"); fwrite($fp, $response->getBody()); fclose($fp); } } } /** * Returns file metadata. * * @param string $file_name Name of a file (with folder path) * * @throws Exception */ public function file_info($file_name) { list($file, $repo_id) = $this->find_collection($file_name); $file_id = $this->find_file_id($file, $repo_id); $response = $this->client->request("GET", "v4/fs/{$file_id}"); $json = json_decode($response->getBody(), true); if (empty($json)) { throw new Exception("Storage error. File not found.", file_storage::ERROR); } $file = $this->from_file_object($json); return array( 'name' => $file['name'], 'size' => (int) $file['size'], 'type' => (string) $file['type'], 'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']), 'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']), 'modified' => $file['changed'] ? $file['changed']->format('U') : 0, 'created' => $file['created'] ? $file['created']->format('U') : 0, ); } /** * List files in a folder. * * @param string $folder_name Name of a folder with full path * @param array $params List parameters ('sort', 'reverse', 'search', 'prefix') * * @return array List of files (file properties array indexed by filename) * @throws Exception */ public function file_list($folder_name, $params = array()) { // mount point contains only folders if (!is_string($folder_name) || $folder_name === '') { return array(); } list($folder, $repo_id) = $this->find_collection($folder_name); // prepare search filter if (!empty($params['search'])) { foreach ($params['search'] as $idx => $value) { if ($idx == 'name') { $params['search'][$idx] = mb_strtoupper($value); } else if ($idx == 'class') { $params['search'][$idx] = file_utils::class2mimetypes($value); } } } $response = $this->client()->request('GET', "v4/fs?parent={$repo_id}&type=file"); $json = json_decode($response->getBody(), true); $entries = $json['list']; $result = array(); foreach ((array) $entries as $idx => $file) { //TODO file['name'] //TODO file['type'] = 'file' //TODO file['type'] if ($file['type'] != 'file') { continue; } $file_id = $file['id']; //FIXME slightly wasteful to just get the size and file modification timestamps $response = $this->client->request("GET", "v4/fs/{$file_id}"); $json = json_decode($response->getBody(), true); $file = $this->from_file_object($json); // search filter if (!empty($params['search'])) { foreach ($params['search'] as $idx => $value) { if ($idx == 'name') { if (strpos(mb_strtoupper($file['name']), $value) === false) { continue 2; } } else if ($idx == 'class') { foreach ($value as $v) { if (stripos($file['type'], $v) !== false) { continue 2; } } continue 2; } } } $filename = ($params['prefix'] ?? "") . $folder_name . file_storage::SEPARATOR . $file['name']; $result[$filename] = array( 'name' => $file['name'], 'size' => (int) $file['size'], 'type' => (string) $file['type'], 'mtime' => file_utils::date_format($file['changed'], null, $this->config['timezone']), 'ctime' => file_utils::date_format($file['created'], null, $this->config['timezone']), 'modified' => $file['changed'] ? $file['changed']->format('U') : 0, 'created' => $file['created'] ? $file['created']->format('U') : 0, ); unset($entries[$idx]); } // @TODO: pagination, search (by filename, mimetype) // Sorting $sort = !empty($params['sort']) ? $params['sort'] : 'name'; $index = array(); if ($sort == 'mtime') { $sort = 'modified'; } if (in_array($sort, array('name', 'size', 'modified'))) { foreach ($result as $key => $val) { $index[$key] = $val[$sort]; } array_multisort($index, SORT_ASC, SORT_NUMERIC, $result); } if ($params['reverse']) { $result = array_reverse($result, true); } return $result; } /** * Copy a file. * * @param string $file_name Name of a file (with folder path) * @param string $new_name New name of a file (with folder path) * * @throws Exception */ public function file_copy($file_name, $new_name) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Move (or rename) a file. * * @param string $file_name Name of a file (with folder path) * @param string $new_name New name of a file (with folder path) * * @throws Exception */ public function file_move($file_name, $new_name) { list($src_name, $repo_id) = $this->find_collection($file_name); list($dst_name, $dst_repo_id) = $this->find_collection($new_name); $file_id = $this->find_file_id($src_name, $repo_id); $response = $this->client->request("PUT", "v4/fs/$file_id?name=$dst_name", ['headers' => ["X-Kolab-Parents" => $dst_repo_id]]); $success = $response->getStatusCode() == 200; if (!$success) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Error moving file on Kolab server"), true, false); throw new Exception("Storage error. File rename failed.", file_storage::ERROR); } } /** * Create a folder. * * @param string $folder_name Name of a folder with full path * * @throws Exception on error */ public function folder_create($folder_name) { list($folder, $repo_id) = $this->find_collection($folder_name, true); if (empty($repo_id)) { $response = $this->client->request("POST", "v4/fs?name=$folder_name&type=collection"); } else { $response = $this->client->request("POST", "v4/fs?name=$folder&type=collection&parent={$repo_id}"); } $success = $response->getStatusCode() == 200; if (!$success) { throw new Exception("Storage error. Unable to create folder", file_storage::ERROR); } // clear the cache if (empty($repo_id)) { $this->collections = null; } } /** * Delete a folder. * * @param string $folder_name Name of a folder with full path * * @throws Exception on error */ public function folder_delete($folder_name) { list($folder, $repo_id) = $this->find_collection($folder_name, true); $response = $this->client->request("DELETE", "v4/fs/$repo_id"); $success = $response->getStatusCode() == 200; if (!$success) { throw new Exception("Storage error. Unable to delete folder.", file_storage::ERROR); } } /** * Move/Rename a folder. * * @param string $folder_name Name of a folder with full path * @param string $new_name New name of a folder with full path * * @throws Exception on error */ public function folder_move($folder_name, $new_name) { list($folder_name, $repo_id, $collection) = $this->find_collection($folder_name, true); list($dest_folder_name, $dest_repo_id) = $this->find_collection($new_name, true); $response = $this->client->request("PUT", "v4/fs/$repo_id?name=$dest_folder_name", ['headers' => ["X-Kolab-Parents" => $dest_repo_id]]); $success = $response->getStatusCode() == 200; if (!$success) { throw new Exception("Storage error. Unable to rename/move folder", file_storage::ERROR); } } /** * Subscribe a folder. * * @param string $folder_name Name of a folder with full path * * @throws Exception */ public function folder_subscribe($folder_name) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Unsubscribe a folder. * * @param string $folder_name Name of a folder with full path * * @throws Exception */ public function folder_unsubscribe($folder_name) { throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); } /** * Returns list of folders. * * @param array $params List parameters ('type', 'search', 'path', 'level') * * @return array List of folders * @throws Exception */ public function folder_list($params = array()) { // $collections = $this->collections(); // $writable = ($params['type'] & file_storage::FILTER_WRITABLE) ? true : false; // $prefix = (string) $params['path']; // $prefix_len = strlen($prefix); $folders = array(); $collections = $this->collections(); foreach ($collections as $folder) { $item = array('folder' => $folder['name']); $item['readonly'] = false; //TODO? // 'mtime' => $collection['mtime'], // 'permission' => $collection['permission'], $folders[$folder['name']] = $item; } if (empty($params['extended'])) { $folders = array_keys($folders); } // sort folders usort($folders, array('file_utils', 'sort_folder_comparator')); return $folders; } /** * Check folder rights. * * @param string $folder_name Name of a folder with full path * * @return int Folder rights (sum of file_storage::ACL_*) */ public function folder_rights($folder_name) { list($folder, $repo_id, $collection) = $this->find_collection($folder_name); $response = $this->client()->request('GET', "v4/fs/$repo_id"); $json = json_decode($response->getBody(), true); if ($json['canUpdate']) { return file_storage::ACL_READ | file_storage::ACL_WRITE; } return file_storage::ACL_READ; } /** * Returns a list of locks * * This method should return all the locks for a particular URI, including * locks that might be set on a parent URI. * * If child_locks is set to true, this method should also look for * any locks in the subtree of the URI for locks. * * @param string $path File/folder path * @param bool $child_locks Enables subtree checks * * @return array List of locks * @throws Exception */ public function lock_list($path, $child_locks = false) { $this->init_lock_db(); // convert URI to global resource string $uri = $this->path2uri($path); // get locks list $list = $this->lock_db->lock_list($uri, $child_locks); // convert back resource string into URIs foreach ($list as $idx => $lock) { $list[$idx]['uri'] = $this->uri2path($lock['uri']); } return $list; } /** * Locks a URI * * @param string $path File/folder path * @param array $lock Lock data * - depth: 0/'infinite' * - scope: 'shared'/'exclusive' * - owner: string * - token: string * - timeout: int * * @throws Exception */ public function lock($path, $lock) { $this->init_lock_db(); // convert URI to global resource string $uri = $this->path2uri($path); if (!$this->lock_db->lock($uri, $lock)) { throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR); } } /** * Removes a lock from a URI * * @param string $path File/folder path * @param array $lock Lock data * * @throws Exception */ public function unlock($path, $lock) { $this->init_lock_db(); // convert URI to global resource string $uri = $this->path2uri($path); if (!$this->lock_db->unlock($uri, $lock)) { throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR); } } /** * Return disk quota information for specified folder. * * @param string $folder_name Name of a folder with full path * * @return array Quota * @throws Exception */ public function quota($folder) { // throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED); return []; } /** * Sharing interface * * @param string $folder_name Name of a folder with full path * @param int $mode Sharing action mode * @param array $args POST/GET parameters * * @return mixed Sharing response * @throws Exception */ public function sharing($folder, $mode, $args = array()) { throw new Exception("Search not implemented", file_storage::ERROR_UNSUPPORTED); } /** * User/group search (autocompletion) * * @param string $search Search string * @param int $mode Search mode * * @return array Users/Groups list * @throws Exception */ public function autocomplete($search, $mode) { throw new Exception("Autocomplete not implemented", file_storage::ERROR_UNSUPPORTED); } private static function getHost($url) { // Remove protocol prefix and path, we work with host only return preg_replace('#(^https?://|/.*$)#i', '', $url); } /** * Convert file/folder path into a global URI. * * @param string $path File/folder path * * @return string URI * @throws Exception */ public function path2uri($path) { $host = self::getHost($this->config['baseuri']); if (!is_string($path) || !strlen($path)) { $user = $_SESSION[$this->title . 'user']; return 'kolabfiles://' . rawurlencode($user) . '@' . $host . '/'; } list($file, $repo_id, $collection) = $this->find_collection($path); //FIXME we don't currently have an owner return 'kolabfiles://' . rawurlencode($collection['owner']) . '@' . $host . '/' . file_utils::encode_path($path); } /** * Convert global URI into file/folder path. * * @param string $uri URI * * @return string File/folder path * @throws Exception */ public function uri2path($uri) { if (!preg_match('|^kolabfiles://([^@]+)@([^/]+)/(.*)$|', $uri, $matches)) { throw new Exception("Internal storage error. Unexpected data format. $uri", file_storage::ERROR); } $user = rawurldecode($matches[1]); $host = $matches[2]; $path = file_utils::decode_path($matches[3]); $c_host = self::getHost($this->config['baseuri']); if (strlen($path)) { list($file, $repo_id, $collection) = $this->find_collection($path, true); if (empty($collection) || $host != $c_host || $user != $collection['owner']) { throw new Exception("Internal storage error. Unresolvable URI.", file_storage::ERROR); } } return $path; } /** * Get list of collections */ private function collections($parent = null, $path = null) { if ($this->collections) { return $this->collections; } $folders = array(); //FIXME If we could just fetch all collections, we could assemble the tree after a single fetch. if ($parent) { $parentId = $parent['id']; $response = $this->client()->request('GET', "v4/fs?parent=$parentId&type=collection"); } else { $response = $this->client()->request('GET', "v4/fs?type=collection"); } //FIXME should we just always throw on request errors? Probably? if ($response->getStatusCode() != 200) { rcube::write_log('kolabfiles', "The request failed: " . $response->getStatusCode()); throw new Exception("Get request was unsuccessful"); } $json = json_decode($response->getBody(), true); // rcube::write_log('kolabfiles', var_export($json, true)); $collections = $json['list']; if (!$collections) { return []; } $collections = array_map(function ($entry) use ($path) { //FIXME: retrieve the actual owner from the api. (Do we need the owner though?), not sure it matters $entry['owner'] = $_SESSION[$this->title . 'user']; if ($path) { $entry['name'] = $path . "/" . $entry['name']; } return $entry; }, $collections); $tmp = $collections; foreach ($tmp as $lib) { $subfolders = $this->collections($lib, $lib['name']); $collections = array_merge($collections, $subfolders); } if (!$parent) { $this->collections = $collections; } return $collections; } protected function find_file_id($file_name, $repo_id) { $response = $this->client()->request('GET', "v4/fs?parent={$repo_id}&type=file"); $json = json_decode($response->getBody(), true); foreach ($json['list'] as $idx => $file) { if ($file['name'] == $file_name) { return $file['id']; } } rcube::write_log('console', "Failed to find the file $file_name in $repo_id"); throw new Exception("Failed to find the file $file_name in $repo_id"); } /** * Find collection ID from folder name */ protected function find_collection($folder_name, $no_exception = false) { $collections = $this->collections(); foreach ($collections as $lib) { $path = $lib['name'] . '/'; if ($folder_name == $lib['name'] || strpos($folder_name, $path) === 0) { if (empty($collection) || strlen($collection['name']) < strlen($lib['name'])) { $collection = $lib; } } } if (empty($collection)) { if (!$no_exception) { throw new Exception("Storage error. Collection not found.", file_storage::ERROR); } return array(null, null); } else { $folder = substr($folder_name, strlen($collection['name']) + 1); } return array( // '/' . ($folder ? $folder : ''), ($folder ? $folder : ''), $collection['id'], $collection ); } /** * Simplify internal structure of the file object */ protected function from_file_object($file) { if ($file['created_at']) { try { $file['created'] = new DateTime($file['created_at']); } catch (Exception $e) {} } unset($file['created_at']); if ($file['updated_at']) { try { $file['changed'] = new DateTime($file['updated_at']); } catch (Exception $e) { } } unset($file['updated_at']); // We're not taking the servers filetype. The server might have octet/stream stored for a file, // which will break the editor which needs some odt mimetype (it will silently fall back to downloading the file instead). $file['type'] = file_utils::ext_to_type($file['name']); unset($file['id']); return $file; } /** * Initializes file_locks object */ protected function init_lock_db() { if (!$this->lock_db) { $this->lock_db = new file_locks; } } /** * Create display-username-with-email string */ protected function user_label($name, $email) { if ($name && $name != $email) { $label = "{$name} ({$email})"; } else { $label = $email; } return $label; } }