MDL-60318 filestorage: Fixed undefined method move_to_trash()
[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 {
16a34ae1 46
3a1055a5
PS
47 /** @var string tempdir */
48 private $tempdir;
16a34ae1
AN
49
50 /** @var file_system filesystem */
51 private $filesystem;
52
172dd12c 53 /**
d2b7803e 54 * Constructor - do not use directly use {@link get_file_storage()} call instead.
172dd12c 55 */
16a34ae1
AN
56 public function __construct() {
57 // The tempdir must always remain on disk, but shared between all ndoes in a cluster. Its content is not subject
58 // to the file_system abstraction.
59 $this->tempdir = make_temp_directory('filestorage');
eb459f71 60
16a34ae1
AN
61 $this->setup_file_system();
62 }
172dd12c 63
16a34ae1
AN
64 /**
65 * Complete setup procedure for the file_system component.
66 *
67 * @return file_system
68 */
69 public function setup_file_system() {
70 global $CFG;
71 if ($this->filesystem === null) {
72 require_once($CFG->libdir . '/filestorage/file_system.php');
73 if (!empty($CFG->alternative_file_system_class)) {
74 $class = $CFG->alternative_file_system_class;
75 } else {
76 // The default file_system is the filedir.
77 require_once($CFG->libdir . '/filestorage/file_system_filedir.php');
78 $class = file_system_filedir::class;
1aa01caf 79 }
16a34ae1 80 $this->filesystem = new $class();
172dd12c 81 }
16a34ae1
AN
82
83 return $this->filesystem;
84 }
85
86 /**
87 * Return the file system instance.
88 *
89 * @return file_system
90 */
91 public function get_file_system() {
92 return $this->filesystem;
172dd12c 93 }
94
95 /**
bf9ffe27
PS
96 * Calculates sha1 hash of unique full path name information.
97 *
98 * This hash is a unique file identifier - it is used to improve
99 * performance and overcome db index size limits.
100 *
d2b7803e
DC
101 * @param int $contextid context ID
102 * @param string $component component
103 * @param string $filearea file area
104 * @param int $itemid item ID
105 * @param string $filepath file path
106 * @param string $filename file name
bf9ffe27 107 * @return string sha1 hash
172dd12c 108 */
64f93798
PS
109 public static function get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename) {
110 return sha1("/$contextid/$component/$filearea/$itemid".$filepath.$filename);
172dd12c 111 }
112
113 /**
114 * Does this file exist?
bf9ffe27 115 *
d2b7803e
DC
116 * @param int $contextid context ID
117 * @param string $component component
118 * @param string $filearea file area
119 * @param int $itemid item ID
120 * @param string $filepath file path
121 * @param string $filename file name
172dd12c 122 * @return bool
123 */
64f93798 124 public function file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename) {
172dd12c 125 $filepath = clean_param($filepath, PARAM_PATH);
126 $filename = clean_param($filename, PARAM_FILE);
127
128 if ($filename === '') {
129 $filename = '.';
130 }
131
64f93798 132 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
172dd12c 133 return $this->file_exists_by_hash($pathnamehash);
134 }
135
136 /**
d2b7803e 137 * Whether or not the file exist
bf9ffe27 138 *
d2b7803e 139 * @param string $pathnamehash path name hash
172dd12c 140 * @return bool
141 */
142 public function file_exists_by_hash($pathnamehash) {
143 global $DB;
144
145 return $DB->record_exists('files', array('pathnamehash'=>$pathnamehash));
146 }
147
693ef3a8
PS
148 /**
149 * Create instance of file class from database record.
150 *
04e3b007 151 * @param stdClass $filerecord record from the files table left join files_reference table
693ef3a8
PS
152 * @return stored_file instance of file abstraction class
153 */
67233725 154 public function get_file_instance(stdClass $filerecord) {
16a34ae1 155 $storedfile = new stored_file($this, $filerecord);
67233725 156 return $storedfile;
693ef3a8
PS
157 }
158
2e76c14e
DW
159 /**
160 * Get converted document.
161 *
162 * Get an alternate version of the specified document, if it is possible to convert.
163 *
164 * @param stored_file $file the file we want to preview
165 * @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
300852ee 166 * @param boolean $forcerefresh If true, the file will be converted every time (not cached).
2e76c14e
DW
167 * @return stored_file|bool false if unable to create the conversion, stored file otherwise
168 */
300852ee 169 public function get_converted_document(stored_file $file, $format, $forcerefresh = false) {
15681687
AN
170 debugging('The get_converted_document function has been deprecated and the unoconv functions been removed. '
171 . 'The file has not been converted. '
172 . 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
2e76c14e 173
15681687 174 return false;
2e76c14e
DW
175 }
176
177 /**
178 * Verify the format is supported.
179 *
180 * @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
181 * @return bool - True if the format is supported for input.
182 */
1356d851 183 protected function is_format_supported_by_unoconv($format) {
15681687
AN
184 debugging('The is_format_supported_by_unoconv function has been deprecated and the unoconv functions been removed. '
185 . 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
1356d851 186
15681687 187 return false;
2e76c14e
DW
188 }
189
0eecf876
AG
190 /**
191 * Check if the installed version of unoconv is supported.
192 *
193 * @return bool true if the present version is supported, false otherwise.
194 */
195 public static function can_convert_documents() {
15681687
AN
196 debugging('The can_convert_documents function has been deprecated and the unoconv functions been removed. '
197 . 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
198
37eafbb6 199 return false;
0eecf876
AG
200 }
201
202 /**
300852ee 203 * Regenerate the test pdf and send it direct to the browser.
0eecf876
AG
204 */
205 public static function send_test_pdf() {
15681687
AN
206 debugging('The send_test_pdf function has been deprecated and the unoconv functions been removed. '
207 . 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
0eecf876 208
15681687 209 return false;
0eecf876
AG
210 }
211
212 /**
213 * Check if unoconv configured path is correct and working.
214 *
215 * @return \stdClass an object with the test status and the UNOCONVPATH_ constant message.
216 */
217 public static function test_unoconv_path() {
15681687
AN
218 debugging('The test_unoconv_path function has been deprecated and the unoconv functions been removed. '
219 . 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
0eecf876 220
15681687 221 return false;
2e76c14e
DW
222 }
223
c4d19c5a
DM
224 /**
225 * Returns an image file that represent the given stored file as a preview
226 *
227 * At the moment, only GIF, JPEG and PNG files are supported to have previews. In the
228 * future, the support for other mimetypes can be added, too (eg. generate an image
229 * preview of PDF, text documents etc).
230 *
231 * @param stored_file $file the file we want to preview
232 * @param string $mode preview mode, eg. 'thumb'
233 * @return stored_file|bool false if unable to create the preview, stored file otherwise
234 */
235 public function get_file_preview(stored_file $file, $mode) {
236
237 $context = context_system::instance();
238 $path = '/' . trim($mode, '/') . '/';
239 $preview = $this->get_file($context->id, 'core', 'preview', 0, $path, $file->get_contenthash());
240
241 if (!$preview) {
242 $preview = $this->create_file_preview($file, $mode);
243 if (!$preview) {
244 return false;
245 }
246 }
247
248 return $preview;
249 }
250
d7d69396
FM
251 /**
252 * Return an available file name.
253 *
254 * This will return the next available file name in the area, adding/incrementing a suffix
255 * of the file, ie: file.txt > file (1).txt > file (2).txt > etc...
256 *
257 * If the file name passed is available without modification, it is returned as is.
258 *
259 * @param int $contextid context ID.
260 * @param string $component component.
261 * @param string $filearea file area.
262 * @param int $itemid area item ID.
263 * @param string $filepath the file path.
264 * @param string $filename the file name.
265 * @return string available file name.
266 * @throws coding_exception if the file name is invalid.
5bcfd504 267 * @since Moodle 2.5
d7d69396
FM
268 */
269 public function get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, $filename) {
270 global $DB;
271
272 // Do not accept '.' or an empty file name (zero is acceptable).
273 if ($filename == '.' || (empty($filename) && !is_numeric($filename))) {
274 throw new coding_exception('Invalid file name passed', $filename);
275 }
276
277 // The file does not exist, we return the same file name.
278 if (!$this->file_exists($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
279 return $filename;
280 }
281
282 // Trying to locate a file name using the used pattern. We remove the used pattern from the file name first.
283 $pathinfo = pathinfo($filename);
284 $basename = $pathinfo['filename'];
285 $matches = array();
286 if (preg_match('~^(.+) \(([0-9]+)\)$~', $basename, $matches)) {
287 $basename = $matches[1];
288 }
289
290 $filenamelike = $DB->sql_like_escape($basename) . ' (%)';
291 if (isset($pathinfo['extension'])) {
292 $filenamelike .= '.' . $DB->sql_like_escape($pathinfo['extension']);
293 }
294
295 $filenamelikesql = $DB->sql_like('f.filename', ':filenamelike');
296 $filenamelen = $DB->sql_length('f.filename');
297 $sql = "SELECT filename
298 FROM {files} f
299 WHERE
300 f.contextid = :contextid AND
301 f.component = :component AND
302 f.filearea = :filearea AND
303 f.itemid = :itemid AND
304 f.filepath = :filepath AND
305 $filenamelikesql
306 ORDER BY
307 $filenamelen DESC,
308 f.filename DESC";
309 $params = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid,
310 'filepath' => $filepath, 'filenamelike' => $filenamelike);
311 $results = $DB->get_fieldset_sql($sql, $params, IGNORE_MULTIPLE);
312
313 // Loop over the results to make sure we are working on a valid file name. Because 'file (1).txt' and 'file (copy).txt'
314 // would both be returned, but only the one only containing digits should be used.
315 $number = 1;
316 foreach ($results as $result) {
317 $resultbasename = pathinfo($result, PATHINFO_FILENAME);
318 $matches = array();
319 if (preg_match('~^(.+) \(([0-9]+)\)$~', $resultbasename, $matches)) {
320 $number = $matches[2] + 1;
321 break;
322 }
323 }
324
325 // Constructing the new filename.
326 $newfilename = $basename . ' (' . $number . ')';
327 if (isset($pathinfo['extension'])) {
328 $newfilename .= '.' . $pathinfo['extension'];
329 }
330
331 return $newfilename;
332 }
333
da4b1ee4
MG
334 /**
335 * Return an available directory name.
336 *
337 * This will return the next available directory name in the area, adding/incrementing a suffix
338 * of the last portion of path, ie: /path/ > /path (1)/ > /path (2)/ > etc...
339 *
340 * If the file path passed is available without modification, it is returned as is.
341 *
342 * @param int $contextid context ID.
343 * @param string $component component.
344 * @param string $filearea file area.
345 * @param int $itemid area item ID.
346 * @param string $suggestedpath the suggested file path.
347 * @return string available file path
5bcfd504 348 * @since Moodle 2.5
da4b1ee4
MG
349 */
350 public function get_unused_dirname($contextid, $component, $filearea, $itemid, $suggestedpath) {
351 global $DB;
352
353 // Ensure suggestedpath has trailing '/'
354 $suggestedpath = rtrim($suggestedpath, '/'). '/';
355
356 // The directory does not exist, we return the same file path.
357 if (!$this->file_exists($contextid, $component, $filearea, $itemid, $suggestedpath, '.')) {
358 return $suggestedpath;
359 }
360
361 // Trying to locate a file path using the used pattern. We remove the used pattern from the path first.
362 if (preg_match('~^(/.+) \(([0-9]+)\)/$~', $suggestedpath, $matches)) {
363 $suggestedpath = $matches[1]. '/';
364 }
365
366 $filepathlike = $DB->sql_like_escape(rtrim($suggestedpath, '/')) . ' (%)/';
367
368 $filepathlikesql = $DB->sql_like('f.filepath', ':filepathlike');
369 $filepathlen = $DB->sql_length('f.filepath');
370 $sql = "SELECT filepath
371 FROM {files} f
372 WHERE
373 f.contextid = :contextid AND
374 f.component = :component AND
375 f.filearea = :filearea AND
376 f.itemid = :itemid AND
377 f.filename = :filename AND
378 $filepathlikesql
379 ORDER BY
380 $filepathlen DESC,
381 f.filepath DESC";
382 $params = array('contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid,
383 'filename' => '.', 'filepathlike' => $filepathlike);
384 $results = $DB->get_fieldset_sql($sql, $params, IGNORE_MULTIPLE);
385
386 // Loop over the results to make sure we are working on a valid file path. Because '/path (1)/' and '/path (copy)/'
387 // would both be returned, but only the one only containing digits should be used.
388 $number = 1;
389 foreach ($results as $result) {
390 if (preg_match('~ \(([0-9]+)\)/$~', $result, $matches)) {
391 $number = (int)($matches[1]) + 1;
392 break;
393 }
394 }
395
396 return rtrim($suggestedpath, '/'). ' (' . $number . ')/';
397 }
398
c4d19c5a
DM
399 /**
400 * Generates a preview image for the stored file
401 *
402 * @param stored_file $file the file we want to preview
403 * @param string $mode preview mode, eg. 'thumb'
404 * @return stored_file|bool the newly created preview file or false
405 */
406 protected function create_file_preview(stored_file $file, $mode) {
407
408 $mimetype = $file->get_mimetype();
409
fe68aac7 410 if ($mimetype === 'image/gif' or $mimetype === 'image/jpeg' or $mimetype === 'image/png') {
c4d19c5a
DM
411 // make a preview of the image
412 $data = $this->create_imagefile_preview($file, $mode);
413
414 } else {
415 // unable to create the preview of this mimetype yet
416 return false;
417 }
418
419 if (empty($data)) {
420 return false;
421 }
422
c4d19c5a 423 $context = context_system::instance();
c4d19c5a
DM
424 $record = array(
425 'contextid' => $context->id,
426 'component' => 'core',
427 'filearea' => 'preview',
428 'itemid' => 0,
429 'filepath' => '/' . trim($mode, '/') . '/',
430 'filename' => $file->get_contenthash(),
431 );
432
5d706151 433 $imageinfo = getimagesizefromstring($data);
c4d19c5a
DM
434 if ($imageinfo) {
435 $record['mimetype'] = $imageinfo['mime'];
436 }
437
438 return $this->create_file_from_string($record, $data);
439 }
440
441 /**
442 * Generates a preview for the stored image file
443 *
444 * @param stored_file $file the image we want to preview
445 * @param string $mode preview mode, eg. 'thumb'
446 * @return string|bool false if a problem occurs, the thumbnail image data otherwise
447 */
448 protected function create_imagefile_preview(stored_file $file, $mode) {
449 global $CFG;
450 require_once($CFG->libdir.'/gdlib.php');
451
fe68aac7 452 if ($mode === 'tinyicon') {
2b53b13f 453 $data = $file->generate_image_thumbnail(24, 24);
c4d19c5a 454
fe68aac7 455 } else if ($mode === 'thumb') {
2b53b13f 456 $data = $file->generate_image_thumbnail(90, 90);
c4d19c5a 457
8f5cbbd6 458 } else if ($mode === 'bigthumb') {
2b53b13f 459 $data = $file->generate_image_thumbnail(250, 250);
8f5cbbd6 460
c4d19c5a
DM
461 } else {
462 throw new file_exception('storedfileproblem', 'Invalid preview mode requested');
463 }
464
c4d19c5a
DM
465 return $data;
466 }
467
172dd12c 468 /**
25aebf09 469 * Fetch file using local file id.
bf9ffe27 470 *
25aebf09 471 * Please do not rely on file ids, it is usually easier to use
472 * pathname hashes instead.
bf9ffe27 473 *
d2b7803e
DC
474 * @param int $fileid file ID
475 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 476 */
477 public function get_file_by_id($fileid) {
478 global $DB;
479
3447100c 480 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725
DC
481 FROM {files} f
482 LEFT JOIN {files_reference} r
483 ON f.referencefileid = r.id
484 WHERE f.id = ?";
485 if ($filerecord = $DB->get_record_sql($sql, array($fileid))) {
486 return $this->get_file_instance($filerecord);
172dd12c 487 } else {
488 return false;
489 }
490 }
491
492 /**
493 * Fetch file using local file full pathname hash
bf9ffe27 494 *
d2b7803e
DC
495 * @param string $pathnamehash path name hash
496 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 497 */
498 public function get_file_by_hash($pathnamehash) {
499 global $DB;
500
3447100c 501 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725
DC
502 FROM {files} f
503 LEFT JOIN {files_reference} r
504 ON f.referencefileid = r.id
505 WHERE f.pathnamehash = ?";
506 if ($filerecord = $DB->get_record_sql($sql, array($pathnamehash))) {
507 return $this->get_file_instance($filerecord);
172dd12c 508 } else {
509 return false;
510 }
511 }
512
513 /**
bf9ffe27
PS
514 * Fetch locally stored file.
515 *
d2b7803e
DC
516 * @param int $contextid context ID
517 * @param string $component component
518 * @param string $filearea file area
519 * @param int $itemid item ID
520 * @param string $filepath file path
521 * @param string $filename file name
522 * @return stored_file|bool stored_file instance if exists, false if not
172dd12c 523 */
64f93798 524 public function get_file($contextid, $component, $filearea, $itemid, $filepath, $filename) {
172dd12c 525 $filepath = clean_param($filepath, PARAM_PATH);
526 $filename = clean_param($filename, PARAM_FILE);
527
528 if ($filename === '') {
529 $filename = '.';
530 }
531
64f93798 532 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
172dd12c 533 return $this->get_file_by_hash($pathnamehash);
534 }
535
16741cac
PS
536 /**
537 * Are there any files (or directories)
d2b7803e
DC
538 *
539 * @param int $contextid context ID
540 * @param string $component component
541 * @param string $filearea file area
542 * @param bool|int $itemid item id or false if all items
543 * @param bool $ignoredirs whether or not ignore directories
16741cac
PS
544 * @return bool empty
545 */
546 public function is_area_empty($contextid, $component, $filearea, $itemid = false, $ignoredirs = true) {
547 global $DB;
548
549 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
550 $where = "contextid = :contextid AND component = :component AND filearea = :filearea";
551
552 if ($itemid !== false) {
553 $params['itemid'] = $itemid;
554 $where .= " AND itemid = :itemid";
555 }
556
557 if ($ignoredirs) {
558 $sql = "SELECT 'x'
559 FROM {files}
560 WHERE $where AND filename <> '.'";
561 } else {
562 $sql = "SELECT 'x'
563 FROM {files}
564 WHERE $where AND (filename <> '.' OR filepath <> '/')";
565 }
566
567 return !$DB->record_exists_sql($sql, $params);
568 }
569
67233725
DC
570 /**
571 * Returns all files belonging to given repository
572 *
573 * @param int $repositoryid
9f4789b8 574 * @param string $sort A fragment of SQL to use for sorting
67233725 575 */
d5f59658 576 public function get_external_files($repositoryid, $sort = '') {
67233725 577 global $DB;
3447100c 578 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725
DC
579 FROM {files} f
580 LEFT JOIN {files_reference} r
581 ON f.referencefileid = r.id
9f4789b8
SH
582 WHERE r.repositoryid = ?";
583 if (!empty($sort)) {
584 $sql .= " ORDER BY {$sort}";
585 }
67233725
DC
586
587 $result = array();
588 $filerecords = $DB->get_records_sql($sql, array($repositoryid));
589 foreach ($filerecords as $filerecord) {
590 $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
591 }
592 return $result;
593 }
594
172dd12c 595 /**
596 * Returns all area files (optionally limited by itemid)
bf9ffe27 597 *
d2b7803e
DC
598 * @param int $contextid context ID
599 * @param string $component component
cebce76f 600 * @param mixed $filearea file area/s, you cannot specify multiple fileareas as well as an itemid
d2b7803e 601 * @param int $itemid item ID or all files if not specified
9f4789b8 602 * @param string $sort A fragment of SQL to use for sorting
d2b7803e 603 * @param bool $includedirs whether or not include directories
cebce76f 604 * @param int $updatedsince return files updated since this time
66234de2 605 * @param int $limitfrom return a subset of records, starting at this point (optional).
606 * @param int $limitnum return a subset comprising this many records in total (optional, required if $limitfrom is set).
5dc361e1 607 * @return stored_file[] array of stored_files indexed by pathanmehash
172dd12c 608 */
1896b800 609 public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort = "itemid, filepath, filename",
66234de2 610 $includedirs = true, $updatedsince = 0, $limitfrom = 0, $limitnum = 0) {
172dd12c 611 global $DB;
612
cebce76f
RS
613 list($areasql, $conditions) = $DB->get_in_or_equal($filearea, SQL_PARAMS_NAMED);
614 $conditions['contextid'] = $contextid;
615 $conditions['component'] = $component;
616
617 if ($itemid !== false && is_array($filearea)) {
618 throw new coding_exception('You cannot specify multiple fileareas as well as an itemid.');
619 } else if ($itemid !== false) {
67233725 620 $itemidsql = ' AND f.itemid = :itemid ';
172dd12c 621 $conditions['itemid'] = $itemid;
67233725
DC
622 } else {
623 $itemidsql = '';
172dd12c 624 }
625
cebce76f
RS
626 $updatedsincesql = '';
627 if (!empty($updatedsince)) {
628 $conditions['time'] = $updatedsince;
629 $updatedsincesql = 'AND f.timemodified > :time';
630 }
631
66234de2 632 $includedirssql = '';
633 if (!$includedirs) {
634 $includedirssql = 'AND f.filename != :dot';
635 $conditions['dot'] = '.';
636 }
637
638 if ($limitfrom && !$limitnum) {
639 throw new coding_exception('If specifying $limitfrom you must also specify $limitnum');
640 }
641
3447100c 642 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725
DC
643 FROM {files} f
644 LEFT JOIN {files_reference} r
645 ON f.referencefileid = r.id
646 WHERE f.contextid = :contextid
647 AND f.component = :component
cebce76f 648 AND f.filearea $areasql
66234de2 649 $includedirssql
cebce76f 650 $updatedsincesql
9f4789b8
SH
651 $itemidsql";
652 if (!empty($sort)) {
653 $sql .= " ORDER BY {$sort}";
654 }
67233725 655
172dd12c 656 $result = array();
66234de2 657 $filerecords = $DB->get_records_sql($sql, $conditions, $limitfrom, $limitnum);
67233725 658 foreach ($filerecords as $filerecord) {
67233725 659 $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
172dd12c 660 }
661 return $result;
662 }
663
752b9f42 664 /**
665 * Returns array based tree structure of area files
bf9ffe27 666 *
d2b7803e
DC
667 * @param int $contextid context ID
668 * @param string $component component
669 * @param string $filearea file area
670 * @param int $itemid item ID
752b9f42 671 * @return array each dir represented by dirname, subdirs, files and dirfile array elements
672 */
64f93798 673 public function get_area_tree($contextid, $component, $filearea, $itemid) {
752b9f42 674 $result = array('dirname'=>'', 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array());
db232bb0 675 $files = $this->get_area_files($contextid, $component, $filearea, $itemid, '', true);
752b9f42 676 // first create directory structure
677 foreach ($files as $hash=>$dir) {
678 if (!$dir->is_directory()) {
679 continue;
680 }
681 unset($files[$hash]);
682 if ($dir->get_filepath() === '/') {
683 $result['dirfile'] = $dir;
684 continue;
685 }
686 $parts = explode('/', trim($dir->get_filepath(),'/'));
687 $pointer =& $result;
688 foreach ($parts as $part) {
3b607678 689 if ($part === '') {
690 continue;
691 }
752b9f42 692 if (!isset($pointer['subdirs'][$part])) {
693 $pointer['subdirs'][$part] = array('dirname'=>$part, 'dirfile'=>null, 'subdirs'=>array(), 'files'=>array());
694 }
695 $pointer =& $pointer['subdirs'][$part];
696 }
697 $pointer['dirfile'] = $dir;
698 unset($pointer);
699 }
700 foreach ($files as $hash=>$file) {
701 $parts = explode('/', trim($file->get_filepath(),'/'));
702 $pointer =& $result;
703 foreach ($parts as $part) {
3b607678 704 if ($part === '') {
705 continue;
706 }
752b9f42 707 $pointer =& $pointer['subdirs'][$part];
708 }
709 $pointer['files'][$file->get_filename()] = $file;
710 unset($pointer);
711 }
db232bb0 712 $result = $this->sort_area_tree($result);
752b9f42 713 return $result;
714 }
715
db232bb0
FM
716 /**
717 * Sorts the result of {@link file_storage::get_area_tree()}.
718 *
52ebfade 719 * @param array $tree Array of results provided by {@link file_storage::get_area_tree()}
db232bb0
FM
720 * @return array of sorted results
721 */
722 protected function sort_area_tree($tree) {
723 foreach ($tree as $key => &$value) {
724 if ($key == 'subdirs') {
2f1e464a 725 core_collator::ksort($value, core_collator::SORT_NATURAL);
bc42e979
FM
726 foreach ($value as $subdirname => &$subtree) {
727 $subtree = $this->sort_area_tree($subtree);
728 }
db232bb0 729 } else if ($key == 'files') {
2f1e464a 730 core_collator::ksort($value, core_collator::SORT_NATURAL);
db232bb0
FM
731 }
732 }
733 return $tree;
734 }
735
ee03a651 736 /**
bf9ffe27
PS
737 * Returns all files and optionally directories
738 *
d2b7803e
DC
739 * @param int $contextid context ID
740 * @param string $component component
741 * @param string $filearea file area
742 * @param int $itemid item ID
ee03a651 743 * @param int $filepath directory path
744 * @param bool $recursive include all subdirectories
46fcbcf4 745 * @param bool $includedirs include files and directories
9f4789b8 746 * @param string $sort A fragment of SQL to use for sorting
cd5be217 747 * @return array of stored_files indexed by pathanmehash
ee03a651 748 */
64f93798 749 public function get_directory_files($contextid, $component, $filearea, $itemid, $filepath, $recursive = false, $includedirs = true, $sort = "filepath, filename") {
ee03a651 750 global $DB;
751
64f93798 752 if (!$directory = $this->get_file($contextid, $component, $filearea, $itemid, $filepath, '.')) {
ee03a651 753 return array();
754 }
755
9f4789b8
SH
756 $orderby = (!empty($sort)) ? " ORDER BY {$sort}" : '';
757
ee03a651 758 if ($recursive) {
759
46fcbcf4 760 $dirs = $includedirs ? "" : "AND filename <> '.'";
2f1e464a 761 $length = core_text::strlen($filepath);
ee03a651 762
3447100c 763 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
462c4955
DC
764 FROM {files} f
765 LEFT JOIN {files_reference} r
766 ON f.referencefileid = r.id
767 WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea AND f.itemid = :itemid
768 AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
769 AND f.id <> :dirid
ee03a651 770 $dirs
9f4789b8 771 $orderby";
64f93798 772 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id());
ee03a651 773
774 $files = array();
775 $dirs = array();
67233725
DC
776 $filerecords = $DB->get_records_sql($sql, $params);
777 foreach ($filerecords as $filerecord) {
778 if ($filerecord->filename == '.') {
779 $dirs[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
ee03a651 780 } else {
67233725 781 $files[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
ee03a651 782 }
783 }
784 $result = array_merge($dirs, $files);
785
786 } else {
787 $result = array();
64f93798 788 $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id());
ee03a651 789
2f1e464a 790 $length = core_text::strlen($filepath);
ee03a651 791
46fcbcf4 792 if ($includedirs) {
3447100c 793 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
462c4955
DC
794 FROM {files} f
795 LEFT JOIN {files_reference} r
796 ON f.referencefileid = r.id
797 WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea
798 AND f.itemid = :itemid AND f.filename = '.'
799 AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
800 AND f.id <> :dirid
9f4789b8 801 $orderby";
ee03a651 802 $reqlevel = substr_count($filepath, '/') + 1;
67233725
DC
803 $filerecords = $DB->get_records_sql($sql, $params);
804 foreach ($filerecords as $filerecord) {
805 if (substr_count($filerecord->filepath, '/') !== $reqlevel) {
ee03a651 806 continue;
807 }
67233725 808 $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
ee03a651 809 }
810 }
811
3447100c 812 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
462c4955
DC
813 FROM {files} f
814 LEFT JOIN {files_reference} r
815 ON f.referencefileid = r.id
816 WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea AND f.itemid = :itemid
817 AND f.filepath = :filepath AND f.filename <> '.'
9f4789b8 818 $orderby";
ee03a651 819
67233725
DC
820 $filerecords = $DB->get_records_sql($sql, $params);
821 foreach ($filerecords as $filerecord) {
822 $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
ee03a651 823 }
824 }
825
826 return $result;
827 }
828
172dd12c 829 /**
bf9ffe27
PS
830 * Delete all area files (optionally limited by itemid).
831 *
d2b7803e
DC
832 * @param int $contextid context ID
833 * @param string $component component
834 * @param string $filearea file area or all areas in context if not specified
835 * @param int $itemid item ID or all files if not specified
bf9ffe27 836 * @return bool success
172dd12c 837 */
64f93798 838 public function delete_area_files($contextid, $component = false, $filearea = false, $itemid = false) {
172dd12c 839 global $DB;
840
6311eb61 841 $conditions = array('contextid'=>$contextid);
64f93798
PS
842 if ($component !== false) {
843 $conditions['component'] = $component;
844 }
6311eb61 845 if ($filearea !== false) {
846 $conditions['filearea'] = $filearea;
847 }
172dd12c 848 if ($itemid !== false) {
849 $conditions['itemid'] = $itemid;
850 }
851
67233725
DC
852 $filerecords = $DB->get_records('files', $conditions);
853 foreach ($filerecords as $filerecord) {
854 $this->get_file_instance($filerecord)->delete();
172dd12c 855 }
856
bf9ffe27 857 return true; // BC only
172dd12c 858 }
859
af7b3673
TH
860 /**
861 * Delete all the files from certain areas where itemid is limited by an
862 * arbitrary bit of SQL.
863 *
864 * @param int $contextid the id of the context the files belong to. Must be given.
865 * @param string $component the owning component. Must be given.
866 * @param string $filearea the file area name. Must be given.
867 * @param string $itemidstest an SQL fragment that the itemid must match. Used
868 * in the query like WHERE itemid $itemidstest. Must used named parameters,
869 * and may not used named parameters called contextid, component or filearea.
870 * @param array $params any query params used by $itemidstest.
871 */
872 public function delete_area_files_select($contextid, $component,
873 $filearea, $itemidstest, array $params = null) {
874 global $DB;
875
876 $where = "contextid = :contextid
877 AND component = :component
878 AND filearea = :filearea
879 AND itemid $itemidstest";
880 $params['contextid'] = $contextid;
881 $params['component'] = $component;
882 $params['filearea'] = $filearea;
883
67233725
DC
884 $filerecords = $DB->get_recordset_select('files', $where, $params);
885 foreach ($filerecords as $filerecord) {
886 $this->get_file_instance($filerecord)->delete();
af7b3673 887 }
67233725 888 $filerecords->close();
af7b3673
TH
889 }
890
546b8864
DM
891 /**
892 * Delete all files associated with the given component.
893 *
894 * @param string $component the component owning the file
895 */
896 public function delete_component_files($component) {
897 global $DB;
898
899 $filerecords = $DB->get_recordset('files', array('component' => $component));
900 foreach ($filerecords as $filerecord) {
901 $this->get_file_instance($filerecord)->delete();
902 }
903 $filerecords->close();
904 }
905
d2af1014
TH
906 /**
907 * Move all the files in a file area from one context to another.
d2b7803e
DC
908 *
909 * @param int $oldcontextid the context the files are being moved from.
910 * @param int $newcontextid the context the files are being moved to.
d2af1014
TH
911 * @param string $component the plugin that these files belong to.
912 * @param string $filearea the name of the file area.
d2b7803e
DC
913 * @param int $itemid file item ID
914 * @return int the number of files moved, for information.
d2af1014
TH
915 */
916 public function move_area_files_to_new_context($oldcontextid, $newcontextid, $component, $filearea, $itemid = false) {
917 // Note, this code is based on some code that Petr wrote in
918 // forum_move_attachments in mod/forum/lib.php. I moved it here because
919 // I needed it in the question code too.
920 $count = 0;
921
922 $oldfiles = $this->get_area_files($oldcontextid, $component, $filearea, $itemid, 'id', false);
923 foreach ($oldfiles as $oldfile) {
924 $filerecord = new stdClass();
925 $filerecord->contextid = $newcontextid;
926 $this->create_file_from_storedfile($filerecord, $oldfile);
927 $count += 1;
928 }
929
930 if ($count) {
931 $this->delete_area_files($oldcontextid, $component, $filearea, $itemid);
932 }
933
934 return $count;
935 }
936
172dd12c 937 /**
bf9ffe27
PS
938 * Recursively creates directory.
939 *
d2b7803e
DC
940 * @param int $contextid context ID
941 * @param string $component component
942 * @param string $filearea file area
943 * @param int $itemid item ID
944 * @param string $filepath file path
945 * @param int $userid the user ID
172dd12c 946 * @return bool success
947 */
64f93798 948 public function create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid = null) {
172dd12c 949 global $DB;
950
951 // validate all parameters, we do not want any rubbish stored in database, right?
952 if (!is_number($contextid) or $contextid < 1) {
145a0a31 953 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 954 }
955
aff24313
PS
956 $component = clean_param($component, PARAM_COMPONENT);
957 if (empty($component)) {
64f93798
PS
958 throw new file_exception('storedfileproblem', 'Invalid component');
959 }
960
aff24313
PS
961 $filearea = clean_param($filearea, PARAM_AREA);
962 if (empty($filearea)) {
145a0a31 963 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 964 }
965
966 if (!is_number($itemid) or $itemid < 0) {
145a0a31 967 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 968 }
969
970 $filepath = clean_param($filepath, PARAM_PATH);
971 if (strpos($filepath, '/') !== 0 or strrpos($filepath, '/') !== strlen($filepath)-1) {
972 // path must start and end with '/'
145a0a31 973 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 974 }
975
64f93798 976 $pathnamehash = $this->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, '.');
172dd12c 977
978 if ($dir_info = $this->get_file_by_hash($pathnamehash)) {
979 return $dir_info;
980 }
981
982 static $contenthash = null;
983 if (!$contenthash) {
b48f3e06 984 $this->add_string_to_pool('');
a30a04fa 985 $contenthash = self::hash_from_string('');
172dd12c 986 }
987
988 $now = time();
989
ac6f1a82 990 $dir_record = new stdClass();
172dd12c 991 $dir_record->contextid = $contextid;
64f93798 992 $dir_record->component = $component;
172dd12c 993 $dir_record->filearea = $filearea;
994 $dir_record->itemid = $itemid;
995 $dir_record->filepath = $filepath;
996 $dir_record->filename = '.';
997 $dir_record->contenthash = $contenthash;
998 $dir_record->filesize = 0;
999
1000 $dir_record->timecreated = $now;
1001 $dir_record->timemodified = $now;
1002 $dir_record->mimetype = null;
1003 $dir_record->userid = $userid;
1004
1005 $dir_record->pathnamehash = $pathnamehash;
1006
1007 $DB->insert_record('files', $dir_record);
1008 $dir_info = $this->get_file_by_hash($pathnamehash);
1009
1010 if ($filepath !== '/') {
1011 //recurse to parent dirs
1012 $filepath = trim($filepath, '/');
1013 $filepath = explode('/', $filepath);
1014 array_pop($filepath);
1015 $filepath = implode('/', $filepath);
1016 $filepath = ($filepath === '') ? '/' : "/$filepath/";
64f93798 1017 $this->create_directory($contextid, $component, $filearea, $itemid, $filepath, $userid);
172dd12c 1018 }
1019
1020 return $dir_info;
1021 }
1022
023037dc 1023 /**
1024 * Add new file record to database and handle callbacks.
1025 *
1026 * @param stdClass $newrecord
1027 */
1028 protected function create_file($newrecord) {
1029 global $DB;
1030 $newrecord->id = $DB->insert_record('files', $newrecord);
1031
1032 if ($newrecord->filename !== '.') {
1033 // Callback for file created.
1034 if ($pluginsfunction = get_plugins_with_function('after_file_created')) {
1035 foreach ($pluginsfunction as $plugintype => $plugins) {
1036 foreach ($plugins as $pluginfunction) {
1037 $pluginfunction($newrecord);
1038 }
1039 }
1040 }
1041 }
1042 }
1043
172dd12c 1044 /**
bf9ffe27
PS
1045 * Add new local file based on existing local file.
1046 *
67233725 1047 * @param stdClass|array $filerecord object or array describing changes
d2b7803e 1048 * @param stored_file|int $fileorid id or stored_file instance of the existing local file
bf9ffe27 1049 * @return stored_file instance of newly created file
172dd12c 1050 */
67233725 1051 public function create_file_from_storedfile($filerecord, $fileorid) {
4fb2306e 1052 global $DB;
172dd12c 1053
72d0aed6 1054 if ($fileorid instanceof stored_file) {
1055 $fid = $fileorid->get_id();
1056 } else {
1057 $fid = $fileorid;
8eb1e0a1 1058 }
1059
67233725 1060 $filerecord = (array)$filerecord; // We support arrays too, do not modify the submitted record!
ec8b711f 1061
67233725
DC
1062 unset($filerecord['id']);
1063 unset($filerecord['filesize']);
1064 unset($filerecord['contenthash']);
1065 unset($filerecord['pathnamehash']);
172dd12c 1066
3447100c 1067 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725
DC
1068 FROM {files} f
1069 LEFT JOIN {files_reference} r
1070 ON f.referencefileid = r.id
1071 WHERE f.id = ?";
1072
1073 if (!$newrecord = $DB->get_record_sql($sql, array($fid))) {
145a0a31 1074 throw new file_exception('storedfileproblem', 'File does not exist');
172dd12c 1075 }
1076
1077 unset($newrecord->id);
1078
67233725 1079 foreach ($filerecord as $key => $value) {
172dd12c 1080 // validate all parameters, we do not want any rubbish stored in database, right?
1081 if ($key == 'contextid' and (!is_number($value) or $value < 1)) {
145a0a31 1082 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 1083 }
1084
64f93798 1085 if ($key == 'component') {
aff24313
PS
1086 $value = clean_param($value, PARAM_COMPONENT);
1087 if (empty($value)) {
64f93798
PS
1088 throw new file_exception('storedfileproblem', 'Invalid component');
1089 }
1090 }
1091
172dd12c 1092 if ($key == 'filearea') {
aff24313
PS
1093 $value = clean_param($value, PARAM_AREA);
1094 if (empty($value)) {
145a0a31 1095 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 1096 }
1097 }
1098
1099 if ($key == 'itemid' and (!is_number($value) or $value < 0)) {
145a0a31 1100 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 1101 }
1102
1103
1104 if ($key == 'filepath') {
1105 $value = clean_param($value, PARAM_PATH);
00c32c54 1106 if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
172dd12c 1107 // path must start and end with '/'
145a0a31 1108 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 1109 }
1110 }
1111
1112 if ($key == 'filename') {
1113 $value = clean_param($value, PARAM_FILE);
1114 if ($value === '') {
1115 // path must start and end with '/'
145a0a31 1116 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 1117 }
1118 }
1119
260c4a5b
PS
1120 if ($key === 'timecreated' or $key === 'timemodified') {
1121 if (!is_number($value)) {
1122 throw new file_exception('storedfileproblem', 'Invalid file '.$key);
1123 }
1124 if ($value < 0) {
1125 //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)
1126 $value = 0;
1127 }
1128 }
1129
87355560 1130 if ($key == 'referencefileid' or $key == 'referencelastsync') {
67233725
DC
1131 $value = clean_param($value, PARAM_INT);
1132 }
1133
172dd12c 1134 $newrecord->$key = $value;
1135 }
1136
64f93798 1137 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 1138
cd5be217 1139 if ($newrecord->filename === '.') {
1140 // special case - only this function supports directories ;-)
64f93798 1141 $directory = $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
cd5be217 1142 // update the existing directory with the new data
1143 $newrecord->id = $directory->get_id();
b8ac7ece 1144 $DB->update_record('files', $newrecord);
693ef3a8 1145 return $this->get_file_instance($newrecord);
cd5be217 1146 }
1147
d83ce953
DM
1148 // note: referencefileid is copied from the original file so that
1149 // creating a new file from an existing alias creates new alias implicitly.
1150 // here we just check the database consistency.
67233725 1151 if (!empty($newrecord->repositoryid)) {
13b449f4
DW
1152 // It is OK if the current reference does not exist. It may have been altered by a repository plugin when the files
1153 // where saved from a draft area.
141ee541 1154 $newrecord->referencefileid = $this->get_or_create_referencefileid($newrecord->repositoryid, $newrecord->reference);
67233725
DC
1155 }
1156
172dd12c 1157 try {
023037dc 1158 $this->create_file($newrecord);
8a680500 1159 } catch (dml_exception $e) {
64f93798 1160 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 1161 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 1162 }
1163
67233725 1164
64f93798 1165 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 1166
693ef3a8 1167 return $this->get_file_instance($newrecord);
172dd12c 1168 }
1169
6e73ac42 1170 /**
bf9ffe27
PS
1171 * Add new local file.
1172 *
67233725 1173 * @param stdClass|array $filerecord object or array describing file
d2b7803e
DC
1174 * @param string $url the URL to the file
1175 * @param array $options {@link download_file_content()} options
3a1055a5 1176 * @param bool $usetempfile use temporary file for download, may prevent out of memory problems
d2b7803e 1177 * @return stored_file
6e73ac42 1178 */
67233725 1179 public function create_file_from_url($filerecord, $url, array $options = null, $usetempfile = false) {
ec8b711f 1180
67233725
DC
1181 $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects.
1182 $filerecord = (object)$filerecord; // We support arrays too.
6e73ac42 1183
1184 $headers = isset($options['headers']) ? $options['headers'] : null;
1185 $postdata = isset($options['postdata']) ? $options['postdata'] : null;
1186 $fullresponse = isset($options['fullresponse']) ? $options['fullresponse'] : false;
1187 $timeout = isset($options['timeout']) ? $options['timeout'] : 300;
1188 $connecttimeout = isset($options['connecttimeout']) ? $options['connecttimeout'] : 20;
1189 $skipcertverify = isset($options['skipcertverify']) ? $options['skipcertverify'] : false;
5f1c825d 1190 $calctimeout = isset($options['calctimeout']) ? $options['calctimeout'] : false;
6e73ac42 1191
67233725 1192 if (!isset($filerecord->filename)) {
6e73ac42 1193 $parts = explode('/', $url);
1194 $filename = array_pop($parts);
67233725 1195 $filerecord->filename = clean_param($filename, PARAM_FILE);
6e73ac42 1196 }
67233725
DC
1197 $source = !empty($filerecord->source) ? $filerecord->source : $url;
1198 $filerecord->source = clean_param($source, PARAM_URL);
6e73ac42 1199
3a1055a5 1200 if ($usetempfile) {
c426ef3a 1201 check_dir_exists($this->tempdir);
3a1055a5 1202 $tmpfile = tempnam($this->tempdir, 'newfromurl');
60b5a2fe 1203 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, $tmpfile, $calctimeout);
3a1055a5
PS
1204 if ($content === false) {
1205 throw new file_exception('storedfileproblem', 'Can not fetch file form URL');
1206 }
1207 try {
67233725 1208 $newfile = $this->create_file_from_pathname($filerecord, $tmpfile);
3a1055a5
PS
1209 @unlink($tmpfile);
1210 return $newfile;
1211 } catch (Exception $e) {
1212 @unlink($tmpfile);
1213 throw $e;
1214 }
1215
1216 } else {
60b5a2fe 1217 $content = download_file_content($url, $headers, $postdata, $fullresponse, $timeout, $connecttimeout, $skipcertverify, NULL, $calctimeout);
3a1055a5
PS
1218 if ($content === false) {
1219 throw new file_exception('storedfileproblem', 'Can not fetch file form URL');
1220 }
67233725 1221 return $this->create_file_from_string($filerecord, $content);
3a1055a5 1222 }
6e73ac42 1223 }
1224
172dd12c 1225 /**
bf9ffe27
PS
1226 * Add new local file.
1227 *
67233725 1228 * @param stdClass|array $filerecord object or array describing file
d2b7803e
DC
1229 * @param string $pathname path to file or content of file
1230 * @return stored_file
172dd12c 1231 */
67233725 1232 public function create_file_from_pathname($filerecord, $pathname) {
4fb2306e 1233 global $DB;
172dd12c 1234
67233725
DC
1235 $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects.
1236 $filerecord = (object)$filerecord; // We support arrays too.
172dd12c 1237
1238 // validate all parameters, we do not want any rubbish stored in database, right?
67233725 1239 if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) {
145a0a31 1240 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 1241 }
1242
67233725
DC
1243 $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT);
1244 if (empty($filerecord->component)) {
64f93798
PS
1245 throw new file_exception('storedfileproblem', 'Invalid component');
1246 }
1247
67233725
DC
1248 $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA);
1249 if (empty($filerecord->filearea)) {
145a0a31 1250 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 1251 }
1252
67233725 1253 if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) {
145a0a31 1254 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 1255 }
1256
67233725
DC
1257 if (!empty($filerecord->sortorder)) {
1258 if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) {
1259 $filerecord->sortorder = 0;
f79321f1
DC
1260 }
1261 } else {
67233725 1262 $filerecord->sortorder = 0;
f79321f1
DC
1263 }
1264
67233725
DC
1265 $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH);
1266 if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) {
172dd12c 1267 // path must start and end with '/'
145a0a31 1268 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 1269 }
1270
67233725
DC
1271 $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE);
1272 if ($filerecord->filename === '') {
e1dcb950 1273 // filename must not be empty
145a0a31 1274 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 1275 }
1276
1277 $now = time();
67233725
DC
1278 if (isset($filerecord->timecreated)) {
1279 if (!is_number($filerecord->timecreated)) {
260c4a5b
PS
1280 throw new file_exception('storedfileproblem', 'Invalid file timecreated');
1281 }
67233725 1282 if ($filerecord->timecreated < 0) {
260c4a5b 1283 //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)
67233725 1284 $filerecord->timecreated = 0;
260c4a5b
PS
1285 }
1286 } else {
67233725 1287 $filerecord->timecreated = $now;
260c4a5b
PS
1288 }
1289
67233725
DC
1290 if (isset($filerecord->timemodified)) {
1291 if (!is_number($filerecord->timemodified)) {
260c4a5b
PS
1292 throw new file_exception('storedfileproblem', 'Invalid file timemodified');
1293 }
67233725 1294 if ($filerecord->timemodified < 0) {
260c4a5b 1295 //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)
67233725 1296 $filerecord->timemodified = 0;
260c4a5b
PS
1297 }
1298 } else {
67233725 1299 $filerecord->timemodified = $now;
260c4a5b 1300 }
172dd12c 1301
ac6f1a82 1302 $newrecord = new stdClass();
172dd12c 1303
67233725
DC
1304 $newrecord->contextid = $filerecord->contextid;
1305 $newrecord->component = $filerecord->component;
1306 $newrecord->filearea = $filerecord->filearea;
1307 $newrecord->itemid = $filerecord->itemid;
1308 $newrecord->filepath = $filerecord->filepath;
1309 $newrecord->filename = $filerecord->filename;
1310
1311 $newrecord->timecreated = $filerecord->timecreated;
1312 $newrecord->timemodified = $filerecord->timemodified;
4c2fcbfc 1313 $newrecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($pathname, $filerecord->filename) : $filerecord->mimetype;
67233725
DC
1314 $newrecord->userid = empty($filerecord->userid) ? null : $filerecord->userid;
1315 $newrecord->source = empty($filerecord->source) ? null : $filerecord->source;
1316 $newrecord->author = empty($filerecord->author) ? null : $filerecord->author;
1317 $newrecord->license = empty($filerecord->license) ? null : $filerecord->license;
cfc4db40 1318 $newrecord->status = empty($filerecord->status) ? 0 : $filerecord->status;
67233725 1319 $newrecord->sortorder = $filerecord->sortorder;
172dd12c 1320
82437065 1321 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_file_to_pool($pathname, null, $newrecord);
172dd12c 1322
64f93798 1323 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 1324
1325 try {
023037dc 1326 $this->create_file($newrecord);
8a680500 1327 } catch (dml_exception $e) {
172dd12c 1328 if ($newfile) {
5ca85a1e 1329 $this->filesystem->remove_file($newrecord->contenthash);
172dd12c 1330 }
64f93798 1331 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 1332 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 1333 }
1334
64f93798 1335 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 1336
693ef3a8 1337 return $this->get_file_instance($newrecord);
172dd12c 1338 }
1339
1340 /**
bf9ffe27
PS
1341 * Add new local file.
1342 *
67233725 1343 * @param stdClass|array $filerecord object or array describing file
172dd12c 1344 * @param string $content content of file
d2b7803e 1345 * @return stored_file
172dd12c 1346 */
67233725 1347 public function create_file_from_string($filerecord, $content) {
4fb2306e 1348 global $DB;
172dd12c 1349
67233725
DC
1350 $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects.
1351 $filerecord = (object)$filerecord; // We support arrays too.
172dd12c 1352
1353 // validate all parameters, we do not want any rubbish stored in database, right?
67233725 1354 if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) {
145a0a31 1355 throw new file_exception('storedfileproblem', 'Invalid contextid');
172dd12c 1356 }
1357
67233725
DC
1358 $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT);
1359 if (empty($filerecord->component)) {
64f93798
PS
1360 throw new file_exception('storedfileproblem', 'Invalid component');
1361 }
1362
67233725
DC
1363 $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA);
1364 if (empty($filerecord->filearea)) {
145a0a31 1365 throw new file_exception('storedfileproblem', 'Invalid filearea');
172dd12c 1366 }
1367
67233725 1368 if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) {
145a0a31 1369 throw new file_exception('storedfileproblem', 'Invalid itemid');
172dd12c 1370 }
1371
67233725
DC
1372 if (!empty($filerecord->sortorder)) {
1373 if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) {
1374 $filerecord->sortorder = 0;
f79321f1
DC
1375 }
1376 } else {
67233725 1377 $filerecord->sortorder = 0;
f79321f1
DC
1378 }
1379
67233725
DC
1380 $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH);
1381 if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) {
172dd12c 1382 // path must start and end with '/'
145a0a31 1383 throw new file_exception('storedfileproblem', 'Invalid file path');
172dd12c 1384 }
1385
67233725
DC
1386 $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE);
1387 if ($filerecord->filename === '') {
172dd12c 1388 // path must start and end with '/'
145a0a31 1389 throw new file_exception('storedfileproblem', 'Invalid file name');
172dd12c 1390 }
1391
1392 $now = time();
67233725
DC
1393 if (isset($filerecord->timecreated)) {
1394 if (!is_number($filerecord->timecreated)) {
260c4a5b
PS
1395 throw new file_exception('storedfileproblem', 'Invalid file timecreated');
1396 }
67233725 1397 if ($filerecord->timecreated < 0) {
260c4a5b 1398 //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)
67233725 1399 $filerecord->timecreated = 0;
260c4a5b
PS
1400 }
1401 } else {
67233725 1402 $filerecord->timecreated = $now;
260c4a5b
PS
1403 }
1404
67233725
DC
1405 if (isset($filerecord->timemodified)) {
1406 if (!is_number($filerecord->timemodified)) {
260c4a5b
PS
1407 throw new file_exception('storedfileproblem', 'Invalid file timemodified');
1408 }
67233725 1409 if ($filerecord->timemodified < 0) {
260c4a5b 1410 //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)
67233725 1411 $filerecord->timemodified = 0;
260c4a5b
PS
1412 }
1413 } else {
67233725 1414 $filerecord->timemodified = $now;
260c4a5b 1415 }
172dd12c 1416
ac6f1a82 1417 $newrecord = new stdClass();
172dd12c 1418
67233725
DC
1419 $newrecord->contextid = $filerecord->contextid;
1420 $newrecord->component = $filerecord->component;
1421 $newrecord->filearea = $filerecord->filearea;
1422 $newrecord->itemid = $filerecord->itemid;
1423 $newrecord->filepath = $filerecord->filepath;
1424 $newrecord->filename = $filerecord->filename;
1425
1426 $newrecord->timecreated = $filerecord->timecreated;
1427 $newrecord->timemodified = $filerecord->timemodified;
67233725
DC
1428 $newrecord->userid = empty($filerecord->userid) ? null : $filerecord->userid;
1429 $newrecord->source = empty($filerecord->source) ? null : $filerecord->source;
1430 $newrecord->author = empty($filerecord->author) ? null : $filerecord->author;
1431 $newrecord->license = empty($filerecord->license) ? null : $filerecord->license;
cfc4db40 1432 $newrecord->status = empty($filerecord->status) ? 0 : $filerecord->status;
67233725 1433 $newrecord->sortorder = $filerecord->sortorder;
1dce6261 1434
82437065 1435 list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content, $newrecord);
16a34ae1
AN
1436 if (empty($filerecord->mimetype)) {
1437 $newrecord->mimetype = $this->filesystem->mimetype_from_hash($newrecord->contenthash, $newrecord->filename);
1438 } else {
1439 $newrecord->mimetype = $filerecord->mimetype;
1440 }
172dd12c 1441
64f93798 1442 $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
172dd12c 1443
1444 try {
023037dc 1445 $this->create_file($newrecord);
8a680500 1446 } catch (dml_exception $e) {
172dd12c 1447 if ($newfile) {
5ca85a1e 1448 $this->filesystem->remove_file($newrecord->contenthash);
172dd12c 1449 }
64f93798 1450 throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
694f3b74 1451 $newrecord->filepath, $newrecord->filename, $e->debuginfo);
172dd12c 1452 }
1453
64f93798 1454 $this->create_directory($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->userid);
172dd12c 1455
693ef3a8 1456 return $this->get_file_instance($newrecord);
172dd12c 1457 }
1458
03756f0b
CB
1459 /**
1460 * Synchronise stored file from file.
1461 *
1462 * @param stored_file $file Stored file to synchronise.
1463 * @param string $path Path to the file to synchronise from.
1464 * @param stdClass $filerecord The file record from the database.
1465 */
1466 public function synchronise_stored_file_from_file(stored_file $file, $path, $filerecord) {
1467 list($contenthash, $filesize) = $this->add_file_to_pool($path, null, $filerecord);
1468 $file->set_synchronized($contenthash, $filesize);
1469 }
1470
1471 /**
1472 * Synchronise stored file from string.
1473 *
1474 * @param stored_file $file Stored file to synchronise.
1475 * @param string $content File content.
1476 * @param stdClass $filerecord The file record from the database.
1477 */
1478 public function synchronise_stored_file_from_string(stored_file $file, $content, $filerecord) {
1479 list($contenthash, $filesize) = $this->add_string_to_pool($content, $filerecord);
1480 $file->set_synchronized($contenthash, $filesize);
1481 }
1482
67233725 1483 /**
d83ce953 1484 * Create a new alias/shortcut file from file reference information
67233725 1485 *
d83ce953
DM
1486 * @param stdClass|array $filerecord object or array describing the new file
1487 * @param int $repositoryid the id of the repository that provides the original file
1488 * @param string $reference the information required by the repository to locate the original file
1489 * @param array $options options for creating the new file
67233725
DC
1490 * @return stored_file
1491 */
1492 public function create_file_from_reference($filerecord, $repositoryid, $reference, $options = array()) {
1493 global $DB;
1494
1495 $filerecord = (array)$filerecord; // Do not modify the submitted record, this cast unlinks objects.
1496 $filerecord = (object)$filerecord; // We support arrays too.
1497
1498 // validate all parameters, we do not want any rubbish stored in database, right?
1499 if (!is_number($filerecord->contextid) or $filerecord->contextid < 1) {
1500 throw new file_exception('storedfileproblem', 'Invalid contextid');
1501 }
1502
1503 $filerecord->component = clean_param($filerecord->component, PARAM_COMPONENT);
1504 if (empty($filerecord->component)) {
1505 throw new file_exception('storedfileproblem', 'Invalid component');
1506 }
1507
1508 $filerecord->filearea = clean_param($filerecord->filearea, PARAM_AREA);
1509 if (empty($filerecord->filearea)) {
1510 throw new file_exception('storedfileproblem', 'Invalid filearea');
1511 }
1512
1513 if (!is_number($filerecord->itemid) or $filerecord->itemid < 0) {
1514 throw new file_exception('storedfileproblem', 'Invalid itemid');
1515 }
1516
1517 if (!empty($filerecord->sortorder)) {
1518 if (!is_number($filerecord->sortorder) or $filerecord->sortorder < 0) {
1519 $filerecord->sortorder = 0;
1520 }
1521 } else {
1522 $filerecord->sortorder = 0;
1523 }
1524
8177b7b9 1525 $filerecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($filerecord->filename) : $filerecord->mimetype;
67233725
DC
1526 $filerecord->userid = empty($filerecord->userid) ? null : $filerecord->userid;
1527 $filerecord->source = empty($filerecord->source) ? null : $filerecord->source;
1528 $filerecord->author = empty($filerecord->author) ? null : $filerecord->author;
1529 $filerecord->license = empty($filerecord->license) ? null : $filerecord->license;
cfc4db40 1530 $filerecord->status = empty($filerecord->status) ? 0 : $filerecord->status;
67233725
DC
1531 $filerecord->filepath = clean_param($filerecord->filepath, PARAM_PATH);
1532 if (strpos($filerecord->filepath, '/') !== 0 or strrpos($filerecord->filepath, '/') !== strlen($filerecord->filepath)-1) {
1533 // Path must start and end with '/'.
1534 throw new file_exception('storedfileproblem', 'Invalid file path');
1535 }
1536
1537 $filerecord->filename = clean_param($filerecord->filename, PARAM_FILE);
1538 if ($filerecord->filename === '') {
1539 // Path must start and end with '/'.
1540 throw new file_exception('storedfileproblem', 'Invalid file name');
1541 }
1542
1543 $now = time();
1544 if (isset($filerecord->timecreated)) {
1545 if (!is_number($filerecord->timecreated)) {
1546 throw new file_exception('storedfileproblem', 'Invalid file timecreated');
1547 }
1548 if ($filerecord->timecreated < 0) {
1549 // 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)
1550 $filerecord->timecreated = 0;
1551 }
1552 } else {
1553 $filerecord->timecreated = $now;
1554 }
1555
1556 if (isset($filerecord->timemodified)) {
1557 if (!is_number($filerecord->timemodified)) {
1558 throw new file_exception('storedfileproblem', 'Invalid file timemodified');
1559 }
1560 if ($filerecord->timemodified < 0) {
1561 // 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)
1562 $filerecord->timemodified = 0;
1563 }
1564 } else {
1565 $filerecord->timemodified = $now;
1566 }
1567
e3c02118
DC
1568 $transaction = $DB->start_delegated_transaction();
1569
67233725 1570 try {
437f5dc4 1571 $filerecord->referencefileid = $this->get_or_create_referencefileid($repositoryid, $reference);
d83ce953
DM
1572 } catch (Exception $e) {
1573 throw new file_reference_exception($repositoryid, $reference, null, null, $e->getMessage());
67233725
DC
1574 }
1575
16a34ae1
AN
1576 $existingfile = null;
1577 if (isset($filerecord->contenthash)) {
1578 $existingfile = $DB->get_record('files', array('contenthash' => $filerecord->contenthash));
1579 }
1580 if (!empty($existingfile)) {
1581 // There is an existing file already available.
437f5dc4 1582 if (empty($filerecord->filesize)) {
16a34ae1 1583 $filerecord->filesize = $existingfile->filesize;
437f5dc4
MG
1584 } else {
1585 $filerecord->filesize = clean_param($filerecord->filesize, PARAM_INT);
1586 }
1587 } else {
16a34ae1 1588 // Attempt to get the result of last synchronisation for this reference.
437f5dc4
MG
1589 $lastcontent = $DB->get_record('files', array('referencefileid' => $filerecord->referencefileid),
1590 'id, contenthash, filesize', IGNORE_MULTIPLE);
1591 if ($lastcontent) {
1592 $filerecord->contenthash = $lastcontent->contenthash;
1593 $filerecord->filesize = $lastcontent->filesize;
1594 } else {
1595 // External file doesn't have content in moodle.
1596 // So we create an empty file for it.
82437065 1597 list($filerecord->contenthash, $filerecord->filesize, $newfile) = $this->add_string_to_pool(null, $filerecord);
437f5dc4
MG
1598 }
1599 }
67233725
DC
1600
1601 $filerecord->pathnamehash = $this->get_pathname_hash($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->filename);
1602
1603 try {
1604 $filerecord->id = $DB->insert_record('files', $filerecord);
1605 } catch (dml_exception $e) {
437f5dc4 1606 if (!empty($newfile)) {
5ca85a1e 1607 $this->filesystem->remove_file($filerecord->contenthash);
67233725
DC
1608 }
1609 throw new stored_file_creation_exception($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid,
1610 $filerecord->filepath, $filerecord->filename, $e->debuginfo);
1611 }
1612
1613 $this->create_directory($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->userid);
1614
e3c02118
DC
1615 $transaction->allow_commit();
1616
42aa6e15
MG
1617 // this will retrieve all reference information from DB as well
1618 return $this->get_file_by_id($filerecord->id);
67233725
DC
1619 }
1620
797f19e8 1621 /**
1622 * Creates new image file from existing.
bf9ffe27 1623 *
67233725 1624 * @param stdClass|array $filerecord object or array describing new file
d2b7803e 1625 * @param int|stored_file $fid file id or stored file object
797f19e8 1626 * @param int $newwidth in pixels
1627 * @param int $newheight in pixels
d2b7803e 1628 * @param bool $keepaspectratio whether or not keep aspect ratio
bf9ffe27 1629 * @param int $quality depending on image type 0-100 for jpeg, 0-9 (0 means no compression) for png
d2b7803e 1630 * @return stored_file
797f19e8 1631 */
67233725 1632 public function convert_image($filerecord, $fid, $newwidth = null, $newheight = null, $keepaspectratio = true, $quality = null) {
6b2f2184
AD
1633 if (!function_exists('imagecreatefromstring')) {
1634 //Most likely the GD php extension isn't installed
1635 //image conversion cannot succeed
1636 throw new file_exception('storedfileproblem', 'imagecreatefromstring() doesnt exist. The PHP extension "GD" must be installed for image conversion.');
1637 }
1638
797f19e8 1639 if ($fid instanceof stored_file) {
1640 $fid = $fid->get_id();
1641 }
1642
67233725 1643 $filerecord = (array)$filerecord; // We support arrays too, do not modify the submitted record!
797f19e8 1644
67233725 1645 if (!$file = $this->get_file_by_id($fid)) { // Make sure file really exists and we we correct data.
797f19e8 1646 throw new file_exception('storedfileproblem', 'File does not exist');
1647 }
1648
1649 if (!$imageinfo = $file->get_imageinfo()) {
1650 throw new file_exception('storedfileproblem', 'File is not an image');
1651 }
1652
67233725
DC
1653 if (!isset($filerecord['filename'])) {
1654 $filerecord['filename'] = $file->get_filename();
797f19e8 1655 }
1656
67233725 1657 if (!isset($filerecord['mimetype'])) {
8177b7b9 1658 $filerecord['mimetype'] = $imageinfo['mimetype'];
797f19e8 1659 }
1660
1661 $width = $imageinfo['width'];
1662 $height = $imageinfo['height'];
797f19e8 1663
1664 if ($keepaspectratio) {
1665 if (0 >= $newwidth and 0 >= $newheight) {
1666 // no sizes specified
1667 $newwidth = $width;
1668 $newheight = $height;
1669
1670 } else if (0 < $newwidth and 0 < $newheight) {
1671 $xheight = ($newwidth*($height/$width));
1672 if ($xheight < $newheight) {
1673 $newheight = (int)$xheight;
1674 } else {
1675 $newwidth = (int)($newheight*($width/$height));
1676 }
1677
1678 } else if (0 < $newwidth) {
1679 $newheight = (int)($newwidth*($height/$width));
1680
1681 } else { //0 < $newheight
1682 $newwidth = (int)($newheight*($width/$height));
1683 }
1684
1685 } else {
1686 if (0 >= $newwidth) {
1687 $newwidth = $width;
1688 }
1689 if (0 >= $newheight) {
1690 $newheight = $height;
1691 }
1692 }
1693
a33e5deb 1694 // The original image.
797f19e8 1695 $img = imagecreatefromstring($file->get_content());
e2458431 1696
a33e5deb
JP
1697 // A new true color image where we will copy our original image.
1698 $newimg = imagecreatetruecolor($newwidth, $newheight);
1699
1700 // Determine if the file supports transparency.
1701 $hasalpha = $filerecord['mimetype'] == 'image/png' || $filerecord['mimetype'] == 'image/gif';
1702
1703 // Maintain transparency.
1704 if ($hasalpha) {
1705 imagealphablending($newimg, true);
1706
1707 // Get the current transparent index for the original image.
1708 $colour = imagecolortransparent($img);
1709 if ($colour == -1) {
1710 // Set a transparent colour index if there's none.
1711 $colour = imagecolorallocatealpha($newimg, 255, 255, 255, 127);
1712 // Save full alpha channel.
e2458431
AG
1713 imagesavealpha($newimg, true);
1714 }
a33e5deb
JP
1715 imagecolortransparent($newimg, $colour);
1716 imagefill($newimg, 0, 0, $colour);
1717 }
e2458431 1718
a33e5deb
JP
1719 // Process the image to be output.
1720 if ($height != $newheight or $width != $newwidth) {
1721 // Resample if the dimensions differ from the original.
e2458431 1722 if (!imagecopyresampled($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) {
797f19e8 1723 // weird
1724 throw new file_exception('storedfileproblem', 'Can not resize image');
1725 }
1726 imagedestroy($img);
1727 $img = $newimg;
a33e5deb
JP
1728
1729 } else if ($hasalpha) {
1730 // Just copy to the new image with the alpha channel.
1731 if (!imagecopy($newimg, $img, 0, 0, 0, 0, $width, $height)) {
1732 // Weird.
1733 throw new file_exception('storedfileproblem', 'Can not copy image');
1734 }
1735 imagedestroy($img);
1736 $img = $newimg;
1737
1738 } else {
1739 // No particular processing needed for the original image.
1740 imagedestroy($newimg);
797f19e8 1741 }
1742
1743 ob_start();
67233725 1744 switch ($filerecord['mimetype']) {
797f19e8 1745 case 'image/gif':
1746 imagegif($img);
1747 break;
1748
1749 case 'image/jpeg':
1750 if (is_null($quality)) {
1751 imagejpeg($img);
1752 } else {
1753 imagejpeg($img, NULL, $quality);
1754 }
1755 break;
1756
1757 case 'image/png':
8bd49ec0 1758 $quality = (int)$quality;
623c947f
JC
1759
1760 // Woah nelly! Because PNG quality is in the range 0 - 9 compared to JPEG quality,
1761 // the latter of which can go to 100, we need to make sure that quality here is
1762 // in a safe range or PHP WILL CRASH AND DIE. You have been warned.
1763 $quality = $quality > 9 ? (int)(max(1.0, (float)$quality / 100.0) * 9.0) : $quality;
797f19e8 1764 imagepng($img, NULL, $quality, NULL);
1765 break;
1766
1767 default:
1768 throw new file_exception('storedfileproblem', 'Unsupported mime type');
1769 }
1770
1771 $content = ob_get_contents();
1772 ob_end_clean();
1773 imagedestroy($img);
1774
1775 if (!$content) {
1776 throw new file_exception('storedfileproblem', 'Can not convert image');
1777 }
1778
67233725 1779 return $this->create_file_from_string($filerecord, $content);
797f19e8 1780 }
1781
172dd12c 1782 /**
bf9ffe27
PS
1783 * Add file content to sha1 pool.
1784 *
172dd12c 1785 * @param string $pathname path to file
82437065
CB
1786 * @param string|null $contenthash sha1 hash of content if known (performance only)
1787 * @param stdClass|null $newrecord New file record
bf9ffe27 1788 * @return array (contenthash, filesize, newfile)
172dd12c 1789 */
82437065
CB
1790 public function add_file_to_pool($pathname, $contenthash = null, $newrecord = null) {
1791 $this->call_before_file_created_plugin_functions($newrecord, $pathname);
16a34ae1 1792 return $this->filesystem->add_file_from_path($pathname, $contenthash);
172dd12c 1793 }
1794
1795 /**
bf9ffe27
PS
1796 * Add string content to sha1 pool.
1797 *
172dd12c 1798 * @param string $content file content - binary string
bf9ffe27 1799 * @return array (contenthash, filesize, newfile)
172dd12c 1800 */
82437065
CB
1801 public function add_string_to_pool($content, $newrecord = null) {
1802 $this->call_before_file_created_plugin_functions($newrecord, null, $content);
16a34ae1 1803 return $this->filesystem->add_file_from_string($content);
172dd12c 1804 }
1805
82437065
CB
1806 /**
1807 * before_file_created hook.
1808 *
1809 * @param stdClass|null $newrecord New file record.
1810 * @param string|null $pathname Path to file.
1811 * @param string|null $content File content.
1812 */
1813 protected function call_before_file_created_plugin_functions($newrecord, $pathname = null, $content = null) {
1814 $pluginsfunction = get_plugins_with_function('before_file_created');
1815 foreach ($pluginsfunction as $plugintype => $plugins) {
1816 foreach ($plugins as $pluginfunction) {
1817 $pluginfunction($newrecord, ['pathname' => $pathname, 'content' => $content]);
1818 }
1819 }
1820 }
1821
d5dd0540
PS
1822 /**
1823 * Serve file content using X-Sendfile header.
1824 * Please make sure that all headers are already sent
1825 * and the all access control checks passed.
1826 *
1827 * @param string $contenthash sah1 hash of the file content to be served
1828 * @return bool success
1829 */
1830 public function xsendfile($contenthash) {
16a34ae1 1831 return $this->filesystem->xsendfile($contenthash);
d5dd0540
PS
1832 }
1833
67233725
DC
1834 /**
1835 * Content exists
1836 *
1837 * @param string $contenthash
1838 * @return bool
16a34ae1 1839 * @deprecated since 3.3
67233725
DC
1840 */
1841 public function content_exists($contenthash) {
16a34ae1 1842 debugging('The content_exists function has been deprecated and should no longer be used.', DEBUG_DEVELOPER);
67233725 1843
16a34ae1 1844 return false;
1aa01caf 1845 }
1846
1847 /**
bf9ffe27
PS
1848 * Tries to recover missing content of file from trash.
1849 *
d2b7803e 1850 * @param stored_file $file stored_file instance
1aa01caf 1851 * @return bool success
16a34ae1 1852 * @deprecated since 3.3
1aa01caf 1853 */
1854 public function try_content_recovery($file) {
16a34ae1 1855 debugging('The try_content_recovery function has been deprecated and should no longer be used.', DEBUG_DEVELOPER);
e029dff4 1856
16a34ae1 1857 return false;
172dd12c 1858 }
1859
67233725
DC
1860 /**
1861 * When user referring to a moodle file, we build the reference field
1862 *
1863 * @param array $params
1864 * @return string
1865 */
1866 public static function pack_reference($params) {
1867 $params = (array)$params;
1868 $reference = array();
1869 $reference['contextid'] = is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT);
1870 $reference['component'] = is_null($params['component']) ? null : clean_param($params['component'], PARAM_COMPONENT);
1871 $reference['itemid'] = is_null($params['itemid']) ? null : clean_param($params['itemid'], PARAM_INT);
1872 $reference['filearea'] = is_null($params['filearea']) ? null : clean_param($params['filearea'], PARAM_AREA);
0e35ba6f 1873 $reference['filepath'] = is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH);
67233725
DC
1874 $reference['filename'] = is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE);
1875 return base64_encode(serialize($reference));
1876 }
1877
1878 /**
1879 * Unpack reference field
1880 *
1881 * @param string $str
0b2bfbd1 1882 * @param bool $cleanparams if set to true, array elements will be passed through {@link clean_param()}
483afa44 1883 * @throws file_reference_exception if the $str does not have the expected format
67233725
DC
1884 * @return array
1885 */
0b2bfbd1 1886 public static function unpack_reference($str, $cleanparams = false) {
6feae1d2
DM
1887 $decoded = base64_decode($str, true);
1888 if ($decoded === false) {
1889 throw new file_reference_exception(null, $str, null, null, 'Invalid base64 format');
1890 }
1891 $params = @unserialize($decoded); // hide E_NOTICE
1892 if ($params === false) {
1893 throw new file_reference_exception(null, $decoded, null, null, 'Not an unserializeable value');
1894 }
0b2bfbd1
MG
1895 if (is_array($params) && $cleanparams) {
1896 $params = array(
1897 'component' => is_null($params['component']) ? '' : clean_param($params['component'], PARAM_COMPONENT),
1898 'filearea' => is_null($params['filearea']) ? '' : clean_param($params['filearea'], PARAM_AREA),
1899 'itemid' => is_null($params['itemid']) ? 0 : clean_param($params['itemid'], PARAM_INT),
1900 'filename' => is_null($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE),
1901 'filepath' => is_null($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH),
1902 'contextid' => is_null($params['contextid']) ? null : clean_param($params['contextid'], PARAM_INT)
1903 );
1904 }
1905 return $params;
67233725
DC
1906 }
1907
7a79030f
FM
1908 /**
1909 * Search through the server files.
1910 *
1911 * The query parameter will be used in conjuction with the SQL directive
1912 * LIKE, so include '%' in it if you need to. This search will always ignore
1913 * user files and directories. Note that the search is case insensitive.
1914 *
1915 * This query can quickly become inefficient so use it sparignly.
1916 *
1917 * @param string $query The string used with SQL LIKE.
1918 * @param integer $from The offset to start the search at.
1919 * @param integer $limit The maximum number of results.
1920 * @param boolean $count When true this methods returns the number of results availabe,
1921 * disregarding the parameters $from and $limit.
1922 * @return int|array Integer when count, otherwise array of stored_file objects.
1923 */
1924 public function search_server_files($query, $from = 0, $limit = 20, $count = false) {
1925 global $DB;
1926 $params = array(
1927 'contextlevel' => CONTEXT_USER,
1928 'directory' => '.',
1929 'query' => $query
1930 );
1931
1932 if ($count) {
1933 $select = 'COUNT(1)';
1934 } else {
1935 $select = self::instance_sql_fields('f', 'r');
1936 }
1937 $like = $DB->sql_like('f.filename', ':query', false);
1938
1939 $sql = "SELECT $select
1940 FROM {files} f
1941 LEFT JOIN {files_reference} r
1942 ON f.referencefileid = r.id
1943 JOIN {context} c
1944 ON f.contextid = c.id
1945 WHERE c.contextlevel <> :contextlevel
1946 AND f.filename <> :directory
1947 AND " . $like . "";
1948
1949 if ($count) {
1950 return $DB->count_records_sql($sql, $params);
1951 }
1952
1953 $sql .= " ORDER BY f.filename";
1954
1955 $result = array();
1956 $filerecords = $DB->get_recordset_sql($sql, $params, $from, $limit);
1957 foreach ($filerecords as $filerecord) {
1958 $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
1959 }
1960 $filerecords->close();
1961
1962 return $result;
1963 }
1964
67233725 1965 /**
483afa44
DM
1966 * Returns all aliases that refer to some stored_file via the given reference
1967 *
1968 * All repositories that provide access to a stored_file are expected to use
1969 * {@link self::pack_reference()}. This method can't be used if the given reference
1970 * does not use this format or if you are looking for references to an external file
1971 * (for example it can't be used to search for all aliases that refer to a given
1972 * Dropbox or Box.net file).
67233725 1973 *
0ad654dc
DM
1974 * Aliases in user draft areas are excluded from the returned list.
1975 *
1976 * @param string $reference identification of the referenced file
1977 * @return array of stored_file indexed by its pathnamehash
67233725 1978 */
0ad654dc 1979 public function search_references($reference) {
67233725 1980 global $DB;
0ad654dc
DM
1981
1982 if (is_null($reference)) {
1983 throw new coding_exception('NULL is not a valid reference to an external file');
1984 }
1985
483afa44
DM
1986 // Give {@link self::unpack_reference()} a chance to throw exception if the
1987 // reference is not in a valid format.
1988 self::unpack_reference($reference);
1989
0ad654dc
DM
1990 $referencehash = sha1($reference);
1991
3447100c 1992 $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
67233725 1993 FROM {files} f
0ad654dc
DM
1994 JOIN {files_reference} r ON f.referencefileid = r.id
1995 JOIN {repository_instances} ri ON r.repositoryid = ri.id
1996 WHERE r.referencehash = ?
1997 AND (f.component <> ? OR f.filearea <> ?)";
67233725 1998
0ad654dc 1999 $rs = $DB->get_recordset_sql($sql, array($referencehash, 'user', 'draft'));
67233725
DC
2000 $files = array();
2001 foreach ($rs as $filerecord) {
0ad654dc 2002 $files[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
67233725
DC
2003 }
2004
2005 return $files;
2006 }
2007
2008 /**
483afa44
DM
2009 * Returns the number of aliases that refer to some stored_file via the given reference
2010 *
2011 * All repositories that provide access to a stored_file are expected to use
2012 * {@link self::pack_reference()}. This method can't be used if the given reference
2013 * does not use this format or if you are looking for references to an external file
2014 * (for example it can't be used to count aliases that refer to a given Dropbox or
2015 * Box.net file).
67233725 2016 *
0ad654dc
DM
2017 * Aliases in user draft areas are not counted.
2018 *
2019 * @param string $reference identification of the referenced file
67233725
DC
2020 * @return int
2021 */
0ad654dc 2022 public function search_references_count($reference) {
67233725 2023 global $DB;
0ad654dc
DM
2024
2025 if (is_null($reference)) {
2026 throw new coding_exception('NULL is not a valid reference to an external file');
2027 }
2028
483afa44
DM
2029 // Give {@link self::unpack_reference()} a chance to throw exception if the
2030 // reference is not in a valid format.
2031 self::unpack_reference($reference);
2032
0ad654dc
DM
2033 $referencehash = sha1($reference);
2034
67233725
DC
2035 $sql = "SELECT COUNT(f.id)
2036 FROM {files} f
0ad654dc
DM
2037 JOIN {files_reference} r ON f.referencefileid = r.id
2038 JOIN {repository_instances} ri ON r.repositoryid = ri.id
2039 WHERE r.referencehash = ?
2040 AND (f.component <> ? OR f.filearea <> ?)";
67233725 2041
483afa44 2042 return (int)$DB->count_records_sql($sql, array($referencehash, 'user', 'draft'));
67233725
DC
2043 }
2044
2045 /**
0ad654dc
DM
2046 * Returns all aliases that link to the given stored_file
2047 *
2048 * Aliases in user draft areas are excluded from the returned list.
67233725
DC
2049 *
2050 * @param stored_file $storedfile
0ad654dc 2051 * @return array of stored_file
67233725 2052 */
0ad654dc 2053 public function get_references_by_storedfile(stored_file $storedfile) {
67233725
DC
2054 global $DB;
2055
2056 $params = array();
2057 $params['contextid'] = $storedfile->get_contextid();
2058 $params['component'] = $storedfile->get_component();
2059 $params['filearea'] = $storedfile->get_filearea();
2060 $params['itemid'] = $storedfile->get_itemid();
2061 $params['filename'] = $storedfile->get_filename();
2062 $params['filepath'] = $storedfile->get_filepath();
67233725 2063
0ad654dc 2064 return $this->search_references(self::pack_reference($params));
67233725
DC
2065 }
2066
2067 /**
0ad654dc
DM
2068 * Returns the number of aliases that link to the given stored_file
2069 *
2070 * Aliases in user draft areas are not counted.
67233725
DC
2071 *
2072 * @param stored_file $storedfile
2073 * @return int
2074 */
0ad654dc 2075 public function get_references_count_by_storedfile(stored_file $storedfile) {
67233725
DC
2076 global $DB;
2077
2078 $params = array();
2079 $params['contextid'] = $storedfile->get_contextid();
2080 $params['component'] = $storedfile->get_component();
2081 $params['filearea'] = $storedfile->get_filearea();
2082 $params['itemid'] = $storedfile->get_itemid();
2083 $params['filename'] = $storedfile->get_filename();
2084 $params['filepath'] = $storedfile->get_filepath();
67233725 2085
0ad654dc 2086 return $this->search_references_count(self::pack_reference($params));
67233725
DC
2087 }
2088
14b7e500
MG
2089 /**
2090 * Updates all files that are referencing this file with the new contenthash
2091 * and filesize
2092 *
2093 * @param stored_file $storedfile
2094 */
2095 public function update_references_to_storedfile(stored_file $storedfile) {
ff37d63c 2096 global $CFG, $DB;
14b7e500
MG
2097 $params = array();
2098 $params['contextid'] = $storedfile->get_contextid();
2099 $params['component'] = $storedfile->get_component();
2100 $params['filearea'] = $storedfile->get_filearea();
2101 $params['itemid'] = $storedfile->get_itemid();
2102 $params['filename'] = $storedfile->get_filename();
2103 $params['filepath'] = $storedfile->get_filepath();
2104 $reference = self::pack_reference($params);
2105 $referencehash = sha1($reference);
2106
2107 $sql = "SELECT repositoryid, id FROM {files_reference}
898d4975
MG
2108 WHERE referencehash = ?";
2109 $rs = $DB->get_recordset_sql($sql, array($referencehash));
14b7e500
MG
2110
2111 $now = time();
2112 foreach ($rs as $record) {
87355560 2113 $this->update_references($record->id, $now, null,
58e5ab1b 2114 $storedfile->get_contenthash(), $storedfile->get_filesize(), 0, $storedfile->get_timemodified());
14b7e500
MG
2115 }
2116 $rs->close();
2117 }
2118
67233725
DC
2119 /**
2120 * Convert file alias to local file
2121 *
bc6f241c
MG
2122 * @throws moodle_exception if file could not be downloaded
2123 *
67233725 2124 * @param stored_file $storedfile a stored_file instances
bc6f241c 2125 * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
fc4e8034 2126 * @return stored_file stored_file
67233725 2127 */
bc6f241c 2128 public function import_external_file(stored_file $storedfile, $maxbytes = 0) {
67233725 2129 global $CFG;
bc6f241c 2130 $storedfile->import_external_file_contents($maxbytes);
fc4e8034
DC
2131 $storedfile->delete_reference();
2132 return $storedfile;
67233725
DC
2133 }
2134
8177b7b9 2135 /**
16a34ae1 2136 * Return mimetype by given file pathname.
8177b7b9 2137 *
ae7f35b9
MG
2138 * If file has a known extension, we return the mimetype based on extension.
2139 * Otherwise (when possible) we try to get the mimetype from file contents.
8177b7b9 2140 *
16a34ae1
AN
2141 * @param string $fullpath Full path to the file on disk
2142 * @param string $filename Correct file name with extension, if omitted will be taken from $path
8177b7b9
DC
2143 * @return string
2144 */
16a34ae1 2145 public static function mimetype($fullpath, $filename = null) {
4c2fcbfc 2146 if (empty($filename)) {
16a34ae1 2147 $filename = $fullpath;
4c2fcbfc 2148 }
16a34ae1
AN
2149
2150 // The mimeinfo function determines the mimetype purely based on the file extension.
4c2fcbfc 2151 $type = mimeinfo('type', $filename);
16a34ae1
AN
2152
2153 if ($type === 'document/unknown') {
2154 // The type is unknown. Inspect the file now.
2155 $type = self::mimetype_from_file($fullpath);
8177b7b9 2156 }
ae7f35b9 2157 return $type;
8177b7b9
DC
2158 }
2159
16a34ae1
AN
2160 /**
2161 * Inspect a file on disk for it's mimetype.
2162 *
2163 * @param string $fullpath Path to file on disk
2164 * @return string The mimetype
2165 */
2166 public static function mimetype_from_file($fullpath) {
2167 if (file_exists($fullpath)) {
2168 // The type is unknown. Attempt to look up the file type now.
2169 $finfo = new finfo(FILEINFO_MIME_TYPE);
2170 return mimeinfo_from_type('type', $finfo->file($fullpath));
2171 }
2172
2173 return 'document/unknown';
2174 }
2175
172dd12c 2176 /**
2177 * Cron cleanup job.
2178 */
2179 public function cron() {
a881f970 2180 global $CFG, $DB;
bfaed432 2181 require_once($CFG->libdir.'/cronlib.php');
64f93798 2182
2e69ea4a
PS
2183 // find out all stale draft areas (older than 4 days) and purge them
2184 // those are identified by time stamp of the /. root dir
2185 mtrace('Deleting old draft files... ', '');
658b9372 2186 cron_trace_time_and_memory();
2e69ea4a
PS
2187 $old = time() - 60*60*24*4;
2188 $sql = "SELECT *
2189 FROM {files}
2190 WHERE component = 'user' AND filearea = 'draft' AND filepath = '/' AND filename = '.'
2191 AND timecreated < :old";
2192 $rs = $DB->get_recordset_sql($sql, array('old'=>$old));
2193 foreach ($rs as $dir) {
2194 $this->delete_area_files($dir->contextid, $dir->component, $dir->filearea, $dir->itemid);
2195 }
be981316 2196 $rs->close();
b5541735 2197 mtrace('done.');
64f93798 2198
9120a462
DM
2199 // remove orphaned preview files (that is files in the core preview filearea without
2200 // the existing original file)
2201 mtrace('Deleting orphaned preview files... ', '');
658b9372 2202 cron_trace_time_and_memory();
9120a462
DM
2203 $sql = "SELECT p.*
2204 FROM {files} p
2205 LEFT JOIN {files} o ON (p.filename = o.contenthash)
2206 WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'preview' AND p.itemid = 0
f7eec6ce 2207 AND o.id IS NULL";
9120a462
DM
2208 $syscontext = context_system::instance();
2209 $rs = $DB->get_recordset_sql($sql, array($syscontext->id));
2210 foreach ($rs as $orphan) {
f7eec6ce
DM
2211 $file = $this->get_file_instance($orphan);
2212 if (!$file->is_directory()) {
2213 $file->delete();
2214 }
9120a462
DM
2215 }
2216 $rs->close();
2217 mtrace('done.');
2218
6853cd5e
DW
2219 // Remove orphaned converted files (that is files in the core documentconversion filearea without
2220 // the existing original file).
2e76c14e
DW
2221 mtrace('Deleting orphaned document conversion files... ', '');
2222 cron_trace_time_and_memory();
2223 $sql = "SELECT p.*
2224 FROM {files} p
2225 LEFT JOIN {files} o ON (p.filename = o.contenthash)
2226 WHERE p.contextid = ? AND p.component = 'core' AND p.filearea = 'documentconversion' AND p.itemid = 0
2227 AND o.id IS NULL";
2228 $syscontext = context_system::instance();
2229 $rs = $DB->get_recordset_sql($sql, array($syscontext->id));
2230 foreach ($rs as $orphan) {
2231 $file = $this->get_file_instance($orphan);
2232 if (!$file->is_directory()) {
2233 $file->delete();
2234 }
2235 }
2236 $rs->close();
2237 mtrace('done.');
2238
1aa01caf 2239 // remove trash pool files once a day
2240 // if you want to disable purging of trash put $CFG->fileslastcleanup=time(); into config.php
2241 if (empty($CFG->fileslastcleanup) or $CFG->fileslastcleanup < time() - 60*60*24) {
2242 require_once($CFG->libdir.'/filelib.php');
a881f970
SH
2243 // Delete files that are associated with a context that no longer exists.
2244 mtrace('Cleaning up files from deleted contexts... ', '');
658b9372 2245 cron_trace_time_and_memory();
a881f970
SH
2246 $sql = "SELECT DISTINCT f.contextid
2247 FROM {files} f
2248 LEFT OUTER JOIN {context} c ON f.contextid = c.id
2249 WHERE c.id IS NULL";
be981316
EL
2250 $rs = $DB->get_recordset_sql($sql);
2251 if ($rs->valid()) {
a881f970
SH
2252 $fs = get_file_storage();
2253 foreach ($rs as $ctx) {
2254 $fs->delete_area_files($ctx->contextid);
2255 }
2256 }
be981316 2257 $rs->close();
a881f970
SH
2258 mtrace('done.');
2259
16a34ae1 2260 mtrace('Call filesystem cron tasks.', '');
658b9372 2261 cron_trace_time_and_memory();
16a34ae1 2262 $this->filesystem->cron();
1aa01caf 2263 mtrace('done.');
172dd12c 2264 }
2265 }
3447100c
DP
2266
2267 /**
2268 * Get the sql formated fields for a file instance to be created from a
2269 * {files} and {files_refernece} join.
2270 *
2271 * @param string $filesprefix the table prefix for the {files} table
2272 * @param string $filesreferenceprefix the table prefix for the {files_reference} table
2273 * @return string the sql to go after a SELECT
2274 */
2275 private static function instance_sql_fields($filesprefix, $filesreferenceprefix) {
2276 // Note, these fieldnames MUST NOT overlap between the two tables,
2277 // else problems like MDL-33172 occur.
2278 $filefields = array('contenthash', 'pathnamehash', 'contextid', 'component', 'filearea',
2279 'itemid', 'filepath', 'filename', 'userid', 'filesize', 'mimetype', 'status', 'source',
42aa6e15 2280 'author', 'license', 'timecreated', 'timemodified', 'sortorder', 'referencefileid');
3447100c 2281
42aa6e15
MG
2282 $referencefields = array('repositoryid' => 'repositoryid',
2283 'reference' => 'reference',
87355560 2284 'lastsync' => 'referencelastsync');
3447100c
DP
2285
2286 // id is specifically named to prevent overlaping between the two tables.
2287 $fields = array();
2288 $fields[] = $filesprefix.'.id AS id';
2289 foreach ($filefields as $field) {
2290 $fields[] = "{$filesprefix}.{$field}";
2291 }
2292
42aa6e15
MG
2293 foreach ($referencefields as $field => $alias) {
2294 $fields[] = "{$filesreferenceprefix}.{$field} AS {$alias}";
3447100c
DP
2295 }
2296
2297 return implode(', ', $fields);
2298 }
bf9ffe27 2299
d83ce953
DM
2300 /**
2301 * Returns the id of the record in {files_reference} that matches the passed repositoryid and reference
2302 *
2303 * If the record already exists, its id is returned. If there is no such record yet,
87355560 2304 * new one is created (using the lastsync provided, too) and its id is returned.
d83ce953
DM
2305 *
2306 * @param int $repositoryid
2307 * @param string $reference
87355560
MG
2308 * @param int $lastsync
2309 * @param int $lifetime argument not used any more
d83ce953
DM
2310 * @return int
2311 */
2312 private function get_or_create_referencefileid($repositoryid, $reference, $lastsync = null, $lifetime = null) {
2313 global $DB;
2314
2315 $id = $this->get_referencefileid($repositoryid, $reference, IGNORE_MISSING);
2316
2317 if ($id !== false) {
2318 // bah, that was easy
2319 return $id;
2320 }
2321
2322 // no such record yet, create one
2323 try {
2324 $id = $DB->insert_record('files_reference', array(
2325 'repositoryid' => $repositoryid,
2326 'reference' => $reference,
dccba8bc 2327 'referencehash' => sha1($reference),
87355560 2328 'lastsync' => $lastsync));
d83ce953
DM
2329 } catch (dml_exception $e) {
2330 // if inserting the new record failed, chances are that the race condition has just
2331 // occured and the unique index did not allow to create the second record with the same
2332 // repositoryid + reference combo
2333 $id = $this->get_referencefileid($repositoryid, $reference, MUST_EXIST);
2334 }
2335
2336 return $id;
2337 }
2338
2339 /**
2340 * Returns the id of the record in {files_reference} that matches the passed parameters
2341 *
2342 * Depending on the required strictness, false can be returned. The behaviour is consistent
2343 * with standard DML methods.
2344 *
2345 * @param int $repositoryid
2346 * @param string $reference
2347 * @param int $strictness either {@link IGNORE_MISSING}, {@link IGNORE_MULTIPLE} or {@link MUST_EXIST}
2348 * @return int|bool
2349 */
2350 private function get_referencefileid($repositoryid, $reference, $strictness) {
2351 global $DB;
2352
0ad654dc
DM
2353 return $DB->get_field('files_reference', 'id',
2354 array('repositoryid' => $repositoryid, 'referencehash' => sha1($reference)), $strictness);
d83ce953 2355 }
14b7e500
MG
2356
2357 /**
2358 * Updates a reference to the external resource and all files that use it
2359 *
2360 * This function is called after synchronisation of an external file and updates the
2361 * contenthash, filesize and status of all files that reference this external file
87355560 2362 * as well as time last synchronised.
14b7e500
MG
2363 *
2364 * @param int $referencefileid
2365 * @param int $lastsync
87355560 2366 * @param int $lifetime argument not used any more, liefetime is returned by repository
14b7e500
MG
2367 * @param string $contenthash
2368 * @param int $filesize
2369 * @param int $status 0 if ok or 666 if source is missing
58e5ab1b 2370 * @param int $timemodified last time modified of the source, if known
14b7e500 2371 */
58e5ab1b 2372 public function update_references($referencefileid, $lastsync, $lifetime, $contenthash, $filesize, $status, $timemodified = null) {
14b7e500
MG
2373 global $DB;
2374 $referencefileid = clean_param($referencefileid, PARAM_INT);
2375 $lastsync = clean_param($lastsync, PARAM_INT);
14b7e500
MG
2376 validate_param($contenthash, PARAM_TEXT, NULL_NOT_ALLOWED);
2377 $filesize = clean_param($filesize, PARAM_INT);
2378 $status = clean_param($status, PARAM_INT);
2379 $params = array('contenthash' => $contenthash,
2380 'filesize' => $filesize,
2381 'status' => $status,
58e5ab1b
MG
2382 'referencefileid' => $referencefileid,
2383 'timemodified' => $timemodified);
14b7e500 2384 $DB->execute('UPDATE {files} SET contenthash = :contenthash, filesize = :filesize,
58e5ab1b 2385 status = :status ' . ($timemodified ? ', timemodified = :timemodified' : '') . '
14b7e500 2386 WHERE referencefileid = :referencefileid', $params);
87355560 2387 $data = array('id' => $referencefileid, 'lastsync' => $lastsync);
14b7e500
MG
2388 $DB->update_record('files_reference', (object)$data);
2389 }
151b0f94 2390
a30a04fa
AN
2391 /**
2392 * Calculate and return the contenthash of the supplied file.
2393 *
2394 * @param string $filepath The path to the file on disk
2395 * @return string The file's content hash
2396 */
2397 public static function hash_from_path($filepath) {
2398 return sha1_file($filepath);
2399 }
2400
2401 /**
2402 * Calculate and return the contenthash of the supplied content.
2403 *
2404 * @param string $content The file content
2405 * @return string The file's content hash
2406 */
2407 public static function hash_from_string($content) {
2408 return sha1($content);
2409 }
d83ce953 2410}