Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'wip-MDL-34290-MOODLE_23_STABLE' of git://github.com/mar…

…inaglancy/moodle into MOODLE_23_STABLE
  • Loading branch information...
commit 3afd86559e1e93b41146faea3046fa7b94c02761 2 parents accaf1d + 074455a
@stronk7 stronk7 authored
View
1  lang/en/repository.php
@@ -100,6 +100,7 @@
$string['errornotyourfile'] = 'You cannot pick file which is not added by your';
$string['erroruniquename'] = 'Repository instance name should be unique';
$string['errorpostmaxsize'] = 'The uploaded file may exceed max_post_size directive in php.ini.';
+$string['errorwhiledownload'] = 'An error occured while downloading the file: {$a}';
$string['existingrepository'] = 'This repository already exists';
$string['federatedsearch'] = 'Federated search';
$string['fileexists'] = 'File name already being used, please use another name';
View
22 lib/boxlib.php
@@ -176,7 +176,7 @@ function getfiletree($path, $params = array()) {
$params['action'] = 'get_account_tree';
$params['onelevel'] = 1;
$params['params[]'] = 'nozip';
- $c = new curl(array('debug'=>$this->debug, 'cache'=>true, 'module_cache'=>'repository'));
+ $c = new curl(array('debug'=>$this->debug));
$c->setopt(array('CURLOPT_FOLLOWLOCATION'=>1));
try {
$args = array();
@@ -196,23 +196,25 @@ function getfiletree($path, $params = array()) {
* Get box.net file info
*
* @param string $fileid
- * @return string|null
+ * @param int $timeout request timeout in seconds
+ * @return stdClass|null
*/
- function get_file_info($fileid) {
+ function get_file_info($fileid, $timeout = 0) {
$this->_clearErrors();
$params = array();
$params['action'] = 'get_file_info';
$params['file_id'] = $fileid;
$params['auth_token'] = $this->auth_token;
$params['api_key'] = $this->api_key;
- $http = new curl(array('debug'=>$this->debug, 'cache'=>true, 'module_cache'=>'repository'));
- $xml = $http->get($this->_box_api_url, $params);
- $o = simplexml_load_string(trim($xml));
- if ($o->status == 's_get_file_info') {
- return $o->info;
- } else {
- return null;
+ $http = new curl(array('debug'=>$this->debug));
+ $xml = $http->get($this->_box_api_url, $params, array('timeout' => $timeout));
+ if (!$http->get_errno()) {
+ $o = simplexml_load_string(trim($xml));
+ if ($o->status == 's_get_file_info') {
+ return $o->info;
+ }
}
+ return null;
}
/**
View
4 lib/cronlib.php
@@ -466,10 +466,6 @@ function cron_run() {
$fs = get_file_storage();
$fs->cron();
- mtrace("Clean up cached external files");
- // 1 week
- cache_file::cleanup(array(), 60 * 60 * 24 * 7);
-
mtrace("Cron script completed correctly");
$difftime = microtime_diff($starttime, microtime());
View
261 lib/filelib.php
@@ -804,10 +804,6 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea
continue;
}
- // Replaced file content
- if ($oldfile->get_contenthash() != $newfile->get_contenthash()) {
- $oldfile->replace_content_with($newfile);
- }
// Updated author
if ($oldfile->get_author() != $newfile->get_author()) {
$oldfile->set_author($newfile->get_author());
@@ -827,16 +823,18 @@ function file_save_draft_area_files($draftitemid, $contextid, $component, $filea
$oldfile->set_sortorder($newfile->get_sortorder());
}
- // Update file size
- if ($oldfile->get_filesize() != $newfile->get_filesize()) {
- $oldfile->set_filesize($newfile->get_filesize());
- }
-
// Update file timemodified
if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
$oldfile->set_timemodified($newfile->get_timemodified());
}
+ // Replaced file content
+ if ($oldfile->get_contenthash() != $newfile->get_contenthash() || $oldfile->get_filesize() != $newfile->get_filesize()) {
+ $oldfile->replace_content_with($newfile);
+ // push changes to all local files that are referencing this file
+ $fs->update_references_to_storedfile($this);
+ }
+
// unchanged file or directory - we keep it as is
unset($newhashes[$oldhash]);
if (!$oldfile->is_directory()) {
@@ -2328,7 +2326,7 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl
}
// handle external resource
- if ($stored_file && $stored_file->is_external_file()) {
+ if ($stored_file && $stored_file->is_external_file() && !isset($options['sendcachedexternalfile'])) {
$stored_file->send_file($lifetime, $filter, $forcedownload, $options);
die;
}
@@ -2752,6 +2750,8 @@ class curl {
public $info;
/** @var string error */
public $error;
+ /** @var int error code */
+ public $errno;
/** @var array cURL options */
private $options;
@@ -2895,6 +2895,14 @@ public function cleanopt(){
unset($this->options['CURLOPT_INFILE']);
unset($this->options['CURLOPT_INFILESIZE']);
unset($this->options['CURLOPT_CUSTOMREQUEST']);
+ unset($this->options['CURLOPT_FILE']);
+ }
+
+ /**
+ * Resets the HTTP Request headers (to prepare for the new request)
+ */
+ public function resetHeader() {
+ $this->header = array();
}
/**
@@ -3119,6 +3127,7 @@ protected function request($url, $options = array()){
$this->info = curl_getinfo($curl);
$this->error = curl_error($curl);
+ $this->errno = curl_errno($curl);
if ($this->debug){
echo '<h1>Return Data</h1>';
@@ -3203,6 +3212,62 @@ public function get($url, $params = array(), $options = array()){
}
/**
+ * Downloads one file and writes it to the specified file handler
+ *
+ * <code>
+ * $c = new curl();
+ * $file = fopen('savepath', 'w');
+ * $result = $c->download_one('http://localhost/', null,
+ * array('file' => $file, 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));
+ * fclose($file);
+ * $download_info = $c->get_info();
+ * if ($result === true) {
+ * // file downloaded successfully
+ * } else {
+ * $error_text = $result;
+ * $error_code = $c->get_errno();
+ * }
+ * </code>
+ *
+ * <code>
+ * $c = new curl();
+ * $result = $c->download_one('http://localhost/', null,
+ * array('filepath' => 'savepath', 'timeout' => 5, 'followlocation' => true, 'maxredirs' => 3));
+ * // ... see above, no need to close handle and remove file if unsuccessful
+ * </code>
+ *
+ * @param string $url
+ * @param array|null $params key-value pairs to be added to $url as query string
+ * @param array $options request options. Must include either 'file' or 'filepath'
+ * @return bool|string true on success or error string on failure
+ */
+ public function download_one($url, $params, $options = array()) {
+ $options['CURLOPT_HTTPGET'] = 1;
+ $options['CURLOPT_BINARYTRANSFER'] = true;
+ if (!empty($params)){
+ $url .= (stripos($url, '?') !== false) ? '&' : '?';
+ $url .= http_build_query($params, '', '&');
+ }
+ if (!empty($options['filepath']) && empty($options['file'])) {
+ // open file
+ if (!($options['file'] = fopen($options['filepath'], 'w'))) {
+ $this->errno = 100;
+ return get_string('cannotwritefile', 'error', $options['filepath']);
+ }
+ $filepath = $options['filepath'];
+ }
+ unset($options['filepath']);
+ $result = $this->request($url, $options);
+ if (isset($filepath)) {
+ fclose($options['file']);
+ if ($result !== true) {
+ unlink($filepath);
+ }
+ }
+ return $result;
+ }
+
+ /**
* HTTP PUT method
*
* @param string $url
@@ -3279,6 +3344,15 @@ public function options($url, $options = array()){
public function get_info() {
return $this->info;
}
+
+ /**
+ * Get curl error code
+ *
+ * @return int
+ */
+ public function get_errno() {
+ return $this->errno;
+ }
}
/**
@@ -4197,170 +4271,3 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
}
}
-
-/**
- * Universe file cacheing class
- *
- * @package core_files
- * @category files
- * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class cache_file {
- /** @var string */
- public $cachedir = '';
-
- /**
- * static method to create cache_file class instance
- *
- * @param array $options caching ooptions
- */
- public static function get_instance($options = array()) {
- return new cache_file($options);
- }
-
- /**
- * Constructor
- *
- * @param array $options
- */
- private function __construct($options = array()) {
- global $CFG;
-
- // Path to file caches.
- if (isset($options['cachedir'])) {
- $this->cachedir = $options['cachedir'];
- } else {
- $this->cachedir = $CFG->cachedir . '/filedir';
- }
-
- // Create cache directory.
- if (!file_exists($this->cachedir)) {
- mkdir($this->cachedir, $CFG->directorypermissions, true);
- }
-
- // When use cache_file::get, it will check ttl.
- if (isset($options['ttl']) && is_numeric($options['ttl'])) {
- $this->ttl = $options['ttl'];
- } else {
- // One day.
- $this->ttl = 60 * 60 * 24;
- }
- }
-
- /**
- * Get cached file, false if file expires
- *
- * @param mixed $param
- * @param array $options caching options
- * @return bool|string
- */
- public static function get($param, $options = array()) {
- $instance = self::get_instance($options);
- $filepath = $instance->generate_filepath($param);
- if (file_exists($filepath)) {
- $lasttime = filemtime($filepath);
- if (time() - $lasttime > $instance->ttl) {
- // Remove cache file.
- unlink($filepath);
- return false;
- } else {
- return $filepath;
- }
- } else {
- return false;
- }
- }
-
- /**
- * Static method to create cache from a file
- *
- * @param mixed $ref
- * @param string $srcfile
- * @param array $options
- * @return string cached file path
- */
- public static function create_from_file($ref, $srcfile, $options = array()) {
- $instance = self::get_instance($options);
- $cachedfilepath = $instance->generate_filepath($ref);
- copy($srcfile, $cachedfilepath);
- return $cachedfilepath;
- }
-
- /**
- * Static method to create cache from url
- *
- * @param mixed $ref file reference
- * @param string $url file url
- * @param array $options options
- * @return string cached file path
- */
- public static function create_from_url($ref, $url, $options = array()) {
- $instance = self::get_instance($options);
- $cachedfilepath = $instance->generate_filepath($ref);
- $fp = fopen($cachedfilepath, 'w');
- $curl = new curl;
- $curl->download(array(array('url'=>$url, 'file'=>$fp)));
- // Must close file handler.
- fclose($fp);
- return $cachedfilepath;
- }
-
- /**
- * Static method to create cache from string
- *
- * @param mixed $ref file reference
- * @param string $url file url
- * @param array $options options
- * @return string cached file path
- */
- public static function create_from_string($ref, $string, $options = array()) {
- $instance = self::get_instance($options);
- $cachedfilepath = $instance->generate_filepath($ref);
- $fp = fopen($cachedfilepath, 'w');
- fwrite($fp, $string);
- // Must close file handler.
- fclose($fp);
- return $cachedfilepath;
- }
-
- /**
- * Build path to cache file
- *
- * @param mixed $ref
- * @return string
- */
- private function generate_filepath($ref) {
- global $CFG;
- $hash = sha1(serialize($ref));
- $l1 = $hash[0].$hash[1];
- $l2 = $hash[2].$hash[3];
- $dir = $this->cachedir . "/$l1/$l2";
- if (!file_exists($dir)) {
- mkdir($dir, $CFG->directorypermissions, true);
- }
- return "$dir/$hash";
- }
-
- /**
- * Remove cache files
- *
- * @param array $options options
- * @param int $expire The number of seconds before expiry
- */
- public static function cleanup($options = array(), $expire) {
- global $CFG;
- $instance = self::get_instance($options);
- if ($dir = opendir($instance->cachedir)) {
- while (($file = readdir($dir)) !== false) {
- if (!is_dir($file) && $file != '.' && $file != '..') {
- $lasttime = @filemtime($instance->cachedir . $file);
- if(time() - $lasttime > $expire){
- @unlink($instance->cachedir . $file);
- }
- }
- }
- closedir($dir);
- }
- }
-}
View
140 lib/filestorage/file_storage.php
@@ -976,10 +976,6 @@ public function create_file_from_pathname($filerecord, $pathname) {
$filerecord->sortorder = 0;
}
- $filerecord->referencefileid = !isset($filerecord->referencefileid) ? 0 : $filerecord->referencefileid;
- $filerecord->referencelastsync = !isset($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync;
- $filerecord->referencelifetime = !isset($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime;
-
$filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH);
if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) {
// path must start and end with '/'
@@ -1094,9 +1090,6 @@ public function create_file_from_string($filerecord, $content) {
} else {
$filerecord->sortorder = 0;
}
- $filerecord->referencefileid = !isset($filerecord->referencefileid) ? 0 : $filerecord->referencefileid;
- $filerecord->referencelastsync = !isset($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync;
- $filerecord->referencelifetime = !isset($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime;
$filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH);
if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) {
@@ -1217,9 +1210,10 @@ public function create_file_from_reference($filerecord, $repositoryid, $referenc
$filerecord->sortorder = 0;
}
- $filerecord->referencefileid = empty($filerecord->referencefileid) ? 0 : $filerecord->referencefileid;
- $filerecord->referencelastsync = empty($filerecord->referencelastsync) ? 0 : $filerecord->referencelastsync;
- $filerecord->referencelifetime = empty($filerecord->referencelifetime) ? 0 : $filerecord->referencelifetime;
+ // TODO MDL-33416 [2.4] fields referencelastsync and referencelifetime to be removed from {files} table completely
+ unset($filerecord->referencelastsync);
+ unset($filerecord->referencelifetime);
+
$filerecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($filerecord->filename) : $filerecord->mimetype;
$filerecord->userid = empty($filerecord->userid) ? null : $filerecord->userid;
$filerecord->source = empty($filerecord->source) ? null : $filerecord->source;
@@ -1266,22 +1260,39 @@ public function create_file_from_reference($filerecord, $repositoryid, $referenc
$transaction = $DB->start_delegated_transaction();
try {
- $filerecord->referencefileid = $this->get_or_create_referencefileid($repositoryid, $reference,
- $filerecord->referencelastsync, $filerecord->referencelifetime);
+ $filerecord->referencefileid = $this->get_or_create_referencefileid($repositoryid, $reference);
} catch (Exception $e) {
throw new file_reference_exception($repositoryid, $reference, null, null, $e->getMessage());
}
- // External file doesn't have content in moodle.
- // So we create an empty file for it.
- list($filerecord->contenthash, $filerecord->filesize, $newfile) = $this->add_string_to_pool(null);
+ if (isset($filerecord->contenthash) && $this->content_exists($filerecord->contenthash)) {
+ // there was specified the contenthash for a file already stored in moodle filepool
+ if (empty($filerecord->filesize)) {
+ $filepathname = $this->path_from_hash($filerecord->contenthash) . '/' . $filerecord->contenthash;
+ $filerecord->filesize = filesize($filepathname);
+ } else {
+ $filerecord->filesize = clean_param($filerecord->filesize, PARAM_INT);
+ }
+ } else {
+ // atempt to get the result of last synchronisation for this reference
+ $lastcontent = $DB->get_record('files', array('referencefileid' => $filerecord->referencefileid),
+ 'id, contenthash, filesize', IGNORE_MULTIPLE);
+ if ($lastcontent) {
+ $filerecord->contenthash = $lastcontent->contenthash;
+ $filerecord->filesize = $lastcontent->filesize;
+ } else {
+ // External file doesn't have content in moodle.
+ // So we create an empty file for it.
+ list($filerecord->contenthash, $filerecord->filesize, $newfile) = $this->add_string_to_pool(null);
+ }
+ }
$filerecord->pathnamehash = $this->get_pathname_hash($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->filename);
try {
$filerecord->id = $DB->insert_record('files', $filerecord);
} catch (dml_exception $e) {
- if ($newfile) {
+ if (!empty($newfile)) {
$this->deleted_file_cleanup($filerecord->contenthash);
}
throw new stored_file_creation_exception($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid,
@@ -1292,10 +1303,8 @@ public function create_file_from_reference($filerecord, $repositoryid, $referenc
$transaction->allow_commit();
- // Adding repositoryid and reference to file record to create stored_file instance
- $filerecord->repositoryid = $repositoryid;
- $filerecord->reference = $reference;
- return $this->get_file_instance($filerecord);
+ // this will retrieve all reference information from DB as well
+ return $this->get_file_by_id($filerecord->id);
}
/**
@@ -1806,17 +1815,50 @@ public function get_references_count_by_storedfile(stored_file $storedfile) {
}
/**
+ * Updates all files that are referencing this file with the new contenthash
+ * and filesize
+ *
+ * @param stored_file $storedfile
+ */
+ public function update_references_to_storedfile(stored_file $storedfile) {
+ global $CFG;
+ $params = array();
+ $params['contextid'] = $storedfile->get_contextid();
+ $params['component'] = $storedfile->get_component();
+ $params['filearea'] = $storedfile->get_filearea();
+ $params['itemid'] = $storedfile->get_itemid();
+ $params['filename'] = $storedfile->get_filename();
+ $params['filepath'] = $storedfile->get_filepath();
+ $reference = self::pack_reference($params);
+ $referencehash = sha1($reference);
+
+ $sql = "SELECT repositoryid, id FROM {files_reference}
+ WHERE referencehash = ? and reference = ?";
+ $rs = $DB->get_recordset_sql($sql, array($referencehash, $reference));
+
+ $now = time();
+ foreach ($rs as $record) {
+ require_once($CFG->dirroot.'/repository/lib.php');
+ $repo = repository::get_instance($record->repositoryid);
+ $lifetime = $repo->get_reference_file_lifetime($reference);
+ $this->update_references($record->id, $now, $lifetime,
+ $storedfile->get_contenthash(), $storedfile->get_filesize(), 0);
+ }
+ $rs->close();
+ }
+
+ /**
* Convert file alias to local file
*
+ * @throws moodle_exception if file could not be downloaded
+ *
* @param stored_file $storedfile a stored_file instances
+ * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
* @return stored_file stored_file
*/
- public function import_external_file(stored_file $storedfile) {
+ public function import_external_file(stored_file $storedfile, $maxbytes = 0) {
global $CFG;
- require_once($CFG->dirroot.'/repository/lib.php');
- // sync external file
- repository::sync_external_file($storedfile);
- // Remove file references
+ $storedfile->import_external_file_contents($maxbytes);
$storedfile->delete_reference();
return $storedfile;
}
@@ -1923,10 +1965,12 @@ private static function instance_sql_fields($filesprefix, $filesreferenceprefix)
// else problems like MDL-33172 occur.
$filefields = array('contenthash', 'pathnamehash', 'contextid', 'component', 'filearea',
'itemid', 'filepath', 'filename', 'userid', 'filesize', 'mimetype', 'status', 'source',
- 'author', 'license', 'timecreated', 'timemodified', 'sortorder', 'referencefileid',
- 'referencelastsync', 'referencelifetime');
+ 'author', 'license', 'timecreated', 'timemodified', 'sortorder', 'referencefileid');
- $referencefields = array('repositoryid', 'reference');
+ $referencefields = array('repositoryid' => 'repositoryid',
+ 'reference' => 'reference',
+ 'lastsync' => 'referencelastsync',
+ 'lifetime' => 'referencelifetime');
// id is specifically named to prevent overlaping between the two tables.
$fields = array();
@@ -1935,8 +1979,8 @@ private static function instance_sql_fields($filesprefix, $filesreferenceprefix)
$fields[] = "{$filesprefix}.{$field}";
}
- foreach ($referencefields as $field) {
- $fields[] = "{$filesreferenceprefix}.{$field}";
+ foreach ($referencefields as $field => $alias) {
+ $fields[] = "{$filesreferenceprefix}.{$field} AS {$alias}";
}
return implode(', ', $fields);
@@ -1997,4 +2041,40 @@ private function get_referencefileid($repositoryid, $reference, $strictness) {
return $DB->get_field('files_reference', 'id',
array('repositoryid' => $repositoryid, 'referencehash' => sha1($reference)), $strictness);
}
+
+ /**
+ * Updates a reference to the external resource and all files that use it
+ *
+ * This function is called after synchronisation of an external file and updates the
+ * contenthash, filesize and status of all files that reference this external file
+ * as well as time last synchronised and sync lifetime (how long we don't need to call
+ * synchronisation for this reference).
+ *
+ * @param int $referencefileid
+ * @param int $lastsync
+ * @param int $lifetime
+ * @param string $contenthash
+ * @param int $filesize
+ * @param int $status 0 if ok or 666 if source is missing
+ */
+ public function update_references($referencefileid, $lastsync, $lifetime, $contenthash, $filesize, $status) {
+ global $DB;
+ $referencefileid = clean_param($referencefileid, PARAM_INT);
+ $lastsync = clean_param($lastsync, PARAM_INT);
+ $lifetime = clean_param($lifetime, PARAM_INT);
+ validate_param($contenthash, PARAM_TEXT, NULL_NOT_ALLOWED);
+ $filesize = clean_param($filesize, PARAM_INT);
+ $status = clean_param($status, PARAM_INT);
+ $params = array('contenthash' => $contenthash,
+ 'filesize' => $filesize,
+ 'status' => $status,
+ 'referencefileid' => $referencefileid,
+ 'lastsync' => $lastsync,
+ 'lifetime' => $lifetime);
+ $DB->execute('UPDATE {files} SET contenthash = :contenthash, filesize = :filesize,
+ status = :status, referencelastsync = :lastsync, referencelifetime = :lifetime
+ WHERE referencefileid = :referencefileid', $params);
+ $data = array('id' => $referencefileid, 'lastsync' => $lastsync, 'lifetime' => $lifetime);
+ $DB->update_record('files_reference', (object)$data);
+ }
}
View
74 lib/filestorage/stored_file.php
@@ -70,6 +70,12 @@ public function __construct(file_storage $fs, stdClass $file_record, $filedir) {
} else {
$this->repository = null;
}
+ // make sure all reference fields exist in file_record even when it is not a reference
+ foreach (array('referencelastsync', 'referencelifetime', 'referencefileid', 'reference', 'repositoryid') as $key) {
+ if (empty($this->file_record->$key)) {
+ $this->file_record->$key = null;
+ }
+ }
}
/**
@@ -142,12 +148,18 @@ protected function update($dataobject) {
}
}
- if ($field === 'referencefileid' or $field === 'referencelastsync' or $field === 'referencelifetime') {
+ if ($field === 'referencefileid') {
if (!is_null($value) and !is_number($value)) {
throw new file_exception('storedfileproblem', 'Invalid reference info');
}
}
+ if ($field === 'referencelastsync' or $field === 'referencelifetime') {
+ // do not update those fields
+ // TODO MDL-33416 [2.4] fields referencelastsync and referencelifetime to be removed from {files} table completely
+ continue;
+ }
+
// adding the field
$this->file_record->$field = $value;
} else {
@@ -195,6 +207,7 @@ public function rename($filepath, $filename) {
public function replace_content_with(stored_file $storedfile) {
$contenthash = $storedfile->get_contenthash();
$this->set_contenthash($contenthash);
+ $this->set_filesize($storedfile->get_filesize());
}
/**
@@ -225,8 +238,6 @@ public function delete_reference() {
// Update the underlying record in the database.
$update = new stdClass();
$update->referencefileid = null;
- $update->referencelastsync = null;
- $update->referencelifetime = null;
$this->update($update);
$transaction->allow_commit();
@@ -877,36 +888,43 @@ public function get_reference_details() {
* We update contenthash, filesize and status in files table if changed
* and we always update lastsync in files_reference table
*
- * @param type $contenthash
- * @param type $filesize
+ * @param string $contenthash
+ * @param int $filesize
+ * @param int $status
+ * @param int $lifetime the life time of this synchronisation results
*/
- public function set_synchronized($contenthash, $filesize, $status = 0) {
+ public function set_synchronized($contenthash, $filesize, $status = 0, $lifetime = null) {
global $DB;
if (!$this->is_external_file()) {
return;
}
$now = time();
- $filerecord = new stdClass();
- if ($this->get_contenthash() !== $contenthash) {
- $filerecord->contenthash = $contenthash;
- }
- if ($this->get_filesize() != $filesize) {
- $filerecord->filesize = $filesize;
+ if ($contenthash != $this->file_record->contenthash) {
+ $oldcontenthash = $this->file_record->contenthash;
}
- if ($this->get_status() != $status) {
- $filerecord->status = $status;
+ if ($lifetime === null) {
+ $lifetime = $this->file_record->referencelifetime;
}
- $filerecord->referencelastsync = $now; // TODO MDL-33416 remove this
- if (!empty($filerecord)) {
- $this->update($filerecord);
+ // this will update all entries in {files} that have the same filereference id
+ $this->fs->update_references($this->file_record->referencefileid, $now, $lifetime, $contenthash, $filesize, $status);
+ // we don't need to call update() for this object, just set the values of changed fields
+ $this->file_record->contenthash = $contenthash;
+ $this->file_record->filesize = $filesize;
+ $this->file_record->status = $status;
+ $this->file_record->referencelastsync = $now;
+ $this->file_record->referencelifetime = $lifetime;
+ if (isset($oldcontenthash)) {
+ $this->fs->deleted_file_cleanup($oldcontenthash);
}
-
- $DB->set_field('files_reference', 'lastsync', $now, array('id'=>$this->get_referencefileid()));
- // $this->file_record->lastsync = $now; // TODO MDL-33416 uncomment or remove
}
- public function set_missingsource() {
- $this->set_synchronized($this->get_contenthash(), 0, 666);
+ /**
+ * Sets the error status for a file that could not be synchronised
+ *
+ * @param int $lifetime the life time of this synchronisation results
+ */
+ public function set_missingsource($lifetime = null) {
+ $this->set_synchronized($this->get_contenthash(), $this->get_filesize(), 666, $lifetime);
}
/**
@@ -920,4 +938,16 @@ public function set_missingsource() {
public function send_file($lifetime, $filter, $forcedownload, $options) {
$this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
}
+
+ /**
+ * Imports the contents of an external file into moodle filepool.
+ *
+ * @throws moodle_exception if file could not be downloaded or is too big
+ * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
+ */
+ public function import_external_file_contents($maxbytes = 0) {
+ if ($this->repository) {
+ $this->repository->import_external_file_contents($this, $maxbytes);
+ }
+ }
}
View
17 lib/googleapi.php
@@ -185,12 +185,21 @@ public function send_file($file) {
*
* @param string $url url of file
* @param string $path path to save file to
+ * @param int $timeout request timeout, default 0 which means no timeout
* @return array stucture for repository download_file
*/
- public function download_file($url, $path) {
- $content = $this->googleoauth->get($url);
- file_put_contents($path, $content);
- return array('path'=>$path, 'url'=>$url);
+ public function download_file($url, $path, $timeout = 0) {
+ $result = $this->googleoauth->download_one($url, null, array('filepath' => $path, 'timeout' => $timeout));
+ if ($result === true) {
+ $info = $this->googleoauth->get_info();
+ if (isset($info['http_code']) && $info['http_code'] == 200) {
+ return array('path'=>$path, 'url'=>$url);
+ } else {
+ throw new moodle_exception('cannotdownload', 'repository');
+ }
+ } else {
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
}
}
View
27 lib/oauthlib.php
@@ -59,6 +59,8 @@ class oauth_helper {
protected $access_token_api;
/** @var curl */
protected $http;
+ /** @var array options to pass to the next curl request */
+ protected $http_options;
/**
* Contructor for oauth_helper.
@@ -106,6 +108,7 @@ function __construct($args) {
$this->access_token_secret = $args['access_token_secret'];
}
$this->http = new curl(array('debug'=>false));
+ $this->http_options = array();
}
/**
@@ -203,6 +206,15 @@ public function setup_oauth_http_header($params) {
}
/**
+ * Sets the options for the next curl request
+ *
+ * @param array $options
+ */
+ public function setup_oauth_http_options($options) {
+ $this->http_options = $options;
+ }
+
+ /**
* Request token for authentication
* This is the first step to use OAuth, it will return oauth_token and oauth_token_secret
* @return array
@@ -210,7 +222,7 @@ public function setup_oauth_http_header($params) {
public function request_token() {
$this->sign_secret = $this->consumer_secret.'&';
$params = $this->prepare_oauth_parameters($this->request_token_api, array(), 'GET');
- $content = $this->http->get($this->request_token_api, $params);
+ $content = $this->http->get($this->request_token_api, $params, $this->http_options);
// Including:
// oauth_token
// oauth_token_secret
@@ -252,7 +264,7 @@ public function get_access_token($token, $secret, $verifier='') {
$this->sign_secret = $this->consumer_secret.'&'.$secret;
$params = $this->prepare_oauth_parameters($this->access_token_api, array('oauth_token'=>$token, 'oauth_verifier'=>$verifier), 'POST');
$this->setup_oauth_http_header($params);
- $content = $this->http->post($this->access_token_api, $params);
+ $content = $this->http->post($this->access_token_api, $params, $this->http_options);
$keys = $this->parse_result($content);
$this->set_access_token($keys['oauth_token'], $keys['oauth_token_secret']);
return $keys;
@@ -274,9 +286,16 @@ public function request($method, $url, $params=array(), $token='', $secret='') {
}
// to access protected resource, sign_secret will alwasy be consumer_secret+token_secret
$this->sign_secret = $this->consumer_secret.'&'.$secret;
- $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token), $method);
+ if (strtolower($method) === 'post' && !empty($params)) {
+ $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token) + $params, $method);
+ } else {
+ $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token), $method);
+ }
$this->setup_oauth_http_header($oauth_params);
- $content = call_user_func_array(array($this->http, strtolower($method)), array($url, $params));
+ $content = call_user_func_array(array($this->http, strtolower($method)), array($url, $params, $this->http_options));
+ // reset http header and options to prepare for the next request
+ $this->http->resetHeader();
+ // return request return value
return $content;
}
View
7 repository/alfresco/lib.php
@@ -202,12 +202,7 @@ public function get_listing($uuid = '', $path = '') {
public function get_file($uuid, $file = '') {
$node = $this->user_session->getNode($this->store, $uuid);
$url = $this->get_url($node);
- $path = $this->prepare_file($file);
- $fp = fopen($path, 'w');
- $c = new curl;
- $c->download(array(array('url'=>$url, 'file'=>$fp)));
- fclose($fp);
- return array('path'=>$path, 'url'=>$url);
+ return parent::get_file($url, $file);
}
/**
View
39 repository/boxnet/lib.php
@@ -277,12 +277,21 @@ public function get_file_reference($source) {
* @return null|stdClass with attribute 'filepath'
*/
public function get_file_by_reference($reference) {
- $boxnetfile = $this->get_file($reference->reference);
- // Please note that here we will ALWAYS receive a file
- // If source file has been removed from external server, box.com still returns
- // a plain/text file with content 'no such file' (filesize will be 12 bytes)
- if (!empty($boxnetfile['path'])) {
- return (object)array('filepath' => $boxnetfile['path']);
+ $array = explode('/', $reference->reference);
+ $fileid = array_pop($array);
+ $fileinfo = $this->boxclient->get_file_info($fileid, self::SYNCFILE_TIMEOUT);
+ if ($fileinfo) {
+ $size = (int)$fileinfo->size;
+ if (file_extension_in_typegroup($fileinfo->file_name, 'web_image')) {
+ // this is an image - download it to moodle
+ $path = $this->prepare_file('');
+ $c = new curl;
+ $result = $c->download_one($reference->reference, null, array('filepath' => $path, 'timeout' => self::SYNCIMAGE_TIMEOUT));
+ if ($result === true) {
+ return (object)array('filepath' => $path);
+ }
+ }
+ return (object)array('filesize' => $size);
}
return null;
}
@@ -297,13 +306,16 @@ public function get_file_by_reference($reference) {
*/
public function get_reference_details($reference, $filestatus = 0) {
// Indicate it's from box.net repository + secure URL
+ $array = explode('/', $reference);
+ $fileid = array_pop($array);
+ $fileinfo = $this->boxclient->get_file_info($fileid, self::SYNCFILE_TIMEOUT);
+ if (!empty($fileinfo)) {
+ $reference = (string)$fileinfo->file_name;
+ }
$details = $this->get_name() . ': ' . $reference;
- if (!$filestatus) {
+ if (!empty($fileinfo)) {
return $details;
} else {
- // at the moment for box.net files we never can be sure that source is missing
- // because box.com never returns 404 error.
- // So we never change the status and actually this part is unreachable
return get_string('lostsource', 'repository', $details);
}
}
@@ -315,13 +327,14 @@ public function get_reference_details($reference, $filestatus = 0) {
* @return string|null
*/
public function get_file_source_info($url) {
+ global $USER;
$array = explode('/', $url);
$fileid = array_pop($array);
- $fileinfo = $this->boxclient->get_file_info($fileid);
+ $fileinfo = $this->boxclient->get_file_info($fileid, self::SYNCFILE_TIMEOUT);
if (!empty($fileinfo)) {
- return 'Box: ' . (string)$fileinfo->file_name;
+ return 'Box ('. fullname($USER). '): '. (string)$fileinfo->file_name. ': '. $url;
} else {
- return $url;
+ return 'Box: '. $url;
}
}
View
39 repository/dropbox/db/upgrade.php
@@ -0,0 +1,39 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_repository_dropbox_upgrade($oldversion) {
+ global $CFG, $DB;
+
+ $dbman = $DB->get_manager();
+
+ // Moodle v2.3.0 release upgrade line
+ // Put any upgrade step following this
+
+ if ($oldversion < 2012080702) {
+ // Set the default value for dropbox_cachelimit
+ $value = get_config('dropbox', 'dropbox_cachelimit');
+ if (empty($value)) {
+ set_config('dropbox_cachelimit', 1024*1024, 'dropbox');
+ }
+ upgrade_plugin_savepoint(true, 2012080702, 'repository', 'dropbox');
+ }
+
+ return true;
+}
View
3  repository/dropbox/lang/en/repository_dropbox.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -31,4 +30,6 @@
$string['dropbox'] = 'Dropbox';
$string['secret'] = 'Dropbox secret';
$string['instruction'] = 'You can get your API Key and secret from <a href="http://www.dropbox.com/developers/apps">Dropbox developers</a>. When setting up your key please select "Full Dropbox" as the "Access level".';
+$string['cachelimit'] = 'Cache limit';
+$string['cachelimit_info'] = 'Enter the maximum size of files (in bytes) to be cached on server for Dropbox aliases/shortcuts. Cached files will be served when the source is no longer available. Empty value or zero mean caching of all files regardless of size.';
$string['dropbox:view'] = 'View a Dropbox folder';
View
351 repository/dropbox/lib.php
@@ -19,12 +19,20 @@
*
* @since 2.0
* @package repository_dropbox
+ * @copyright 2012 Marina Glancy
* @copyright 2010 Dongsheng Cai {@link http://dongsheng.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot . '/repository/lib.php');
require_once(dirname(__FILE__).'/locallib.php');
+/**
+ * Repository to access Dropbox files
+ *
+ * @package repository_dropbox
+ * @copyright 2010 Dongsheng Cai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
class repository_dropbox extends repository {
/** @var dropbox the instance of dropbox client */
private $dropbox;
@@ -32,6 +40,8 @@ class repository_dropbox extends repository {
public $files;
/** @var bool flag of login status */
public $logged=false;
+ /** @var int maximum size of file to cache in moodle filepool */
+ public $cachelimit=null;
/** @var int cached file ttl */
private $cachedfilettl = null;
@@ -166,7 +176,7 @@ public function get_listing($path = '', $page = '1') {
$list = array();
$list['list'] = array();
- $list['manage'] = false;
+ $list['manage'] = 'https://www.dropbox.com/home';
$list['dynload'] = true;
$list['nosearch'] = true;
// process breadcrumb trail
@@ -210,28 +220,68 @@ public function get_listing($path = '', $page = '1') {
return $list;
}
$files = $result->contents;
+ $dirslist = array();
+ $fileslist = array();
foreach ($files as $file) {
if ($file->is_dir) {
- $list['list'][] = array(
+ $dirslist[] = array(
'title' => substr($file->path, strpos($file->path, $current_path)+strlen($current_path)),
'path' => file_correct_filepath($file->path),
- 'size' => $file->size,
- 'date' => $file->modified,
- 'thumbnail' => $OUTPUT->pix_url(file_folder_icon(90))->out(false),
+ 'date' => strtotime($file->modified),
+ 'thumbnail' => $OUTPUT->pix_url(file_folder_icon(64))->out(false),
+ 'thumbnail_height' => 64,
+ 'thumbnail_width' => 64,
'children' => array(),
);
} else {
- $list['list'][] = array(
+ $thumbnail = null;
+ if ($file->thumb_exists) {
+ $thumburl = new moodle_url('/repository/dropbox/thumbnail.php',
+ array('repo_id' => $this->id,
+ 'ctx_id' => $this->context->id,
+ 'source' => $file->path,
+ 'rev' => $file->rev // include revision to avoid cache problems
+ ));
+ $thumbnail = $thumburl->out(false);
+ }
+ $fileslist[] = array(
'title' => substr($file->path, strpos($file->path, $current_path)+strlen($current_path)),
'source' => $file->path,
- 'size' => $file->size,
- 'date' => $file->modified,
- 'thumbnail' => $OUTPUT->pix_url(file_extension_icon($file->path, 90))->out(false)
+ 'size' => $file->bytes,
+ 'date' => strtotime($file->modified),
+ 'thumbnail' => $OUTPUT->pix_url(file_extension_icon($file->path, 64))->out(false),
+ 'realthumbnail' => $thumbnail,
+ 'thumbnail_height' => 64,
+ 'thumbnail_width' => 64,
);
}
}
+ $fileslist = array_filter($fileslist, array($this, 'filter'));
+ $list['list'] = array_merge($dirslist, array_values($fileslist));
return $list;
}
+
+ /**
+ * Displays a thumbnail for current user's dropbox file
+ *
+ * @param string $string
+ */
+ public function send_thumbnail($source) {
+ $saveas = $this->prepare_file('');
+ try {
+ $access_key = get_user_preferences($this->setting.'_access_key', '');
+ $access_secret = get_user_preferences($this->setting.'_access_secret', '');
+ $this->dropbox->set_access_token($access_key, $access_secret);
+ $this->dropbox->get_thumbnail($source, $saveas, self::SYNCIMAGE_TIMEOUT);
+ $content = file_get_contents($saveas);
+ unlink($saveas);
+ // set 30 days lifetime for the image. If the image is changed in dropbox it will have
+ // different revision number and URL will be different. It is completely safe
+ // to cache thumbnail in the browser for a long time
+ send_file($content, basename($source), 30*24*60*60, 0, true);
+ } catch (Exception $e) {}
+ }
+
/**
* Logout from dropbox
* @return array
@@ -256,8 +306,13 @@ public function set_option($options = array()) {
if (!empty($options['dropbox_secret'])) {
set_config('dropbox_secret', trim($options['dropbox_secret']), 'dropbox');
}
+ if (!empty($options['dropbox_cachelimit'])) {
+ $this->cachelimit = (int)trim($options['dropbox_cachelimit']);
+ set_config('dropbox_cachelimit', $this->cachelimit, 'dropbox');
+ }
unset($options['dropbox_key']);
unset($options['dropbox_secret']);
+ unset($options['dropbox_cachelimit']);
$ret = parent::set_option($options);
return $ret;
}
@@ -272,29 +327,118 @@ public function get_option($config = '') {
return trim(get_config('dropbox', 'dropbox_key'));
} elseif ($config==='dropbox_secret') {
return trim(get_config('dropbox', 'dropbox_secret'));
+ } elseif ($config==='dropbox_cachelimit') {
+ return $this->max_cache_bytes();
} else {
+ $options = parent::get_option();
$options['dropbox_key'] = trim(get_config('dropbox', 'dropbox_key'));
$options['dropbox_secret'] = trim(get_config('dropbox', 'dropbox_secret'));
+ $options['dropbox_cachelimit'] = $this->max_cache_bytes();
}
- $options = parent::get_option($config);
return $options;
}
/**
+ * Fixes references in DB that contains user credentials
+ *
+ * @param string $reference contents of DB field files_reference.reference
+ */
+ public function fix_old_style_reference($reference) {
+ $ref = unserialize($reference);
+ if (!isset($ref->url)) {
+ $this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
+ $ref->url = $this->dropbox->get_file_share_link($ref->path, self::GETFILE_TIMEOUT);
+ if (!$ref->url) {
+ // some error occurred, do not fix reference for now
+ return $reference;
+ }
+ }
+ unset($ref->access_key);
+ unset($ref->access_secret);
+ $newreference = serialize($ref);
+ if ($newreference !== $reference) {
+ // we need to update references in the database
+ global $DB;
+ $params = array(
+ 'newreference' => $newreference,
+ 'newhash' => sha1($newreference),
+ 'reference' => $reference,
+ 'hash' => sha1($reference),
+ 'repoid' => $this->id
+ );
+ $refid = $DB->get_field_sql('SELECT id FROM {files_reference}
+ WHERE reference = :reference AND referencehash = :hash
+ AND repositoryid = :repoid', $params);
+ if (!$refid) {
+ return $newreference;
+ }
+ $existingrefid = $DB->get_field_sql('SELECT id FROM {files_reference}
+ WHERE reference = :newreference AND referencehash = :newhash
+ AND repositoryid = :repoid', $params);
+ if ($existingrefid) {
+ // the same reference already exists, we unlink all files from it,
+ // link them to the current reference and remove the old one
+ $DB->execute('UPDATE {files} SET referencefileid = :refid
+ WHERE referencefileid = :existingrefid',
+ array('refid' => $refid, 'existingrefid' => $existingrefid));
+ $DB->delete_records('files_reference', array('id' => $existingrefid));
+ }
+ // update the reference
+ $params['refid'] = $refid;
+ $DB->execute('UPDATE {files_reference}
+ SET reference = :newreference, referencehash = :newhash
+ WHERE id = :refid', $params);
+ }
+ return $newreference;
+ }
+
+ /**
+ * Converts a URL received from dropbox API function 'shares' into URL that
+ * can be used to download/access file directly
*
- * @param string $photo_id
- * @param string $file
+ * @param string $sharedurl
* @return string
*/
- public function get_file($filepath, $saveas = '') {
- $this->dropbox->set_access_token($this->access_key, $this->access_secret);
+ private function get_file_download_link($sharedurl) {
+ return preg_replace('|^(\w*://)www(.dropbox.com)|','\1dl\2',$sharedurl);
+ }
+
+ /**
+ * Downloads a file from external repository and saves it in temp dir
+ *
+ * @throws moodle_exception when file could not be downloaded
+ *
+ * @param string $reference the content of files.reference field or result of
+ * function {@link repository_dropbox::get_file_reference()}
+ * @param string $saveas filename (without path) to save the downloaded file in the
+ * temporary directory, if omitted or file already exists the new filename will be generated
+ * @return array with elements:
+ * path: internal location of the file
+ * url: URL to the source (from parameters)
+ */
+ public function get_file($reference, $saveas = '') {
+ $ref = unserialize($reference);
$saveas = $this->prepare_file($saveas);
- return $this->dropbox->get_file($filepath, $saveas);
+ if (isset($ref->access_key) && isset($ref->access_secret) && isset($ref->path)) {
+ $this->dropbox->set_access_token($ref->access_key, $ref->access_secret);
+ return $this->dropbox->get_file($ref->path, $saveas, self::GETFILE_TIMEOUT);
+ } else if (isset($ref->url)) {
+ $c = new curl;
+ $url = $this->get_file_download_link($ref->url);
+ $result = $c->download_one($url, null, array('filepath' => $saveas, 'timeout' => self::GETFILE_TIMEOUT, 'followlocation' => true));
+ $info = $c->get_info();
+ if ($result !== true || !isset($info['http_code']) || $info['http_code'] != 200) {
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
+ return array('path'=>$saveas, 'url'=>$url);
+ }
+ throw new moodle_exception('cannotdownload', 'repository');
}
/**
* Add Plugin settings input to Moodle form
*
- * @param object $mform
+ * @param moodleform $mform Moodle form (passed by reference)
+ * @param string $classname repository class name
*/
public static function type_config_form($mform, $classname = 'repository') {
global $CFG;
@@ -318,6 +462,11 @@ public static function type_config_form($mform, $classname = 'repository') {
$mform->addRule('dropbox_secret', $strrequired, 'required', null, 'client');
$str_getkey = get_string('instruction', 'repository_dropbox');
$mform->addElement('static', null, '', $str_getkey);
+
+ $mform->addElement('text', 'dropbox_cachelimit', get_string('cachelimit', 'repository_dropbox'), array('size' => '40'));
+ $mform->addRule('dropbox_cachelimit', null, 'numeric', null, 'client');
+ $mform->setType('dropbox_cachelimit', PARAM_INT);
+ $mform->addElement('static', 'dropbox_cachelimit_info', '', get_string('cachelimit_info', 'repository_dropbox'));
}
/**
@@ -326,7 +475,7 @@ public static function type_config_form($mform, $classname = 'repository') {
* @return array
*/
public static function get_type_option_names() {
- return array('dropbox_key', 'dropbox_secret', 'pluginname');
+ return array('dropbox_key', 'dropbox_secret', 'pluginname', 'dropbox_cachelimit');
}
/**
@@ -344,7 +493,18 @@ public function supported_filetypes() {
* @return int
*/
public function supported_returntypes() {
- return FILE_INTERNAL | FILE_REFERENCE;
+ return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
+ }
+
+ /**
+ * Return file URL for external link
+ *
+ * @param string $reference the result of get_file_reference()
+ * @return string
+ */
+ public function get_link($reference) {
+ $ref = unserialize($reference);
+ return $this->get_file_download_link($ref->url);
}
/**
@@ -354,10 +514,27 @@ public function supported_returntypes() {
* @return string file referece
*/
public function get_file_reference($source) {
+ global $USER;
$reference = new stdClass;
$reference->path = $source;
+ $reference->userid = $USER->id;
+ $reference->username = fullname($USER);
$reference->access_key = get_user_preferences($this->setting.'_access_key', '');
$reference->access_secret = get_user_preferences($this->setting.'_access_secret', '');
+
+ // by API we don't know if we need this reference to just download a file from dropbox
+ // into moodle filepool or create a reference. Since we need to create a shared link
+ // only in case of reference we analyze the script parameter
+ $usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
+ if ($usefilereference) {
+ $this->dropbox->set_access_token($reference->access_key, $reference->access_secret);
+ $url = $this->dropbox->get_file_share_link($source, self::GETFILE_TIMEOUT);
+ if ($url) {
+ unset($reference->access_key);
+ unset($reference->access_secret);
+ $reference->url = $url;
+ }
+ }
return serialize($reference);
}
@@ -372,35 +549,51 @@ public function get_file_reference($source) {
* @return null|stdClass that has 'filepath' property
*/
public function get_file_by_reference($reference) {
- $reference = unserialize($reference->reference);
- $cachedfilepath = cache_file::get($reference, array('ttl' => $this->cachedfilettl));
- if ($cachedfilepath === false) {
- // Cache the file.
- $this->set_access_key($reference->access_key);
- $this->set_access_secret($reference->access_secret);
- $path = $this->get_file($reference->path);
- $cachedfilepath = cache_file::create_from_file($reference, $path['path']);
- }
- if ($cachedfilepath && is_readable($cachedfilepath)) {
- return (object)array('filepath' => $cachedfilepath);
- } else {
+ global $USER;
+ $ref = unserialize($reference->reference);
+ if (!isset($ref->url)) {
+ // this is an old-style reference in DB. We need to fix it
+ $ref = unserialize($this->fix_old_style_reference($reference->reference));
+ }
+ if (!isset($ref->url)) {
return null;
}
+ $c = new curl;
+ $url = $this->get_file_download_link($ref->url);
+ if (file_extension_in_typegroup($ref->path, 'web_image')) {
+ $saveas = $this->prepare_file('');
+ try {
+ $result = $c->download_one($url, array(), array('filepath' => $saveas, 'timeout' => self::SYNCIMAGE_TIMEOUT, 'followlocation' => true));
+ $info = $c->get_info();
+ if ($result === true && isset($info['http_code']) && $info['http_code'] == 200) {
+ return (object)array('filepath' => $saveas);
+ }
+ } catch (Exception $e) {}
+ }
+ $c->get($url, null, array('timeout' => self::SYNCIMAGE_TIMEOUT, 'followlocation' => true, 'nobody' => true));
+ $info = $c->get_info();
+ if (isset($info['http_code']) && $info['http_code'] == 200 &&
+ array_key_exists('download_content_length', $info) &&
+ $info['download_content_length'] >= 0) {
+ return (object)array('filesize' => (int)$info['download_content_length']);
+ }
+ return null;
}
/**
- * Get file from external repository by reference
- * {@link repository::get_file_reference()}
- * {@link repository::get_file()}
+ * Cache file from external repository by reference
+ *
+ * Dropbox repository regularly caches all external files that are smaller than
+ * {@link repository_dropbox::max_cache_bytes()}
*
* @param string $reference this reference is generated by
* repository::get_file_reference()
* @param stored_file $storedfile created file reference
*/
public function cache_file_by_reference($reference, $storedfile) {
- $reference = unserialize($reference);
- $path = $this->get_file($reference->path);
- cache_file::create_from_file($reference, $path['path']);
+ try {
+ $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
+ } catch (Exception $e) {}
}
/**
@@ -412,15 +605,23 @@ public function cache_file_by_reference($reference, $storedfile) {
* @return string
*/
public function get_reference_details($reference, $filestatus = 0) {
+ global $USER;
$ref = unserialize($reference);
- $details = $this->get_name();
+ $detailsprefix = $this->get_name();
+ if (isset($ref->userid) && $ref->userid != $USER->id && isset($ref->username)) {
+ $detailsprefix .= ' ('.$ref->username.')';
+ }
+ $details = $detailsprefix;
if (isset($ref->path)) {
- $details .= ': '. $ref->path;
+ $details .= ': '. $ref->path;
}
if (isset($ref->path) && !$filestatus) {
// Indicate this is from dropbox with path
return $details;
} else {
+ if (isset($ref->url)) {
+ $details = $detailsprefix. ': '. $ref->url;
+ }
return get_string('lostsource', 'repository', $details);
}
}
@@ -428,11 +629,30 @@ public function get_reference_details($reference, $filestatus = 0) {
/**
* Return the source information
*
- * @param stdClass $filepath
- * @return string|null
+ * @param string $source
+ * @return string
+ */
+ public function get_file_source_info($source) {
+ global $USER;
+ return 'Dropbox ('.fullname($USER).'): ' . $source;
+ }
+
+ /**
+ * Returns the maximum size of the Dropbox files to cache in moodle
+ *
+ * Note that {@link repository_dropbox::get_file_by_reference()} called by
+ * {@link repository::sync_external_file()} will try to cache images even
+ * when they are bigger in order to generate thumbnails. However there is
+ * a small timeout for downloading images for synchronisation and it will
+ * probably fail if the image is too big.
+ *
+ * @return int
*/
- public function get_file_source_info($filepath) {
- return 'Dropbox: ' . $filepath;
+ public function max_cache_bytes() {
+ if ($this->cachelimit === null) {
+ $this->cachelimit = (int)get_config('dropbox', 'dropbox_cachelimit');
+ }
+ return $this->cachelimit;
}
/**
@@ -449,33 +669,42 @@ public function get_file_source_info($filepath) {
* @param array $options additional options affecting the file serving
*/
public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
- $fileinfo = $this->get_file_by_reference((object)array('reference' => $storedfile->get_reference()));
- if ($fileinfo && !empty($fileinfo->filepath) && is_readable($fileinfo->filepath)) {
- $filename = $storedfile->get_filename();
- if ($options && isset($options['filename'])) {
- $filename = $options['filename'];
+ $ref = unserialize($storedfile->get_reference());
+ if ($storedfile->get_filesize() > $this->max_cache_bytes()) {
+ header('Location: '.$this->get_file_download_link($ref->url));
+ die;
+ }
+ try {
+ $this->import_external_file_contents($storedfile, $this->max_cache_bytes());
+ if (!is_array($options)) {
+ $options = array();
}
- $dontdie = ($options && isset($options['dontdie']));
- send_file($fileinfo->filepath, $filename, $lifetime , $filter, false, $forcedownload, '', $dontdie);
- } else {
- send_file_not_found();
+ $options['sendcachedexternalfile'] = true;
+ send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
+ } catch (moodle_exception $e) {
+ // redirect to Dropbox, it will show the error.
+ // We redirect to Dropbox shared link, not to download link here!
+ header('Location: '.$ref->url);
+ die;
}
}
+ /**
+ * Caches all references to Dropbox files in moodle filepool
+ *
+ * Invoked by {@link repository_dropbox_cron()}. Only files smaller than
+ * {@link repository_dropbox::max_cache_bytes()} and only files which
+ * synchronisation timeout have not expired are cached.
+ */
public function cron() {
$fs = get_file_storage();
$files = $fs->get_external_files($this->id);
foreach ($files as $file) {
- $reference = unserialize($file->get_reference());
-
- $cachedfile = cache_file::get($reference);
- if ($cachedfile === false) {
- // Re-fetch resource.
- $this->set_access_key($reference->access_key);
- $this->set_access_secret($reference->access_secret);
- $path = $this->get_file($reference->path);
- cache_file::create_from_file($reference, $path['path']);
- }
+ try {
+ // This call will cache all files that are smaller than max_cache_bytes()
+ // and synchronise file size of all others
+ $this->import_external_file_contents($file, $this->max_cache_bytes());
+ } catch (moodle_exception $e) {}
}
}
}
View
125 repository/dropbox/locallib.php
@@ -1,5 +1,4 @@
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -16,34 +15,50 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * dropbox class
* A helper class to access dropbox resources
*
* @since 2.0
- * @package repository
- * @subpackage dropbox
+ * @package repository_dropbox
+ * @copyright 2012 Marina Glancy
* @copyright 2010 Dongsheng Cai
* @author Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
require_once($CFG->libdir.'/oauthlib.php');
+/**
+ * Authentication class to access Dropbox API
+ *
+ * @package repository_dropbox
+ * @copyright 2010 Dongsheng Cai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
class dropbox extends oauth_helper {
- /** dropbox access type, can be dropbox or sandbox */
+ /** @var string dropbox access type, can be dropbox or sandbox */
private $mode = 'dropbox';
- /** dropbox api url*/
+ /** @var string dropbox api url*/
private $dropbox_api = 'https://api.dropbox.com/1';
- /** dropbox content api url*/
+ /** @var string dropbox content api url*/
private $dropbox_content_api = 'https://api-content.dropbox.com/1';
+ /**
+ * Constructor for dropbox class
+ *
+ * @param array $args
+ */
function __construct($args) {
parent::__construct($args);
}
+
/**
* Get file listing from dropbox
+ *
+ * @param string $path
+ * @param string $token
+ * @param string $secret
+ * @return array
*/
public function get_listing($path='/', $token='', $secret='') {
$url = $this->dropbox_api.'/metadata/'.$this->mode.$path;
@@ -53,9 +68,12 @@ public function get_listing($path='/', $token='', $secret='') {
}
/**
- * Download a file
+ * Prepares the filename to pass to Dropbox API as part of URL
+ *
+ * @param string $filepath
+ * @return string
*/
- public function get_file($filepath, $saveas = '') {
+ protected function prepare_filepath($filepath) {
$info = pathinfo($filepath);
$dirname = $info['dirname'];
$basename = $info['basename'];
@@ -64,33 +82,86 @@ public function get_file($filepath, $saveas = '') {
$filepath = $dirname . '/' . $basename;
$filepath = str_replace("%2F", "/", rawurlencode($filepath));
}
+ return $filepath;
+ }
- $url = $this->dropbox_content_api.'/files/'.$this->mode.$filepath;
- $content = $this->get($url, array());
- file_put_contents($saveas, $content);
- return array('path'=>$saveas, 'url'=>$url);
+ /**
+ * Retrieves the default (64x64) thumbnail for dropbox file
+ *
+ * @throws moodle_exception when file could not be downloaded
+ *
+ * @param string $filepath local path in Dropbox
+ * @param string $saveas path to file to save the result
+ * @param int $timeout request timeout in seconds, 0 means no timeout
+ * @return array with attributes 'path' and 'url'
+ */
+ public function get_thumbnail($filepath, $saveas, $timeout = 0) {
+ $url = $this->dropbox_content_api.'/thumbnails/'.$this->mode.$this->prepare_filepath($filepath);
+ if (!($fp = fopen($saveas, 'w'))) {
+ throw new moodle_exception('cannotwritefile', 'error', '', $saveas);
+ }
+ $this->setup_oauth_http_options(array('timeout' => $timeout, 'file' => $fp, 'BINARYTRANSFER' => true));
+ $result = $this->get($url);
+ fclose($fp);
+ if ($result === true) {
+ return array('path'=>$saveas, 'url'=>$url);
+ } else {
+ unlink($saveas);
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
}
/**
- * Get file url
+ * Downloads a file from Dropbox and saves it locally
+ *
+ * @throws moodle_exception when file could not be downloaded
*
- * @param string $filepath file path
- * @return string file url
+ * @param string $filepath local path in Dropbox
+ * @param string $saveas path to file to save the result
+ * @param int $timeout request timeout in seconds, 0 means no timeout
+ * @return array with attributes 'path' and 'url'
*/
- public function get_file_url($filepath) {
- $info = pathinfo($filepath);
- $dirname = $info['dirname'];
- $basename = $info['basename'];
- $filepath = $dirname . rawurlencode($basename);
- if ($dirname != '/') {
- $filepath = $dirname . '/' . $basename;
- $filepath = str_replace("%2F", "/", rawurlencode($filepath));
+ public function get_file($filepath, $saveas, $timeout = 0) {
+ $url = $this->dropbox_content_api.'/files/'.$this->mode.$this->prepare_filepath($filepath);
+ if (!($fp = fopen($saveas, 'w'))) {
+ throw new moodle_exception('cannotwritefile', 'error', '', $saveas);
}
+ $this->setup_oauth_http_options(array('timeout' => $timeout, 'file' => $fp, 'BINARYTRANSFER' => true));
+ $result = $this->get($url);
+ fclose($fp);
+ if ($result === true) {
+ return array('path'=>$saveas, 'url'=>$url);
+ } else {
+ unlink($saveas);
+ throw new moodle_exception('errorwhiledownload', 'repository', '', $result);
+ }
+ }
- $url = $this->dropbox_content_api.'/files/'.$this->mode.$filepath;
- return $url;
+ /**
+ * Returns direct link to Dropbox file
+ *
+ * @param string $filepath local path in Dropbox
+ * @param int $timeout request timeout in seconds, 0 means no timeout
+ * @return string|null information object or null if request failed with an error
+ */
+ public function get_file_share_link($filepath, $timeout = 0) {
+ $url = $this->dropbox_api.'/shares/'.$this->mode.$this->prepare_filepath($filepath);
+ $this->setup_oauth_http_options(array('timeout' => $timeout));
+ $result = $this->post($url, array('short_url'=>0));
+ if (!$this->http->get_errno()) {
+ $data = json_decode($result);
+ if (isset($data->url)) {
+ return $data->url;
+ }
+ }
+ return null;
}
+ /**
+ * Sets Dropbox API mode (dropbox or sandbox, default dropbox)
+ *
+ * @param string $mode
+ */
public function set_mode($mode) {
$this->mode = $mode;
}
View
44 repository/dropbox/thumbnail.php
@@ -0,0 +1,44 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This script displays one thumbnail of the image in current user's dropbox.
+ * If {@link repository_dropbox::send_thumbnail()} can not display image
+ * the default 64x64 filetype icon is returned
+ *
+ * @package repository_dropbox
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once(dirname(__FILE__).'/lib.php');
+
+$repo_id = optional_param('repo_id', 0, PARAM_INT); // Repository ID
+$contextid = optional_param('ctx_id', SYSCONTEXTID, PARAM_INT); // Context ID
+$source = optional_param('source', '', PARAM_TEXT); // File path in current user's dropbox
+
+if (isloggedin() && $repo_id && $source
+ && ($repo = repository::get_repository_by_id($repo_id, $contextid))
+ && method_exists($repo, 'send_thumbnail')) {
+ // try requesting thumbnail and outputting it. This function exits if thumbnail was retrieved
+ $repo->send_thumbnail($source);
+}
+
+// send default icon for the file type
+$fileicon = file_extension_icon($source, 64);
+send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');
View
5 repository/dropbox/version.php
@@ -17,8 +17,7 @@
/**
* Version details
*
- * @package repository
- * @subpackage dropbox
+ * @package repository_dropbox
* @copyright 2010 Dongsheng Cai
* @author Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -26,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2012061700; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2012080702; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2012061700; // Requires this Moodle version
$plugin->component = 'repository_dropbox'; // Full name of the plugin (used for diagnostics)
View
122 repository/equella/lib.php
@@ -123,59 +123,125 @@ public function get_file_reference($source) {
}
/**
+ * Counts the number of failed connections.
+ *
+ * If we received the connection timeout more than 3 times in a row, we don't attemt to
+ * connect to the server any more during this request.
+ *
+ * This function is used by {@link repository_equella::get_file_by_reference()} that
+ * synchronises the file size of referenced files.
+ *
+ * @param int $errno omit if we just want to know the return value, the last curl_errno otherwise
+ * @return bool true if we had less than 3 failed connections, false if no more connections
+ * attempts recommended
+ */
+ private function connection_result($errno = null) {
+ static $countfailures = array();
+ $sess = sesskey();
+ if (!array_key_exists($sess, $countfailures)) {
+ $countfailures[$sess] = 0;
+ }
+ if ($errno !== null) {
+ if ($errno == 0) {
+ // reset count of failed connections
+ $countfailures[$sess] = 0;
+ } else if ($errno == 7 /*CURLE_COULDNT_CONNECT*/ || $errno == 9 /*CURLE_REMOTE_ACCESS_DENIED*/) {
+ // problems with server
+ $countfailures[$sess]++;
+ }
+ }
+ return ($countfailures[$sess] < 3);
+ }
+
+ /**
+ * Decide whether or not the file should be synced
+ *
+ * @param stored_file $storedfile
+ * @return bool
+ */
+ public function sync_individual_file(stored_file $storedfile) {
+ // if we had several unsuccessfull attempts to connect to server - do not try any more
+ return $this->connection_result();
+ }
+
+
+ /**
* Download a file, this function can be overridden by subclass. {@link curl}
*
- * @param string $url the url of file
- * @param string $filename save location
- * @return string the location of the file
+ * @param string $reference the source of the file
+ * @param string $filename filename (without path) to save the downloaded file in the
+ * temporary directory
+ * @return null|array null if download failed or array with elements:
+ * path: internal location of the file
+ * url: URL to the source (from parameters)
*/
- public function get_file($url, $filename = '') {
+ public function get_file($reference, $filename = '') {
global $USER;
- $cookiename = uniqid('', true) . '.cookie';
- $dir = make_temp_directory('repository/equella/' . $USER->id);
- $cookiepathname = $dir . '/' . $cookiename;
+ $ref = @unserialize(base64_decode($reference));
+ if (!isset($ref->url) || !($url = $this->appendtoken($ref->url))) {
+ // Occurs when the user isn't known..
+ return null;
+ }
$path = $this->prepare_file($filename);
- $fp = fopen($path, 'w');
+ $cookiepathname = $this->prepare_file($USER->id. '_'. uniqid('', true). '.cookie');
$c = new curl(array('cookie'=>$cookiepathname));
- $c->download(array(array('url'=>$url, 'file'=>$fp)), array('CURLOPT_FOLLOWLOCATION'=>true));
- // Close file handler.
- fclose($fp);
+ $result = $c->download_one($url, null, array('filepath' => $path, 'followlocation' => true, 'timeout'