MDL-33550 Correctly process situation when file reference source is missing
authorMarina Glancy <marina@moodle.com>
Wed, 13 Jun 2012 04:11:06 +0000 (12:11 +0800)
committerMarina Glancy <marina@moodle.com>
Wed, 13 Jun 2012 04:11:06 +0000 (12:11 +0800)
- do not die with fatal error if source file in moodle internal repository is missing;
- moved code duplication for moodle repositories into class repository (functions send_file, get_reference_details, get_file_by_reference, get_file_reference);
- update file status after repository::sync_external_file so we know that it is missing (or not missing anymore). Do not run this function more than once for file within one request;
- display readable name for Private Files and Server files with the new format;
- display broken icon in filemanager if we know that source is missing, display information (for admin) where it was located before: see repository::get_reference_details() and extending classes;
- removed unnecessary queries in stored_file::sync_external_file();
- syncronize files before displaying it's size in mod_resource, do not query  directly

16 files changed:
lang/en/repository.php
lib/filebrowser/file_info.php
lib/filebrowser/file_info_stored.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/stored_file.php
lib/form/filemanager.js
mod/resource/locallib.php
repository/boxnet/lib.php
repository/coursefiles/lib.php
repository/dropbox/lib.php
repository/equella/lib.php
repository/filesystem/lib.php
repository/lib.php
repository/user/lib.php
theme/base/style/filemanager.css

index c0ca916..d8d90f0 100644 (file)
@@ -138,6 +138,7 @@ $string['listview'] = 'View as list';
 $string['loading'] = 'Loading...';
 $string['login'] = 'Login';
 $string['logout'] = 'Logout';
+$string['lostsource'] = 'Error. Source is missing. {$a}';
 $string['makefileinternal'] = 'Make a copy of the file';
 $string['makefilelink'] = 'Link to the file directly';
 $string['makefilereference'] = 'Create an alias/shortcut to the file';
@@ -168,6 +169,7 @@ $string['pluginerror'] = 'Errors in repository plugin.';
 $string['popup'] = 'Click "Login" button to login';
 $string['popupblockeddownload'] = 'The downloading window is blocked, please allow the popup window, and try again.';
 $string['preview'] = 'Preview';
+$string['privatefilesof'] = '{$a} Private files';
 $string['readonlyinstance'] = 'You cannot edit/delete a read-only instance';
 $string['referencesexist'] = 'There are {$a} alias/shortcut files that use this file as their source';
 $string['referenceslist'] = 'Aliases/Shortcuts';
@@ -202,6 +204,7 @@ $string['unknownoriginal'] = 'Unknown';
 $string['upload'] = 'Upload this file';
 $string['uploading'] = 'Uploading...';
 $string['uploadsucc'] = 'The file has been uploaded successfully';
+$string['undisclosedsource'] = '(Undisclosed)';
 $string['undisclosedreference'] = '(Undisclosed)';
 $string['uselatestfile'] = 'Use latest file';
 $string['usercontextrepositorydisabled'] = 'You cannot edit this repository in user context';
index 2771e04..6f9d302 100644 (file)
@@ -227,17 +227,13 @@ abstract class file_info {
     }
 
     /**
-     * Returns the localised human-readable name of the file together with
-     * virtual path
+     * Returns the localised human-readable name of the file together with virtual path
      *
+     * @see file_info_stored::get_readable_fullname()
      * @return string
      */
     public function get_readable_fullname() {
-        $fpath = array();
-        for ($parent = $this; $parent; $parent = $parent->get_parent()) {
-            array_unshift($fpath, $parent->get_visible_name());
-        }
-        return join('/', $fpath);
+        return null;
     }
 
     /**
index 6eb760f..d16f13c 100644 (file)
@@ -113,6 +113,46 @@ class file_info_stored extends file_info {
         }
     }
 
+    /**
+     * Returns the localised human-readable name of the file together with virtual path
+     *
+     * @return string
+     */
+    public function get_readable_fullname() {
+        global $CFG;
+        // retrieve the readable path with all parents (excluding the top most 'System')
+        $fpath = array();
+        for ($parent = $this; $parent && $parent->get_parent(); $parent = $parent->get_parent()) {
+            array_unshift($fpath, $parent->get_visible_name());
+        }
+
+        if ($this->lf->get_component() == 'user' && $this->lf->get_filearea() == 'private') {
+            // use the special syntax for user private files - 'USERNAME Private files: PATH'
+            $username = array_shift($fpath);
+            array_shift($fpath); // get rid of "Private Files/" in the beginning of the path
+            return get_string('privatefilesof', 'repository', $username). ': '. join('/', $fpath);
+        } else {
+            // for all other files (except user private files) return 'Server files: PATH'
+
+            // first, get and cache the name of the repository_local (will be used as prefix for file names):
+            static $replocalname = null;
+            if ($replocalname === null) {
+                require_once($CFG->dirroot . "/repository/lib.php");
+                $instances = repository::get_instances(array('type' => 'local'));
+                if (count($instances)) {
+                    $firstinstance = reset($instances);
+                    $replocalname = $firstinstance->get_name();
+                } else if (get_string_manager()->string_exists('pluginname', 'repository_local')) {
+                    $replocalname = get_string('pluginname', 'repository_local');
+                } else {
+                    $replocalname = get_string('arearoot', 'repository');
+                }
+            }
+
+            return $replocalname. ': '. join('/', $fpath);
+        }
+    }
+
     /**
      * Returns file download url
      *
index ef1abdb..9820b3b 100644 (file)
@@ -596,6 +596,9 @@ function file_get_drafarea_files($draftitemid, $filepath = '/') {
             $item->datemodified = $file->get_timemodified();
             $item->datecreated = $file->get_timecreated();
             $item->isref = $file->is_external_file();
+            if ($item->isref && $file->get_status() == 666) {
+                $item->originalmissing = true;
+            }
             // find the file this draft file was created from and count all references in local
             // system pointing to that file
             $source = unserialize($file->get_source());
@@ -2310,7 +2313,7 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl
     }
 
     // handle external resource
-    if ($stored_file->is_external_file()) {
+    if ($stored_file && $stored_file->is_external_file()) {
         $stored_file->send_file($lifetime, $filter, $forcedownload, $options);
         die;
     }
index 203337a..fda3e5b 100644 (file)
@@ -1652,10 +1652,22 @@ class file_storage {
      * Unpack reference field
      *
      * @param string $str
+     * @param bool $cleanparams if set to true, array elements will be passed through {@link clean_param()}
      * @return array
      */
-    public static function unpack_reference($str) {
-        return unserialize(base64_decode($str));
+    public static function unpack_reference($str, $cleanparams = false) {
+        $params = unserialize(base64_decode($str));
+        if (is_array($params) && $cleanparams) {
+            $params = array(
+                'component' => is_null($params['component']) ? ''   : clean_param($params['component'], PARAM_COMPONENT),
+                'filearea'  => is_null($params['filearea'])  ? ''   : clean_param($params['filearea'], PARAM_AREA),
+                'itemid'    => is_null($params['itemid'])    ? 0    : clean_param($params['itemid'], PARAM_INT),
+                'filename'  => is_null($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE),
+                'filepath'  => is_null($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH),
+                'contextid' => is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT)
+            );
+        }
+        return $params;
     }
 
     /**
index 4b7bf09..ea718ae 100644 (file)
@@ -522,29 +522,16 @@ class stored_file {
     }
 
     /**
-     * Sync external files
+     * Synchronize file if it is a reference and needs synchronizing
      *
-     * @return bool true if file content changed, false if not
+     * Updates contenthash and filesize
      */
     public function sync_external_file() {
-        global $CFG, $DB;
-        if (empty($this->file_record->referencefileid)) {
-            return false;
-        }
-        if (empty($this->file_record->referencelastsync) or ($this->file_record->referencelastsync + $this->file_record->referencelifetime < time())) {
+        global $CFG;
+        if (!empty($this->file_record->referencefileid)) {
             require_once($CFG->dirroot.'/repository/lib.php');
-            if (repository::sync_external_file($this)) {
-                $prevcontent = $this->file_record->contenthash;
-                $sql = "SELECT f.*, r.repositoryid, r.reference
-                          FROM {files} f
-                     LEFT JOIN {files_reference} r
-                               ON f.referencefileid = r.id
-                         WHERE f.id = ?";
-                $this->file_record = $DB->get_record_sql($sql, array($this->file_record->id), MUST_EXIST);
-                return ($prevcontent !== $this->file_record->contenthash);
-            }
+            repository::sync_external_file($this);
         }
-        return false;
     }
 
     /**
@@ -858,7 +845,45 @@ class stored_file {
      * @return string
      */
     public function get_reference_details() {
-        return $this->repository->get_reference_details($this->get_reference());
+        return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
+    }
+
+    /**
+     * Called after reference-file has been synchronized with the repository
+     *
+     * 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
+     */
+    public function set_synchronized($contenthash, $filesize, $status = 0) {
+        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 ($this->get_status() != $status) {
+            $filerecord->status = $status;
+        }
+        $filerecord->referencelastsync = $now; // TODO MDL-33416 remove this
+        if (!empty($filerecord)) {
+            $this->update($filerecord);
+        }
+
+        $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);
     }
 
     /**
index 3b2bd3e..fe328da 100644 (file)
@@ -528,6 +528,9 @@ M.form_filemanager.init = function(Y, options) {
                     if (node.refcount) {
                         classname = classname + ' fp-hasreferences';
                     }
+                    if (node.originalmissing) {
+                        classname = classname + ' fp-originalmissing';
+                    }
                     if (node.sortorder == 1) { classname = classname + ' fp-mainfile';}
                     return Y.Lang.trim(classname);
                 }
index e2d9758..a117614 100644 (file)
@@ -295,23 +295,27 @@ function resource_get_optional_details($resource, $cm) {
         $context = context_module::instance($cm->id);
         $size = '';
         $type = '';
-        if (!empty($options['showsize'])) {
-            $size = display_size($DB->get_field_sql(
-                    'SELECT SUM(filesize) FROM {files} WHERE contextid=?', array($context->id)));
+        $fs = get_file_storage();
+        $files = $fs->get_area_files($context->id, 'mod_resource', 'content', 0, 'sortorder DESC, id ASC', false);
+        if (!empty($options['showsize']) && count($files)) {
+            $sizebytes = 0;
+            foreach ($files as $file) {
+                // this will also synchronize the file size for external files if needed
+                $sizebytes += $file->get_filesize();
+            }
+            if ($sizebytes) {
+                $size = display_size($sizebytes);
+            }
         }
-        if (!empty($options['showtype'])) {
+        if (!empty($options['showtype']) && count($files)) {
             // For a typical file resource, the sortorder is 1 for the main file
             // and 0 for all other files. This sort approach is used just in case
             // there are situations where the file has a different sort order
-            $record = $DB->get_record_sql(
-                    'SELECT filename, mimetype FROM {files} WHERE contextid=? ORDER BY sortorder DESC',
-                    array($context->id), IGNORE_MULTIPLE);
+            $mainfile = reset($files);
+            $type = get_mimetype_description($mainfile);
             // Only show type if it is not unknown
-            if ($record) {
-                $type = get_mimetype_description($record);
-                if ($type === get_mimetype_description('document/unknown')) {
-                    $type = '';
-                }
+            if ($type === get_mimetype_description('document/unknown')) {
+                $type = '';
             }
         }
 
index d4102d3..b64d36a 100644 (file)
@@ -267,18 +267,24 @@ class repository_boxnet extends repository {
     }
 
     /**
-     * Get file from external repository by reference
+     * Returns information about file in this repository by reference
      * {@link repository::get_file_reference()}
      * {@link repository::get_file()}
      *
+     * Returns null if file not found or is not readable
+     *
      * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
+     * @return null|stdClass with attribute 'filepath'
      */
     public function get_file_by_reference($reference) {
-        $fileinfo = new stdClass;
         $boxnetfile = $this->get_file($reference->reference);
-        $fileinfo->filepath = $boxnetfile['path'];
-        return $fileinfo;
+        // 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']);
+        }
+        return null;
     }
 
     /**
@@ -286,17 +292,26 @@ class repository_boxnet extends repository {
      * {@link stored_file::get_reference()}
      *
      * @param string $reference
-     * @return string|null
+     * @param int $filestatus status of the file, 0 - ok, 666 - source missing
+     * @return string
      */
-    public function get_reference_details($reference) {
+    public function get_reference_details($reference, $filestatus = 0) {
         // Indicate it's from box.net repository + secure URL
-        return $this->get_name() . ': ' . $reference;
+        $details = $this->get_name() . ': ' . $reference;
+        if (!$filestatus) {
+            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);
+        }
     }
 
     /**
-     * Repository method to serve file
+     * Repository method to serve the referenced file
      *
-     * @param stored_file $storedfile
+     * @param stored_file $storedfile the file that contains the reference
      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
@@ -304,7 +319,8 @@ class repository_boxnet extends repository {
      */
     public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
         $ref = $storedfile->get_reference();
-        // Let box.net serve the file.
+        // Let box.net serve the file. It will return 'no such file' content if file not found
+        // also if file has the different name than alias, it will be returned with the box.net filename
         header('Location: ' . $ref);
     }
 }
index 16353b8..ea62bf7 100644 (file)
@@ -202,83 +202,6 @@ class repository_coursefiles extends repository {
         return true;
     }
 
-    /**
-     * Unpack file info and pack it, mainly for data validation
-     *
-     * @param string $source
-     * @return string file referece
-     */
-    public function get_file_reference($source) {
-        $params = unserialize(base64_decode($source));
-
-        if (!is_array($params)) {
-            throw new repository_exception('invalidparams', 'repository');
-        }
-
-        $filename  = is_null($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE);
-        $filepath  = is_null($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH);;
-        $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT);
-
-        $reference = array();
-        // hard coded filearea, component and itemid for security
-        $reference['component'] = 'course';
-        $reference['filearea']  = 'legacy';
-        $reference['itemid']    = 0;
-        $reference['contextid'] = $contextid;
-        $reference['filepath']  = $filepath;
-        $reference['filename']  = $filename;
-
-        return file_storage::pack_reference($reference);
-    }
-
-    /**
-     * Get file from external repository by reference
-     * {@link repository::get_file_reference()}
-     * {@link repository::get_file()}
-     *
-     * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
-     */
-    public function get_file_by_reference($reference) {
-        $fs = get_file_storage();
-        $ref = $reference->reference;
-        $params = file_storage::unpack_reference($ref);
-        if (!is_array($params)) {
-            throw new repository_exception('invalidparams', 'repository');
-        }
-        $filename  = is_null($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE);
-        $filepath  = is_null($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH);;
-        $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT);
-
-        // hard coded filearea, component and itemid for security
-        $storedfile = $fs->get_file($contextid, 'course', 'legacy', 0, $filepath, $filename);
-
-        $fileinfo = new stdClass;
-        $fileinfo->contenthash = $storedfile->get_contenthash();
-        $fileinfo->filesize    = $storedfile->get_filesize();
-        return $fileinfo;
-    }
-
-    /**
-     * Return human readable reference information
-     * {@link stored_file::get_reference()}
-     *
-     * @param string $reference
-     * @return string|null
-     */
-    public function get_reference_details($reference) {
-        $params = file_storage::unpack_reference($reference);
-        list($context, $course, $cm) = get_context_info_array($params['contextid']);
-        $coursename = '';
-        if (!empty($course)) {
-            $coursename = '"' . format_string($course->shortname, true, array('context' => get_course_context($context))) . '" ' . get_string('courselegacyfiles');
-        } else {
-            $coursename = get_string('courselegacyfiles');
-        }
-        // Indicate this is from user private area
-        return $coursename . ': ' . $params['filepath'] . $params['filename'];
-    }
-
     /**
      * Return reference file life time
      *
@@ -289,29 +212,4 @@ class repository_coursefiles extends repository {
         // this should be realtime
         return 0;
     }
-
-    /**
-     * Repository method to serve file
-     *
-     * @param stored_file $storedfile
-     * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
-     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
-     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
-     * @param array $options additional options affecting the file serving
-     */
-    public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
-        $fs = get_file_storage();
-
-        $reference = $storedfile->get_reference();
-        $params = file_storage::unpack_reference($reference);
-
-        $filename  = is_null($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE);
-        $filepath  = is_null($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH);;
-        $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT);
-
-        // hard coded file area and component for security
-        $srcfile = $fs->get_file($contextid, 'course', 'legacy', 0, $filepath, $filename);
-
-        send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options);
-    }
 }
index e454615..dccf84f 100644 (file)
@@ -362,12 +362,14 @@ class repository_dropbox extends repository {
     }
 
     /**
-     * Get file from external repository by reference
+     * Returns information about file in this repository by reference
      * {@link repository::get_file_reference()}
      * {@link repository::get_file()}
      *
+     * Returns null if file not found or is not readable
+     *
      * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
+     * @return null|stdClass that has 'filepath' property
      */
     public function get_file_by_reference($reference) {
         $reference  = unserialize($reference->reference);
@@ -379,11 +381,11 @@ class repository_dropbox extends repository {
             $path = $this->get_file($reference->path);
             $cachedfilepath = cache_file::create_from_file($reference, $path['path']);
         }
-
-        $fileinfo = new stdClass;
-        $fileinfo->filepath = $cachedfilepath;
-
-        return $fileinfo;
+        if ($cachedfilepath && is_readable($cachedfilepath)) {
+            return (object)array('filepath' => $cachedfilepath);
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -406,36 +408,48 @@ class repository_dropbox extends repository {
      * {@link stored_file::get_reference()}
      *
      * @param string $reference
-     * @return string|null
+     * @param int $filestatus status of the file, 0 - ok, 666 - source missing
+     * @return string
      */
-    public function get_reference_details($reference) {
+    public function get_reference_details($reference, $filestatus = 0) {
         $ref  = unserialize($reference);
-        // Indicate this is from dropbox with path
-        return $this->get_name() . ': ' . $ref->path;
+        $details = $this->get_name();
+        if (isset($ref->path)) {
+            $details .=  ': '. $ref->path;
+        }
+        if (isset($ref->path) && !$filestatus) {
+            // Indicate this is from dropbox with path
+            return $details;
+        } else {
+            return get_string('lostsource', 'repository', $details);
+        }
     }
 
     /**
-     * Repository method to serve file
+     * Repository method to serve the referenced file
+     *
+     * This method is ivoked from {@link send_stored_file()}.
+     * Dropbox repository first caches the file by reading it into temporary folder and then
+     * serves from there.
      *
-     * @param stored_file $storedfile
+     * @param stored_file $storedfile the file that contains the reference
      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
      * @param array $options additional options affecting the file serving
      */
     public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
-        $reference = unserialize($storedfile->get_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']);
+        $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'];
+            }
+            $dontdie = ($options && isset($options['dontdie']));
+            send_file($fileinfo->filepath, $filename, $lifetime , $filter, false, $forcedownload, '', $dontdie);
+        } else {
+            send_file_not_found();
         }
-
-        send_file($cachedfilepath, $storedfile->get_filename(), 'default' , $filter, false, $forcedownload);
     }
 
     public function cron() {
index 25a544f..2efc5a8 100644 (file)
@@ -146,12 +146,14 @@ class repository_equella extends repository {
     }
 
     /**
-     * Get file from external repository by reference
+     * Returns information about file in this repository by reference
      * {@link repository::get_file_reference()}
      * {@link repository::get_file()}
      *
+     * Returns null if file not found or can not be accessed
+     *
      * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
+     * @return null|stdClass containing attribute 'filepath'
      */
     public function get_file_by_reference($reference) {
         $ref = base64_decode($reference->reference);
@@ -159,7 +161,7 @@ class repository_equella extends repository {
 
         if (!$url) {
             // Occurs when the user isn't known..
-            return false;
+            return null;
         }
 
         // We use this cache to get the correct file size.
@@ -170,24 +172,29 @@ class repository_equella extends repository {
             $cachedfilepath = cache_file::create_from_file($url, $path['path']);
         }
 
-        $fileinfo = new stdClass;
-        $fileinfo->filepath = $cachedfilepath;
-
-        return $fileinfo;
+        if ($cachedfilepath && is_readable($cachedfilepath)) {
+            return (object)array('filepath' => $cachedfilepath);
+        }
+        return null;
     }
 
     /**
-     * Send equella file to browser
+     * Repository method to serve the referenced file
      *
-     * @param stored_file $stored_file
+     * @param stored_file $storedfile the file that contains the reference
+     * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
+     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
+     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
+     * @param array $options additional options affecting the file serving
      */
     public function send_file($stored_file, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
         $reference = base64_decode($stored_file->get_reference());
         $url = $this->appendtoken($reference);
         if ($url) {
             header('Location: ' . $url);
+        } else {
+            send_file_not_found();
         }
-        die;
     }
 
     /**
index 99b9dad..aedfa78 100644 (file)
@@ -223,12 +223,18 @@ class repository_filesystem extends repository {
     }
 
     /**
-     * Get file from external repository by reference
+     * Returns information about file in this repository by reference
      * {@link repository::get_file_reference()}
      * {@link repository::get_file()}
      *
+     * Returns null if file not found or is not readable
+     *
      * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
+     * @return stdClass|null contains one of the following:
+     *   - 'contenthash' and 'filesize'
+     *   - 'filepath'
+     *   - 'handle'
+     *   - 'content'
      */
     public function get_file_by_reference($reference) {
         $ref = $reference->reference;
@@ -237,15 +243,19 @@ class repository_filesystem extends repository {
         } else {
             $filepath = $this->root_path.$ref;
         }
-        $fileinfo = new stdClass;
-        $fileinfo->filepath = $filepath;
-        return $fileinfo;
+        if (file_exists($filepath) && is_readable($filepath)) {
+            return (object)array('filepath' => $filepath);
+        } else {
+            return null;
+        }
     }
 
     /**
-     * Repository method to serve file
+     * Repository method to serve the referenced file
+     *
+     * @see send_stored_file
      *
-     * @param stored_file $storedfile
+     * @param stored_file $storedfile the file that contains the reference
      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
@@ -258,6 +268,15 @@ class repository_filesystem extends repository {
         } else {
             $file = $this->root_path.$reference;
         }
-        send_file($file, $storedfile->get_filename(), 'default' , $filter, false, $forcedownload);
+        if (is_readable($file)) {
+            $filename = $storedfile->get_filename();
+            if ($options && isset($options['filename'])) {
+                $filename = $options['filename'];
+            }
+            $dontdie = ($options && isset($options['dontdie']));
+            send_file($file, $filename, $lifetime , $filter, false, $forcedownload, '', $dontdie);
+        } else {
+            send_file_not_found();
+        }
     }
 }
index 5f72baf..ffb6705 100644 (file)
@@ -1064,16 +1064,39 @@ abstract class repository {
     }
 
     /**
-     * Repository method to serve file
+     * Repository method to serve the referenced file
      *
-     * @param stored_file $storedfile
+     * @see send_stored_file
+     *
+     * @param stored_file $storedfile the file that contains the reference
      * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
      * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
      * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
      * @param array $options additional options affecting the file serving
      */
     public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
-        throw new coding_exception("Repository plugin must implement send_file() method.");
+        if ($this->has_moodle_files()) {
+            $fs = get_file_storage();
+            $params = file_storage::unpack_reference($storedfile->get_reference(), true);
+            $srcfile = null;
+            if (is_array($params)) {
+                $srcfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
+                        $params['itemid'], $params['filepath'], $params['filename']);
+            }
+            if (empty($options)) {
+                $options = array();
+            }
+            if (!isset($options['filename'])) {
+                $options['filename'] = $storedfile->get_filename();
+            }
+            if (!$srcfile) {
+                send_file_not_found();
+            } else {
+                send_stored_file($srcfile, $lifetime, $filter, $forcedownload, $options);
+            }
+        } else {
+            throw new coding_exception("Repository plugin must implement send_file() method.");
+        }
     }
 
     /**
@@ -1102,10 +1125,35 @@ abstract class repository {
      * {@link stored_file::get_reference()}
      *
      * @param string $reference
-     * @return string|null
+     * @param int $filestatus status of the file, 0 - ok, 666 - source missing
+     * @return string
      */
-    public function get_reference_details($reference) {
-        return null;
+    public function get_reference_details($reference, $filestatus = 0) {
+        if ($this->has_moodle_files()) {
+            $fileinfo = null;
+            $params = file_storage::unpack_reference($reference, true);
+            if (is_array($params)) {
+                $context = get_context_instance_by_id($params['contextid']);
+                if ($context) {
+                    $browser = get_file_browser();
+                    $fileinfo = $browser->get_file_info($context, $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']);
+                }
+            }
+            if (empty($fileinfo)) {
+                if ($filestatus == 666) {
+                    if (is_siteadmin() || ($context && has_capability('moodle/course:managefiles', $context))) {
+                        return get_string('lostsource', 'repository',
+                                $params['contextid']. '/'. $params['component']. '/'. $params['filearea']. '/'. $params['itemid']. $params['filepath']. $params['filename']);
+                    } else {
+                        return get_string('lostsource', 'repository', '');
+                    }
+                }
+                return get_string('undisclosedsource', 'repository');
+            } else {
+                return $fileinfo->get_readable_fullname();
+            }
+        }
+        return '';
     }
 
     /**
@@ -1122,14 +1170,33 @@ abstract class repository {
     }
 
     /**
-     * Get file from external repository by reference
+     * Returns information about file in this repository by reference
      * {@link repository::get_file_reference()}
      * {@link repository::get_file()}
      *
+     * Returns null if file not found or is not readable
+     *
      * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
+     * @return stdClass|null contains one of the following:
+     *   - 'contenthash' and 'filesize'
+     *   - 'filepath'
+     *   - 'handle'
+     *   - 'content'
      */
     public function get_file_by_reference($reference) {
+        if ($this->has_moodle_files() && isset($reference->reference)) {
+            $fs = get_file_storage();
+            $params = file_storage::unpack_reference($reference->reference, true);
+            if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'],
+                    $params['component'], $params['filearea'], $params['itemid'], $params['filepath'],
+                    $params['filename']))) {
+                return null;
+            }
+            return (object)array(
+                'contenthash' => $storedfile->get_contenthash(),
+                'filesize'    => $storedfile->get_filesize()
+            );
+        }
         return null;
     }
 
@@ -1407,6 +1474,13 @@ abstract class repository {
      * @return string file referece
      */
     public function get_file_reference($source) {
+        if ($this->has_moodle_files() && ($this->supported_returntypes() & FILE_REFERENCE)) {
+            $params = file_storage::unpack_reference($source);
+            if (!is_array($params)) {
+                throw new repository_exception('invalidparams', 'repository');
+            }
+            return file_storage::pack_reference($params);
+        }
         return $source;
     }
     /**
@@ -1458,16 +1532,22 @@ abstract class repository {
      *
      * @param string $url the url of file
      * @param string $filename save location
-     * @return string the location of the file
+     * @return array with elements:
+     *   path: internal location of the file
+     *   url: URL to the source (from parameters)
      */
     public function get_file($url, $filename = '') {
         global $CFG;
         $path = $this->prepare_file($filename);
         $fp = fopen($path, 'w');
         $c = new curl;
-        $c->download(array(array('url'=>$url, 'file'=>$fp)));
+        $result = $c->download(array(array('url'=>$url, 'file'=>$fp)));
         // Close file handler.
         fclose($fp);
+        if (empty($result)) {
+            unlink($path);
+            return null;
+        }
         return array('path'=>$path, 'url'=>$url);
     }
 
@@ -2121,15 +2201,27 @@ abstract class repository {
      */
     public static function sync_external_file(stored_file $file) {
         global $DB;
+        static $synchronized = array();
 
         $fs = get_file_storage();
 
+        if (!$file->get_referencefileid()) {
+            return false;
+        }
+        if (array_key_exists($file->get_id(), $synchronized)) {
+            return $synchronized[$file->get_id()];
+        }
+
+        // remember that we already cached in current request to prevent from querying again
+        $synchronized[$file->get_id()] = false;
+
         if (!$reference = $DB->get_record('files_reference', array('id'=>$file->get_referencefileid()))) {
             return false;
         }
 
         if (!empty($reference->lastsync) and ($reference->lastsync + $reference->lifetime > time())) {
-            return false;
+            $synchronized[$file->get_id()] = true;
+            return true;
         }
 
         if (!$repository = self::get_repository_by_id($reference->repositoryid, SYSCONTEXTID)) {
@@ -2143,14 +2235,10 @@ abstract class repository {
         $fileinfo = $repository->get_file_by_reference($reference);
         if ($fileinfo === null) {
             // does not exist any more - set status to missing
-            $sql = "UPDATE {files} SET status = :missing WHERE referencefileid = :referencefileid";
-            $params = array('referencefileid'=>$reference->id, 'missing'=>666);
-            $DB->execute($sql, $params);
+            $file->set_missingsource();
             //TODO: purge content from pool if we set some other content hash and it is no used any more
+            $synchronized[$file->get_id()] = true;
             return true;
-        } else if ($fileinfo === false) {
-            // error
-            return false;
         }
 
         $contenthash = null;
@@ -2179,15 +2267,9 @@ abstract class repository {
             return false;
         }
 
-        $now = time();
         // update files table
-        $sql = "UPDATE {files} SET contenthash = :contenthash, filesize = :filesize, referencelastsync = :now, referencelifetime = :lifetime, timemodified = :now2 WHERE referencefileid = :referencefileid AND contenthash <> :contenthash2";
-        $params = array('contenthash'=>$contenthash, 'filesize'=>$filesize, 'now'=>$now, 'lifetime'=>$reference->lifetime,
-            'now2'=>$now, 'referencefileid'=>$reference->id, 'contenthash2'=>$contenthash);
-        $DB->execute($sql, $params);
-
-        $DB->set_field('files_reference', 'lastsync', $now, array('id'=>$reference->id));
-
+        $file->set_synchronized($contenthash, $filesize);
+        $synchronized[$file->get_id()] = true;
         return true;
     }
 }
index 7a46557..241d49d 100644 (file)
@@ -153,79 +153,6 @@ class repository_user extends repository {
         return FILE_INTERNAL | FILE_REFERENCE;
     }
 
-
-    /**
-     * Prepare file reference information
-     *
-     * @param string $source
-     * @return string file referece
-     */
-    public function get_file_reference($source) {
-        global $USER;
-        $params = unserialize(base64_decode($source));
-        if (is_array($params)) {
-            $filepath = clean_param($params['filepath'], PARAM_PATH);;
-            $filename = clean_param($params['filename'], PARAM_FILE);
-            $contextid = clean_param($params['contextid'], PARAM_INT);
-        }
-        // We store all file parameters, so file api could
-        // find the refernces later.
-        $reference = array();
-        $reference['contextid'] = $contextid;
-        $reference['component'] = 'user';
-        $reference['filearea']  = 'private';
-        $reference['itemid']    = 0;
-        $reference['filepath']  = $filepath;
-        $reference['filename']  = $filename;
-
-        return file_storage::pack_reference($reference);
-    }
-
-    /**
-     * Get file from external repository by reference
-     * {@link repository::get_file_reference()}
-     * {@link repository::get_file()}
-     *
-     * @param stdClass $reference file reference db record
-     * @return stdClass|null|false
-     */
-    public function get_file_by_reference($reference) {
-        $fs = get_file_storage();
-        $ref = $reference->reference;
-        $params = unserialize(base64_decode($ref));
-        if (!is_array($params)) {
-            throw new repository_exception('invalidparams', 'repository');
-        }
-        $filename  = is_null($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE);
-        $filepath  = is_null($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH);;
-        $contextid = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT);
-
-        // hard coded component, filearea and item for security
-        $component = 'user';
-        $filearea  = 'private';
-        $itemid    = 0;
-
-        $storedfile = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename);
-
-        $fileinfo = new stdClass;
-        $fileinfo->contenthash = $storedfile->get_contenthash();
-        $fileinfo->filesize    = $storedfile->get_filesize();
-        return $fileinfo;
-    }
-
-    /**
-     * Return human readable reference information
-     * {@link stored_file::get_reference()}
-     *
-     * @param string $reference
-     * @return string|null
-     */
-    public function get_reference_details($reference) {
-        $params = file_storage::unpack_reference($reference);
-        // Indicate this is from user private area
-        return $this->get_name() . ': ' . $params['filepath'] . $params['filename'];
-    }
-
     /**
      * Return reference file life time
      *
@@ -236,29 +163,4 @@ class repository_user extends repository {
         // this should be realtime
         return 0;
     }
-
-    /**
-     * Repository method to serve file
-     *
-     * @param stored_file $storedfile
-     * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
-     * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
-     * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
-     * @param array $options additional options affecting the file serving
-     */
-    public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null) {
-        $reference = $storedfile->get_reference();
-        $params = file_storage::unpack_reference($reference);
-        $filepath = clean_param($params['filepath'], PARAM_PATH);;
-        $filename = clean_param($params['filename'], PARAM_FILE);
-        $contextid = clean_param($params['contextid'], PARAM_INT);
-        $filearea  = 'private';
-        $component = 'user';
-        $itemid    = 0;
-
-        $fs = get_file_storage();
-        $storedfile = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename);
-
-        send_stored_file($storedfile, $lifetime, $filter, $forcedownload, $options);
-    }
 }
index b6ed919..55029a5 100644 (file)
@@ -243,6 +243,9 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .filemanager .fp-iconview .fp-file.fp-hasreferences .fp-reficons1 {background: url('[[pix:theme|fp/link]]') no-repeat;background-position:bottom right;}
 .filemanager .fp-iconview .fp-file.fp-isreference .fp-reficons2 {background: url('[[pix:theme|fp/alias]]') no-repeat;background-position:bottom left;}
 
+.filemanager .fp-iconview .fp-file.fp-originalmissing .fp-thumbnail img {display:none;}
+.filemanager .fp-iconview .fp-file.fp-originalmissing .fp-thumbnail {background: url([[pix:s/dead]]) no-repeat;background-position:center center;}
+
 /*
  * Table view (File Manager only)
  */