themes MDL-21984 Removed the need to specify the theme name in layouts within its...
[moodle.git] / pluginfile.php
1 <?php
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/>.
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  */
27 // disable moodle specific debug messages and any errors in output
28 define('NO_DEBUG_DISPLAY', true);
30 require_once('config.php');
31 require_once('lib/filelib.php');
33 $relativepath = get_file_argument();
34 $forcedownload = optional_param('forcedownload', 0, PARAM_BOOL);
36 // relative path must start with '/'
37 if (!$relativepath) {
38     print_error('invalidargorconf');
39 } else if ($relativepath[0] != '/') {
40     print_error('pathdoesnotstartslash');
41 }
43 // extract relative path components
44 $args = explode('/', ltrim($relativepath, '/'));
46 if (count($args) == 0) { // always at least user id
47     print_error('invalidarguments');
48 }
50 $contextid = (int)array_shift($args);
51 $filearea = array_shift($args);
53 if (!$context = get_context_instance_by_id($contextid)) {
54     send_file_not_found();
55 }
56 $fs = get_file_storage();
58 // If the file is a Flash file and that the user flash player is outdated return a flash upgrader MDL-20841
59 $mimetype = mimeinfo('type', $args[count($args)-1]);
60 if (!empty($CFG->excludeoldflashclients) && $mimetype == 'application/x-shockwave-flash'&& !empty($SESSION->flashversion)) {
61     $userplayerversion = explode('.', $SESSION->flashversion);
62     $requiredplayerversion = explode('.', $CFG->excludeoldflashclients);
63     $sendflashupgrader = true;
64 }
65 if (!empty($sendflashupgrader) && (($userplayerversion[0] <  $requiredplayerversion[0]) ||
66         ($userplayerversion[0] == $requiredplayerversion[0] && $userplayerversion[1] < $requiredplayerversion[1]) ||
67         ($userplayerversion[0] == $requiredplayerversion[0] && $userplayerversion[1] == $requiredplayerversion[1]
68          && $userplayerversion[2] < $requiredplayerversion[2]))) {
69         $path = $CFG->dirroot."/lib/flashdetect/flashupgrade.swf";  // Alternate content asking user to upgrade Flash
70         $filename = "flashupgrade.swf";
71         $lifetime = 0;  // Do not cache
72         send_file($path, $filename, $lifetime, 0, false, false, $mimetype);
74 } else if ($context->contextlevel == CONTEXT_SYSTEM) {   
75     if ($filearea === 'blog_attachment' || $filearea === 'blog_post') {
77         if (empty($CFG->bloglevel)) {
78             print_error('siteblogdisable', 'blog');
79         }
80         if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
81             require_login();
82             if (isguestuser()) {
83                 print_error('noguest');
84             }
85             if ($CFG->bloglevel == BLOG_USER_LEVEL) {
86                 if ($USER->id != $entry->userid) {
87                     send_file_not_found();
88                 }
89             }
90         }
91         $entryid = (int)array_shift($args);
92         if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
93             send_file_not_found();
94         }
95         if ('publishstate' === 'public') {
96             if ($CFG->forcelogin) {
97                 require_login();
98             }
100         } else if ('publishstate' === 'site') {
101             require_login();
102             //ok
103         } else if ('publishstate' === 'draft') {
104             require_login();
105             if ($USER->id != $entry->userid) {
106                 send_file_not_found();
107             }
108         }
110         //TODO: implement shared course and shared group access
112         $relativepath = '/'.implode('/', $args);
113         $fullpath = $context->id.$filearea.$entryid.$relativepath;
115         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
116             send_file_not_found();
117         }
119         send_stored_file($file, 10*60, 0, true); // download MUST be forced - security!
120     } else if ($filearea === 'grade_outcome' || $filearea === 'grade_scale') { // CONTEXT_SYSTEM
121         if ($CFG->forcelogin) {
122             require_login();
123         }
125         $fullpath = $context->id.$filearea.implode('/', $args);
127         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
128             send_file_not_found();
129         }
131         session_get_instance()->write_close(); // unlock session during fileserving
132         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
134     } else if ($filearea === 'tag_description') { // CONTEXT_SYSTEM
136         // All tag descriptions are going to be public but we still need to respect forcelogin
137         if ($CFG->forcelogin) {
138             require_login();
139         }
141         $fullpath = $context->id.$filearea.implode('/', $args);
143         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
144             send_file_not_found();
145         }
147         session_get_instance()->write_close(); // unlock session during fileserving
148         send_stored_file($file, 60*60, 0, true); // TODO: change timeout?
150     } else if ($filearea === 'calendar_event_description') { // CONTEXT_SYSTEM
152         // All events here are public the one requirement is that we respect forcelogin
153         if ($CFG->forcelogin) {
154             require_login();
155         }
157         $fullpath = $context->id.$filearea.implode('/', $args);
159         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
160             send_file_not_found();
161         }
163         session_get_instance()->write_close(); // unlock session during fileserving
164         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
166     } else {
167         send_file_not_found();
168     }
170 } else if ($context->contextlevel == CONTEXT_USER) {
172     if ($filearea === 'calendar_event_description') { // CONTEXT_USER
174         // Must be logged in, if they are not then they obviously can't be this user
175         require_login();
177         // Don't want guests here, potentially saves a DB call
178         if (isguestuser()) {
179             send_file_not_found();
180         }
182         // Get the event if from the args array
183         $eventid = array_shift($args);
184         if ((int)$eventid <= 0) {
185             send_file_not_found();
186         }
188         // Load the event from the database
189         $event = $DB->get_record('event', array('id'=>(int)$eventid));
190         // Check that we got an event and that it's userid is that of the user
191         if (!$event || $event->userid !== $USER->id) {
192             send_file_not_found();
193         }
195         // Get the file and serve if succesfull
196         $fullpath = $context->id.$filearea.$eventid.'/'.implode('/', $args);
197         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
198             send_file_not_found();
199         }
201         session_get_instance()->write_close(); // unlock session during fileserving
202         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
203     } else if ($filearea === 'user_profile') { // CONTEXT_USER
205         if ($CFG->forcelogin) {
206             require_login();
207         }
209         $userid = array_shift($args);
210         if ((int)$userid <= 0) {
211             send_file_not_found();
212         }
214         if (!empty($CFG->forceloginforprofiles)) {
215             require_login();
216             if (isguestuser()) {
217                 send_file_not_found();
218             }
220             if ($USER->id !== $userid) {
221                 $usercontext = get_context_instance(CONTEXT_USER, $userid);
222                 // The browsing user is not the current user
223                 if (!has_coursemanager_role($userid) && !has_capability('moodle/user:viewdetails', $usercontext)) {
224                     send_file_not_found();
225                 }
227                 $canview = false;
228                 if (has_capability('moodle/user:viewdetails', $usercontext)) {
229                     $canview = true;
230                 } else {
231                     $courses = get_my_courses($USER->id);
232                 }
234                 while (!$canview && count($courses) > 0) {
235                     $course = array_shift($courses);
236                     if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) {
237                         $canview = true;
238                     }
239                 }
240             }
241         }
243         $fullpath = $context->id.$filearea.$userid.'/'.implode('/', $args);
245         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
246             send_file_not_found();
247         }
249         session_get_instance()->write_close(); // unlock session during fileserving
250         send_stored_file($file, 60*60, 0, true);
251     }
253     send_file_not_found();
256 } else if ($context->contextlevel == CONTEXT_COURSECAT) {
257     if ($filearea !== 'coursecat_intro') {
258         send_file_not_found();
259     }
261     if ($CFG->forcelogin) {
262         // no login necessary - unless login forced everywhere
263         require_login();
264     }
266     $relativepath = '/'.implode('/', $args);
267     $fullpath = $context->id.'coursecat_intro0'.$relativepath;
269     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->get_filename() == '.') {
270         send_file_not_found();
271     }
273     session_get_instance()->write_close(); // unlock session during fileserving
274     send_stored_file($file, 60*60, 0, $forcedownload);
277 } else if ($context->contextlevel == CONTEXT_COURSE) {
278     if (!$course = $DB->get_record('course', array('id'=>$context->instanceid))) {
279         print_error('invalidcourseid');
280     }
282     if ($filearea === 'course_backup') {
283         require_login($course);
284         require_capability('moodle/backup:downloadfile', $context);
286         $relativepath = '/'.implode('/', $args);
287         $fullpath = $context->id.'course_backup0'.$relativepath;
289         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
290             send_file_not_found();
291         }
293         session_get_instance()->write_close(); // unlock session during fileserving
294         send_stored_file($file, 0, 0, true);
296     } else if ($filearea === 'course_intro') {
297         if ($CFG->forcelogin) {
298             require_login();
299         }
301         $relativepath = '/'.implode('/', $args);
302         $fullpath = $context->id.'course_intro0'.$relativepath;
304         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
305             send_file_not_found();
306         }
308         session_get_instance()->write_close(); // unlock session during fileserving
309         send_stored_file($file, 60*60, 0, false); // TODO: change timeout?
311     } else if ($filearea === 'course_summary') {
313         if ($CFG->forcelogin) {
314             require_login();
315         }
317         $fullpath = $context->id.$filearea.implode('/', $args);
319         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
320             send_file_not_found();
321         }
323         session_get_instance()->write_close(); // unlock session during fileserving
324         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
326     } else if ($filearea === 'course_grade_tree_feedback') {
328         if ($CFG->forcelogin || $course->id !== SITEID) {
329             require_login($course);
330         }
332         $fullpath = $context->id.$filearea.implode('/', $args);
334         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
335             send_file_not_found();
336         }
338         session_get_instance()->write_close(); // unlock session during fileserving
339         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
341     } else if ($filearea === 'calendar_event_description') { // CONTEXT_COURSE
343         // This is for content used in course and group events
345         // Respect forcelogin and require login unless this is the site.... it probably
346         // should NEVER be the site
347         if ($CFG->forcelogin || $course->id !== SITEID) {
348             require_login($course);
349         }
351         // Must be able to at least view the course
352         if (!is_enrolled($context) and !is_viewing($context)) {
353             send_file_not_found();
354         }
356         // Get the event id
357         $eventid = array_shift($args);
358         if ((int)$eventid <= 0) {
359             send_file_not_found();
360         }
362         // Load the event from the database we need to check whether it is
363         // a) valid
364         // b) a group event
365         // Group events use the course context (there is no group context)
366         $event = $DB->get_record('event', array('id'=>(int)$eventid));
367         if (!$event || $event->userid !== $USER->id) {
368             send_file_not_found();
369         }
371         // If its a group event require either membership of manage groups capability
372         if ($event->eventtype === 'group' && !has_capability('moodle/course:managegroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
373             send_file_not_found();
374         }
376         // If we get this far we can serve the file
377         $fullpath = $context->id.$filearea.$eventid.'/'.implode('/', $args);
378         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
379             send_file_not_found();
380         }
382         session_get_instance()->write_close(); // unlock session during fileserving
383         send_stored_file($file, 60*60, 0, $forcedownload); // TODO: change timeout?
385     } else if ($filearea === 'course_section') {
386         if ($CFG->forcelogin) {
387             require_login($course);
388         } else if ($course->id !== SITEID) {
389             require_login($course);
390         }
392         $sectionid = (int)array_shift($args);
394         if ($course->numsections < $sectionid) {
395             if (!has_capability('moodle/course:update', $context)) {
396                 // disable access to invisible sections if can not edit course
397                 // this is going to break some ugly hacks, but is necessary
398                 send_file_not_found();
399             }
400         }
402         $relativepath = '/'.implode('/', $args);
403         $fullpath = $context->id.'course_section'.$sectionid.$relativepath;
405         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
406             send_file_not_found();
407         }
409         session_get_instance()->write_close(); // unlock session during fileserving
410         send_stored_file($file, 60*60, 0, false); // TODO: change timeout?
412     } else if ($filearea === 'user_profile') {
413         $userid = (int)array_shift($args);
414         $usercontext = get_context_instance(CONTEXT_USER, $userid);
415         
416         if ($CFG->forcelogin) {
417             require_login();
418         }
420         if (!empty($CFG->forceloginforprofiles)) {
421             require_login();
422             if (isguestuser()) {
423                 print_error('noguest');
424             }
426             if (!has_coursemanager_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) {
427                 print_error('usernotavailable');
428             }
429             if (!has_capability('moodle/user:viewdetails', $context) &&
430                 !has_capability('moodle/user:viewdetails', $usercontext)) {
431                 print_error('cannotviewprofile');
432             }
433             if (!is_enrolled($context, $userid)) {
434                 print_error('notenrolledprofile');
435             }
436             if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
437                 print_error('groupnotamember');
438             }
439         }
441         $relativepath = '/'.implode('/', $args);
442         $fullpath = $usercontext->id.'user_profile0'.$relativepath;
444         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
445             send_file_not_found();
446         }
448         session_get_instance()->write_close(); // unlock session during fileserving
449         send_stored_file($file, 0, 0, true); // must force download - security!
451     } else {
452         send_file_not_found();
453     }
455 } else if ($context->contextlevel == CONTEXT_MODULE) {
457     if (!$coursecontext = get_context_instance_by_id(get_parent_contextid($context))) {
458         send_file_not_found();
459     }
461     if (!$course = $DB->get_record('course', array('id'=>$coursecontext->instanceid))) {
462         send_file_not_found();
463     }
464     $modinfo = get_fast_modinfo($course);
465     if (empty($modinfo->cms[$context->instanceid])) {
466         send_file_not_found();
467     }
469     $cminfo = $modinfo->cms[$context->instanceid];
470     $modname = $cminfo->modname;
471     $libfile = "$CFG->dirroot/mod/$modname/lib.php";
472     if (!file_exists($libfile)) {
473         send_file_not_found();
474     }
476     require_once($libfile);
477     if ($filearea === $modname.'_intro') {
478         if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) {
479             send_file_not_found();
480         }
481         if (!$cm = get_coursemodule_from_instance($modname, $cminfo->instance, $course->id)) {
482             send_file_not_found();
483         }
484         require_course_login($course, true, $cm);
486         if (!$cminfo->uservisible) {
487             send_file_not_found();
488         }
489         // all users may access it
490         $relativepath = '/'.implode('/', $args);
491         $fullpath = $context->id.$filearea.'0'.$relativepath;
493         $fs = get_file_storage();
494         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
495             send_file_not_found();
496         }
498         $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
500         // finally send the file
501         send_stored_file($file, $lifetime, 0);
502     }
504     $filefunction = $modname.'_pluginfile';
505     if (function_exists($filefunction)) {
506         // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
507         $filefunction($course, $cminfo, $context, $filearea, $args, $forcedownload);
508     }
510     send_file_not_found();
512 } else if ($context->contextlevel == CONTEXT_BLOCK) {
514     if (!$context = get_context_instance_by_id($contextid)) {
515         send_file_not_found();
516     }
517     $birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST);
518     $blockinstance = block_instance($birecord->blockname, $birecord);
520     if (strpos(get_class($blockinstance), $filearea) !== 0) {
521         send_file_not_found();
522     }
524     $itemid = array_shift($args);
525     $filename = array_pop($args);
526     $filepath = '/'.join('/', $args);
528     if (method_exists($blockinstance, 'send_file')) {
529         $blockinstance->send_file($context, $filearea, $itemid, $filepath, $filename);
530     }
532     send_file_not_found();
534 } else {
535     send_file_not_found();