/** * 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; }
/** * 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(); }
/** * 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); } }
/** * 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; }
/** * 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); }
/** * 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; }
/** * 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; }
/** * 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); }
/** * 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; }
/** * 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; }
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; }