weekly release 2.3dev
[moodle.git] / lib / filestorage / file_storage.php
CommitLineData
25aebf09 1<?php
25aebf09 2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17
18/**
19 * Core file storage class definition.
20 *
d2b7803e
DC
21 * @package core_files
22 * @copyright 2008 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25aebf09 24 */
172dd12c 25
64f93798
PS
26defined('MOODLE_INTERNAL') || die();
27
28require_once("$CFG->libdir/filestorage/stored_file.php");
172dd12c 29
25aebf09 30/**
31 * File storage class used for low level access to stored files.
bf9ffe27 32 *
25aebf09 33 * Only owner of file area may use this class to access own files,
34 * for example only code in mod/assignment/* may access assignment
bf9ffe27
PS
35 * attachments. When some other part of moodle needs to access
36 * files of modules it has to use file_browser class instead or there
37 * has to be some callback API.
38 *
d2b7803e
DC
39 * @package core_files
40 * @category files
bf9ffe27
PS
41 * @copyright 2008 Petr Skoda {@link http://skodak.org}
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 * @since Moodle 2.0
25aebf09 44 */
172dd12c 45class file_storage {
bf9ffe27 46 /** @var string Directory with file contents */
172dd12c 47 private $filedir;
bf9ffe27 48 /** @var string Contents of deleted files not needed any more */
1aa01caf 49 private $trashdir;
3a1055a5
PS
50 /** @var string tempdir */
51 private $tempdir;
bf9ffe27 52 /** @var int Permissions for new directories */
1aa01caf 53 private $dirpermissions;
bf9ffe27 54 /** @var int Permissions for new files */
1aa01caf 55 private $filepermissions;
bf9ffe27 56
172dd12c 57 /**
d2b7803e 58 * Constructor - do not use directly use {@link get_file_storage()} call instead.
bf9ffe27 59 *
172dd12c 60 * @param string $filedir full path to pool directory
bf9ffe27 61 * @param string $trashdir temporary storage of deleted area
3a1055a5 62 * @param string $tempdir temporary storage of various files
bf9ffe27
PS
63 * @param int $dirpermissions new directory permissions
64 * @param int $filepermissions new file permissions
172dd12c 65 */
3a1055a5 66 public function __construct($filedir, $trashdir, $tempdir, $dirpermissions, $filepermissions) {
1aa01caf 67 $this->filedir = $filedir;
68 $this->trashdir = $trashdir;
3a1055a5 69 $this->tempdir = $tempdir;
1aa01caf 70 $this->dirpermissions = $dirpermissions;
71 $this->filepermissions = $filepermissions;
172dd12c 72
73 // make sure the file pool directory exists
74 if (!is_dir($this->filedir)) {
1aa01caf 75 if (!mkdir($this->filedir, $this->dirpermissions, true)) {
145a0a31 76 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
172dd12c 77 }
78 // place warning file in file pool root
1aa01caf 79 if (!file_exists($this->filedir.'/warning.txt')) {
80 file_put_contents($this->filedir.'/warning.txt',
81 'This directory contains the content of uploaded files and is controlled by Moodle code. Do not manually move, change or rename any of the files and subdirectories here.');
82 }
83 }
84 // make sure the file pool directory exists
85 if (!is_dir($this->trashdir)) {
86 if (!mkdir($this->trashdir, $this->dirpermissions, true)) {
87 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
88 }
172dd12c 89 }
90 }
91
92 /**
bf9ffe27
PS
93 * Calculates sha1 hash of unique full path name information.
94 *
95 * This hash is a unique file identifier - it is used to improve
96 * performance and overcome db index size limits.
97 *
d2b7803e
DC
98 * @param int $contextid context ID
99 * @param string $component component
100 * @param string $filearea file area
101 * @param int $itemid item ID
102 * @param string $filepath file path
103 * @param string $filename file name
bf9ffe27 104 * @return string sha1 hash
172dd12c 105 */
64f93798
PS
106 public static function get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename) {
107 return sha1("/$contextid/$component/$filearea/$itemid".$filepath.$filename);
172dd12c 108 }
109
110 /**
111 * Does this file exist?
bf9ffe27 112 *
d2b7803e
DC
113 * @param int $contextid context ID
114 * @param string $component component
115 * @param string $filearea file area
116 * @param int $itemid item ID
117 * @param string $filepath file path
118 * @param string $filename file name
172dd12c 119 * @return bool
120 */
64f93798 121 public function file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename) {
172dd12c 122 $filepath = clean_param($filepath, PARAM_PATH);
123 $filename = clean_param($filename, PARAM_FILE);
124
125 if ($filename === '') {
126 $filename = '.';
127 }
128
64f93798 129 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
172dd12c 130 return $this->file_exists_by_hash($pathnamehash);
131 }
132
133 /**
d2b7803e 134 * Whether or not the file exist
bf9ffe27 135 *
d2b7803e 136 * @param string $pathnamehash path name hash
172dd12c 137 * @return bool
138 */
139 public function file_exists_by_hash($pathnamehash) {
140 global $DB;
141
142 return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash));
143 }
144
693ef3a8
PS
145 /**
146 * Create instance of file class from database record.
147 *
148 * @param stdClass $file_record record from the files table
149 * @return stored_file instance of file abstraction class
150 */
151 public function get_file_instance(stdClass $file_record) {
152 return new stored_file($this, $file_record, $this->filedir);
153 }
154
c4d19c5a
DM
155 /**
156 * Returns an image file that represent the given stored file as a preview
157 *
158 * At the moment, only GIF, JPEG and PNG files are supported to have previews. In the
159 * future, the support for other mimetypes can be added, too (eg. generate an image
160 * preview of PDF, text documents etc).
161 *
162 * @param stored_file $file the file we want to preview
163 * @param string $mode preview mode, eg. 'thumb'
164 * @return stored_file|bool false if unable to create the preview, stored file otherwise
165 */
166 public function get_file_preview(stored_file $file, $mode) {
167
168 $context = context_system::instance();
169 $path = '/' . trim($mode, '/') . '/';
170 $preview = $this->get_file($context->id, 'core', 'preview', 0, $path, $file->get_contenthash());
171
172 if (!$preview) {
173 $preview = $this->create_file_preview($file, $mode);
174 if (!$preview) {
175 return false;
176 }
177 }
178
179 return $preview;
180 }
181
182 /**
183 * Generates a preview image for the stored file
184 *
185 * @param stored_file $file the file we want to preview
186 * @param string $mode preview mode, eg. 'thumb'
187 * @return stored_file|bool the newly created preview file or false
188 */
189 protected function create_file_preview(stored_file $file, $mode) {
190
191 $mimetype = $file->get_mimetype();
192
fe68aac7 193 if ($mimetype === 'image/gif' or $mimetype === 'image/jpeg' or $mimetype === 'image/png') {
c4d19c5a
DM
194 // make a preview of the image
195 $data = $this->create_imagefile_preview($file, $mode);
196
197 } else {
198 // unable to create the preview of this mimetype yet
199 return false;
200 }
201
202 if (empty($data)) {
203 return false;
204 }
205
206 // getimagesizefromstring() is available from PHP 5.4 but we need to support
207 // lower versions, so...
208 $tmproot = make_temp_directory('thumbnails');
94d10417 209 $tmpfilepath = $tmproot.'/'.$file->get_contenthash().'_'.$mode;
c4d19c5a
DM
210 file_put_contents($tmpfilepath, $data);
211 $imageinfo = getimagesize($tmpfilepath);
212 unlink($tmpfilepath);
213
214 $context = context_system::instance();
215
216 $record = array(
217 'contextid' => $context->id,
218 'component' => 'core',
219 'filearea' => 'preview',
220 'itemid' => 0,
221 'filepath' => '/' . trim($mode, '/') . '/',
222 'filename' => $file->get_contenthash(),
223 );
224
225 if ($imageinfo) {
226 $record['mimetype'] = $imageinfo['mime'];
227 }
228
229 return $this->create_file_from_string($record, $data);
230 }
231
232 /**
233 * Generates a preview for the stored image file
234 *
235 * @param stored_file $file the image we want to preview
236 * @param string $mode preview mode, eg. 'thumb'
237 * @return string|bool false if a problem occurs, the thumbnail image data otherwise
238 */
239 protected function create_imagefile_preview(stored_file $file, $mode) {
240 global $CFG;
241 require_once($CFG->libdir.'/gdlib.php');
242
243 $tmproot = make_temp_directory('thumbnails');
244 $tmpfilepath = $tmproot.'/'.$file->get_contenthash();
245 $file->copy_content_to($tmpfilepath);
246
fe68aac7 247 if ($mode === 'tinyicon') {
c4d19c5a
DM
248 $data = generate_image_thumbnail($tmpfilepath, 16, 16);
249
fe68aac7 250 } else if ($mode === 'thumb') {
c4d19c5a
DM
251 $data = generate_image_thumbnail($tmpfilepath, 90, 90);
252
253 } else {
254 throw new file_exception('storedfileproblem', 'Invalid preview mode requested');
255 }
256
257 unlink($tmpfilepath);
258
259 return $data;
260 }
261
172dd12c 262 /**
25aebf09 263 * Fetch file using local file id.
bf9ffe27 264 *
25aebf09 265 * Please do not rely on file ids, it is usually easier to use
266 * pathname hashes instead.
bf9ffe27 267 *
d2b7803e
DC
268 * @param int $fileid file ID
269 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 270 */
271 public function get_file_by_id($fileid) {
272 global $DB;
273
274 if ($file_record = $DB->get_record('files', array('id'=>$fileid))) {
693ef3a8 275 return $this->get_file_instance($file_record);
172dd12c 276 } else {
277 return false;
278 }
279 }
280
281 /**
282 * Fetch file using local file full pathname hash
bf9ffe27 283 *
d2b7803e
DC
284 * @param string $pathnamehash path name hash
285 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 286 */
287 public function get_file_by_hash($pathnamehash) {
288 global $DB;
289
290 if ($file_record = $DB->get_record('files', array('pathnamehash'=>$pathnamehash))) {
693ef3a8 291 return $this->get_file_instance($file_record);
172dd12c 292 } else {
293 return false;
294 }
295 }
296
297 /**
bf9ffe27
PS
298 * Fetch locally stored file.
299 *
d2b7803e
DC
300 * @param int $contextid context ID
301 * @param string $component component
302 * @param string $filearea file area
303 * @param int $itemid item ID
304 * @param string $filepath file path
305 * @param string $filename file name
306 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 307 */
64f93798 308 public function get_file($contextid, $component, $filearea, $itemid, $filepath, $filename) {
172dd12c 309 $filepath = clean_param($filepath, PARAM_PATH);
310 $filename = clean_param($filename, PARAM_FILE);
311
312 if ($filename === '') {
313 $filename = '.';
314 }
315
64f93798 316 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
172dd12c 317 return $this->get_file_by_hash($pathnamehash);
318 }
319
16741cac
PS
320 /**
321 * Are there any files (or directories)
d2b7803e
DC
322 *
323 * @param int $contextid context ID
324 * @param string $component component
325 * @param string $filearea file area
326 * @param bool|int $itemid item id or false if all items
327 * @param bool $ignoredirs whether or not ignore directories
16741cac
PS
328 * @return bool empty
329 */
330 public function is_area_empty($contextid, $component, $filearea, $itemid = false, $ignoredirs = true) {
331 global $DB;
332
333 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
334 $where = "contextid = :contextid AND component = :component AND filearea = :filearea";
335
336 if ($itemid !== false) {
337 $params['itemid'] = $itemid;
338 $where .= " AND itemid = :itemid";
339 }
340
341 if ($ignoredirs) {
342 $sql = "SELECT 'x'
343 FROM {files}
344 WHERE $where AND filename <> '.'";
345 } else {
346 $sql = "SELECT 'x'
347 FROM {files}
348 WHERE $where AND (filename <> '.' OR filepath <> '/')";
349 }
350
351 return !$DB->record_exists_sql($sql, $params);
352 }
353
172dd12c 354 /**
355 * Returns all area files (optionally limited by itemid)
bf9ffe27 356 *
d2b7803e
DC
357 * @param int $contextid context ID
358 * @param string $component component
359 * @param string $filearea file area
360 * @param int $itemid item ID or all files if not specified
361 * @param string $sort sort fields
362 * @param bool $includedirs whether or not include directories
cd5be217 363 * @return array of stored_files indexed by pathanmehash
172dd12c 364 */
16741cac 365 public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort="sortorder, itemid, filepath, filename", $includedirs = true) {
172dd12c 366 global $DB;
367
64f93798 368 $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
172dd12c 369 if ($itemid !== false) {
370 $conditions['itemid'] = $itemid;
371 }
372
373 $result = array();
374 $file_records = $DB->get_records('files', $conditions, $sort);
375 foreach ($file_records as $file_record) {
46fcbcf4 376 if (!$includedirs and $file_record->filename === '.') {
172dd12c 377 continue;
378 }
693ef3a8 379 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record);
172dd12c 380 }
381 return $result;
382 }
383
752b9f42 384 /**
385 * Returns array based tree structure of area files
bf9ffe27 386 *
d2b7803e
DC
387 * @param int $contextid context ID
388 * @param string $component component
389 * @param string $filearea file area
390 * @param int $itemid item ID
752b9f42 391 * @return array each dir represented by dirname, subdirs, files and dirfile array elements
392 */
64f93798 393 public function get_area_tree($contextid, $component, $filearea, $itemid) {
752b9f42 394 $result = array('dirname'=>'', 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array());
d3427cfe 395 $files = $this->get_area_files($contextid, $component, $filearea, $itemid, "sortorder, itemid, filepath, filename", true);
752b9f42 396 // first create directory structure
397 foreach ($files as $hash=>$dir) {
398 if (!$dir->is_directory()) {
399 continue;
400 }
401 unset($files[$hash]);
402 if ($dir->get_filepath() === '/') {
403 $result['dirfile'] = $dir;
404 continue;
405 }
406 $parts = explode('/', trim($dir->get_filepath(),'/'));
407 $pointer =& $result;
408 foreach ($parts as $part) {
3b607678 409 if ($part === '') {
410 continue;
411 }
752b9f42 412 if (!isset($pointer['subdirs'][$part])) {
413 $pointer['subdirs'][$part] = array('dirname'=>$part, 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array());
414 }
415 $pointer =& $pointer['subdirs'][$part];
416 }
417 $pointer['dirfile'] = $dir;
418 unset($pointer);
419 }
420 foreach ($files as $hash=>$file) {
421 $parts = explode('/', trim($file->get_filepath(),'/'));
422 $pointer =& $result;
423 foreach ($parts as $part) {
3b607678 424 if ($part === '') {
425 continue;
426 }
752b9f42 427 $pointer =& $pointer['subdirs'][$part];
428 }
429 $pointer['files'][$file->get_filename()] = $file;
430 unset($pointer);
431 }
432 return $result;
433 }
434
ee03a651 435 /**
bf9ffe27
PS
436 * Returns all files and optionally directories
437 *
d2b7803e
DC
438 * @param int $contextid context ID
439 * @param string $component component
440 * @param string $filearea file area
441 * @param int $itemid item ID
ee03a651 442 * @param int $filepath directory path
443 * @param bool $recursive include all subdirectories
46fcbcf4 444 * @param bool $includedirs include files and directories
d2b7803e 445 * @param string $sort sort fields
cd5be217 446 * @return array of stored_files indexed by pathanmehash
ee03a651 447 */
64f93798 448 public function get_directory_files($contextid, $component, $filearea, $itemid, $filepath, $recursive = false, $includedirs = true, $sort = "filepath, filename") {
ee03a651 449 global $DB;
450
64f93798 451 if (!$directory = $this->get_file($contextid, $component, $filearea, $itemid, $filepath, '.')) {
ee03a651 452 return array();
453 }
454
455 if ($recursive) {
456
46fcbcf4 457 $dirs = $includedirs ? "" : "AND filename <> '.'";
f8311def 458 $length = textlib::strlen($filepath);
ee03a651 459
460 $sql = "SELECT *
461 FROM {files}
64f93798 462 WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid
655bbf51 463 AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath
ee03a651 464 AND id <> :dirid
465 $dirs
466 ORDER BY $sort";
64f93798 467 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id());
ee03a651 468
469 $files = array();
470 $dirs = array();
471 $file_records = $DB->get_records_sql($sql, $params);
472 foreach ($file_records as $file_record) {
473 if ($file_record->filename == '.') {
693ef3a8 474 $dirs[$file_record->pathnamehash] = $this->get_file_instance($file_record);
ee03a651 475 } else {
693ef3a8 476 $files[$file_record->pathnamehash] = $this->get_file_instance($file_record);
ee03a651 477 }
478 }
479 $result = array_merge($dirs, $files);
480
481 } else {
482 $result = array();
64f93798 483 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id());
ee03a651 484
f8311def 485 $length = textlib::strlen($filepath);
ee03a651 486
46fcbcf4 487 if ($includedirs) {
ee03a651 488 $sql = "SELECT *
489 FROM {files}
64f93798 490 WHERE contextid = :contextid AND component = :component AND filearea = :filearea
ee03a651 491 AND itemid = :itemid AND filename = '.'
655bbf51 492 AND ".$DB->sql_substr("filepath", 1, $length)." = :filepath
ee03a651 493 AND id <> :dirid
494 ORDER BY $sort";
495 $reqlevel = substr_count($filepath, '/') + 1;
496 $file_records = $DB->get_records_sql($sql, $params);
497 foreach ($file_records as $file_record) {
498 if (substr_count($file_record->filepath, '/') !== $reqlevel) {
499 continue;
500 }
693ef3a8 501 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record);
ee03a651 502 }
503 }
504
505 $sql = "SELECT *
506 FROM {files}
64f93798 507 WHERE contextid = :contextid AND component = :component AND filearea = :filearea AND itemid = :itemid
ee03a651 508 AND filepath = :filepath AND filename <> '.'
509 ORDER BY $sort";
510
511 $file_records = $DB->get_records_sql($sql, $params);
512 foreach ($file_records as $file_record) {
693ef3a8 513 $result[$file_record->pathnamehash] = $this->get_file_instance($file_record);
ee03a651 514 }
515 }
516
517 return $result;
518 }
519
172dd12c 520 /**
bf9ffe27
PS
521 * Delete all area files (optionally limited by itemid).
522 *
d2b7803e
DC
523 * @param int $contextid context ID
524 * @param string $component component
525 * @param string $filearea file area or all areas in context if not specified
526 * @param int $itemid item ID or all files if not specified
bf9ffe27 527 * @return bool success
172dd12c 528 */
64f93798 529 public function delete_area_files($contextid, $component = false, $filearea = false, $itemid = false) {
172dd12c 530 global $DB;
531
6311eb61 532 $conditions = array('contextid'=>$contextid);
64f93798
PS
533 if ($component !== false) {
534 $conditions['component'] = $component;
535 }
6311eb61 536 if ($filearea !== false) {
537 $conditions['filearea'] = $filearea;
538 }
172dd12c 539 if ($itemid !== false) {
540 $conditions['itemid'] = $itemid;
541 }
542
172dd12c 543 $file_records = $DB->get_records('files', $conditions);
544 foreach ($file_records as $file_record) {
af7b3673 545 $this->get_file_instance($file_record)->delete();
172dd12c 546 }
547
bf9ffe27 548 return true; // BC only
172dd12c 549 }
550
af7b3673
TH
551 /**
552 * Delete all the files from certain areas where itemid is limited by an
553 * arbitrary bit of SQL.
554 *
555 * @param int $contextid the id of the context the files belong to. Must be given.
556 * @param string $component the owning component. Must be given.
557 * @param string $filearea the file area name. Must be given.
558 * @param string $itemidstest an SQL fragment that the itemid must match. Used
559 * in the query like WHERE itemid $itemidstest. Must used named parameters,
560 * and may not used named parameters called contextid, component or filearea.
561 * @param array $params any query params used by $itemidstest.
562 */
563 public function delete_area_files_select($contextid, $component,
564 $filearea, $itemidstest, array $params = null) {
565 global $DB;
566
567 $where = "contextid = :contextid
568 AND component = :component
569 AND filearea = :filearea
570 AND itemid $itemidstest";
571 $params['contextid'] = $contextid;
572 $params['component'] = $component;
573 $params['filearea'] = $filearea;
574
575 $file_records = $DB->get_recordset_select('files', $where, $params);
576 foreach ($file_records as $file_record) {
577 $this->get_file_instance($file_record)->delete();
578 }
579 $file_records->close();
580 }
581
d2af1014
TH
582 /**
583 * Move all the files in a file area from one context to another.
d2b7803e
DC
584 *
585 * @param int $oldcontextid the context the files are being moved from.
586 * @param int $newcontextid the context the files are being moved to.
d2af1014
TH
587 * @param string $component the plugin that these files belong to.
588 * @param string $filearea the name of the file area.
d2b7803e
DC
589 * @param int $itemid file item ID
590 * @return int the number of files moved, for information.
d2af1014
TH
591 */
592 public function move_area_files_to_new_context($oldcontextid, $newcontextid, $component, $filearea, $itemid = false) {
593 // Note, this code is based on some code that Petr wrote in
594 // forum_move_attachments in mod/forum/lib.php. I moved it here because
595 // I needed it in the question code too.
596 $count = 0;
597
598 $oldfiles = $this->get_area_files($oldcontextid, $component, $filearea, $itemid, 'id', false);
599 foreach ($oldfiles as $oldfile) {
600 $filerecord = new stdClass();
601 $filerecord->contextid = $newcontextid;
602 $this->create_file_from_storedfile($filerecord, $oldfile);
603 $count += 1;
604 }
605
606 if ($count) {
607 $this->delete_area_files($oldcontextid, $component, $filearea, $itemid);
608 }
609
610 return $count;
611 }
612
172dd12c 613 /**
bf9ffe27
PS
614 * Recursively creates directory.
615 *
d2b7803e
DC
616 * @param int $contextid context ID
617 * @param string $component component
618 * @param string $filearea file area
619 * @param int $itemid item ID
620 * @param string $filepath file path
621 * @param int $userid the user ID
172dd12c 622 * @return bool success
623 */
64f93798 624 public function create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid = null) {
172dd12c 625 global $DB;
626
627 // validate all parameters, we do not want any rubbish stored in database, right?
628 if (!is_number($contextid) or $contextid < 1) {
145a0a31 629 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 630 }
631
aff24313
PS
632 $component = clean_param($component, PARAM_COMPONENT);
633 if (empty($component)) {
64f93798
PS
634 throw new file_exception('storedfileproblem', 'Invalid component');
635 }
636
aff24313
PS
637 $filearea = clean_param($filearea, PARAM_AREA);
638 if (empty($filearea)) {
145a0a31 639 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 640 }
641
642 if (!is_number($itemid) or $itemid < 0) {
145a0a31 643 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 644 }
645
646 $filepath = clean_param($filepath, PARAM_PATH);
647 if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) {
648 // path must start and end with '/'
145a0a31 649 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 650 }
651
64f93798 652 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, '.');
172dd12c 653
654 if ($dir_info = $this->get_file_by_hash($pathnamehash)) {
655 return $dir_info;
656 }
657
658 static $contenthash = null;
659 if (!$contenthash) {
b48f3e06 660 $this->add_string_to_pool('');
172dd12c 661 $contenthash = sha1('');
662 }
663
664 $now = time();
665
ac6f1a82 666 $dir_record = new stdClass();
172dd12c 667 $dir_record->contextid = $contextid;
64f93798 668 $dir_record->component = $component;
172dd12c 669 $dir_record->filearea = $filearea;
670 $dir_record->itemid = $itemid;
671 $dir_record->filepath = $filepath;
672 $dir_record->filename = '.';
673 $dir_record->contenthash = $contenthash;
674 $dir_record->filesize = 0;
675
676 $dir_record->timecreated = $now;
677 $dir_record->timemodified = $now;
678 $dir_record->mimetype = null;
679 $dir_record->userid = $userid;
680
681 $dir_record->pathnamehash = $pathnamehash;
682
683 $DB->insert_record('files', $dir_record);
684 $dir_info = $this->get_file_by_hash($pathnamehash);
685
686 if ($filepath !== '/') {
687 //recurse to parent dirs
688 $filepath = trim($filepath, '/');
689 $filepath = explode('/', $filepath);
690 array_pop($filepath);
691 $filepath = implode('/', $filepath);
692 $filepath = ($filepath === '') ? '/' : "/$filepath/";
64f93798 693 $this->create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid);
172dd12c 694 }
695
696 return $dir_info;
697 }
698
699 /**
bf9ffe27
PS
700 * Add new local file based on existing local file.
701 *
d2b7803e
DC
702 * @param stdClass|array $file_record object or array describing changes
703 * @param stored_file|int $fileorid id or stored_file instance of the existing local file
bf9ffe27 704 * @return stored_file instance of newly created file
172dd12c 705 */
72d0aed6 706 public function create_file_from_storedfile($file_record, $fileorid) {
4fb2306e 707 global $DB;
172dd12c 708
72d0aed6 709 if ($fileorid instanceof stored_file) {
710 $fid = $fileorid->get_id();
711 } else {
712 $fid = $fileorid;
8eb1e0a1 713 }
714
ec8b711f 715 $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record!
716
172dd12c 717 unset($file_record['id']);
718 unset($file_record['filesize']);
719 unset($file_record['contenthash']);
8eb1e0a1 720 unset($file_record['pathnamehash']);
172dd12c 721
ebcac6c6 722 if (!$newrecord = $DB->get_record('files', array('id'=>$fid))) {
145a0a31 723 throw new file_exception('storedfileproblem', 'File does not exist');
172dd12c 724 }
725
726 unset($newrecord->id);
727
728 foreach ($file_record as $key=>$value) {
729 // validate all parameters, we do not want any rubbish stored in database, right?
730 if ($key == 'contextid' and (!is_number($value) or $value < 1)) {
145a0a31 731 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 732 }
733
64f93798 734 if ($key == 'component') {
aff24313
PS
735 $value = clean_param($value, PARAM_COMPONENT);
736 if (empty($value)) {
64f93798
PS
737 throw new file_exception('storedfileproblem', 'Invalid component');
738 }
739 }
740
172dd12c 741 if ($key == 'filearea') {
aff24313
PS
742 $value = clean_param($value, PARAM_AREA);
743 if (empty($value)) {
145a0a31 744 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 745 }
746 }
747
748 if ($key == 'itemid' and (!is_number($value) or $value < 0)) {
145a0a31 749 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 750 }
751
752
753 if ($key == 'filepath') {
754 $value = clean_param($value, PARAM_PATH);
00c32c54 755 if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
172dd12c 756 // path must start and end with '/'
145a0a31 757 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 758 }
759 }
760
761 if ($key == 'filename') {
762 $value = clean_param($value, PARAM_FILE);
763 if ($value === '') {
764 // path must start and end with '/'
145a0a31 765 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 766 }
767 }
768
260c4a5b
PS
769 if ($key === 'timecreated' or $key === 'timemodified') {
770 if (!is_number($value)) {
771 throw new file_exception('storedfileproblem', 'Invalid file '.$key);
772 }
773 if ($value < 0) {
774 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
775 $value = 0;
776 }
777 }
778
172dd12c 779 $newrecord->$key = $value;
780 }
781
64f93798 782 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 783
cd5be217 784 if ($newrecord->filename === '.') {
785 // special case - only this function supports directories ;-)
64f93798 786 $directory = $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
cd5be217 787 // update the existing directory with the new data
788 $newrecord->id = $directory->get_id();
b8ac7ece 789 $DB->update_record('files', $newrecord);
693ef3a8 790 return $this->get_file_instance($newrecord);
cd5be217 791 }
792
172dd12c 793 try {
794 $newrecord->id = $DB->insert_record('files', $newrecord);
8a680500 795 } catch (dml_exception $e) {
64f93798 796 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 797 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 798 }
799
64f93798 800 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 801
693ef3a8 802 return $this->get_file_instance($newrecord);
172dd12c 803 }
804
6e73ac42 805 /**
bf9ffe27
PS
806 * Add new local file.
807 *
d2b7803e
DC
808 * @param stdClass|array $file_record object or array describing file
809 * @param string $url the URL to the file
810 * @param array $options {@link download_file_content()} options
3a1055a5 811 * @param bool $usetempfile use temporary file for download, may prevent out of memory problems
d2b7803e 812 * @return stored_file
6e73ac42 813 */
3a1055a5 814 public function create_file_from_url($file_record, $url, array $options = NULL, $usetempfile = false) {
ec8b711f 815
816 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects
6e73ac42 817 $file_record = (object)$file_record; // we support arrays too
818
819 $headers = isset($options['headers']) ? $options['headers'] : null;
820 $postdata = isset($options['postdata']) ? $options['postdata'] : null;
821 $fullresponse = isset($options['fullresponse']) ? $options['fullresponse'] : false;
822 $timeout = isset($options['timeout']) ? $options['timeout'] : 300;
823 $connecttimeout = isset($options['connecttimeout']) ? $options['connecttimeout'] : 20;
824 $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false;
5f1c825d 825 $calctimeout = isset($options['calctimeout']) ? $options['calctimeout'] : false;
6e73ac42 826
6e73ac42 827 if (!isset($file_record->filename)) {
828 $parts = explode('/', $url);
829 $filename = array_pop($parts);
830 $file_record->filename = clean_param($filename, PARAM_FILE);
831 }
1dce6261
DC
832 $source = !empty($file_record->source) ? $file_record->source : $url;
833 $file_record->source = clean_param($source, PARAM_URL);
6e73ac42 834
3a1055a5 835 if ($usetempfile) {
c426ef3a 836 check_dir_exists($this->tempdir);
3a1055a5 837 $tmpfile = tempnam($this->tempdir, 'newfromurl');
60b5a2fe 838 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, $tmpfile, $calctimeout);
3a1055a5
PS
839 if ($content === false) {
840 throw new file_exception('storedfileproblem', 'Can not fetch file form URL');
841 }
842 try {
843 $newfile = $this->create_file_from_pathname($file_record, $tmpfile);
844 @unlink($tmpfile);
845 return $newfile;
846 } catch (Exception $e) {
847 @unlink($tmpfile);
848 throw $e;
849 }
850
851 } else {
60b5a2fe 852 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, NULL, $calctimeout);
3a1055a5
PS
853 if ($content === false) {
854 throw new file_exception('storedfileproblem', 'Can not fetch file form URL');
855 }
856 return $this->create_file_from_string($file_record, $content);
857 }
6e73ac42 858 }
859
172dd12c 860 /**
bf9ffe27
PS
861 * Add new local file.
862 *
d2b7803e
DC
863 * @param stdClass|array $file_record object or array describing file
864 * @param string $pathname path to file or content of file
865 * @return stored_file
172dd12c 866 */
867 public function create_file_from_pathname($file_record, $pathname) {
4fb2306e 868 global $DB;
172dd12c 869
ec8b711f 870 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects
172dd12c 871 $file_record = (object)$file_record; // we support arrays too
872
873 // validate all parameters, we do not want any rubbish stored in database, right?
874 if (!is_number($file_record->contextid) or $file_record->contextid < 1) {
145a0a31 875 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 876 }
877
aff24313
PS
878 $file_record->component = clean_param($file_record->component, PARAM_COMPONENT);
879 if (empty($file_record->component)) {
64f93798
PS
880 throw new file_exception('storedfileproblem', 'Invalid component');
881 }
882
aff24313
PS
883 $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA);
884 if (empty($file_record->filearea)) {
145a0a31 885 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 886 }
887
888 if (!is_number($file_record->itemid) or $file_record->itemid < 0) {
145a0a31 889 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 890 }
891
f79321f1
DC
892 if (!empty($file_record->sortorder)) {
893 if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) {
894 $file_record->sortorder = 0;
895 }
896 } else {
897 $file_record->sortorder = 0;
898 }
899
172dd12c 900 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH);
901 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) {
902 // path must start and end with '/'
145a0a31 903 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 904 }
905
906 $file_record->filename = clean_param($file_record->filename, PARAM_FILE);
907 if ($file_record->filename === '') {
e1dcb950 908 // filename must not be empty
145a0a31 909 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 910 }
911
912 $now = time();
260c4a5b
PS
913 if (isset($file_record->timecreated)) {
914 if (!is_number($file_record->timecreated)) {
915 throw new file_exception('storedfileproblem', 'Invalid file timecreated');
916 }
917 if ($file_record->timecreated < 0) {
918 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
919 $file_record->timecreated = 0;
920 }
921 } else {
922 $file_record->timecreated = $now;
923 }
924
925 if (isset($file_record->timemodified)) {
926 if (!is_number($file_record->timemodified)) {
927 throw new file_exception('storedfileproblem', 'Invalid file timemodified');
928 }
929 if ($file_record->timemodified < 0) {
930 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
931 $file_record->timemodified = 0;
932 }
933 } else {
934 $file_record->timemodified = $now;
935 }
172dd12c 936
ac6f1a82 937 $newrecord = new stdClass();
172dd12c 938
939 $newrecord->contextid = $file_record->contextid;
64f93798 940 $newrecord->component = $file_record->component;
172dd12c 941 $newrecord->filearea = $file_record->filearea;
942 $newrecord->itemid = $file_record->itemid;
943 $newrecord->filepath = $file_record->filepath;
944 $newrecord->filename = $file_record->filename;
945
260c4a5b
PS
946 $newrecord->timecreated = $file_record->timecreated;
947 $newrecord->timemodified = $file_record->timemodified;
172dd12c 948 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
949 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid;
4fb2306e
PS
950 $newrecord->source = empty($file_record->source) ? null : $file_record->source;
951 $newrecord->author = empty($file_record->author) ? null : $file_record->author;
952 $newrecord->license = empty($file_record->license) ? null : $file_record->license;
f79321f1 953 $newrecord->sortorder = $file_record->sortorder;
172dd12c 954
b48f3e06 955 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname);
172dd12c 956
64f93798 957 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 958
959 try {
960 $newrecord->id = $DB->insert_record('files', $newrecord);
8a680500 961 } catch (dml_exception $e) {
172dd12c 962 if ($newfile) {
ead14290 963 $this->deleted_file_cleanup($newrecord->contenthash);
172dd12c 964 }
64f93798 965 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 966 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 967 }
968
64f93798 969 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 970
693ef3a8 971 return $this->get_file_instance($newrecord);
172dd12c 972 }
973
974 /**
bf9ffe27
PS
975 * Add new local file.
976 *
d2b7803e 977 * @param stdClass|array $file_record object or array describing file
172dd12c 978 * @param string $content content of file
d2b7803e 979 * @return stored_file
172dd12c 980 */
981 public function create_file_from_string($file_record, $content) {
4fb2306e 982 global $DB;
172dd12c 983
ec8b711f 984 $file_record = (array)$file_record; //do not modify the submitted record, this cast unlinks objects
172dd12c 985 $file_record = (object)$file_record; // we support arrays too
986
987 // validate all parameters, we do not want any rubbish stored in database, right?
988 if (!is_number($file_record->contextid) or $file_record->contextid < 1) {
145a0a31 989 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 990 }
991
aff24313
PS
992 $file_record->component = clean_param($file_record->component, PARAM_COMPONENT);
993 if (empty($file_record->component)) {
64f93798
PS
994 throw new file_exception('storedfileproblem', 'Invalid component');
995 }
996
65e9eac0 997 $file_record->filearea = clean_param($file_record->filearea, PARAM_AREA);
aff24313 998 if (empty($file_record->filearea)) {
145a0a31 999 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 1000 }
1001
1002 if (!is_number($file_record->itemid) or $file_record->itemid < 0) {
145a0a31 1003 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 1004 }
1005
f79321f1
DC
1006 if (!empty($file_record->sortorder)) {
1007 if (!is_number($file_record->sortorder) or $file_record->sortorder < 0) {
1008 $file_record->sortorder = 0;
1009 }
1010 } else {
1011 $file_record->sortorder = 0;
1012 }
1013
172dd12c 1014 $file_record->filepath = clean_param($file_record->filepath, PARAM_PATH);
1015 if (strpos($file_record->filepath, '/') !== 0 or strrpos($file_record->filepath, '/') !== strlen($file_record->filepath)-1) {
1016 // path must start and end with '/'
145a0a31 1017 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 1018 }
1019
1020 $file_record->filename = clean_param($file_record->filename, PARAM_FILE);
1021 if ($file_record->filename === '') {
1022 // path must start and end with '/'
145a0a31 1023 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 1024 }
1025
1026 $now = time();
260c4a5b
PS
1027 if (isset($file_record->timecreated)) {
1028 if (!is_number($file_record->timecreated)) {
1029 throw new file_exception('storedfileproblem', 'Invalid file timecreated');
1030 }
1031 if ($file_record->timecreated < 0) {
1032 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
1033 $file_record->timecreated = 0;
1034 }
1035 } else {
1036 $file_record->timecreated = $now;
1037 }
1038
1039 if (isset($file_record->timemodified)) {
1040 if (!is_number($file_record->timemodified)) {
1041 throw new file_exception('storedfileproblem', 'Invalid file timemodified');
1042 }
1043 if ($file_record->timemodified < 0) {
1044 //NOTE: unfortunately I make a mistake when creating the "files" table, we can not have negative numbers there, on the other hand no file should be older than 1970, right? (skodak)
1045 $file_record->timemodified = 0;
1046 }
1047 } else {
1048 $file_record->timemodified = $now;
1049 }
172dd12c 1050
ac6f1a82 1051 $newrecord = new stdClass();
172dd12c 1052
1053 $newrecord->contextid = $file_record->contextid;
64f93798 1054 $newrecord->component = $file_record->component;
172dd12c 1055 $newrecord->filearea = $file_record->filearea;
1056 $newrecord->itemid = $file_record->itemid;
1057 $newrecord->filepath = $file_record->filepath;
1058 $newrecord->filename = $file_record->filename;
1059
260c4a5b
PS
1060 $newrecord->timecreated = $file_record->timecreated;
1061 $newrecord->timemodified = $file_record->timemodified;
172dd12c 1062 $newrecord->mimetype = empty($file_record->mimetype) ? mimeinfo('type', $file_record->filename) : $file_record->mimetype;
1063 $newrecord->userid = empty($file_record->userid) ? null : $file_record->userid;
4fb2306e
PS
1064 $newrecord->source = empty($file_record->source) ? null : $file_record->source;
1065 $newrecord->author = empty($file_record->author) ? null : $file_record->author;
1066 $newrecord->license = empty($file_record->license) ? null : $file_record->license;
f79321f1 1067 $newrecord->sortorder = $file_record->sortorder;
1dce6261 1068
b48f3e06 1069 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content);
172dd12c 1070
64f93798 1071 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 1072
1073 try {
1074 $newrecord->id = $DB->insert_record('files', $newrecord);
8a680500 1075 } catch (dml_exception $e) {
172dd12c 1076 if ($newfile) {
ead14290 1077 $this->deleted_file_cleanup($newrecord->contenthash);
172dd12c 1078 }
64f93798 1079 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 1080 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 1081 }
1082
64f93798 1083 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 1084
693ef3a8 1085 return $this->get_file_instance($newrecord);
172dd12c 1086 }
1087
797f19e8 1088 /**
1089 * Creates new image file from existing.
bf9ffe27 1090 *
d2b7803e
DC
1091 * @param stdClass|array $file_record object or array describing new file
1092 * @param int|stored_file $fid file id or stored file object
797f19e8 1093 * @param int $newwidth in pixels
1094 * @param int $newheight in pixels
d2b7803e 1095 * @param bool $keepaspectratio whether or not keep aspect ratio
bf9ffe27 1096 * @param int $quality depending on image type 0-100 for jpeg, 0-9 (0 means no compression) for png
d2b7803e 1097 * @return stored_file
797f19e8 1098 */
bf9ffe27 1099 public function convert_image($file_record, $fid, $newwidth = NULL, $newheight = NULL, $keepaspectratio = true, $quality = NULL) {
6b2f2184
AD
1100 if (!function_exists('imagecreatefromstring')) {
1101 //Most likely the GD php extension isn't installed
1102 //image conversion cannot succeed
1103 throw new file_exception('storedfileproblem', 'imagecreatefromstring() doesnt exist. The PHP extension "GD" must be installed for image conversion.');
1104 }
1105
797f19e8 1106 if ($fid instanceof stored_file) {
1107 $fid = $fid->get_id();
1108 }
1109
1110 $file_record = (array)$file_record; // we support arrays too, do not modify the submitted record!
1111
1112 if (!$file = $this->get_file_by_id($fid)) { // make sure file really exists and we we correct data
1113 throw new file_exception('storedfileproblem', 'File does not exist');
1114 }
1115
1116 if (!$imageinfo = $file->get_imageinfo()) {
1117 throw new file_exception('storedfileproblem', 'File is not an image');
1118 }
1119
1120 if (!isset($file_record['filename'])) {
0ea7fc25 1121 $file_record['filename'] = $file->get_filename();
797f19e8 1122 }
1123
1124 if (!isset($file_record['mimetype'])) {
1125 $file_record['mimetype'] = mimeinfo('type', $file_record['filename']);
1126 }
1127
1128 $width = $imageinfo['width'];
1129 $height = $imageinfo['height'];
1130 $mimetype = $imageinfo['mimetype'];
1131
1132 if ($keepaspectratio) {
1133 if (0 >= $newwidth and 0 >= $newheight) {
1134 // no sizes specified
1135 $newwidth = $width;
1136 $newheight = $height;
1137
1138 } else if (0 < $newwidth and 0 < $newheight) {
1139 $xheight = ($newwidth*($height/$width));
1140 if ($xheight < $newheight) {
1141 $newheight = (int)$xheight;
1142 } else {
1143 $newwidth = (int)($newheight*($width/$height));
1144 }
1145
1146 } else if (0 < $newwidth) {
1147 $newheight = (int)($newwidth*($height/$width));
1148
1149 } else { //0 < $newheight
1150 $newwidth = (int)($newheight*($width/$height));
1151 }
1152
1153 } else {
1154 if (0 >= $newwidth) {
1155 $newwidth = $width;
1156 }
1157 if (0 >= $newheight) {
1158 $newheight = $height;
1159 }
1160 }
1161
1162 $img = imagecreatefromstring($file->get_content());
1163 if ($height != $newheight or $width != $newwidth) {
1164 $newimg = imagecreatetruecolor($newwidth, $newheight);
1165 if (!imagecopyresized($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) {
1166 // weird
1167 throw new file_exception('storedfileproblem', 'Can not resize image');
1168 }
1169 imagedestroy($img);
1170 $img = $newimg;
1171 }
1172
1173 ob_start();
1174 switch ($file_record['mimetype']) {
1175 case 'image/gif':
1176 imagegif($img);
1177 break;
1178
1179 case 'image/jpeg':
1180 if (is_null($quality)) {
1181 imagejpeg($img);
1182 } else {
1183 imagejpeg($img, NULL, $quality);
1184 }
1185 break;
1186
1187 case 'image/png':
8bd49ec0 1188 $quality = (int)$quality;
797f19e8 1189 imagepng($img, NULL, $quality, NULL);
1190 break;
1191
1192 default:
1193 throw new file_exception('storedfileproblem', 'Unsupported mime type');
1194 }
1195
1196 $content = ob_get_contents();
1197 ob_end_clean();
1198 imagedestroy($img);
1199
1200 if (!$content) {
1201 throw new file_exception('storedfileproblem', 'Can not convert image');
1202 }
1203
1204 return $this->create_file_from_string($file_record, $content);
1205 }
1206
172dd12c 1207 /**
bf9ffe27
PS
1208 * Add file content to sha1 pool.
1209 *
172dd12c 1210 * @param string $pathname path to file
bf9ffe27
PS
1211 * @param string $contenthash sha1 hash of content if known (performance only)
1212 * @return array (contenthash, filesize, newfile)
172dd12c 1213 */
bf9ffe27 1214 public function add_file_to_pool($pathname, $contenthash = NULL) {
172dd12c 1215 if (!is_readable($pathname)) {
d610cb89 1216 throw new file_exception('storedfilecannotread', '', $pathname);
172dd12c 1217 }
1218
1219 if (is_null($contenthash)) {
1220 $contenthash = sha1_file($pathname);
1221 }
1222
1223 $filesize = filesize($pathname);
1224
1225 $hashpath = $this->path_from_hash($contenthash);
1226 $hashfile = "$hashpath/$contenthash";
1227
1228 if (file_exists($hashfile)) {
1229 if (filesize($hashfile) !== $filesize) {
1230 throw new file_pool_content_exception($contenthash);
1231 }
1232 $newfile = false;
1233
1234 } else {
1aa01caf 1235 if (!is_dir($hashpath)) {
1236 if (!mkdir($hashpath, $this->dirpermissions, true)) {
1237 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
1238 }
172dd12c 1239 }
1240 $newfile = true;
1241
6c0e2d08 1242 if (!copy($pathname, $hashfile)) {
d610cb89 1243 throw new file_exception('storedfilecannotread', '', $pathname);
172dd12c 1244 }
172dd12c 1245
1246 if (filesize($hashfile) !== $filesize) {
1247 @unlink($hashfile);
1248 throw new file_pool_content_exception($contenthash);
1249 }
1aa01caf 1250 chmod($hashfile, $this->filepermissions); // fix permissions if needed
172dd12c 1251 }
1252
1253
1254 return array($contenthash, $filesize, $newfile);
1255 }
1256
1257 /**
bf9ffe27
PS
1258 * Add string content to sha1 pool.
1259 *
172dd12c 1260 * @param string $content file content - binary string
bf9ffe27 1261 * @return array (contenthash, filesize, newfile)
172dd12c 1262 */
b48f3e06 1263 public function add_string_to_pool($content) {
172dd12c 1264 $contenthash = sha1($content);
1265 $filesize = strlen($content); // binary length
1266
1267 $hashpath = $this->path_from_hash($contenthash);
1268 $hashfile = "$hashpath/$contenthash";
1269
1270
1271 if (file_exists($hashfile)) {
1272 if (filesize($hashfile) !== $filesize) {
1273 throw new file_pool_content_exception($contenthash);
1274 }
1275 $newfile = false;
1276
1277 } else {
1aa01caf 1278 if (!is_dir($hashpath)) {
1279 if (!mkdir($hashpath, $this->dirpermissions, true)) {
1280 throw new file_exception('storedfilecannotcreatefiledirs'); // permission trouble
1281 }
172dd12c 1282 }
1283 $newfile = true;
1284
6c0e2d08 1285 file_put_contents($hashfile, $content);
172dd12c 1286
1287 if (filesize($hashfile) !== $filesize) {
1288 @unlink($hashfile);
1289 throw new file_pool_content_exception($contenthash);
1290 }
1aa01caf 1291 chmod($hashfile, $this->filepermissions); // fix permissions if needed
172dd12c 1292 }
1293
1294 return array($contenthash, $filesize, $newfile);
1295 }
1296
d5dd0540
PS
1297 /**
1298 * Serve file content using X-Sendfile header.
1299 * Please make sure that all headers are already sent
1300 * and the all access control checks passed.
1301 *
1302 * @param string $contenthash sah1 hash of the file content to be served
1303 * @return bool success
1304 */
1305 public function xsendfile($contenthash) {
1306 global $CFG;
1307 require_once("$CFG->libdir/xsendfilelib.php");
1308
1309 $hashpath = $this->path_from_hash($contenthash);
1310 return xsendfile("$hashpath/$contenthash");
1311 }
1312
172dd12c 1313 /**
bf9ffe27 1314 * Return path to file with given hash.
172dd12c 1315 *
17d9269f 1316 * NOTE: must not be public, files in pool must not be modified
172dd12c 1317 *
d2b7803e 1318 * @param string $contenthash content hash
172dd12c 1319 * @return string expected file location
1320 */
17d9269f 1321 protected function path_from_hash($contenthash) {
172dd12c 1322 $l1 = $contenthash[0].$contenthash[1];
1323 $l2 = $contenthash[2].$contenthash[3];
d0b6f92a 1324 return "$this->filedir/$l1/$l2";
172dd12c 1325 }
1326
1aa01caf 1327 /**
bf9ffe27 1328 * Return path to file with given hash.
1aa01caf 1329 *
1330 * NOTE: must not be public, files in pool must not be modified
1331 *
d2b7803e 1332 * @param string $contenthash content hash
1aa01caf 1333 * @return string expected file location
1334 */
1335 protected function trash_path_from_hash($contenthash) {
1336 $l1 = $contenthash[0].$contenthash[1];
1337 $l2 = $contenthash[2].$contenthash[3];
d0b6f92a 1338 return "$this->trashdir/$l1/$l2";
1aa01caf 1339 }
1340
1341 /**
bf9ffe27
PS
1342 * Tries to recover missing content of file from trash.
1343 *
d2b7803e 1344 * @param stored_file $file stored_file instance
1aa01caf 1345 * @return bool success
1346 */
1347 public function try_content_recovery($file) {
1348 $contenthash = $file->get_contenthash();
1349 $trashfile = $this->trash_path_from_hash($contenthash).'/'.$contenthash;
1350 if (!is_readable($trashfile)) {
1351 if (!is_readable($this->trashdir.'/'.$contenthash)) {
1352 return false;
1353 }
1354 // nice, at least alternative trash file in trash root exists
1355 $trashfile = $this->trashdir.'/'.$contenthash;
1356 }
1357 if (filesize($trashfile) != $file->get_filesize() or sha1_file($trashfile) != $contenthash) {
1358 //weird, better fail early
1359 return false;
1360 }
1361 $contentdir = $this->path_from_hash($contenthash);
1362 $contentfile = $contentdir.'/'.$contenthash;
1363 if (file_exists($contentfile)) {
1364 //strange, no need to recover anything
1365 return true;
1366 }
1367 if (!is_dir($contentdir)) {
1368 if (!mkdir($contentdir, $this->dirpermissions, true)) {
1369 return false;
1370 }
1371 }
1372 return rename($trashfile, $contentfile);
1373 }
1374
172dd12c 1375 /**
bf9ffe27
PS
1376 * Marks pool file as candidate for deleting.
1377 *
1378 * DO NOT call directly - reserved for core!!
1379 *
172dd12c 1380 * @param string $contenthash
1381 */
1aa01caf 1382 public function deleted_file_cleanup($contenthash) {
172dd12c 1383 global $DB;
1384
1aa01caf 1385 //Note: this section is critical - in theory file could be reused at the same
1386 // time, if this happens we can still recover the file from trash
1387 if ($DB->record_exists('files', array('contenthash'=>$contenthash))) {
1388 // file content is still used
1389 return;
1390 }
1391 //move content file to trash
1392 $contentfile = $this->path_from_hash($contenthash).'/'.$contenthash;
1393 if (!file_exists($contentfile)) {
1394 //weird, but no problem
172dd12c 1395 return;
1396 }
1aa01caf 1397 $trashpath = $this->trash_path_from_hash($contenthash);
1398 $trashfile = $trashpath.'/'.$contenthash;
1399 if (file_exists($trashfile)) {
1400 // we already have this content in trash, no need to move it there
1401 unlink($contentfile);
1402 return;
1403 }
1404 if (!is_dir($trashpath)) {
1405 mkdir($trashpath, $this->dirpermissions, true);
1406 }
1407 rename($contentfile, $trashfile);
1408 chmod($trashfile, $this->filepermissions); // fix permissions if needed
172dd12c 1409 }
1410
1411 /**
1412 * Cron cleanup job.
1413 */
1414 public function cron() {
a881f970 1415 global $CFG, $DB;
64f93798 1416
2e69ea4a
PS
1417 // find out all stale draft areas (older than 4 days) and purge them
1418 // those are identified by time stamp of the /. root dir
1419 mtrace('Deleting old draft files... ', '');
1420 $old = time() - 60*60*24*4;
1421 $sql = "SELECT *
1422 FROM {files}
1423 WHERE component = 'user' AND filearea = 'draft' AND filepath = '/' AND filename = '.'
1424 AND timecreated < :old";
1425 $rs = $DB->get_recordset_sql($sql, array('old'=>$old));
1426 foreach ($rs as $dir) {
1427 $this->delete_area_files($dir->contextid, $dir->component, $dir->filearea, $dir->itemid);
1428 }
be981316 1429 $rs->close();
b5541735 1430 mtrace('done.');
64f93798 1431
9120a462
DM
1432 // remove orphaned preview files (that is files in the core preview filearea without
1433 // the existing original file)
1434 mtrace('Deleting orphaned preview files... ', '');
1435 $sql = "SELECT p.*
1436 FROM {files} p
1437 LEFT JOIN {files} o ON (p.filename = o.contenthash)
1438 WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'preview' AND p.itemid = 0
f7eec6ce 1439 AND o.id IS NULL";
9120a462
DM
1440 $syscontext = context_system::instance();
1441 $rs = $DB->get_recordset_sql($sql, array($syscontext->id));
1442 foreach ($rs as $orphan) {
f7eec6ce
DM
1443 $file = $this->get_file_instance($orphan);
1444 if (!$file->is_directory()) {
1445 $file->delete();
1446 }
9120a462
DM
1447 }
1448 $rs->close();
1449 mtrace('done.');
1450
1aa01caf 1451 // remove trash pool files once a day
1452 // if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php
1453 if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) {
1454 require_once($CFG->libdir.'/filelib.php');
a881f970
SH
1455 // Delete files that are associated with a context that no longer exists.
1456 mtrace('Cleaning up files from deleted contexts... ', '');
1457 $sql = "SELECT DISTINCT f.contextid
1458 FROM {files} f
1459 LEFT OUTER JOIN {context} c ON f.contextid = c.id
1460 WHERE c.id IS NULL";
be981316
EL
1461 $rs = $DB->get_recordset_sql($sql);
1462 if ($rs->valid()) {
a881f970
SH
1463 $fs = get_file_storage();
1464 foreach ($rs as $ctx) {
1465 $fs->delete_area_files($ctx->contextid);
1466 }
1467 }
be981316 1468 $rs->close();
a881f970
SH
1469 mtrace('done.');
1470
1aa01caf 1471 mtrace('Deleting trash files... ', '');
1472 fulldelete($this->trashdir);
1473 set_config('fileslastcleanup', time());
1474 mtrace('done.');
172dd12c 1475 }
1476 }
1477}
bf9ffe27 1478