1c4fffe59f97cff44dbdd1ef1a4ac73687b309b8
[moodle.git] / mod / folder / lib.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  * Mandatory public API of folder module
20  *
21  * @package   mod_folder
22  * @copyright 2009 Petr Skoda  {@link http://skodak.org}
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 /** Display folder contents on a separate page */
29 define('FOLDER_DISPLAY_PAGE', 0);
30 /** Display folder contents inline in a course */
31 define('FOLDER_DISPLAY_INLINE', 1);
33 /**
34  * List of features supported in Folder module
35  * @param string $feature FEATURE_xx constant for requested feature
36  * @return mixed True if module supports feature, false if not, null if doesn't know
37  */
38 function folder_supports($feature) {
39     switch($feature) {
40         case FEATURE_MOD_ARCHETYPE:           return MOD_ARCHETYPE_RESOURCE;
41         case FEATURE_GROUPS:                  return false;
42         case FEATURE_GROUPINGS:               return false;
43         case FEATURE_MOD_INTRO:               return true;
44         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
45         case FEATURE_GRADE_HAS_GRADE:         return false;
46         case FEATURE_GRADE_OUTCOMES:          return false;
47         case FEATURE_BACKUP_MOODLE2:          return true;
48         case FEATURE_SHOW_DESCRIPTION:        return true;
50         default: return null;
51     }
52 }
54 /**
55  * Returns all other caps used in module
56  * @return array
57  */
58 function folder_get_extra_capabilities() {
59     return array('moodle/site:accessallgroups');
60 }
62 /**
63  * This function is used by the reset_course_userdata function in moodlelib.
64  * @param $data the data submitted from the reset course.
65  * @return array status array
66  */
67 function folder_reset_userdata($data) {
68     return array();
69 }
71 /**
72  * List the actions that correspond to a view of this module.
73  * This is used by the participation report.
74  *
75  * Note: This is not used by new logging system. Event with
76  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
77  *       be considered as view action.
78  *
79  * @return array
80  */
81 function folder_get_view_actions() {
82     return array('view', 'view all');
83 }
85 /**
86  * List the actions that correspond to a post of this module.
87  * This is used by the participation report.
88  *
89  * Note: This is not used by new logging system. Event with
90  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
91  *       will be considered as post action.
92  *
93  * @return array
94  */
95 function folder_get_post_actions() {
96     return array('update', 'add');
97 }
99 /**
100  * Add folder instance.
101  * @param object $data
102  * @param object $mform
103  * @return int new folder instance id
104  */
105 function folder_add_instance($data, $mform) {
106     global $DB;
108     $cmid        = $data->coursemodule;
109     $draftitemid = $data->files;
111     $data->timemodified = time();
112     $data->id = $DB->insert_record('folder', $data);
114     // we need to use context now, so we need to make sure all needed info is already in db
115     $DB->set_field('course_modules', 'instance', $data->id, array('id'=>$cmid));
116     $context = context_module::instance($cmid);
118     if ($draftitemid) {
119         file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
120     }
122     return $data->id;
125 /**
126  * Update folder instance.
127  * @param object $data
128  * @param object $mform
129  * @return bool true
130  */
131 function folder_update_instance($data, $mform) {
132     global $CFG, $DB;
134     $cmid        = $data->coursemodule;
135     $draftitemid = $data->files;
137     $data->timemodified = time();
138     $data->id           = $data->instance;
139     $data->revision++;
141     $DB->update_record('folder', $data);
143     $context = context_module::instance($cmid);
144     if ($draftitemid = file_get_submitted_draft_itemid('files')) {
145         file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
146     }
148     return true;
151 /**
152  * Delete folder instance.
153  * @param int $id
154  * @return bool true
155  */
156 function folder_delete_instance($id) {
157     global $DB;
159     if (!$folder = $DB->get_record('folder', array('id'=>$id))) {
160         return false;
161     }
163     // note: all context files are deleted automatically
165     $DB->delete_records('folder', array('id'=>$folder->id));
167     return true;
170 /**
171  * Lists all browsable file areas
172  *
173  * @package  mod_folder
174  * @category files
175  * @param stdClass $course course object
176  * @param stdClass $cm course module object
177  * @param stdClass $context context object
178  * @return array
179  */
180 function folder_get_file_areas($course, $cm, $context) {
181     $areas = array();
182     $areas['content'] = get_string('foldercontent', 'folder');
184     return $areas;
187 /**
188  * File browsing support for folder module content area.
189  *
190  * @package  mod_folder
191  * @category files
192  * @param file_browser $browser file browser instance
193  * @param array $areas file areas
194  * @param stdClass $course course object
195  * @param stdClass $cm course module object
196  * @param stdClass $context context object
197  * @param string $filearea file area
198  * @param int $itemid item ID
199  * @param string $filepath file path
200  * @param string $filename file name
201  * @return file_info instance or null if not found
202  */
203 function folder_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
204     global $CFG;
207     if ($filearea === 'content') {
208         if (!has_capability('mod/folder:view', $context)) {
209             return NULL;
210         }
211         $fs = get_file_storage();
213         $filepath = is_null($filepath) ? '/' : $filepath;
214         $filename = is_null($filename) ? '.' : $filename;
215         if (!$storedfile = $fs->get_file($context->id, 'mod_folder', 'content', 0, $filepath, $filename)) {
216             if ($filepath === '/' and $filename === '.') {
217                 $storedfile = new virtual_root_file($context->id, 'mod_folder', 'content', 0);
218             } else {
219                 // not found
220                 return null;
221             }
222         }
224         require_once("$CFG->dirroot/mod/folder/locallib.php");
225         $urlbase = $CFG->wwwroot.'/pluginfile.php';
227         // students may read files here
228         $canwrite = has_capability('mod/folder:managefiles', $context);
229         return new folder_content_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, $canwrite, false);
230     }
232     // note: folder_intro handled in file_browser automatically
234     return null;
237 /**
238  * Serves the folder files.
239  *
240  * @package  mod_folder
241  * @category files
242  * @param stdClass $course course object
243  * @param stdClass $cm course module
244  * @param stdClass $context context object
245  * @param string $filearea file area
246  * @param array $args extra arguments
247  * @param bool $forcedownload whether or not force download
248  * @param array $options additional options affecting the file serving
249  * @return bool false if file not found, does not return if found - just send the file
250  */
251 function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
252     global $CFG, $DB;
254     if ($context->contextlevel != CONTEXT_MODULE) {
255         return false;
256     }
258     require_course_login($course, true, $cm);
259     if (!has_capability('mod/folder:view', $context)) {
260         return false;
261     }
263     if ($filearea !== 'content') {
264         // intro is handled automatically in pluginfile.php
265         return false;
266     }
268     array_shift($args); // ignore revision - designed to prevent caching problems only
270     $fs = get_file_storage();
271     $relativepath = implode('/', $args);
272     $fullpath = "/$context->id/mod_folder/content/0/$relativepath";
273     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
274         return false;
275     }
277     // finally send the file
278     // for folder module, we force download file all the time
279     send_stored_file($file, 0, 0, true, $options);
282 /**
283  * Return a list of page types
284  * @param string $pagetype current page type
285  * @param stdClass $parentcontext Block's parent context
286  * @param stdClass $currentcontext Current context of block
287  */
288 function folder_page_type_list($pagetype, $parentcontext, $currentcontext) {
289     $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder'));
290     return $module_pagetype;
293 /**
294  * Export folder resource contents
295  *
296  * @return array of file content
297  */
298 function folder_export_contents($cm, $baseurl) {
299     global $CFG, $DB;
300     $contents = array();
301     $context = context_module::instance($cm->id);
302     $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
304     $fs = get_file_storage();
305     $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
307     foreach ($files as $fileinfo) {
308         $file = array();
309         $file['type'] = 'file';
310         $file['filename']     = $fileinfo->get_filename();
311         $file['filepath']     = $fileinfo->get_filepath();
312         $file['filesize']     = $fileinfo->get_filesize();
313         $file['fileurl']      = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_folder/content/'.$folder->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true);
314         $file['timecreated']  = $fileinfo->get_timecreated();
315         $file['timemodified'] = $fileinfo->get_timemodified();
316         $file['sortorder']    = $fileinfo->get_sortorder();
317         $file['userid']       = $fileinfo->get_userid();
318         $file['author']       = $fileinfo->get_author();
319         $file['license']      = $fileinfo->get_license();
320         $contents[] = $file;
321     }
323     return $contents;
326 /**
327  * Register the ability to handle drag and drop file uploads
328  * @return array containing details of the files / types the mod can handle
329  */
330 function folder_dndupload_register() {
331     return array('files' => array(
332                      array('extension' => 'zip', 'message' => get_string('dnduploadmakefolder', 'mod_folder'))
333                  ));
336 /**
337  * Handle a file that has been uploaded
338  * @param object $uploadinfo details of the file / content that has been uploaded
339  * @return int instance id of the newly created mod
340  */
341 function folder_dndupload_handle($uploadinfo) {
342     global $DB, $USER;
344     // Gather the required info.
345     $data = new stdClass();
346     $data->course = $uploadinfo->course->id;
347     $data->name = $uploadinfo->displayname;
348     $data->intro = '<p>'.$uploadinfo->displayname.'</p>';
349     $data->introformat = FORMAT_HTML;
350     $data->coursemodule = $uploadinfo->coursemodule;
351     $data->files = null; // We will unzip the file and sort out the contents below.
353     $data->id = folder_add_instance($data, null);
355     // Retrieve the file from the draft file area.
356     $context = context_module::instance($uploadinfo->coursemodule);
357     file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_folder', 'temp', 0, array('subdirs'=>true));
358     $fs = get_file_storage();
359     $files = $fs->get_area_files($context->id, 'mod_folder', 'temp', 0, 'sortorder', false);
360     // Only ever one file - extract the contents.
361     $file = reset($files);
363     $success = $file->extract_to_storage(new zip_packer(), $context->id, 'mod_folder', 'content', 0, '/', $USER->id);
364     $fs->delete_area_files($context->id, 'mod_folder', 'temp', 0);
366     if ($success) {
367         return $data->id;
368     }
370     $DB->delete_records('folder', array('id' => $data->id));
371     return false;
374 /**
375  * Given a coursemodule object, this function returns the extra
376  * information needed to print this activity in various places.
377  *
378  * If folder needs to be displayed inline we store additional information
379  * in customdata, so functions {@link folder_cm_info_dynamic()} and
380  * {@link folder_cm_info_view()} do not need to do DB queries
381  *
382  * @param cm_info $cm
383  * @return cached_cm_info info
384  */
385 function folder_get_coursemodule_info($cm) {
386     global $DB;
387     if (!($folder = $DB->get_record('folder', array('id' => $cm->instance),
388             'id, name, display, showexpanded, intro, introformat'))) {
389         return NULL;
390     }
391     $cminfo = new cached_cm_info();
392     $cminfo->name = $folder->name;
393     if ($folder->display == FOLDER_DISPLAY_INLINE) {
394         // prepare folder object to store in customdata
395         $fdata = new stdClass();
396         $fdata->showexpanded = $folder->showexpanded;
397         if ($cm->showdescription && strlen(trim($folder->intro))) {
398             $fdata->intro = $folder->intro;
399             if ($folder->introformat != FORMAT_MOODLE) {
400                 $fdata->introformat = $folder->introformat;
401             }
402         }
403         $cminfo->customdata = $fdata;
404     } else {
405         if ($cm->showdescription) {
406             // Convert intro to html. Do not filter cached version, filters run at display time.
407             $cminfo->content = format_module_intro('folder', $folder, $cm->id, false);
408         }
409     }
410     return $cminfo;
413 /**
414  * Sets dynamic information about a course module
415  *
416  * This function is called from cm_info when displaying the module
417  * mod_folder can be displayed inline on course page and therefore have no course link
418  *
419  * @param cm_info $cm
420  */
421 function folder_cm_info_dynamic(cm_info $cm) {
422     if ($cm->customdata) {
423         // the field 'customdata' is not empty IF AND ONLY IF we display contens inline
424         $cm->set_no_view_link();
425     }
428 /**
429  * Overwrites the content in the course-module object with the folder files list
430  * if folder.display == FOLDER_DISPLAY_INLINE
431  *
432  * @param cm_info $cm
433  */
434 function folder_cm_info_view(cm_info $cm) {
435     global $PAGE;
436     if ($cm->uservisible && $cm->customdata &&
437             has_capability('mod/folder:view', $cm->context)) {
438         // Restore folder object from customdata.
439         // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline.
440         // Otherwise the content is default.
441         $folder = $cm->customdata;
442         $folder->id = (int)$cm->instance;
443         $folder->course = (int)$cm->course;
444         $folder->display = FOLDER_DISPLAY_INLINE;
445         $folder->name = $cm->name;
446         if (empty($folder->intro)) {
447             $folder->intro = '';
448         }
449         if (empty($folder->introformat)) {
450             $folder->introformat = FORMAT_MOODLE;
451         }
452         // display folder
453         $renderer = $PAGE->get_renderer('mod_folder');
454         $cm->set_content($renderer->display_folder($folder));
455     }
458 /**
459  * Mark the activity completed (if required) and trigger the course_module_viewed event.
460  *
461  * @param  stdClass $folder     folder object
462  * @param  stdClass $course     course object
463  * @param  stdClass $cm         course module object
464  * @param  stdClass $context    context object
465  * @since Moodle 3.0
466  */
467 function folder_view($folder, $course, $cm, $context) {
469     // Trigger course_module_viewed event.
470     $params = array(
471         'context' => $context,
472         'objectid' => $folder->id
473     );
475     $event = \mod_folder\event\course_module_viewed::create($params);
476     $event->add_record_snapshot('course_modules', $cm);
477     $event->add_record_snapshot('course', $course);
478     $event->add_record_snapshot('folder', $folder);
479     $event->trigger();
481     // Completion.
482     $completion = new completion_info($course);
483     $completion->set_module_viewed($cm);
486 /**
487  * Check if the folder can be zipped and downloaded.
488  * @param stdClass $folder
489  * @param context_module $cm
490  * @return bool True if the folder can be zipped and downloaded.
491  * @throws \dml_exception
492  */
493 function folder_archive_available($folder, $cm) {
494     if (!$folder->showdownloadfolder) {
495         return false;
496     }
498     $context = context_module::instance($cm->id);
499     $fs = get_file_storage();
500     $dir = $fs->get_area_tree($context->id, 'mod_folder', 'content', 0);
502     $size = folder_get_directory_size($dir);
503     $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
505     if ($size == 0) {
506         return false;
507     }
509     if (!empty($maxsize) && $size > $maxsize) {
510         return false;
511     }
513     return true;
516 /**
517  * Recursively measure the size of the files in a directory.
518  * @param array $directory
519  * @return int size of directory contents in bytes
520  */
521 function folder_get_directory_size($directory) {
522     $size = 0;
524     foreach ($directory['files'] as $file) {
525         $size += $file->get_filesize();
526     }
528     foreach ($directory['subdirs'] as $subdirectory) {
529         $size += folder_get_directory_size($subdirectory);
530     }
532     return $size;
535 /**
536  * Mark the activity completed (if required) and trigger the all_files_downloaded event.
537  *
538  * @param  stdClass $folder     folder object
539  * @param  stdClass $course     course object
540  * @param  stdClass $cm         course module object
541  * @param  stdClass $context    context object
542  * @since Moodle 3.1
543  */
544 function folder_downloaded($folder, $course, $cm, $context) {
545     $params = array(
546         'context' => $context,
547         'objectid' => $folder->id
548     );
549     $event = \mod_folder\event\all_files_downloaded::create($params);
550     $event->add_record_snapshot('course_modules', $cm);
551     $event->add_record_snapshot('course', $course);
552     $event->add_record_snapshot('folder', $folder);
553     $event->trigger();
555     // Completion.
556     $completion = new completion_info($course);
557     $completion->set_module_viewed($cm);
560 /**
561  * Returns all uploads since a given time in specified folder.
562  *
563  * @param array $activities
564  * @param int $index
565  * @param int $timestart
566  * @param int $courseid
567  * @param int $cmid
568  * @param int $userid
569  * @param int $groupid not used, but required for compatibilty with other modules
570  */
571 function folder_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
572     global $COURSE, $DB;
574     if ($COURSE->id == $courseid) {
575         $course = $COURSE;
576     } else {
577         $course = $DB->get_record('course', array('id' => $courseid));
578     }
580     $modinfo = get_fast_modinfo($course);
581     $cm = $modinfo->cms[$cmid];
583     $context = context_module::instance($cm->id);
584     if (!has_capability('mod/folder:view', $context)) {
585         return;
586     }
587     $files = folder_get_recent_activity($context, $timestart, $userid);
589     foreach ($files as $file) {
590         $tmpactivity = new stdClass();
592         $tmpactivity->type       = 'folder';
593         $tmpactivity->cmid       = $cm->id;
594         $tmpactivity->sectionnum = $cm->sectionnum;
595         $tmpactivity->timestamp  = $file->get_timemodified();
596         $tmpactivity->user       = core_user::get_user($file->get_userid());
598         $tmpactivity->content           = new stdClass();
599         $tmpactivity->content->url      = moodle_url::make_pluginfile_url($file->get_contextid(), 'mod_folder', 'content',
600             $file->get_itemid(), $file->get_filepath(), $file->get_filename());
602         if (file_extension_in_typegroup($file->get_filename(), 'web_image')) {
603             $image = $tmpactivity->content->url->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
604             $image = html_writer::empty_tag('img', array('src' => $image));
605         } else {
606             $image = $OUTPUT->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
607         }
609         $tmpactivity->content->image    = $image;
610         $tmpactivity->content->filename = $file->get_filename();
612         $activities[$index++] = $tmpactivity;
613     }
617 /**
618  * Outputs the folder uploads indicated by $activity.
619  *
620  * @param object $activity      the activity object the folder resides in
621  * @param int    $courseid      the id of the course the folder resides in
622  * @param bool   $detail        not used, but required for compatibilty with other modules
623  * @param int    $modnames      not used, but required for compatibilty with other modules
624  * @param bool   $viewfullnames not used, but required for compatibilty with other modules
625  */
626 function folder_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
627     global $OUTPUT;
629     $content = $activity->content;
630     $tableoptions = [
631         'border' => '0',
632         'cellpadding' => '3',
633         'cellspacing' => '0'
634     ];
635     $output = html_writer::start_tag('table', $tableoptions);
636     $output .= html_writer::start_tag('tr');
637     $output .= html_writer::tag('td', $content->image, ['class' => 'fp-icon', 'valign' => 'top']);
638     $output .= html_writer::start_tag('td');
639     $output .= html_writer::start_div('fp-filename');
640     $output .= html_writer::link($content->url, $content->filename);
641     $output .= html_writer::end_div();
643     // Show the uploader.
644     $fullname = fullname($activity->user, $viewfullnames);
645     $userurl = new moodle_url('/user/view.php');
646     $userurl->params(['id' => $activity->user->id, 'course' => $courseid]);
647     $by = new stdClass();
648     $by->name = html_writer::link($userurl, $fullname);
649     $by->date = userdate($activity->timestamp);
650     $authornamedate = get_string('bynameondate', 'folder', $by);
651     $output .= html_writer::div($authornamedate, 'user');
653     // Finish up the table.
654     $output .= html_writer::end_tag('tr');
655     $output .= html_writer::end_tag('table');
657     echo $output;
660 /**
661  * Gets recent file uploads in a given folder. Does not perform security checks.
662  *
663  * @param object $context
664  * @param int $timestart
665  * @param int $userid
666  *
667  * @return array
668  */
669 function folder_get_recent_activity($context, $timestart, $userid=0) {
670     $newfiles = array();
671     $fs = get_file_storage();
672     $files = $fs->get_area_files($context->id, 'mod_folder', 'content');
673     foreach ($files as $file) {
674         if ($file->get_timemodified() <= $timestart) {
675             continue;
676         }
677         if ($file->get_filename() === '.') {
678             continue;
679         }
680         if (!empty($userid) && $userid !== $file->get_userid()) {
681             continue;
682         }
683         $newfiles[] = $file;
684     }
685     return $newfiles;
688 /**
689  * Given a course and a date, prints a summary of all the new
690  * files posted in folder resources since that date
691  *
692  * @uses CONTEXT_MODULE
693  * @param object $course
694  * @param bool $viewfullnames capability
695  * @param int $timestart
696  * @return bool success
697  */
698 function folder_print_recent_activity($course, $viewfullnames, $timestart) {
699     global $OUTPUT;
701     $folders = get_all_instances_in_course('folder', $course);
703     if (empty($folders)) {
704         return false;
705     }
707     $newfiles = array();
709     $modinfo = get_fast_modinfo($course);
710     foreach ($folders as $folder) {
711         // Skip resources if the user can't view them.
712         $cm = $modinfo->cms[$folder->coursemodule];
713         $context = context_module::instance($cm->id);
714         if (!has_capability('mod/folder:view', $context)) {
715             continue;
716         }
718         // Get the files uploaded in the current time frame.
719         $newfiles = array_merge($newfiles, folder_get_recent_activity($context, $timestart));
720     }
722     if (empty($newfiles)) {
723         return false;
724     }
726     // Build list of files.
727     echo $OUTPUT->heading(get_string('newfoldercontent', 'folder').':', 3);
728     $list = html_writer::start_tag('ul', ['class' => 'unlist']);
729     foreach ($newfiles as $file) {
730         $filename = $file->get_filename();
731         $url = moodle_url::make_pluginfile_url($file->get_contextid(), 'mod_folder', 'content',
732             $file->get_itemid(), $file->get_filepath(), $filename);
734         $list .= html_writer::start_tag('li');
735         $list .= html_writer::start_div('head');
736         $list .= html_writer::div(userdate($file->get_timemodified(), get_string('strftimerecent')), 'date');
737         $list .= html_writer::div($file->get_author(), 'name');
738         $list .= html_writer::end_div(); // Head.
740         $list .= html_writer::start_div('info');
741         $list .= html_writer::link($url, $filename);
742         $list .= html_writer::end_div(); // Info.
743         $list .= html_writer::end_tag('li');
744     }
745     $list .= html_writer::end_tag('ul');
746     echo $list;
747     return true;