Example #1
0
 /**
  * Performs synchronisation of an external file if the previous one has expired.
  *
  * This function must be implemented for external repositories supporting
  * FILE_REFERENCE, it is called for existing aliases when their filesize,
  * contenthash or timemodified are requested. It is not called for internal
  * repositories (see {@link repository::has_moodle_files()}), references to
  * internal files are updated immediately when source is modified.
  *
  * Referenced files may optionally keep their content in Moodle filepool (for
  * thumbnail generation or to be able to serve cached copy). In this
  * case both contenthash and filesize need to be synchronized. Otherwise repositories
  * should use contenthash of empty file and correct filesize in bytes.
  *
  * Note that this function may be run for EACH file that needs to be synchronised at the
  * moment. If anything is being downloaded or requested from external sources there
  * should be a small timeout. The synchronisation is performed to update the size of
  * the file and/or to update image and re-generated image preview. There is nothing
  * fatal if syncronisation fails but it is fatal if syncronisation takes too long
  * and hangs the script generating a page.
  *
  * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or
  * $file->get_timemodified() make sure that recursion does not happen.
  *
  * Called from {@link stored_file::sync_external_file()}
  *
  * @uses stored_file::set_missingsource()
  * @uses stored_file::set_synchronized()
  * @param stored_file $file
  * @return bool false when file does not need synchronisation, true if it was synchronised
  */
 public function sync_reference(stored_file $file)
 {
     if ($file->get_repository_id() != $this->id) {
         // This should not really happen because the function can be called from stored_file only.
         return false;
     }
     if ($this->has_moodle_files()) {
         // References to local files need to be synchronised only once.
         // Later they will be synchronised automatically when the source is changed.
         if ($file->get_referencelastsync()) {
             return false;
         }
         $fs = get_file_storage();
         $params = file_storage::unpack_reference($file->get_reference(), true);
         if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'], $params['component'], $params['filearea'], $params['itemid'], $params['filepath'], $params['filename']))) {
             $file->set_missingsource();
         } else {
             $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize(), 0, $storedfile->get_timemodified());
         }
         return true;
     }
     return false;
 }
Example #2
0
 /**
  * What to do when this step is executed.
  */
 protected function define_execution()
 {
     global $DB;
     $this->log('processing file aliases queue', backup::LOG_DEBUG);
     $fs = get_file_storage();
     // Load the queue.
     $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $this->get_restoreid(), 'itemname' => 'file_aliases_queue'), '', 'info');
     // Iterate over aliases in the queue.
     foreach ($rs as $record) {
         $info = backup_controller_dbops::decode_backup_temp_info($record->info);
         // Try to pick a repository instance that should serve the alias.
         $repository = $this->choose_repository($info);
         if (is_null($repository)) {
             $this->notify_failure($info, 'unable to find a matching repository instance');
             continue;
         }
         if ($info->oldfile->repositorytype === 'local' or $info->oldfile->repositorytype === 'coursefiles') {
             // Aliases to Server files and Legacy course files may refer to a file
             // contained in the backup file or to some existing file (if we are on the
             // same site).
             try {
                 $reference = file_storage::unpack_reference($info->oldfile->reference);
             } catch (Exception $e) {
                 $this->notify_failure($info, 'invalid reference field format');
                 continue;
             }
             // Let's see if the referred source file was also included in the backup.
             $candidates = $DB->get_recordset('backup_files_temp', array('backupid' => $this->get_restoreid(), 'contextid' => $reference['contextid'], 'component' => $reference['component'], 'filearea' => $reference['filearea'], 'itemid' => $reference['itemid']), '', 'info, newcontextid, newitemid');
             $source = null;
             foreach ($candidates as $candidate) {
                 $candidateinfo = backup_controller_dbops::decode_backup_temp_info($candidate->info);
                 if ($candidateinfo->filename === $reference['filename'] and $candidateinfo->filepath === $reference['filepath'] and !is_null($candidate->newcontextid) and !is_null($candidate->newitemid)) {
                     $source = $candidateinfo;
                     $source->contextid = $candidate->newcontextid;
                     $source->itemid = $candidate->newitemid;
                     break;
                 }
             }
             $candidates->close();
             if ($source) {
                 // We have an alias that refers to another file also included in
                 // the backup. Let us change the reference field so that it refers
                 // to the restored copy of the original file.
                 $reference = file_storage::pack_reference($source);
                 // Send the new alias to the filepool.
                 $fs->create_file_from_reference($info->newfile, $repository->id, $reference);
                 $this->notify_success($info);
                 continue;
             } else {
                 // This is a reference to some moodle file that was not contained in the backup
                 // file. If we are restoring to the same site, keep the reference untouched
                 // and restore the alias as is if the referenced file exists.
                 if ($this->task->is_samesite()) {
                     if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) {
                         $reference = file_storage::pack_reference($reference);
                         $fs->create_file_from_reference($info->newfile, $repository->id, $reference);
                         $this->notify_success($info);
                         continue;
                     } else {
                         $this->notify_failure($info, 'referenced file not found');
                         continue;
                     }
                     // If we are at other site, we can't restore this alias.
                 } else {
                     $this->notify_failure($info, 'referenced file not included');
                     continue;
                 }
             }
         } else {
             if ($info->oldfile->repositorytype === 'user') {
                 if ($this->task->is_samesite()) {
                     // For aliases to user Private files at the same site, we have a chance to check
                     // if the referenced file still exists.
                     try {
                         $reference = file_storage::unpack_reference($info->oldfile->reference);
                     } catch (Exception $e) {
                         $this->notify_failure($info, 'invalid reference field format');
                         continue;
                     }
                     if ($fs->file_exists($reference['contextid'], $reference['component'], $reference['filearea'], $reference['itemid'], $reference['filepath'], $reference['filename'])) {
                         $reference = file_storage::pack_reference($reference);
                         $fs->create_file_from_reference($info->newfile, $repository->id, $reference);
                         $this->notify_success($info);
                         continue;
                     } else {
                         $this->notify_failure($info, 'referenced file not found');
                         continue;
                     }
                     // If we are at other site, we can't restore this alias.
                 } else {
                     $this->notify_failure($info, 'restoring at another site');
                     continue;
                 }
             } else {
                 // This is a reference to some external file such as in boxnet or dropbox.
                 // If we are restoring to the same site, keep the reference untouched and
                 // restore the alias as is.
                 if ($this->task->is_samesite()) {
                     $fs->create_file_from_reference($info->newfile, $repository->id, $info->oldfile->reference);
                     $this->notify_success($info);
                     continue;
                     // If we are at other site, we can't restore this alias.
                 } else {
                     $this->notify_failure($info, 'restoring at another site');
                     continue;
                 }
             }
         }
     }
     $rs->close();
 }
Example #3
0
/**
 * Saves files from a draft file area to a real one (merging the list of files).
 * Can rewrite URLs in some content at the same time if desired.
 *
 * @category files
 * @global stdClass $USER
 * @param int $draftitemid the id of the draft area to use. Normally obtained
 *      from file_get_submitted_draft_itemid('elementname') or similar.
 * @param int $contextid This parameter and the next two identify the file area to save to.
 * @param string $component
 * @param string $filearea indentifies the file area.
 * @param int $itemid helps identifies the file area.
 * @param array $options area options (subdirs=>false, maxfiles=-1, maxbytes=0)
 * @param string $text some html content that needs to have embedded links rewritten
 *      to the @@PLUGINFILE@@ form for saving in the database.
 * @param bool $forcehttps force https urls.
 * @return string|null if $text was passed in, the rewritten $text is returned. Otherwise NULL.
 */
function file_save_draft_area_files($draftitemid, $contextid, $component, $filearea, $itemid, array $options = null, $text = null, $forcehttps = false)
{
    global $USER;
    $usercontext = context_user::instance($USER->id);
    $fs = get_file_storage();
    $options = (array) $options;
    if (!isset($options['subdirs'])) {
        $options['subdirs'] = false;
    }
    if (!isset($options['maxfiles'])) {
        $options['maxfiles'] = -1;
        // unlimited
    }
    if (!isset($options['maxbytes']) || $options['maxbytes'] == USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
        $options['maxbytes'] = 0;
        // unlimited
    }
    if (!isset($options['areamaxbytes'])) {
        $options['areamaxbytes'] = FILE_AREA_MAX_BYTES_UNLIMITED;
        // Unlimited.
    }
    $allowreferences = true;
    if (isset($options['return_types']) && !($options['return_types'] & FILE_REFERENCE)) {
        // we assume that if $options['return_types'] is NOT specified, we DO allow references.
        // this is not exactly right. BUT there are many places in code where filemanager options
        // are not passed to file_save_draft_area_files()
        $allowreferences = false;
    }
    // Check if the draft area has exceeded the authorised limit. This should never happen as validation
    // should have taken place before, unless the user is doing something nauthly. If so, let's just not save
    // anything at all in the next area.
    if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) {
        return null;
    }
    $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id');
    $oldfiles = $fs->get_area_files($contextid, $component, $filearea, $itemid, 'id');
    // One file in filearea means it is empty (it has only top-level directory '.').
    if (count($draftfiles) > 1 || count($oldfiles) > 1) {
        // we have to merge old and new files - we want to keep file ids for files that were not changed
        // we change time modified for all new and changed files, we keep time created as is
        $newhashes = array();
        $filecount = 0;
        foreach ($draftfiles as $file) {
            if (!$options['subdirs'] && $file->get_filepath() !== '/') {
                continue;
            }
            if (!$allowreferences && $file->is_external_file()) {
                continue;
            }
            if (!$file->is_directory()) {
                if ($options['maxbytes'] and $options['maxbytes'] < $file->get_filesize()) {
                    // oversized file - should not get here at all
                    continue;
                }
                if ($options['maxfiles'] != -1 and $options['maxfiles'] <= $filecount) {
                    // more files - should not get here at all
                    continue;
                }
                $filecount++;
            }
            $newhash = $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $file->get_filepath(), $file->get_filename());
            $newhashes[$newhash] = $file;
        }
        // Loop through oldfiles and decide which we need to delete and which to update.
        // After this cycle the array $newhashes will only contain the files that need to be added.
        foreach ($oldfiles as $oldfile) {
            $oldhash = $oldfile->get_pathnamehash();
            if (!isset($newhashes[$oldhash])) {
                // delete files not needed any more - deleted by user
                $oldfile->delete();
                continue;
            }
            $newfile = $newhashes[$oldhash];
            // Now we know that we have $oldfile and $newfile for the same path.
            // Let's check if we can update this file or we need to delete and create.
            if ($newfile->is_directory()) {
                // Directories are always ok to just update.
            } else {
                if (($source = @unserialize($newfile->get_source())) && isset($source->original)) {
                    // File has the 'original' - we need to update the file (it may even have not been changed at all).
                    $original = file_storage::unpack_reference($source->original);
                    if ($original['filename'] !== $oldfile->get_filename() || $original['filepath'] !== $oldfile->get_filepath()) {
                        // Very odd, original points to another file. Delete and create file.
                        $oldfile->delete();
                        continue;
                    }
                } else {
                    // The same file name but absence of 'original' means that file was deteled and uploaded again.
                    // By deleting and creating new file we properly manage all existing references.
                    $oldfile->delete();
                    continue;
                }
            }
            // status changed, we delete old file, and create a new one
            if ($oldfile->get_status() != $newfile->get_status()) {
                // file was changed, use updated with new timemodified data
                $oldfile->delete();
                // This file will be added later
                continue;
            }
            // Updated author
            if ($oldfile->get_author() != $newfile->get_author()) {
                $oldfile->set_author($newfile->get_author());
            }
            // Updated license
            if ($oldfile->get_license() != $newfile->get_license()) {
                $oldfile->set_license($newfile->get_license());
            }
            // Updated file source
            // Field files.source for draftarea files contains serialised object with source and original information.
            // We only store the source part of it for non-draft file area.
            $newsource = $newfile->get_source();
            if ($source = @unserialize($newfile->get_source())) {
                $newsource = $source->source;
            }
            if ($oldfile->get_source() !== $newsource) {
                $oldfile->set_source($newsource);
            }
            // Updated sort order
            if ($oldfile->get_sortorder() != $newfile->get_sortorder()) {
                $oldfile->set_sortorder($newfile->get_sortorder());
            }
            // Update file timemodified
            if ($oldfile->get_timemodified() != $newfile->get_timemodified()) {
                $oldfile->set_timemodified($newfile->get_timemodified());
            }
            // Replaced file content
            if (!$oldfile->is_directory() && ($oldfile->get_contenthash() != $newfile->get_contenthash() || $oldfile->get_filesize() != $newfile->get_filesize() || $oldfile->get_referencefileid() != $newfile->get_referencefileid() || $oldfile->get_userid() != $newfile->get_userid())) {
                $oldfile->replace_file_with($newfile);
            }
            // unchanged file or directory - we keep it as is
            unset($newhashes[$oldhash]);
        }
        // Add fresh file or the file which has changed status
        // the size and subdirectory tests are extra safety only, the UI should prevent it
        foreach ($newhashes as $file) {
            $file_record = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid, 'timemodified' => time());
            if ($source = @unserialize($file->get_source())) {
                // Field files.source for draftarea files contains serialised object with source and original information.
                // We only store the source part of it for non-draft file area.
                $file_record['source'] = $source->source;
            }
            if ($file->is_external_file()) {
                $repoid = $file->get_repository_id();
                if (!empty($repoid)) {
                    $file_record['repositoryid'] = $repoid;
                    $file_record['reference'] = $file->get_reference();
                }
            }
            $fs->create_file_from_storedfile($file_record, $file);
        }
    }
    // note: do not purge the draft area - we clean up areas later in cron,
    //       the reason is that user might press submit twice and they would loose the files,
    //       also sometimes we might want to use hacks that save files into two different areas
    if (is_null($text)) {
        return null;
    } else {
        return file_rewrite_urls_to_pluginfile($text, $draftitemid, $forcehttps);
    }
}
Example #4
0
    /**
     * Performs synchronisation of an external file if the previous one has expired.
     *
     * This function must be implemented for external repositories supporting
     * FILE_REFERENCE, it is called for existing aliases when their filesize,
     * contenthash or timemodified are requested. It is not called for internal
     * repositories (see {@link repository::has_moodle_files()}), references to
     * internal files are updated immediately when source is modified.
     *
     * Referenced files may optionally keep their content in Moodle filepool (for
     * thumbnail generation or to be able to serve cached copy). In this
     * case both contenthash and filesize need to be synchronized. Otherwise repositories
     * should use contenthash of empty file and correct filesize in bytes.
     *
     * Note that this function may be run for EACH file that needs to be synchronised at the
     * moment. If anything is being downloaded or requested from external sources there
     * should be a small timeout. The synchronisation is performed to update the size of
     * the file and/or to update image and re-generated image preview. There is nothing
     * fatal if syncronisation fails but it is fatal if syncronisation takes too long
     * and hangs the script generating a page.
     *
     * Note: If you wish to call $file->get_filesize(), $file->get_contenthash() or
     * $file->get_timemodified() make sure that recursion does not happen.
     *
     * Called from {@link stored_file::sync_external_file()}
     *
     * @uses stored_file::set_missingsource()
     * @uses stored_file::set_synchronized()
     * @param stored_file $file
     * @return bool false when file does not need synchronisation, true if it was synchronised
     */
    public function sync_reference(stored_file $file) {
        if ($file->get_repository_id() != $this->id) {
            // This should not really happen because the function can be called from stored_file only.
            return false;
        }

        if ($this->has_moodle_files()) {
            // References to local files need to be synchronised only once.
            // Later they will be synchronised automatically when the source is changed.
            if ($file->get_referencelastsync()) {
                return false;
            }
            $fs = get_file_storage();
            $params = file_storage::unpack_reference($file->get_reference(), true);
            if (!is_array($params) || !($storedfile = $fs->get_file($params['contextid'],
                    $params['component'], $params['filearea'], $params['itemid'], $params['filepath'],
                    $params['filename']))) {
                $file->set_missingsource();
            } else {
                $file->set_synchronized($storedfile->get_contenthash(), $storedfile->get_filesize());
            }
            return true;
        }

        // Backward compatibility (Moodle 2.3-2.5) implementation that calls
        // methods repository::get_reference_file_lifetime(), repository::sync_individual_file()
        // and repository::get_file_by_reference(). These methods are removed from the
        // base repository class but may still be implemented by the child classes.

        // THIS IS NOT A GOOD EXAMPLE of implementation. For good examples see the overwriting methods.

        if (!method_exists($this, 'get_file_by_reference')) {
            // Function get_file_by_reference() is not implemented. No synchronisation.
            return false;
        }

        // Check if the previous sync result is still valid.
        if (method_exists($this, 'get_reference_file_lifetime')) {
            $lifetime = $this->get_reference_file_lifetime($file->get_reference());
        } else {
            // Default value that was hardcoded in Moodle 2.3 - 2.5.
            $lifetime =  60 * 60 * 24;
        }
        if (($lastsynced = $file->get_referencelastsync()) && $lastsynced + $lifetime >= time()) {
            return false;
        }

        $cache = cache::make('core', 'repositories');
        if (($lastsyncresult = $cache->get('sync:'.$file->get_referencefileid())) !== false) {
            if ($lastsyncresult === true) {
                // We are in the process of synchronizing this reference.
                // Avoid recursion when calling $file->get_filesize() and $file->get_contenthash().
                return false;
            } else {
                // We have synchronised the same reference inside this request already.
                // It looks like the object $file was created before the synchronisation and contains old data.
                if (!empty($lastsyncresult['missing'])) {
                    $file->set_missingsource();
                } else {
                    $cache->set('sync:'.$file->get_referencefileid(), true);
                    if ($file->get_contenthash() != $lastsyncresult['contenthash'] ||
                            $file->get_filesize() != $lastsyncresult['filesize']) {
                        $file->set_synchronized($lastsyncresult['contenthash'], $lastsyncresult['filesize']);
                    }
                    $cache->set('sync:'.$file->get_referencefileid(), $lastsyncresult);
                }
                return true;
            }
        }

        // Weird function sync_individual_file() that was present in API in 2.3 - 2.5, default value was true.
        if (method_exists($this, 'sync_individual_file') && !$this->sync_individual_file($file)) {
            return false;
        }

        // Set 'true' into the cache to indicate that file is in the process of synchronisation.
        $cache->set('sync:'.$file->get_referencefileid(), true);

        // Create object with the structure that repository::get_file_by_reference() expects.
        $reference = new stdClass();
        $reference->id = $file->get_referencefileid();
        $reference->reference = $file->get_reference();
        $reference->referencehash = sha1($file->get_reference());
        $reference->lastsync = $file->get_referencelastsync();
        $reference->lifetime = $lifetime;

        $fileinfo = $this->get_file_by_reference($reference);

        $contenthash = null;
        $filesize = null;
        $fs = get_file_storage();
        if (!empty($fileinfo->filesize)) {
            // filesize returned
            if (!empty($fileinfo->contenthash) && $fs->content_exists($fileinfo->contenthash)) {
                // contenthash is specified and valid
                $contenthash = $fileinfo->contenthash;
            } else if ($fileinfo->filesize == $file->get_filesize()) {
                // we don't know the new contenthash but the filesize did not change,
                // assume the contenthash did not change either
                $contenthash = $file->get_contenthash();
            } else {
                // we can't save empty contenthash so generate contenthash from empty string
                list($contenthash, $unused1, $unused2) = $fs->add_string_to_pool('');
            }
            $filesize = $fileinfo->filesize;
        } else if (!empty($fileinfo->filepath)) {
            // File path returned
            list($contenthash, $filesize, $newfile) = $fs->add_file_to_pool($fileinfo->filepath);
        } else if (!empty($fileinfo->handle) && is_resource($fileinfo->handle)) {
            // File handle returned
            $contents = '';
            while (!feof($fileinfo->handle)) {
                $contents .= fread($fileinfo->handle, 8192);
            }
            fclose($fileinfo->handle);
            list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($contents);
        } else if (isset($fileinfo->content)) {
            // File content returned
            list($contenthash, $filesize, $newfile) = $fs->add_string_to_pool($fileinfo->content);
        }

        if (!isset($contenthash) or !isset($filesize)) {
            $file->set_missingsource(null);
            $cache->set('sync:'.$file->get_referencefileid(), array('missing' => true));
        } else {
            // update files table
            $file->set_synchronized($contenthash, $filesize);
            $cache->set('sync:'.$file->get_referencefileid(),
                    array('contenthash' => $contenthash, 'filesize' => $filesize));
        }

        return true;
    }
Example #5
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);
 }
Example #6
0
 /**
  * Prepare file reference information
  *
  * @param string $source
  * @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;
 }
Example #7
0
 /**
  * Prepare file reference information
  *
  * @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
  * @return string file reference, ready to be stored
  */
 public function get_file_reference($source) {
     if ($source && $this->has_moodle_files()) {
         $params = @json_decode(base64_decode($source), true);
         if (!$params && !in_array($this->get_typename(), array('recent', 'user', 'local', 'coursefiles'))) {
             // IMPORTANT! Since default format for moodle files was changed in the minor release as a security fix
             // we maintain an old code here in order not to break 3rd party repositories that deal
             // with moodle files. Repositories are strongly encouraged to be upgraded, see MDL-45616.
             // In Moodle 2.8 this fallback will be removed.
             $params = file_storage::unpack_reference($source, true);
             return file_storage::pack_reference($params);
         }
         if (!is_array($params) || empty($params['contextid'])) {
             throw new repository_exception('invalidparams', 'repository');
         }
         $params = array(
             'component' => empty($params['component']) ? ''   : clean_param($params['component'], PARAM_COMPONENT),
             'filearea'  => empty($params['filearea'])  ? ''   : clean_param($params['filearea'], PARAM_AREA),
             'itemid'    => empty($params['itemid'])    ? 0    : clean_param($params['itemid'], PARAM_INT),
             'filename'  => empty($params['filename'])  ? null : clean_param($params['filename'], PARAM_FILE),
             'filepath'  => empty($params['filepath'])  ? null : clean_param($params['filepath'], PARAM_PATH),
             'contextid' => clean_param($params['contextid'], PARAM_INT)
         );
         // Check if context exists.
         if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
             throw new repository_exception('invalidparams', 'repository');
         }
         return file_storage::pack_reference($params);
     }
     return $source;
 }
Example #8
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);
 }
Example #9
0
/**
 * Gets main file in a file area
 *
 * if the main file is a link from an external repository
 * look for the target file in the main file's repository
 * Note: this functionality only exists in Moodle 2.3+
 *
 * @param stdclass $context
 * @param string $component 'mod_hotpot'
 * @param string $filearea  'sourcefile', 'entrytext' or 'exittext'
 * @param string $filepath  despite the name, this is a dir path with leading and trailing "/"
 * @param string $filename
 * @param array $file_record
 * @return stdclass if external file found, false otherwise
 */
function hotpot_pluginfile_externalfile($context, $component, $filearea, $filepath, $filename, $file_record)
{
    // get file storage
    $fs = get_file_storage();
    // get main file for this $component/$filearea
    // typically this will be the HotPot quiz file
    $mainfile = hotpot_pluginfile_mainfile($context, $component, $filearea);
    // get repository - cautiously :-)
    if (!$mainfile) {
        return false;
        // no main file - shouldn't happen !!
    }
    if (!method_exists($mainfile, 'get_repository_id')) {
        return false;
        // no file linking in Moodle 2.0 - 2.2
    }
    if (!($repositoryid = $mainfile->get_repository_id())) {
        return false;
        // $mainfile is not from an external repository
    }
    if (!($repository = repository::get_repository_by_id($repositoryid, $context))) {
        return false;
        // $repository is not accessible in this context - shouldn't happen !!
    }
    // get repository type
    switch (true) {
        case isset($repository->options['type']):
            $type = $repository->options['type'];
            break;
        case isset($repository->instance->typeid):
            $type = repository::get_type_by_id($repository->instance->typeid);
            $type = $type->get_typename();
            break;
        default:
            $type = '';
            // shouldn't happen !!
    }
    // set paths (within repository) to required file
    // how we do this depends on the repository $typename
    // "filesystem" path is in plain text, others are encoded
    $mainreference = $mainfile->get_reference();
    switch ($type) {
        case 'filesystem':
            $maindirname = dirname($mainreference);
            $encodepath = false;
            break;
        case 'user':
        case 'coursefiles':
            $params = file_storage::unpack_reference($mainreference, true);
            $maindirname = $params['filepath'];
            $encodepath = true;
            break;
        default:
            echo 'unknown repository type in hotpot_pluginfile_externalfile(): ' . $type;
            die;
    }
    // remove leading and trailing "/" from dir names
    $maindirname = trim($maindirname, '/');
    $dirname = trim($filepath, '/');
    // assume path to target dir is same as path to main dir
    $path = explode('/', $maindirname);
    // traverse back up folder hierarchy if necessary
    $count = count(explode('/', $dirname));
    array_splice($path, -$count);
    // reconstruct expected dir path for source file
    if ($dirname) {
        $path[] = $dirname;
    }
    $source = $path;
    $source[] = $filename;
    $source = implode('/', $source);
    $path = implode('/', $path);
    // filepaths in the repository to search for the file
    $paths = array();
    // add to the list of possible paths
    $paths[$path] = $source;
    if ($dirname) {
        $paths[$dirname] = $dirname . '/' . $filename;
    }
    if ($maindirname) {
        $paths[$maindirname] = $maindirname . '/' . $filename;
    }
    if ($maindirname && $dirname) {
        $paths[$maindirname . '/' . $dirname] = $maindirname . '/' . $dirname . '/' . $filename;
        $paths[$dirname . '/' . $maindirname] = $dirname . '/' . $maindirname . '/' . $filename;
    }
    // add leading and trailing "/" to dir names
    $dirname = $dirname == '' ? '/' : '/' . $dirname . '/';
    $maindirname = $maindirname == '' ? '/' : '/' . $maindirname . '/';
    // locate $dirname within $maindirname
    // typically it will be absent or occur just once,
    // but it could possibly occur several times
    $search = '/' . preg_quote($dirname, '/') . '/i';
    if (preg_match_all($search, $maindirname, $matches, PREG_OFFSET_CAPTURE)) {
        $i_max = count($matches[0]);
        for ($i = 0; $i < $i_max; $i++) {
            list($match, $start) = $matches[0][$i];
            $path = substr($maindirname, 0, $start) . $match;
            $path = trim($path, '/');
            // e.g. hp6.2/html_files
            $paths[$path] = $path . '/' . $filename;
        }
    }
    // setup $params for path encoding, if necessary
    $params = array();
    if ($encodepath) {
        $listing = $repository->get_listing();
        if (isset($listing['list'][0]['path'])) {
            $params = file_storage::unpack_reference($listing['list'][0]['path'], true);
        }
    }
    foreach ($paths as $path => $source) {
        if (!hotpot_pluginfile_dirpath_exists($path, $repository, $encodepath, $params)) {
            continue;
        }
        if ($encodepath) {
            $params['filepath'] = '/' . $path . ($path == '' ? '' : '/');
            $params['filename'] = '.';
            // "." signifies a directory
            $path = file_storage::pack_reference($params);
        }
        $listing = $repository->get_listing($path);
        foreach ($listing['list'] as $file) {
            if (empty($file['source'])) {
                continue;
                // a directory - shouldn't happen !!
            }
            if ($encodepath) {
                $file['source'] = file_storage::unpack_reference($file['source']);
                $file['source'] = trim($file['source']['filepath'], '/') . '/' . $file['source']['filename'];
            }
            if ($file['source'] == $source) {
                if ($encodepath) {
                    $params['filename'] = $filename;
                    $source = file_storage::pack_reference($params);
                }
                if ($file = $fs->create_file_from_reference($file_record, $repositoryid, $source)) {
                    return $file;
                }
                break;
                // couldn't create file, so give up and try a different $path
            }
        }
    }
    // external file not found (or found but not created)
    return false;
}
Example #10
0
 /**
  * Get file from external repository by reference
  * {@link repository::get_file_reference()}
  * {@link repository::get_file()}
  *
  * @param stdClass $ref    file reference db record
  * @return stdClass|null|false
  */
 public function get_file_by_reference($ref) {
     // ob_start();
     // var_dump($ref);
     // $tmp = ob_get_contents();
     // ob_end_clean();
     // error_log("get_file_by_reference(ref = {$tmp});");
     // return parent::get_file_by_reference($ref); // DEPRECATED
     if ($this->has_moodle_files() && isset($ref->reference)) {
         $fs = get_file_storage();
         $params = file_storage::unpack_reference($ref->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;
 }
Example #11
0
function xmldb_hotpot_locate_externalfile($contextid, $component, $filearea, $itemid, $filepath, $filename)
{
    global $CFG, $DB;
    if (!class_exists('repository')) {
        return false;
        // Moodle <= 2.2 has no repositories
    }
    static $repositories = null;
    if ($repositories === null) {
        $exclude_types = array('recent', 'upload', 'user', 'areafiles');
        $repositories = repository::get_instances();
        foreach (array_keys($repositories) as $id) {
            if (method_exists($repositories[$id], 'get_typename')) {
                $type = $repositories[$id]->get_typename();
            } else {
                $type = $repositories[$id]->options['type'];
            }
            if (in_array($type, $exclude_types)) {
                unset($repositories[$id]);
            }
        }
        // ensure upgraderunning is set
        if (empty($CFG->upgraderunning)) {
            $CFG->upgraderunning = null;
        }
    }
    // get file storage
    $fs = get_file_storage();
    // the following types repository use encoded params
    $encoded_types = array('user', 'areafiles', 'coursefiles');
    foreach ($repositories as $id => $repository) {
        // "filesystem" path is in plain text, others are encoded
        if (method_exists($repositories[$id], 'get_typename')) {
            $type = $repositories[$id]->get_typename();
        } else {
            $type = $repositories[$id]->options['type'];
        }
        $encodepath = in_array($type, $encoded_types);
        // save $root_path, because it may get messed up by
        // $repository->get_listing($path), if $path is non-existant
        if (method_exists($repository, 'get_rootpath')) {
            $root_path = $repository->get_rootpath();
        } else {
            if (isset($repository->root_path)) {
                $root_path = $repository->root_path;
            } else {
                $root_path = false;
            }
        }
        // get repository type
        switch (true) {
            case isset($repository->options['type']):
                $type = $repository->options['type'];
                break;
            case isset($repository->instance->typeid):
                $type = repository::get_type_by_id($repository->instance->typeid);
                $type = $type->get_typename();
                break;
            default:
                $type = '';
                // shouldn't happen !!
        }
        $path = $filepath;
        $source = trim($filepath . $filename, '/');
        // setup $params for path encoding, if necessary
        $params = array();
        if ($encodepath) {
            $listing = $repository->get_listing();
            switch (true) {
                case isset($listing['list'][0]['source']):
                    $param = 'source';
                    break;
                    // file
                // file
                case isset($listing['list'][0]['path']):
                    $param = 'path';
                    break;
                    // dir
                // dir
                default:
                    return false;
                    // shouldn't happen !!
            }
            $params = file_storage::unpack_reference($listing['list'][0][$param], true);
            $params['filepath'] = '/' . $path . ($path == '' ? '' : '/');
            $params['filename'] = '.';
            // "." signifies a directory
            $path = file_storage::pack_reference($params);
        }
        // reset $repository->root_path (filesystem repository only)
        if ($root_path) {
            $repository->root_path = $root_path;
        }
        // unset upgraderunning because it can cause get_listing() to fail
        $upgraderunning = $CFG->upgraderunning;
        $CFG->upgraderunning = null;
        // Note: we use "@" to suppress warnings in case $path does not exist
        $listing = @$repository->get_listing($path);
        // restore upgraderunning flag
        $CFG->upgraderunning = $upgraderunning;
        // check each file to see if it is the one we want
        foreach ($listing['list'] as $file) {
            switch (true) {
                case isset($file['source']):
                    $param = 'source';
                    break;
                    // file
                // file
                case isset($file['path']):
                    $param = 'path';
                    break;
                    // dir
                // dir
                default:
                    continue;
                    // shouldn't happen !!
            }
            if ($encodepath) {
                $file[$param] = file_storage::unpack_reference($file[$param]);
                $file[$param] = trim($file[$param]['filepath'], '/') . '/' . $file[$param]['filename'];
            }
            if ($file[$param] == $source) {
                if ($encodepath) {
                    $params['filename'] = $filename;
                    $source = file_storage::pack_reference($params);
                }
                $file_record = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'sortorder' => 0, 'itemid' => 0, 'filepath' => $filepath, 'filename' => $filename);
                if ($file = $fs->create_file_from_reference($file_record, $id, $source)) {
                    return $file;
                }
                break;
                // try another repository
            }
        }
    }
    // external file not found (or found but not created)
    return false;
}