ba34c8c0238cd9b52996d64419fbec8f83d27c7a
[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    core
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);
29 //TODO: uncomment this once the file api stabilises a bit more
31 require_once('config.php');
32 require_once('lib/filelib.php');
34 $relativepath = get_file_argument();
35 $forcedownload = optional_param('forcedownload', 0, PARAM_BOOL);
37 // relative path must start with '/'
38 if (!$relativepath) {
39     print_error('invalidargorconf');
40 } else if ($relativepath[0] != '/') {
41     print_error('pathdoesnotstartslash');
42 }
44 // extract relative path components
45 $args = explode('/', ltrim($relativepath, '/'));
47 if (count($args) < 3) { // always at least context, component and filearea
48     print_error('invalidarguments');
49 }
51 $contextid = (int)array_shift($args);
52 $component = clean_param(array_shift($args), PARAM_SAFEDIR);
53 $filearea  = clean_param(array_shift($args), PARAM_SAFEDIR);
55 list($context, $course, $cm) = get_context_info_array($contextid);
57 $fs = get_file_storage();
59 // If the file is a Flash file and that the user flash player is outdated return a flash upgrader MDL-20841
60 $mimetype = mimeinfo('type', $args[count($args)-1]);
61 if (!empty($CFG->excludeoldflashclients) && $mimetype == 'application/x-shockwave-flash'&& !empty($SESSION->flashversion)) {
62     $userplayerversion = explode('.', $SESSION->flashversion);
63     $requiredplayerversion = explode('.', $CFG->excludeoldflashclients);
64     if (($userplayerversion[0] <  $requiredplayerversion[0]) ||
65         ($userplayerversion[0] == $requiredplayerversion[0] && $userplayerversion[1] < $requiredplayerversion[1]) ||
66         ($userplayerversion[0] == $requiredplayerversion[0] && $userplayerversion[1] == $requiredplayerversion[1]
67          && $userplayerversion[2] < $requiredplayerversion[2])) {
68         $path = $CFG->dirroot."/lib/flashdetect/flashupgrade.swf";  // Alternate content asking user to upgrade Flash
69         $filename = "flashupgrade.swf";
70         send_file($path, $filename, O, 0, false, false, 'application/x-shockwave-flash'); // Do not cache
71     }
72 }
74 // ========================================================================================================================
75 if ($component === 'blog') {
76     // Blog file serving
77     if ($context->contextlevel != CONTEXT_SYSTEM) {
78         send_file_not_found();
79     }
80     if ($filearea !== 'attachment' and $filearea !== 'post') {
81         send_file_not_found();
82     }
84     if (empty($CFG->bloglevel)) {
85         print_error('siteblogdisable', 'blog');
86     }
88     if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) {
89         require_login();
90         if (isguestuser()) {
91             print_error('noguest');
92         }
93         if ($CFG->bloglevel == BLOG_USER_LEVEL) {
94             if ($USER->id != $entry->userid) {
95                 send_file_not_found();
96             }
97         }
98     }
99     $entryid = (int)array_shift($args);
100     if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) {
101         send_file_not_found();
102     }
104     if ('publishstate' === 'public') {
105         if ($CFG->forcelogin) {
106             require_login();
107         }
109     } else if ('publishstate' === 'site') {
110         require_login();
111         //ok
112     } else if ('publishstate' === 'draft') {
113         require_login();
114         if ($USER->id != $entry->userid) {
115             send_file_not_found();
116         }
117     }
119     $filename = array_pop($args);
120     $filepath = '/'.implode('/', $args);
122     if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) {
123         send_file_not_found();
124     }
126     send_stored_file($file, 10*60, 0, true); // download MUST be forced - security!
128 // ========================================================================================================================
129 } else if ($component === 'grade') {
130     if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) {
131         // Global gradebook files
132         if ($CFG->forcelogin) {
133             require_login();
134         }
136         $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
138         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
139             send_file_not_found();
140         }
142         session_get_instance()->write_close(); // unlock session during fileserving
143         send_stored_file($file, 60*60, 0, $forcedownload);
145     } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
146         //TODO: nobody implemented this yet in grade edit form!!
147         send_file_not_found();
149         if ($CFG->forcelogin || $course->id !== SITEID) {
150             require_login($course);
151         }
153         $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
155         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
156             send_file_not_found();
157         }
159         session_get_instance()->write_close(); // unlock session during fileserving
160         send_stored_file($file, 60*60, 0, $forcedownload);
161     } else {
162         send_file_not_found();
163     }
165 // ========================================================================================================================
166 } else if ($component === 'tag') {
167     if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) {
169         // All tag descriptions are going to be public but we still need to respect forcelogin
170         if ($CFG->forcelogin) {
171             require_login();
172         }
174         $fullpath = "/$context->id/tage/description/".implode('/', $args);
176         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
177             send_file_not_found();
178         }
180         session_get_instance()->write_close(); // unlock session during fileserving
181         send_stored_file($file, 60*60, 0, true);
183     } else {
184         send_file_not_found();
185     }
187 // ========================================================================================================================
188 } else if ($component === 'calendar') {
189     if ($filearea === 'event_description'  and $context->contextlevel == CONTEXT_SYSTEM) {
191         // All events here are public the one requirement is that we respect forcelogin
192         if ($CFG->forcelogin) {
193             require_login();
194         }
196         // Get the event if from the args array
197         $eventid = array_shift($args);
199         // Load the event from the database
200         if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) {
201             send_file_not_found();
202         }
203         // Check that we got an event and that it's userid is that of the user
205         // Get the file and serve if successful
206         $filename = array_pop($args);
207         $filepath = '/'.implode('/', $args);
208         if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
209             send_file_not_found();
210         }
212         session_get_instance()->write_close(); // unlock session during fileserving
213         send_stored_file($file, 60*60, 0, $forcedownload);
215     } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) {
217         // Must be logged in, if they are not then they obviously can't be this user
218         require_login();
220         // Don't want guests here, potentially saves a DB call
221         if (isguestuser()) {
222             send_file_not_found();
223         }
225         // Get the event if from the args array
226         $eventid = array_shift($args);
228         // Load the event from the database - user id must match
229         if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) {
230             send_file_not_found();
231         }
233         // Get the file and serve if successful
234         $filename = array_pop($args);
235         $filepath = '/'.implode('/', $args);
236         if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
237             send_file_not_found();
238         }
240         session_get_instance()->write_close(); // unlock session during fileserving
241         send_stored_file($file, 60*60, 0, $forcedownload);
243     } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) {
245         // Respect forcelogin and require login unless this is the site.... it probably
246         // should NEVER be the site
247         if ($CFG->forcelogin || $course->id !== SITEID) {
248             require_login($course);
249         }
251         // Must be able to at least view the course
252         if (!is_enrolled($context) and !is_viewing($context)) {
253             //TODO: hmm, do we really want to block guests here?
254             send_file_not_found();
255         }
257         // Get the event id
258         $eventid = array_shift($args);
260         // Load the event from the database we need to check whether it is
261         // a) valid course event
262         // b) a group event
263         // Group events use the course context (there is no group context)
264         if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) {
265             send_file_not_found();
266         }
268         // If its a group event require either membership of view all groups capability
269         if ($event->eventtype === 'group') {
270             if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) {
271                 send_file_not_found();
272             }
273         } else if ($event->eventtype === 'course') {
274             //ok
275         } else {
276             // some other type
277             send_file_not_found();
278         }
280         // If we get this far we can serve the file
281         $filename = array_pop($args);
282         $filepath = '/'.implode('/', $args);
283         if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) {
284             send_file_not_found();
285         }
287         session_get_instance()->write_close(); // unlock session during fileserving
288         send_stored_file($file, 60*60, 0, $forcedownload);
290     } else {
291         send_file_not_found();
292     }
294 // ========================================================================================================================
295 } else if ($component === 'user') {
296     if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) {
297         require_login();
299         if (isguestuser()) {
300             send_file_not_found();
301         }
303         if ($USER->id !== $context->instanceid) {
304             send_file_not_found();
305         }
307         $filename = array_pop($args);
308         $filepath = '/'.implode('/', $args);
309         if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
310             send_file_not_found();
311         }
313         session_get_instance()->write_close(); // unlock session during fileserving
314         send_stored_file($file, 0, 0, true); // must force download - security!
316     } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) {
318         if ($CFG->forcelogin) {
319             require_login();
320         }
322         $userid = $context->instanceid;
324         if ($USER->id == $userid) {
325             // always can access own
327         } else if (!empty($CFG->forceloginforprofiles)) {
328             require_login();
330             if (isguestuser()) {
331                 send_file_not_found();
332             }
334             // we allow access to site profile of all course contacts (usually teachers)
335             if (!has_coursecontact_role($userid) && !has_capability('moodle/user:viewdetails', $context)) {
336                 send_file_not_found();
337             }
339             $canview = false;
340             if (has_capability('moodle/user:viewdetails', $context)) {
341                 $canview = true;
342             } else {
343                 $courses = enrol_get_my_courses();
344             }
346             while (!$canview && count($courses) > 0) {
347                 $course = array_shift($courses);
348                 if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) {
349                     $canview = true;
350                 }
351             }
352         }
354         $filename = array_pop($args);
355         $filepath = '/'.implode('/', $args);
356         if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) {
357             send_file_not_found();
358         }
360         session_get_instance()->write_close(); // unlock session during fileserving
361         send_stored_file($file, 0, 0, true); // must force download - security!
363     } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) {
364         $userid = (int)array_shift($args);
365         $usercontext = get_context_instance(CONTEXT_USER, $userid);
367         if ($CFG->forcelogin) {
368             require_login();
369         }
371         if (!empty($CFG->forceloginforprofiles)) {
372             require_login();
373             if (isguestuser()) {
374                 print_error('noguest');
375             }
377             //TODO: review this logic of user profile access prevention
378             if (!has_coursecontact_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) {
379                 print_error('usernotavailable');
380             }
381             if (!has_capability('moodle/user:viewdetails', $context) && !has_capability('moodle/user:viewdetails', $usercontext)) {
382                 print_error('cannotviewprofile');
383             }
384             if (!is_enrolled($context, $userid)) {
385                 print_error('notenrolledprofile');
386             }
387             if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
388                 print_error('groupnotamember');
389             }
390         }
392         $filename = array_pop($args);
393         $filepath = '/'.implode('/', $args);
394         if (!$file = $fs->get_file($context->id, 'user', 'profile', 0, $filepath, $filename) or $file->is_directory()) {
395             send_file_not_found();
396         }
398         session_get_instance()->write_close(); // unlock session during fileserving
399         send_stored_file($file, 0, 0, true); // must force download - security!
401     } else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) {
402         require_login();
404         if (isguestuser()) {
405             send_file_not_found();
406         }
407         if ($USER->id != $userid) {
408             send_file_not_found();
409         }
411         $filename = array_pop($args);
412         $filepath = '/'.implode('/', $args);
413         if (!$file = $fs->get_file($context->id, 'user', 'backup', 0, $filepath, $filename) or $file->is_directory()) {
414             send_file_not_found();
415         }
417         session_get_instance()->write_close(); // unlock session during fileserving
418         send_stored_file($file, 0, 0, true); // must force download - security!
420     } else {
421         send_file_not_found();
422     }
424 // ========================================================================================================================
425 } else if ($component === 'coursecat') {
426     if ($context->contextlevel != CONTEXT_COURSECAT) {
427         send_file_not_found();
428     }
430     if ($filearea === 'description') {
431         if ($CFG->forcelogin) {
432             // no login necessary - unless login forced everywhere
433             require_login();
434         }
436         $filename = array_pop($args);
437         $filepath = '/'.implode('/', $args);
438         if (!$file = $fs->get_file($context->id, 'coursecat', 'description', 0, $filepath, $filename) or $file->is_directory()) {
439             send_file_not_found();
440         }
442         session_get_instance()->write_close(); // unlock session during fileserving
443         send_stored_file($file, 60*60, 0, $forcedownload);
444     } else {
445         send_file_not_found();
446     }
448 // ========================================================================================================================
449 } else if ($component === 'course') {
450     if ($context->contextlevel != CONTEXT_COURSE) {
451         send_file_not_found();
452     }
454     if ($filearea === 'summary') {
455         if ($CFG->forcelogin) {
456             require_login();
457         }
459         $filename = array_pop($args);
460         $filepath = '/'.implode('/', $args);
461         if (!$file = $fs->get_file($context->id, 'course', 'summary', 0, $filepath, $filename) or $file->is_directory()) {
462             send_file_not_found();
463         }
465         session_get_instance()->write_close(); // unlock session during fileserving
466         send_stored_file($file, 60*60, 0, $forcedownload);
468     } else if ($filearea === 'section') {
469         if ($CFG->forcelogin) {
470             require_login($course);
471         } else if ($course->id !== SITEID) {
472             require_login($course);
473         }
475         $sectionid = (int)array_shift($args);
477         if ($course->numsections < $sectionid) {
478             if (!has_capability('moodle/course:update', $context)) {
479                 // disable access to invisible sections if can not edit course
480                 // this is going to break some ugly hacks, but is necessary
481                 send_file_not_found();
482             }
483         }
485         $filename = array_pop($args);
486         $filepath = '/'.implode('/', $args);
487         if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
488             send_file_not_found();
489         }
491         session_get_instance()->write_close(); // unlock session during fileserving
492         send_stored_file($file, 60*60, 0, $forcedownload);
494     } else {
495         send_file_not_found();
496     }
498 } else if ($component === 'group') {
499     if ($context->contextlevel != CONTEXT_COURSE) {
500         send_file_not_found();
501     }
503     require_login($course);
505     $groupid = (int)array_shift($args);
507     $group = $DB->get_record('groups', array('id'=>$groupid, 'courseid'=>$course->id), '*', MUST_EXIST);
508     if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($group->id, $USER->id)) {
509         send_file_not_found();
510     }
512     if ($filearea === 'description' or $filearea === 'icon') {
514         //TODO: implement group image storage in file pool
516         $filename = array_pop($args);
517         $filepath = '/'.implode('/', $args);
518         if (!$file = $fs->get_file($context->id, 'group', 'description', $group->id, $filepath, $filename) or $file->is_directory()) {
519             send_file_not_found();
520         }
522         session_get_instance()->write_close(); // unlock session during fileserving
523         send_stored_file($file, 60*60, 0, $forcedownload);
525     } else {
526         send_file_not_found();
527     }
529 } else if ($component === 'grouping') {
530     if ($context->contextlevel != CONTEXT_COURSE) {
531         send_file_not_found();
532     }
534     require_login($course);
536     $groupingid = (int)array_shift($args);
538     // note: everybody has access to grouping desc images for now
539     if ($filearea === 'description') {
541         $filename = array_pop($args);
542         $filepath = '/'.implode('/', $args);
543         if (!$file = $fs->get_file($context->id, 'grouping', 'description', $groupingid->id, $filepath, $filename) or $file->is_directory()) {
544             send_file_not_found();
545         }
547         session_get_instance()->write_close(); // unlock session during fileserving
548         send_stored_file($file, 60*60, 0, $forcedownload);
550     } else {
551         send_file_not_found();
552     }
554 // ========================================================================================================================
555 } else if ($component === 'backup') {
556     if ($filearea === 'course' and $context->contextlevel == CONTEXT_COURSE) {
557         require_login($course);
558         require_capability('moodle/backup:downloadfile', $context);
560         $filename = array_pop($args);
561         $filepath = '/'.implode('/', $args);
562         if (!$file = $fs->get_file($context->id, 'backup', 'course', 0, $filepath, $filename) or $file->is_directory()) {
563             send_file_not_found();
564         }
566         session_get_instance()->write_close(); // unlock session during fileserving
567         send_stored_file($file, 0, 0, $forcedownload);
569     } else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) {
570         require_login($course);
571         require_capability('moodle/backup:downloadfile', $context);
573         $sectionid = (int)array_shift($args);
575         $filename = array_pop($args);
576         $filepath = '/'.implode('/', $args);
577         if (!$file = $fs->get_file($context->id, 'backup', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) {
578             send_file_not_found();
579         }
581         session_get_instance()->write_close();
582         send_stored_file($file, 60*60, 0, $forcedownload);
584     } else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) {
585         require_login($course, false, $cm);
586         require_capability('moodle/backup:downloadfile', $context);
588         $filename = array_pop($args);
589         $filepath = '/'.implode('/', $args);
590         if (!$file = $fs->get_file($context->id, 'backup', 'activity', 0, $filepath, $filename) or $file->is_directory()) {
591             send_file_not_found();
592         }
594         session_get_instance()->write_close();
595         send_stored_file($file, 60*60, 0, $forcedownload);
597     } else {
598         send_file_not_found();
599     }
601 // ========================================================================================================================
602 } else if (strpos($component, 'mod_') === 0) {
603     $modname = substr($component, 4);
604     if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
605         send_file_not_found();
606     }
607     require_once("$CFG->dirroot/mod/$modname/lib.php");
609     if ($context->contextlevel == CONTEXT_MODULE) {
610         if ($cm->modname !== $modname) {
611             // somebody tries to gain illegal access, cm type must match the component!
612             send_file_not_found();
613         }
614     }
616     if ($filearea === 'intro') {
617         if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) {
618             send_file_not_found();
619         }
620         require_course_login($course, true, $cm);
622         // all users may access it
623         $filename = array_pop($args);
624         $filepath = '/'.implode('/', $args);
625         if (!$file = $fs->get_file($context->id, 'mod_'.$modname, 'intro', 0, $filepath, $filename) or $file->is_directory()) {
626             send_file_not_found();
627         }
629         $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
631         // finally send the file
632         send_stored_file($file, $lifetime, 0);
633     }
635     $filefunction = $component.'_pluginfile';
636     $filefunctionold = $modname.'_pluginfile';
637     if (function_exists($filefunction)) {
638         // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
639         $filefunction($course, $cm, $context, $filearea, $args, $forcedownload);
640     } else if (function_exists($filefunctionold)) {
641         // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
642         $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload);
643     }
645     send_file_not_found();
647 // ========================================================================================================================
648 } else if (strpos($component, 'block_') === 0) {
649     $blockname = substr($component, 6);
650     // note: no more class methods in blocks please, that is ....
651     if (!file_exists("$CFG->dirroot/blocks/$blockname/lib.php")) {
652         send_file_not_found();
653     }
654     require_once("$CFG->dirroot/blocks/$blockname/lib.php");
656     if ($context->contextlevel == CONTEXT_BLOCK) {
657         $birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST);
658         if ($birecord->blockname !== $blockname) {
659             // somebody tries to gain illegal access, cm type must match the component!
660             send_file_not_found();
661         }
662     } else {
663         $birecord = null;
664     }
666     $filefunction = $component.'_pluginfile';
667     if (function_exists($filefunction)) {
668         // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
669         $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload);
670     }
672     send_file_not_found();
674 } else if (strpos($component, '_') === false) {
675     // all core subsystems have to be specified above, no more guessing here!
676     send_file_not_found();
678 } else {
679     // try to serve general plugin file in arbitrary context
680     $dir = get_component_directory($component);
681     if (!file_exists("$dir/lib.php")) {
682         send_file_not_found();
683     }
684     include_once("$dir/lib.php");
686     $filefunction = $component.'_pluginfile';
687     if (function_exists($filefunction)) {
688         // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found"
689         $filefunction($course, $cm, $context, $filearea, $args, $forcedownload);
690     }
692     send_file_not_found();