mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
09a55f952e
New function get_total_content_size() is introduced in the stored_file class. The puprose of this function is to calculate and return the total size (in bytes) of the content of an archive file.
1198 lines
40 KiB
PHP
1198 lines
40 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
/**
|
|
* Definition of a class stored_file.
|
|
*
|
|
* @package core_files
|
|
* @copyright 2008 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
require_once($CFG->dirroot . '/lib/filestorage/file_progress.php');
|
|
require_once($CFG->dirroot . '/lib/filestorage/file_system.php');
|
|
|
|
/**
|
|
* Class representing local files stored in a sha1 file pool.
|
|
*
|
|
* Since Moodle 2.0 file contents are stored in sha1 pool and
|
|
* all other file information is stored in new "files" database table.
|
|
*
|
|
* @package core_files
|
|
* @category files
|
|
* @copyright 2008 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
* @since Moodle 2.0
|
|
*/
|
|
class stored_file {
|
|
/** @var file_storage file storage pool instance */
|
|
private $fs;
|
|
/** @var stdClass record from the files table left join files_reference table */
|
|
private $file_record;
|
|
/** @var repository repository plugin instance */
|
|
private $repository;
|
|
/** @var file_system filesystem instance */
|
|
private $filesystem;
|
|
|
|
/**
|
|
* @var int Indicates a file handle of the type returned by fopen.
|
|
*/
|
|
const FILE_HANDLE_FOPEN = 0;
|
|
|
|
/**
|
|
* @var int Indicates a file handle of the type returned by gzopen.
|
|
*/
|
|
const FILE_HANDLE_GZOPEN = 1;
|
|
|
|
|
|
/**
|
|
* Constructor, this constructor should be called ONLY from the file_storage class!
|
|
*
|
|
* @param file_storage $fs file storage instance
|
|
* @param stdClass $file_record description of file
|
|
* @param string $deprecated
|
|
*/
|
|
public function __construct(file_storage $fs, stdClass $file_record, $deprecated = null) {
|
|
global $DB, $CFG;
|
|
$this->fs = $fs;
|
|
$this->file_record = clone($file_record); // prevent modifications
|
|
|
|
if (!empty($file_record->repositoryid)) {
|
|
require_once("$CFG->dirroot/repository/lib.php");
|
|
$this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
|
|
if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
|
|
// Repository cannot do file reference.
|
|
throw new moodle_exception('error');
|
|
}
|
|
} else {
|
|
$this->repository = null;
|
|
}
|
|
// make sure all reference fields exist in file_record even when it is not a reference
|
|
foreach (array('referencelastsync', 'referencefileid', 'reference', 'repositoryid') as $key) {
|
|
if (empty($this->file_record->$key)) {
|
|
$this->file_record->$key = null;
|
|
}
|
|
}
|
|
|
|
$this->filesystem = $fs->get_file_system();
|
|
}
|
|
|
|
/**
|
|
* Magic method, called during serialization.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function __sleep() {
|
|
// We only ever want the file_record saved, not the file_storage object.
|
|
return ['file_record'];
|
|
}
|
|
|
|
/**
|
|
* Magic method, called during unserialization.
|
|
*/
|
|
public function __wakeup() {
|
|
// Recreate our stored_file based on the file_record, and using file storage retrieved the correct way.
|
|
$this->__construct(get_file_storage(), $this->file_record);
|
|
}
|
|
|
|
/**
|
|
* Whether or not this is a external resource
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_external_file() {
|
|
return !empty($this->repository);
|
|
}
|
|
|
|
/**
|
|
* Whether or not this is a controlled link. Note that repositories cannot support FILE_REFERENCE and FILE_CONTROLLED_LINK.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_controlled_link() {
|
|
return $this->is_external_file() && $this->repository->supported_returntypes() & FILE_CONTROLLED_LINK;
|
|
}
|
|
|
|
/**
|
|
* Update some file record fields
|
|
* NOTE: Must remain protected
|
|
*
|
|
* @param stdClass $dataobject
|
|
*/
|
|
protected function update($dataobject) {
|
|
global $DB;
|
|
$updatereferencesneeded = false;
|
|
$updatemimetype = false;
|
|
$keys = array_keys((array)$this->file_record);
|
|
$filepreupdate = clone($this->file_record);
|
|
foreach ($dataobject as $field => $value) {
|
|
if (in_array($field, $keys)) {
|
|
if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid contextid');
|
|
}
|
|
|
|
if ($field == 'component') {
|
|
$value = clean_param($value, PARAM_COMPONENT);
|
|
if (empty($value)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid component');
|
|
}
|
|
}
|
|
|
|
if ($field == 'filearea') {
|
|
$value = clean_param($value, PARAM_AREA);
|
|
if (empty($value)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid filearea');
|
|
}
|
|
}
|
|
|
|
if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid itemid');
|
|
}
|
|
|
|
|
|
if ($field == 'filepath') {
|
|
$value = clean_param($value, PARAM_PATH);
|
|
if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
|
|
// path must start and end with '/'
|
|
throw new file_exception('storedfileproblem', 'Invalid file path');
|
|
}
|
|
}
|
|
|
|
if ($field == 'filename') {
|
|
// folder has filename == '.', so we pass this
|
|
if ($value != '.') {
|
|
$value = clean_param($value, PARAM_FILE);
|
|
}
|
|
if ($value === '') {
|
|
throw new file_exception('storedfileproblem', 'Invalid file name');
|
|
}
|
|
}
|
|
|
|
if ($field === 'timecreated' or $field === 'timemodified') {
|
|
if (!is_number($value)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid timestamp');
|
|
}
|
|
if ($value < 0) {
|
|
$value = 0;
|
|
}
|
|
}
|
|
|
|
if ($field === 'referencefileid') {
|
|
if (!is_null($value) and !is_number($value)) {
|
|
throw new file_exception('storedfileproblem', 'Invalid reference info');
|
|
}
|
|
}
|
|
|
|
if (($field == 'contenthash' || $field == 'filesize') && $this->file_record->$field != $value) {
|
|
$updatereferencesneeded = true;
|
|
}
|
|
|
|
if ($updatereferencesneeded || ($field === 'filename' && $this->file_record->filename != $value)) {
|
|
$updatemimetype = true;
|
|
}
|
|
|
|
// adding the field
|
|
$this->file_record->$field = $value;
|
|
} else {
|
|
throw new coding_exception("Invalid field name, $field doesn't exist in file record");
|
|
}
|
|
}
|
|
// Validate mimetype field
|
|
if ($updatemimetype) {
|
|
$mimetype = $this->filesystem->mimetype_from_storedfile($this);
|
|
$this->file_record->mimetype = $mimetype;
|
|
}
|
|
|
|
$DB->update_record('files', $this->file_record);
|
|
if ($updatereferencesneeded) {
|
|
// Either filesize or contenthash of this file have changed. Update all files that reference to it.
|
|
$this->fs->update_references_to_storedfile($this);
|
|
}
|
|
|
|
// Callback for file update.
|
|
if (!$this->is_directory()) {
|
|
if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
|
|
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
foreach ($plugins as $pluginfunction) {
|
|
$pluginfunction($this->file_record, $filepreupdate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rename filename
|
|
*
|
|
* @param string $filepath file path
|
|
* @param string $filename file name
|
|
*/
|
|
public function rename($filepath, $filename) {
|
|
if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
|
|
$a = new stdClass();
|
|
$a->contextid = $this->get_contextid();
|
|
$a->component = $this->get_component();
|
|
$a->filearea = $this->get_filearea();
|
|
$a->itemid = $this->get_itemid();
|
|
$a->filepath = $filepath;
|
|
$a->filename = $filename;
|
|
throw new file_exception('storedfilenotcreated', $a, 'file exists, cannot rename');
|
|
}
|
|
$filerecord = new stdClass;
|
|
$filerecord->filepath = $filepath;
|
|
$filerecord->filename = $filename;
|
|
// populate the pathname hash
|
|
$filerecord->pathnamehash = $this->fs->get_pathname_hash($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath, $filename);
|
|
$this->update($filerecord);
|
|
}
|
|
|
|
/**
|
|
* Function stored_file::replace_content_with() is deprecated. Please use stored_file::replace_file_with()
|
|
*
|
|
* @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
|
|
* @see stored_file::replace_file_with()
|
|
*/
|
|
public function replace_content_with(stored_file $storedfile) {
|
|
throw new coding_exception('Function stored_file::replace_content_with() can not be used any more . ' .
|
|
'Please use stored_file::replace_file_with()');
|
|
}
|
|
|
|
/**
|
|
* Replaces the fields that might have changed when file was overriden in filepicker:
|
|
* reference, contenthash, filesize, userid
|
|
*
|
|
* Note that field 'source' must be updated separately because
|
|
* it has different format for draft and non-draft areas and
|
|
* this function will usually be used to replace non-draft area
|
|
* file with draft area file.
|
|
*
|
|
* @param stored_file $newfile
|
|
* @throws coding_exception
|
|
*/
|
|
public function replace_file_with(stored_file $newfile) {
|
|
if ($newfile->get_referencefileid() &&
|
|
$this->fs->get_references_count_by_storedfile($this)) {
|
|
// The new file is a reference.
|
|
// The current file has other local files referencing to it.
|
|
// Double reference is not allowed.
|
|
throw new moodle_exception('errordoublereference', 'repository');
|
|
}
|
|
|
|
$filerecord = new stdClass;
|
|
if ($this->filesystem->is_file_readable_remotely_by_storedfile($newfile)) {
|
|
$contenthash = $newfile->get_contenthash();
|
|
$filerecord->contenthash = $contenthash;
|
|
} else {
|
|
throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
|
|
}
|
|
$filerecord->filesize = $newfile->get_filesize();
|
|
$filerecord->referencefileid = $newfile->get_referencefileid();
|
|
$filerecord->userid = $newfile->get_userid();
|
|
$oldcontenthash = $this->get_contenthash();
|
|
$this->update($filerecord);
|
|
$this->filesystem->remove_file($oldcontenthash);
|
|
}
|
|
|
|
/**
|
|
* Unlink the stored file from the referenced file
|
|
*
|
|
* This methods destroys the link to the record in files_reference table. This effectively
|
|
* turns the stored file from being an alias to a plain copy. However, the caller has
|
|
* to make sure that the actual file's content has beed synced prior to calling this method.
|
|
*/
|
|
public function delete_reference() {
|
|
global $DB;
|
|
|
|
if (!$this->is_external_file()) {
|
|
throw new coding_exception('An attempt to unlink a non-reference file.');
|
|
}
|
|
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
// Are we the only one referring to the original file? If so, delete the
|
|
// referenced file record. Note we do not use file_storage::search_references_count()
|
|
// here because we want to count draft files too and we are at a bit lower access level here.
|
|
$countlinks = $DB->count_records('files',
|
|
array('referencefileid' => $this->file_record->referencefileid));
|
|
if ($countlinks == 1) {
|
|
$DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
|
|
}
|
|
|
|
// Update the underlying record in the database.
|
|
$update = new stdClass();
|
|
$update->referencefileid = null;
|
|
$this->update($update);
|
|
|
|
$transaction->allow_commit();
|
|
|
|
// Update our properties and the record in the memory.
|
|
$this->repository = null;
|
|
$this->file_record->repositoryid = null;
|
|
$this->file_record->reference = null;
|
|
$this->file_record->referencefileid = null;
|
|
$this->file_record->referencelastsync = null;
|
|
}
|
|
|
|
/**
|
|
* Is this a directory?
|
|
*
|
|
* Directories are only emulated, internally they are stored as empty
|
|
* files with a "." instead of name - this means empty directory contains
|
|
* exactly one empty file with name dot.
|
|
*
|
|
* @return bool true means directory, false means file
|
|
*/
|
|
public function is_directory() {
|
|
return ($this->file_record->filename === '.');
|
|
}
|
|
|
|
/**
|
|
* Delete file from files table.
|
|
*
|
|
* The content of files stored in sha1 pool is reclaimed
|
|
* later - the occupied disk space is reclaimed much later.
|
|
*
|
|
* @return bool always true or exception if error occurred
|
|
*/
|
|
public function delete() {
|
|
global $DB;
|
|
|
|
if ($this->is_directory()) {
|
|
// Directories can not be referenced, just delete the record.
|
|
$DB->delete_records('files', array('id'=>$this->file_record->id));
|
|
|
|
} else {
|
|
$transaction = $DB->start_delegated_transaction();
|
|
|
|
// If there are other files referring to this file, convert them to copies.
|
|
if ($files = $this->fs->get_references_by_storedfile($this)) {
|
|
foreach ($files as $file) {
|
|
$this->fs->import_external_file($file);
|
|
}
|
|
}
|
|
|
|
// If this file is a reference (alias) to another file, unlink it first.
|
|
if ($this->is_external_file()) {
|
|
$this->delete_reference();
|
|
}
|
|
|
|
// Now delete the file record.
|
|
$DB->delete_records('files', array('id'=>$this->file_record->id));
|
|
|
|
$transaction->allow_commit();
|
|
|
|
if (!$this->is_directory()) {
|
|
// Callback for file deletion.
|
|
if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
|
|
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
foreach ($plugins as $pluginfunction) {
|
|
$pluginfunction($this->file_record);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move pool file to trash if content not needed any more.
|
|
$this->filesystem->remove_file($this->file_record->contenthash);
|
|
return true; // BC only
|
|
}
|
|
|
|
/**
|
|
* adds this file path to a curl request (POST only)
|
|
*
|
|
* @param curl $curlrequest the curl request object
|
|
* @param string $key what key to use in the POST request
|
|
* @return void
|
|
*/
|
|
public function add_to_curl_request(&$curlrequest, $key) {
|
|
return $this->filesystem->add_to_curl_request($this, $curlrequest, $key);
|
|
}
|
|
|
|
/**
|
|
* Returns file handle - read only mode, no writing allowed into pool files!
|
|
*
|
|
* When you want to modify a file, create a new file and delete the old one.
|
|
*
|
|
* @param int $type Type of file handle (FILE_HANDLE_xx constant)
|
|
* @return resource file handle
|
|
*/
|
|
public function get_content_file_handle($type = self::FILE_HANDLE_FOPEN) {
|
|
return $this->filesystem->get_content_file_handle($this, $type);
|
|
}
|
|
|
|
/**
|
|
* Dumps file content to page.
|
|
*/
|
|
public function readfile() {
|
|
return $this->filesystem->readfile($this);
|
|
}
|
|
|
|
/**
|
|
* Returns file content as string.
|
|
*
|
|
* @return string content
|
|
*/
|
|
public function get_content() {
|
|
return $this->filesystem->get_content($this);
|
|
}
|
|
|
|
/**
|
|
* Copy content of file to given pathname.
|
|
*
|
|
* @param string $pathname real path to the new file
|
|
* @return bool success
|
|
*/
|
|
public function copy_content_to($pathname) {
|
|
return $this->filesystem->copy_content_from_storedfile($this, $pathname);
|
|
}
|
|
|
|
/**
|
|
* Copy content of file to temporary folder and returns file path
|
|
*
|
|
* @param string $dir name of the temporary directory
|
|
* @param string $fileprefix prefix of temporary file.
|
|
* @return string|bool path of temporary file or false.
|
|
*/
|
|
public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
|
|
$tempfile = false;
|
|
if (!$dir = make_temp_directory($dir)) {
|
|
return false;
|
|
}
|
|
if (!$tempfile = tempnam($dir, $fileprefix)) {
|
|
return false;
|
|
}
|
|
if (!$this->copy_content_to($tempfile)) {
|
|
// something went wrong
|
|
@unlink($tempfile);
|
|
return false;
|
|
}
|
|
return $tempfile;
|
|
}
|
|
|
|
/**
|
|
* List contents of archive.
|
|
*
|
|
* @param file_packer $packer file packer instance
|
|
* @return array of file infos
|
|
*/
|
|
public function list_files(file_packer $packer) {
|
|
return $this->filesystem->list_files($this, $packer);
|
|
}
|
|
|
|
/**
|
|
* Returns the total size (in bytes) of the contents of an archive.
|
|
*
|
|
* @param file_packer $packer file packer instance
|
|
* @return int|null total size in bytes
|
|
*/
|
|
public function get_total_content_size(file_packer $packer): ?int {
|
|
// Fetch the contents of the archive.
|
|
$files = $this->list_files($packer);
|
|
|
|
// Early return if the value of $files is not of type array.
|
|
// This can happen when the utility class is unable to open or read the contents of the archive.
|
|
if (!is_array($files)) {
|
|
return null;
|
|
}
|
|
|
|
return array_reduce($files, function ($contentsize, $file) {
|
|
return $contentsize + $file->size;
|
|
}, 0);
|
|
}
|
|
|
|
/**
|
|
* Extract file to given file path (real OS filesystem), existing files are overwritten.
|
|
*
|
|
* @param file_packer $packer file packer instance
|
|
* @param string $pathname target directory
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return array|bool list of processed files; false if error
|
|
*/
|
|
public function extract_to_pathname(file_packer $packer, $pathname,
|
|
file_progress $progress = null) {
|
|
return $this->filesystem->extract_to_pathname($this, $packer, $pathname, $progress);
|
|
}
|
|
|
|
/**
|
|
* Extract file to given file path (real OS filesystem), existing files are overwritten.
|
|
*
|
|
* @param file_packer $packer file packer instance
|
|
* @param int $contextid context ID
|
|
* @param string $component component
|
|
* @param string $filearea file area
|
|
* @param int $itemid item ID
|
|
* @param string $pathbase path base
|
|
* @param int $userid user ID
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return array|bool list of processed files; false if error
|
|
*/
|
|
public function extract_to_storage(file_packer $packer, $contextid,
|
|
$component, $filearea, $itemid, $pathbase, $userid = null, file_progress $progress = null) {
|
|
|
|
return $this->filesystem->extract_to_storage($this, $packer, $contextid, $component, $filearea,
|
|
$itemid, $pathbase, $userid, $progress);
|
|
}
|
|
|
|
/**
|
|
* Add file/directory into archive.
|
|
*
|
|
* @param file_archive $filearch file archive instance
|
|
* @param string $archivepath pathname in archive
|
|
* @return bool success
|
|
*/
|
|
public function archive_file(file_archive $filearch, $archivepath) {
|
|
if ($this->repository) {
|
|
$this->sync_external_file();
|
|
if ($this->compare_to_string('')) {
|
|
// This file is not stored locally - attempt to retrieve it from the repository.
|
|
// This may happen if the repository deliberately does not fetch files, or if there is a failure with the sync.
|
|
$fileinfo = $this->repository->get_file($this->get_reference());
|
|
if (isset($fileinfo['path'])) {
|
|
return $filearch->add_file_from_pathname($archivepath, $fileinfo['path']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
|
|
}
|
|
|
|
/**
|
|
* Returns information about image,
|
|
* information is determined from the file content
|
|
*
|
|
* @return mixed array with width, height and mimetype; false if not an image
|
|
*/
|
|
public function get_imageinfo() {
|
|
return $this->filesystem->get_imageinfo($this);
|
|
}
|
|
|
|
/**
|
|
* Verifies the file is a valid web image - gif, png and jpeg only.
|
|
*
|
|
* It should be ok to serve this image from server without any other security workarounds.
|
|
*
|
|
* @return bool true if file ok
|
|
*/
|
|
public function is_valid_image() {
|
|
$mimetype = $this->get_mimetype();
|
|
if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
|
|
return false;
|
|
}
|
|
if (!$info = $this->get_imageinfo()) {
|
|
return false;
|
|
}
|
|
if ($info['mimetype'] !== $mimetype) {
|
|
return false;
|
|
}
|
|
// ok, GD likes this image
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns parent directory, creates missing parents if needed.
|
|
*
|
|
* @return stored_file
|
|
*/
|
|
public function get_parent_directory() {
|
|
if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
|
|
//root dir does not have parent
|
|
return null;
|
|
}
|
|
|
|
if ($this->file_record->filename !== '.') {
|
|
return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $this->file_record->filepath);
|
|
}
|
|
|
|
$filepath = $this->file_record->filepath;
|
|
$filepath = trim($filepath, '/');
|
|
$dirs = explode('/', $filepath);
|
|
array_pop($dirs);
|
|
$filepath = implode('/', $dirs);
|
|
$filepath = ($filepath === '') ? '/' : "/$filepath/";
|
|
|
|
return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
|
|
}
|
|
|
|
/**
|
|
* Set synchronised content from file.
|
|
*
|
|
* @param string $path Path to the file.
|
|
*/
|
|
public function set_synchronised_content_from_file($path) {
|
|
$this->fs->synchronise_stored_file_from_file($this, $path, $this->file_record);
|
|
}
|
|
|
|
/**
|
|
* Set synchronised content from content.
|
|
*
|
|
* @param string $content File content.
|
|
*/
|
|
public function set_synchronised_content_from_string($content) {
|
|
$this->fs->synchronise_stored_file_from_string($this, $content, $this->file_record);
|
|
}
|
|
|
|
/**
|
|
* Synchronize file if it is a reference and needs synchronizing
|
|
*
|
|
* Updates contenthash and filesize
|
|
*/
|
|
public function sync_external_file() {
|
|
if (!empty($this->repository)) {
|
|
$this->repository->sync_reference($this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns context id of the file
|
|
*
|
|
* @return int context id
|
|
*/
|
|
public function get_contextid() {
|
|
return $this->file_record->contextid;
|
|
}
|
|
|
|
/**
|
|
* Returns component name - this is the owner of the areas,
|
|
* nothing else is allowed to read or modify the files directly!!
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_component() {
|
|
return $this->file_record->component;
|
|
}
|
|
|
|
/**
|
|
* Returns file area name, this divides files of one component into groups with different access control.
|
|
* All files in one area have the same access control.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_filearea() {
|
|
return $this->file_record->filearea;
|
|
}
|
|
|
|
/**
|
|
* Returns returns item id of file.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_itemid() {
|
|
return $this->file_record->itemid;
|
|
}
|
|
|
|
/**
|
|
* Returns file path - starts and ends with /, \ are not allowed.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_filepath() {
|
|
return $this->file_record->filepath;
|
|
}
|
|
|
|
/**
|
|
* Returns file name or '.' in case of directories.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_filename() {
|
|
return $this->file_record->filename;
|
|
}
|
|
|
|
/**
|
|
* Returns id of user who created the file.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_userid() {
|
|
return $this->file_record->userid;
|
|
}
|
|
|
|
/**
|
|
* Returns the size of file in bytes.
|
|
*
|
|
* @return int bytes
|
|
*/
|
|
public function get_filesize() {
|
|
$this->sync_external_file();
|
|
return $this->file_record->filesize;
|
|
}
|
|
|
|
/**
|
|
* Function stored_file::set_filesize() is deprecated. Please use stored_file::replace_file_with
|
|
*
|
|
* @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
|
|
* @see stored_file::replace_file_with()
|
|
*/
|
|
public function set_filesize($filesize) {
|
|
throw new coding_exception('Function stored_file::set_filesize() can not be used any more. ' .
|
|
'Please use stored_file::replace_file_with()');
|
|
}
|
|
|
|
/**
|
|
* Returns mime type of file.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_mimetype() {
|
|
return $this->file_record->mimetype;
|
|
}
|
|
|
|
/**
|
|
* Returns unix timestamp of file creation date.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_timecreated() {
|
|
return $this->file_record->timecreated;
|
|
}
|
|
|
|
/**
|
|
* Returns unix timestamp of last file modification.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_timemodified() {
|
|
$this->sync_external_file();
|
|
return $this->file_record->timemodified;
|
|
}
|
|
|
|
/**
|
|
* set timemodified
|
|
*
|
|
* @param int $timemodified
|
|
*/
|
|
public function set_timemodified($timemodified) {
|
|
$filerecord = new stdClass;
|
|
$filerecord->timemodified = $timemodified;
|
|
$this->update($filerecord);
|
|
}
|
|
|
|
/**
|
|
* Returns file status flag.
|
|
*
|
|
* @return int 0 means file OK, anything else is a problem and file can not be used
|
|
*/
|
|
public function get_status() {
|
|
return $this->file_record->status;
|
|
}
|
|
|
|
/**
|
|
* Returns file id.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_id() {
|
|
return $this->file_record->id;
|
|
}
|
|
|
|
/**
|
|
* Returns sha1 hash of file content.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_contenthash() {
|
|
$this->sync_external_file();
|
|
return $this->file_record->contenthash;
|
|
}
|
|
|
|
/**
|
|
* Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_pathnamehash() {
|
|
return $this->file_record->pathnamehash;
|
|
}
|
|
|
|
/**
|
|
* Returns the license type of the file, it is a short name referred from license table.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_license() {
|
|
return $this->file_record->license;
|
|
}
|
|
|
|
/**
|
|
* Set license
|
|
*
|
|
* @param string $license license
|
|
*/
|
|
public function set_license($license) {
|
|
$filerecord = new stdClass;
|
|
$filerecord->license = $license;
|
|
$this->update($filerecord);
|
|
}
|
|
|
|
/**
|
|
* Returns the author name of the file.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_author() {
|
|
return $this->file_record->author;
|
|
}
|
|
|
|
/**
|
|
* Set author
|
|
*
|
|
* @param string $author
|
|
*/
|
|
public function set_author($author) {
|
|
$filerecord = new stdClass;
|
|
$filerecord->author = $author;
|
|
$this->update($filerecord);
|
|
}
|
|
|
|
/**
|
|
* Returns the source of the file, usually it is a url.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_source() {
|
|
return $this->file_record->source;
|
|
}
|
|
|
|
/**
|
|
* Set license
|
|
*
|
|
* @param string $license license
|
|
*/
|
|
public function set_source($source) {
|
|
$filerecord = new stdClass;
|
|
$filerecord->source = $source;
|
|
$this->update($filerecord);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the sort order of file
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_sortorder() {
|
|
return $this->file_record->sortorder;
|
|
}
|
|
|
|
/**
|
|
* Set file sort order
|
|
*
|
|
* @param int $sortorder
|
|
* @return int
|
|
*/
|
|
public function set_sortorder($sortorder) {
|
|
$oldorder = $this->file_record->sortorder;
|
|
$filerecord = new stdClass;
|
|
$filerecord->sortorder = $sortorder;
|
|
$this->update($filerecord);
|
|
if (!$this->is_directory()) {
|
|
// Callback for file sort order change.
|
|
if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
|
|
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
foreach ($plugins as $pluginfunction) {
|
|
$pluginfunction($this->file_record, $oldorder, $sortorder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns repository id
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function get_repository_id() {
|
|
if (!empty($this->repository)) {
|
|
return $this->repository->id;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns repository type.
|
|
*
|
|
* @return mixed str|null the repository type or null if is not an external file
|
|
* @since Moodle 3.3
|
|
*/
|
|
public function get_repository_type() {
|
|
|
|
if (!empty($this->repository)) {
|
|
return $this->repository->get_typename();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* get reference file id
|
|
* @return int
|
|
*/
|
|
public function get_referencefileid() {
|
|
return $this->file_record->referencefileid;
|
|
}
|
|
|
|
/**
|
|
* Get reference last sync time
|
|
* @return int
|
|
*/
|
|
public function get_referencelastsync() {
|
|
return $this->file_record->referencelastsync;
|
|
}
|
|
|
|
/**
|
|
* Function stored_file::get_referencelifetime() is deprecated as reference
|
|
* life time is no longer stored in DB or returned by repository. Each
|
|
* repository should decide by itself when to synchronise the references.
|
|
*
|
|
* @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
|
|
* @see repository::sync_reference()
|
|
*/
|
|
public function get_referencelifetime() {
|
|
throw new coding_exception('Function stored_file::get_referencelifetime() can not be used any more. ' .
|
|
'See repository::sync_reference().');
|
|
}
|
|
/**
|
|
* Returns file reference
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_reference() {
|
|
return $this->file_record->reference;
|
|
}
|
|
|
|
/**
|
|
* Get human readable file reference information
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_reference_details() {
|
|
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 null|string $contenthash if set to null contenthash is not changed
|
|
* @param int $filesize new size of the file
|
|
* @param int $status new status of the file (0 means OK, 666 - source missing)
|
|
* @param int $timemodified last time modified of the source, if known
|
|
*/
|
|
public function set_synchronized($contenthash, $filesize, $status = 0, $timemodified = null) {
|
|
if (!$this->is_external_file()) {
|
|
return;
|
|
}
|
|
$now = time();
|
|
if ($contenthash === null) {
|
|
$contenthash = $this->file_record->contenthash;
|
|
}
|
|
if ($contenthash != $this->file_record->contenthash) {
|
|
$oldcontenthash = $this->file_record->contenthash;
|
|
}
|
|
// this will update all entries in {files} that have the same filereference id
|
|
$this->fs->update_references($this->file_record->referencefileid, $now, null, $contenthash, $filesize, $status, $timemodified);
|
|
// we don't need to call update() for this object, just set the values of changed fields
|
|
$this->file_record->contenthash = $contenthash;
|
|
$this->file_record->filesize = $filesize;
|
|
$this->file_record->status = $status;
|
|
$this->file_record->referencelastsync = $now;
|
|
if ($timemodified) {
|
|
$this->file_record->timemodified = $timemodified;
|
|
}
|
|
if (isset($oldcontenthash)) {
|
|
$this->filesystem->remove_file($oldcontenthash);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the error status for a file that could not be synchronised
|
|
*/
|
|
public function set_missingsource() {
|
|
$this->set_synchronized($this->file_record->contenthash, $this->file_record->filesize, 666);
|
|
}
|
|
|
|
/**
|
|
* Send file references
|
|
*
|
|
* @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($lifetime, $filter, $forcedownload, $options) {
|
|
$this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
|
|
}
|
|
|
|
/**
|
|
* Imports the contents of an external file into moodle filepool.
|
|
*
|
|
* @throws moodle_exception if file could not be downloaded or is too big
|
|
* @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
|
|
*/
|
|
public function import_external_file_contents($maxbytes = 0) {
|
|
if ($this->repository) {
|
|
$this->repository->import_external_file_contents($this, $maxbytes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a file relative to this file in the repository and sends it to the browser.
|
|
* Checks the function repository::supports_relative_file() to make sure it can be used.
|
|
*
|
|
* @param string $relativepath the relative path to the file we are trying to access
|
|
*/
|
|
public function send_relative_file($relativepath) {
|
|
if ($this->repository && $this->repository->supports_relative_file()) {
|
|
$relativepath = clean_param($relativepath, PARAM_PATH);
|
|
$this->repository->send_relative_file($this, $relativepath);
|
|
} else {
|
|
send_file_not_found();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a thumbnail for this stored_file.
|
|
*
|
|
* If the GD library has at least version 2 and PNG support is available, the returned data
|
|
* is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
|
|
* returns contents of a JPEG file with black background containing the thumbnail.
|
|
*
|
|
* @param int $width the width of the requested thumbnail
|
|
* @param int $height the height of the requested thumbnail
|
|
* @return string|bool false if a problem occurs, the thumbnail image data otherwise
|
|
*/
|
|
public function generate_image_thumbnail($width, $height) {
|
|
global $CFG;
|
|
require_once($CFG->libdir . '/gdlib.php');
|
|
|
|
if (empty($width) or empty($height)) {
|
|
return false;
|
|
}
|
|
|
|
$content = $this->get_content();
|
|
|
|
// Fetch the image information for this image.
|
|
$imageinfo = @getimagesizefromstring($content);
|
|
if (empty($imageinfo)) {
|
|
return false;
|
|
}
|
|
|
|
// Create a new image from the file.
|
|
$original = @imagecreatefromstring($content);
|
|
|
|
// Generate the thumbnail.
|
|
return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
|
|
}
|
|
|
|
/**
|
|
* Generate a resized image for this stored_file.
|
|
*
|
|
* @param int|null $width The desired width, or null to only use the height.
|
|
* @param int|null $height The desired height, or null to only use the width.
|
|
* @return string|false False when a problem occurs, else the image data.
|
|
*/
|
|
public function resize_image($width, $height) {
|
|
global $CFG;
|
|
require_once($CFG->libdir . '/gdlib.php');
|
|
|
|
$content = $this->get_content();
|
|
|
|
// Fetch the image information for this image.
|
|
$imageinfo = @getimagesizefromstring($content);
|
|
if (empty($imageinfo)) {
|
|
return false;
|
|
}
|
|
|
|
// Create a new image from the file.
|
|
$original = @imagecreatefromstring($content);
|
|
|
|
// Generate the resized image.
|
|
return resize_image_from_image($original, $imageinfo, $width, $height);
|
|
}
|
|
|
|
/**
|
|
* Check whether the supplied file is the same as this file.
|
|
*
|
|
* @param string $path The path to the file on disk
|
|
* @return boolean
|
|
*/
|
|
public function compare_to_path($path) {
|
|
return $this->get_contenthash() === file_storage::hash_from_path($path);
|
|
}
|
|
|
|
/**
|
|
* Check whether the supplied content is the same as this file.
|
|
*
|
|
* @param string $content The file content
|
|
* @return boolean
|
|
*/
|
|
public function compare_to_string($content) {
|
|
return $this->get_contenthash() === file_storage::hash_from_string($content);
|
|
}
|
|
|
|
/**
|
|
* Generate a rotated image for this stored_file based on exif information.
|
|
*
|
|
* @return array|false False when a problem occurs, else the image data and image size.
|
|
* @since Moodle 3.8
|
|
*/
|
|
public function rotate_image() {
|
|
$content = $this->get_content();
|
|
$mimetype = $this->get_mimetype();
|
|
|
|
if ($mimetype === "image/jpeg" && function_exists("exif_read_data")) {
|
|
$exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($content));
|
|
if (isset($exif['ExifImageWidth']) && isset($exif['ExifImageLength']) && isset($exif['Orientation'])) {
|
|
$rotation = [
|
|
3 => -180,
|
|
6 => -90,
|
|
8 => -270,
|
|
];
|
|
$orientation = $exif['Orientation'];
|
|
if ($orientation !== 1) {
|
|
$source = @imagecreatefromstring($content);
|
|
$data = @imagerotate($source, $rotation[$orientation], 0);
|
|
if (!empty($data)) {
|
|
if ($orientation == 1 || $orientation == 3) {
|
|
$size = [
|
|
'width' => $exif["ExifImageWidth"],
|
|
'height' => $exif["ExifImageLength"],
|
|
];
|
|
} else {
|
|
$size = [
|
|
'height' => $exif["ExifImageWidth"],
|
|
'width' => $exif["ExifImageLength"],
|
|
];
|
|
}
|
|
imagedestroy($source);
|
|
return [$data, $size];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return [false, false];
|
|
}
|
|
}
|