files MDL-20635 Added support for cleaning up files associated with a context when...
[moodle.git] / pluginfile.php
CommitLineData
d92e7c4d 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * This script delegates file serving to individual plugins
20 *
21 * @package moodlecore
22 * @subpackage file
23 * @copyright 2008 Petr Skoda (http://skodak.org)
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
2e9b772f
PS
27// disable moodle specific debug messages and any errors in output
28define('NO_DEBUG_DISPLAY', true);
29
d92e7c4d 30require_once('config.php');
31require_once('lib/filelib.php');
32
d92e7c4d 33$relativepath = get_file_argument();
34$forcedownload = optional_param('forcedownload', 0, PARAM_BOOL);
35
36// relative path must start with '/'
37if (!$relativepath) {
38 print_error('invalidargorconf');
29afd52b 39} else if ($relativepath[0] != '/') {
d92e7c4d 40 print_error('pathdoesnotstartslash');
41}
42
43// extract relative path components
44$args = explode('/', ltrim($relativepath, '/'));
45
46if (count($args) == 0) { // always at least user id
47 print_error('invalidarguments');
48}
49
50$contextid = (int)array_shift($args);
51$filearea = array_shift($args);
52
49375088 53if (!$context = get_context_instance_by_id($contextid)) {
54 send_file_not_found();
55}
d92e7c4d 56$fs = get_file_storage();
57
58
59if ($context->contextlevel == CONTEXT_SYSTEM) {
1c7b8b93 60 if ($filearea === 'blog_attachment' || $filearea === 'blog_post') {
d92e7c4d 61
62 if (empty($CFG->bloglevel)) {
63 print_error('siteblogdisable', 'blog');
64 }
65 if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
66 require_login();
67 if (isguestuser()) {
68 print_error('noguest');
69 }
70 if ($CFG->bloglevel == BLOG_USER_LEVEL) {
71 if ($USER->id != $entry->userid) {
72 send_file_not_found();
73 }
74 }
75 }
76 $entryid = (int)array_shift($args);
77 if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
78 send_file_not_found();
79 }
80 if ('publishstate' === 'public') {
81 if ($CFG->forcelogin) {
82 require_login();
83 }
172dd12c 84
d92e7c4d 85 } else if ('publishstate' === 'site') {
86 require_login();
87 //ok
88 } else if ('publishstate' === 'draft') {
89 require_login();
90 if ($USER->id != $entry->userid) {
91 send_file_not_found();
92 }
93 }
172dd12c 94
d92e7c4d 95 //TODO: implement shared course and shared group access
172dd12c 96
d92e7c4d 97 $relativepath = '/'.implode('/', $args);
1c7b8b93 98 $fullpath = $context->id.$filearea.$entryid.$relativepath;
172dd12c 99
d92e7c4d 100 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
101 send_file_not_found();
102 }
172dd12c 103
d92e7c4d 104 send_stored_file($file, 10*60, 0, true); // download MUST be forced - security!
76d9df3f
SH
105 } else if ($filearea === 'calendar_event_description') { // CONTEXT_SYSTEM
106
107 // All events here are public the one requirement is that we respect forcelogin
108 if ($CFG->forcelogin) {
109 require_login();
110 }
111
112 $fullpath = $context->id.$filearea.implode('/', $args);
113
114 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
115 send_file_not_found();
116 }
117
118 session_get_instance()->write_close(); // unlock session during fileserving
119 send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
172dd12c 120
d92e7c4d 121 } else {
122 send_file_not_found();
172dd12c 123 }
124
d92e7c4d 125} else if ($context->contextlevel == CONTEXT_USER) {
76d9df3f
SH
126
127 if ($filearea === 'calendar_event_description') { // CONTEXT_USER
128
129 // Must be logged in, if they are not then they obviously can't be this user
130 require_login();
131
132 // Don't want guests here, potentially saves a DB call
133 if (isguestuser()) {
134 send_file_not_found();
135 }
136
137 // Get the event if from the args array
138 $eventid = array_shift($args);
139 if ((int)$eventid <= 0) {
140 send_file_not_found();
141 }
142
143 // Load the event from the database
144 $event = $DB->get_record('event', array('id'=>(int)$eventid));
145 // Check that we got an event and that it's userid is that of the user
146 if (!$event || $event->userid !== $USER->id) {
147 send_file_not_found();
148 }
149
150 // Get the file and serve if succesfull
151 $fullpath = $context->id.$filearea.$eventid.'/'.implode('/', $args);
152 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
153 send_file_not_found();
154 }
155
156 session_get_instance()->write_close(); // unlock session during fileserving
157 send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
158 }
159
d92e7c4d 160 send_file_not_found();
172dd12c 161
162
d92e7c4d 163} else if ($context->contextlevel == CONTEXT_COURSECAT) {
164 if ($filearea !== 'coursecat_intro') {
165 send_file_not_found();
166 }
172dd12c 167
d92e7c4d 168 if ($CFG->forcelogin) {
169 // no login necessary - unless login forced everywhere
170 require_login();
171 }
172dd12c 172
d92e7c4d 173 $relativepath = '/'.implode('/', $args);
174 $fullpath = $context->id.'coursecat_intro0'.$relativepath;
172dd12c 175
d92e7c4d 176 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->get_filename() == '.') {
177 send_file_not_found();
178 }
172dd12c 179
d92e7c4d 180 session_get_instance()->write_close(); // unlock session during fileserving
181 send_stored_file($file, 60*60, 0, $forcedownload);
172dd12c 182
172dd12c 183
d92e7c4d 184} else if ($context->contextlevel == CONTEXT_COURSE) {
185 if (!$course = $DB->get_record('course', array('id'=>$context->instanceid))) {
186 print_error('invalidcourseid');
187 }
172dd12c 188
d92e7c4d 189 if ($filearea === 'course_backup') {
190 require_login($course);
191 require_capability('moodle/site:backupdownload', $context);
172dd12c 192
d92e7c4d 193 $relativepath = '/'.implode('/', $args);
194 $fullpath = $context->id.'course_backup0'.$relativepath;
172dd12c 195
d92e7c4d 196 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
9e5fa330 197 send_file_not_found();
172dd12c 198 }
199
d92e7c4d 200 session_get_instance()->write_close(); // unlock session during fileserving
201 send_stored_file($file, 0, 0, true);
202
203 } else if ($filearea === 'course_intro') {
172dd12c 204 if ($CFG->forcelogin) {
172dd12c 205 require_login();
206 }
207
208 $relativepath = '/'.implode('/', $args);
d92e7c4d 209 $fullpath = $context->id.'course_intro0'.$relativepath;
172dd12c 210
d92e7c4d 211 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
9e5fa330 212 send_file_not_found();
172dd12c 213 }
214
56949c17 215 session_get_instance()->write_close(); // unlock session during fileserving
d92e7c4d 216 send_stored_file($file, 60*60, 0, false); // TODO: change timeout?
172dd12c 217
76d9df3f
SH
218 } else if ($filearea === 'calendar_event_description') { // CONTEXT_COURSE
219
220 // This is for content used in course and group events
221
222 // Respect forcelogin and require login unless this is the site.... it probably
223 // should NEVER be the site
224 if ($CFG->forcelogin || $course->id !== SITEID) {
225 require_login($course);
226 }
227
228 // Must be able to at least view the course
229 if (!has_capability('moodle/course:view', $context)) {
230 send_file_not_found();
231 }
232
233 // Get the event id
234 $eventid = array_shift($args);
235 if ((int)$eventid <= 0) {
236 send_file_not_found();
237 }
238
239 // Load the event from the database we need to check whether it is
240 // a) valid
241 // b) a group event
242 // Group events use the course context (there is no group context)
243 $event = $DB->get_record('event', array('id'=>(int)$eventid));
244 if (!$event || $event->userid !== $USER->id) {
245 send_file_not_found();
246 }
247
248 // If its a group event require either membership of manage groups capability
249 if ($event->eventtype === 'group' && !has_capability('moodle/course:managegroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
250 send_file_not_found();
251 }
252
253 // If we get this far we can serve the file
254 $fullpath = $context->id.$filearea.$eventid.'/'.implode('/', $args);
255 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
256 send_file_not_found();
257 }
258
259 session_get_instance()->write_close(); // unlock session during fileserving
260 send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
261
d92e7c4d 262 } else if ($filearea === 'course_section') {
263 if ($CFG->forcelogin) {
172dd12c 264 require_login($course);
d92e7c4d 265 } else if ($course->id !== SITEID) {
266 require_login($course);
267 }
106f3b67 268
d92e7c4d 269 $sectionid = (int)array_shift($args);
106f3b67 270
d92e7c4d 271 if ($course->numsections < $sectionid) {
272 if (!has_capability('moodle/course:update', $context)) {
273 // disable access to invisible sections if can not edit course
274 // this is going to break some ugly hacks, but is necessary
106f3b67 275 send_file_not_found();
276 }
d92e7c4d 277 }
106f3b67 278
d92e7c4d 279 $relativepath = '/'.implode('/', $args);
280 $fullpath = $context->id.'course_section'.$sectionid.$relativepath;
172dd12c 281
d92e7c4d 282 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
283 send_file_not_found();
284 }
172dd12c 285
d92e7c4d 286 session_get_instance()->write_close(); // unlock session during fileserving
287 send_stored_file($file, 60*60, 0, false); // TODO: change timeout?
172dd12c 288
d92e7c4d 289 } else if ($filearea === 'user_profile') {
290 $userid = (int)array_shift($args);
291 $usercontext = get_context_instance(CONTEXT_USER, $userid);
3156b8ca 292
d92e7c4d 293 if (!empty($CFG->forceloginforprofiles)) {
294 require_login();
295 if (isguestuser()) {
296 print_error('noguest');
3156b8ca 297 }
298
d92e7c4d 299 if (!isteacherinanycourse()
300 and !isteacherinanycourse($userid)
301 and !has_capability('moodle/user:viewdetails', $usercontext)) {
302 print_error('usernotavailable');
3156b8ca 303 }
d92e7c4d 304 if (!has_capability('moodle/user:viewdetails', $context) &&
305 !has_capability('moodle/user:viewdetails', $usercontext)) {
306 print_error('cannotviewprofile');
3156b8ca 307 }
d92e7c4d 308 if (!has_capability('moodle/course:view', $context, $userid, false)) {
309 print_error('notenrolledprofile');
106f3b67 310 }
d92e7c4d 311 if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
312 print_error('groupnotamember');
106f3b67 313 }
d92e7c4d 314 }
106f3b67 315
d92e7c4d 316 $relativepath = '/'.implode('/', $args);
317 $fullpath = $usercontext->id.'user_profile0'.$relativepath;
106f3b67 318
d92e7c4d 319 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
106f3b67 320 send_file_not_found();
321 }
172dd12c 322
d92e7c4d 323 session_get_instance()->write_close(); // unlock session during fileserving
324 send_stored_file($file, 0, 0, true); // must force download - security!
106f3b67 325
d92e7c4d 326 } else {
327 send_file_not_found();
328 }
329
330} else if ($context->contextlevel == CONTEXT_MODULE) {
331
332 if (!$coursecontext = get_context_instance_by_id(get_parent_contextid($context))) {
333 send_file_not_found();
334 }
335
336 if (!$course = $DB->get_record('course', array('id'=>$coursecontext->instanceid))) {
337 send_file_not_found();
338 }
339 $modinfo = get_fast_modinfo($course);
340 if (empty($modinfo->cms[$context->instanceid])) {
341 send_file_not_found();
342 }
343
344 $cminfo = $modinfo->cms[$context->instanceid];
345 $modname = $cminfo->modname;
346 $libfile = "$CFG->dirroot/mod/$modname/lib.php";
347 if (!file_exists($libfile)) {
348 send_file_not_found();
349 }
172dd12c 350
d92e7c4d 351 require_once($libfile);
352 if ($filearea === $modname.'_intro') {
353 if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) {
9e5fa330 354 send_file_not_found();
172dd12c 355 }
d92e7c4d 356 if (!$cminfo->uservisible) {
9e5fa330 357 send_file_not_found();
172dd12c 358 }
d92e7c4d 359 // all users may access it
360 $relativepath = '/'.implode('/', $args);
361 $fullpath = $context->id.$filearea.'0'.$relativepath;
172dd12c 362
d92e7c4d 363 $fs = get_file_storage();
364 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
dc5c2bd9 365 send_file_not_found();
366 }
367
d92e7c4d 368 $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
dc5c2bd9 369
d92e7c4d 370 // finally send the file
371 send_stored_file($file, $lifetime, 0);
372 }
373
374 $filefunction = $modname.'_pluginfile';
375 if (function_exists($filefunction)) {
520199ea 376 // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
377 $filefunction($course, $cminfo, $context, $filearea, $args, $forcedownload);
b287ea7a 378 }
520199ea 379
380 send_file_not_found();
172dd12c 381
d92e7c4d 382} else if ($context->contextlevel == CONTEXT_BLOCK) {
383 //not supported yet
384 send_file_not_found();
172dd12c 385
386
d92e7c4d 387} else {
388 send_file_not_found();
389}