Merge branch 'MDL-58399-master' of git://github.com/jleyva/moodle
[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     $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
123     \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
125     return $data->id;
128 /**
129  * Update folder instance.
130  * @param object $data
131  * @param object $mform
132  * @return bool true
133  */
134 function folder_update_instance($data, $mform) {
135     global $CFG, $DB;
137     $cmid        = $data->coursemodule;
138     $draftitemid = $data->files;
140     $data->timemodified = time();
141     $data->id           = $data->instance;
142     $data->revision++;
144     $DB->update_record('folder', $data);
146     $context = context_module::instance($cmid);
147     if ($draftitemid = file_get_submitted_draft_itemid('files')) {
148         file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
149     }
151     $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
152     \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
154     return true;
157 /**
158  * Delete folder instance.
159  * @param int $id
160  * @return bool true
161  */
162 function folder_delete_instance($id) {
163     global $DB;
165     if (!$folder = $DB->get_record('folder', array('id'=>$id))) {
166         return false;
167     }
169     $cm = get_coursemodule_from_instance('folder', $id);
170     \core_completion\api::update_completion_date_event($cm->id, 'folder', $folder->id, null);
172     // note: all context files are deleted automatically
174     $DB->delete_records('folder', array('id'=>$folder->id));
176     return true;
179 /**
180  * Lists all browsable file areas
181  *
182  * @package  mod_folder
183  * @category files
184  * @param stdClass $course course object
185  * @param stdClass $cm course module object
186  * @param stdClass $context context object
187  * @return array
188  */
189 function folder_get_file_areas($course, $cm, $context) {
190     $areas = array();
191     $areas['content'] = get_string('foldercontent', 'folder');
193     return $areas;
196 /**
197  * File browsing support for folder module content area.
198  *
199  * @package  mod_folder
200  * @category files
201  * @param file_browser $browser file browser instance
202  * @param array $areas file areas
203  * @param stdClass $course course object
204  * @param stdClass $cm course module object
205  * @param stdClass $context context object
206  * @param string $filearea file area
207  * @param int $itemid item ID
208  * @param string $filepath file path
209  * @param string $filename file name
210  * @return file_info instance or null if not found
211  */
212 function folder_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
213     global $CFG;
216     if ($filearea === 'content') {
217         if (!has_capability('mod/folder:view', $context)) {
218             return NULL;
219         }
220         $fs = get_file_storage();
222         $filepath = is_null($filepath) ? '/' : $filepath;
223         $filename = is_null($filename) ? '.' : $filename;
224         if (!$storedfile = $fs->get_file($context->id, 'mod_folder', 'content', 0, $filepath, $filename)) {
225             if ($filepath === '/' and $filename === '.') {
226                 $storedfile = new virtual_root_file($context->id, 'mod_folder', 'content', 0);
227             } else {
228                 // not found
229                 return null;
230             }
231         }
233         require_once("$CFG->dirroot/mod/folder/locallib.php");
234         $urlbase = $CFG->wwwroot.'/pluginfile.php';
236         // students may read files here
237         $canwrite = has_capability('mod/folder:managefiles', $context);
238         return new folder_content_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, $canwrite, false);
239     }
241     // note: folder_intro handled in file_browser automatically
243     return null;
246 /**
247  * Serves the folder files.
248  *
249  * @package  mod_folder
250  * @category files
251  * @param stdClass $course course object
252  * @param stdClass $cm course module
253  * @param stdClass $context context object
254  * @param string $filearea file area
255  * @param array $args extra arguments
256  * @param bool $forcedownload whether or not force download
257  * @param array $options additional options affecting the file serving
258  * @return bool false if file not found, does not return if found - just send the file
259  */
260 function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
261     global $CFG, $DB;
263     if ($context->contextlevel != CONTEXT_MODULE) {
264         return false;
265     }
267     require_course_login($course, true, $cm);
268     if (!has_capability('mod/folder:view', $context)) {
269         return false;
270     }
272     if ($filearea !== 'content') {
273         // intro is handled automatically in pluginfile.php
274         return false;
275     }
277     array_shift($args); // ignore revision - designed to prevent caching problems only
279     $fs = get_file_storage();
280     $relativepath = implode('/', $args);
281     $fullpath = "/$context->id/mod_folder/content/0/$relativepath";
282     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
283         return false;
284     }
286     // finally send the file
287     // for folder module, we force download file all the time
288     send_stored_file($file, 0, 0, true, $options);
291 /**
292  * Return a list of page types
293  * @param string $pagetype current page type
294  * @param stdClass $parentcontext Block's parent context
295  * @param stdClass $currentcontext Current context of block
296  */
297 function folder_page_type_list($pagetype, $parentcontext, $currentcontext) {
298     $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder'));
299     return $module_pagetype;
302 /**
303  * Export folder resource contents
304  *
305  * @return array of file content
306  */
307 function folder_export_contents($cm, $baseurl) {
308     global $CFG, $DB;
309     $contents = array();
310     $context = context_module::instance($cm->id);
311     $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
313     $fs = get_file_storage();
314     $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
316     foreach ($files as $fileinfo) {
317         $file = array();
318         $file['type'] = 'file';
319         $file['filename']     = $fileinfo->get_filename();
320         $file['filepath']     = $fileinfo->get_filepath();
321         $file['filesize']     = $fileinfo->get_filesize();
322         $file['fileurl']      = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_folder/content/'.$folder->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true);
323         $file['timecreated']  = $fileinfo->get_timecreated();
324         $file['timemodified'] = $fileinfo->get_timemodified();
325         $file['sortorder']    = $fileinfo->get_sortorder();
326         $file['userid']       = $fileinfo->get_userid();
327         $file['author']       = $fileinfo->get_author();
328         $file['license']      = $fileinfo->get_license();
329         $file['mimetype']     = $fileinfo->get_mimetype();
330         $file['isexternalfile'] = $fileinfo->is_external_file();
331         if ($file['isexternalfile']) {
332             $file['repositorytype'] = $fileinfo->get_repository_type();
333         }
334         $contents[] = $file;
335     }
337     return $contents;
340 /**
341  * Register the ability to handle drag and drop file uploads
342  * @return array containing details of the files / types the mod can handle
343  */
344 function folder_dndupload_register() {
345     return array('files' => array(
346                      array('extension' => 'zip', 'message' => get_string('dnduploadmakefolder', 'mod_folder'))
347                  ));
350 /**
351  * Handle a file that has been uploaded
352  * @param object $uploadinfo details of the file / content that has been uploaded
353  * @return int instance id of the newly created mod
354  */
355 function folder_dndupload_handle($uploadinfo) {
356     global $DB, $USER;
358     // Gather the required info.
359     $data = new stdClass();
360     $data->course = $uploadinfo->course->id;
361     $data->name = $uploadinfo->displayname;
362     $data->intro = '<p>'.$uploadinfo->displayname.'</p>';
363     $data->introformat = FORMAT_HTML;
364     $data->coursemodule = $uploadinfo->coursemodule;
365     $data->files = null; // We will unzip the file and sort out the contents below.
367     $data->id = folder_add_instance($data, null);
369     // Retrieve the file from the draft file area.
370     $context = context_module::instance($uploadinfo->coursemodule);
371     file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_folder', 'temp', 0, array('subdirs'=>true));
372     $fs = get_file_storage();
373     $files = $fs->get_area_files($context->id, 'mod_folder', 'temp', 0, 'sortorder', false);
374     // Only ever one file - extract the contents.
375     $file = reset($files);
377     $success = $file->extract_to_storage(new zip_packer(), $context->id, 'mod_folder', 'content', 0, '/', $USER->id);
378     $fs->delete_area_files($context->id, 'mod_folder', 'temp', 0);
380     if ($success) {
381         return $data->id;
382     }
384     $DB->delete_records('folder', array('id' => $data->id));
385     return false;
388 /**
389  * Given a coursemodule object, this function returns the extra
390  * information needed to print this activity in various places.
391  *
392  * If folder needs to be displayed inline we store additional information
393  * in customdata, so functions {@link folder_cm_info_dynamic()} and
394  * {@link folder_cm_info_view()} do not need to do DB queries
395  *
396  * @param cm_info $cm
397  * @return cached_cm_info info
398  */
399 function folder_get_coursemodule_info($cm) {
400     global $DB;
401     if (!($folder = $DB->get_record('folder', array('id' => $cm->instance),
402             'id, name, display, showexpanded, showdownloadfolder, intro, introformat'))) {
403         return NULL;
404     }
405     $cminfo = new cached_cm_info();
406     $cminfo->name = $folder->name;
407     if ($folder->display == FOLDER_DISPLAY_INLINE) {
408         // prepare folder object to store in customdata
409         $fdata = new stdClass();
410         $fdata->showexpanded = $folder->showexpanded;
411         $fdata->showdownloadfolder = $folder->showdownloadfolder;
412         if ($cm->showdescription && strlen(trim($folder->intro))) {
413             $fdata->intro = $folder->intro;
414             if ($folder->introformat != FORMAT_MOODLE) {
415                 $fdata->introformat = $folder->introformat;
416             }
417         }
418         $cminfo->customdata = $fdata;
419     } else {
420         if ($cm->showdescription) {
421             // Convert intro to html. Do not filter cached version, filters run at display time.
422             $cminfo->content = format_module_intro('folder', $folder, $cm->id, false);
423         }
424     }
425     return $cminfo;
428 /**
429  * Sets dynamic information about a course module
430  *
431  * This function is called from cm_info when displaying the module
432  * mod_folder can be displayed inline on course page and therefore have no course link
433  *
434  * @param cm_info $cm
435  */
436 function folder_cm_info_dynamic(cm_info $cm) {
437     if ($cm->customdata) {
438         // the field 'customdata' is not empty IF AND ONLY IF we display contens inline
439         $cm->set_no_view_link();
440     }
443 /**
444  * Overwrites the content in the course-module object with the folder files list
445  * if folder.display == FOLDER_DISPLAY_INLINE
446  *
447  * @param cm_info $cm
448  */
449 function folder_cm_info_view(cm_info $cm) {
450     global $PAGE;
451     if ($cm->uservisible && $cm->customdata &&
452             has_capability('mod/folder:view', $cm->context)) {
453         // Restore folder object from customdata.
454         // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline.
455         // Otherwise the content is default.
456         $folder = $cm->customdata;
457         $folder->id = (int)$cm->instance;
458         $folder->course = (int)$cm->course;
459         $folder->display = FOLDER_DISPLAY_INLINE;
460         $folder->name = $cm->name;
461         if (empty($folder->intro)) {
462             $folder->intro = '';
463         }
464         if (empty($folder->introformat)) {
465             $folder->introformat = FORMAT_MOODLE;
466         }
467         // display folder
468         $renderer = $PAGE->get_renderer('mod_folder');
469         $cm->set_content($renderer->display_folder($folder));
470     }
473 /**
474  * Mark the activity completed (if required) and trigger the course_module_viewed event.
475  *
476  * @param  stdClass $folder     folder object
477  * @param  stdClass $course     course object
478  * @param  stdClass $cm         course module object
479  * @param  stdClass $context    context object
480  * @since Moodle 3.0
481  */
482 function folder_view($folder, $course, $cm, $context) {
484     // Trigger course_module_viewed event.
485     $params = array(
486         'context' => $context,
487         'objectid' => $folder->id
488     );
490     $event = \mod_folder\event\course_module_viewed::create($params);
491     $event->add_record_snapshot('course_modules', $cm);
492     $event->add_record_snapshot('course', $course);
493     $event->add_record_snapshot('folder', $folder);
494     $event->trigger();
496     // Completion.
497     $completion = new completion_info($course);
498     $completion->set_module_viewed($cm);
501 /**
502  * Check if the folder can be zipped and downloaded.
503  * @param stdClass $folder
504  * @param context_module $cm
505  * @return bool True if the folder can be zipped and downloaded.
506  * @throws \dml_exception
507  */
508 function folder_archive_available($folder, $cm) {
509     if (!$folder->showdownloadfolder) {
510         return false;
511     }
513     $context = context_module::instance($cm->id);
514     $fs = get_file_storage();
515     $dir = $fs->get_area_tree($context->id, 'mod_folder', 'content', 0);
517     $size = folder_get_directory_size($dir);
518     $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
520     if ($size == 0) {
521         return false;
522     }
524     if (!empty($maxsize) && $size > $maxsize) {
525         return false;
526     }
528     return true;
531 /**
532  * Recursively measure the size of the files in a directory.
533  * @param array $directory
534  * @return int size of directory contents in bytes
535  */
536 function folder_get_directory_size($directory) {
537     $size = 0;
539     foreach ($directory['files'] as $file) {
540         $size += $file->get_filesize();
541     }
543     foreach ($directory['subdirs'] as $subdirectory) {
544         $size += folder_get_directory_size($subdirectory);
545     }
547     return $size;
550 /**
551  * Mark the activity completed (if required) and trigger the all_files_downloaded event.
552  *
553  * @param  stdClass $folder     folder object
554  * @param  stdClass $course     course object
555  * @param  stdClass $cm         course module object
556  * @param  stdClass $context    context object
557  * @since Moodle 3.1
558  */
559 function folder_downloaded($folder, $course, $cm, $context) {
560     $params = array(
561         'context' => $context,
562         'objectid' => $folder->id
563     );
564     $event = \mod_folder\event\all_files_downloaded::create($params);
565     $event->add_record_snapshot('course_modules', $cm);
566     $event->add_record_snapshot('course', $course);
567     $event->add_record_snapshot('folder', $folder);
568     $event->trigger();
570     // Completion.
571     $completion = new completion_info($course);
572     $completion->set_module_viewed($cm);
575 /**
576  * Returns all uploads since a given time in specified folder.
577  *
578  * @param array $activities
579  * @param int $index
580  * @param int $timestart
581  * @param int $courseid
582  * @param int $cmid
583  * @param int $userid
584  * @param int $groupid not used, but required for compatibilty with other modules
585  */
586 function folder_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
587     global $COURSE, $DB, $OUTPUT;
589     if ($COURSE->id == $courseid) {
590         $course = $COURSE;
591     } else {
592         $course = $DB->get_record('course', array('id' => $courseid));
593     }
595     $modinfo = get_fast_modinfo($course);
596     $cm = $modinfo->cms[$cmid];
598     $context = context_module::instance($cm->id);
599     if (!has_capability('mod/folder:view', $context)) {
600         return;
601     }
602     $files = folder_get_recent_activity($context, $timestart, $userid);
604     foreach ($files as $file) {
605         $tmpactivity = new stdClass();
607         $tmpactivity->type       = 'folder';
608         $tmpactivity->cmid       = $cm->id;
609         $tmpactivity->sectionnum = $cm->sectionnum;
610         $tmpactivity->timestamp  = $file->get_timemodified();
611         $tmpactivity->user       = core_user::get_user($file->get_userid());
613         $tmpactivity->content           = new stdClass();
614         $tmpactivity->content->url      = moodle_url::make_pluginfile_url($file->get_contextid(), 'mod_folder', 'content',
615             $file->get_itemid(), $file->get_filepath(), $file->get_filename());
617         if (file_extension_in_typegroup($file->get_filename(), 'web_image')) {
618             $image = $tmpactivity->content->url->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
619             $image = html_writer::empty_tag('img', array('src' => $image));
620         } else {
621             $image = $OUTPUT->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
622         }
624         $tmpactivity->content->image    = $image;
625         $tmpactivity->content->filename = $file->get_filename();
627         $activities[$index++] = $tmpactivity;
628     }
632 /**
633  * Outputs the folder uploads indicated by $activity.
634  *
635  * @param object $activity      the activity object the folder resides in
636  * @param int    $courseid      the id of the course the folder resides in
637  * @param bool   $detail        not used, but required for compatibilty with other modules
638  * @param int    $modnames      not used, but required for compatibilty with other modules
639  * @param bool   $viewfullnames not used, but required for compatibilty with other modules
640  */
641 function folder_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
642     global $OUTPUT;
644     $content = $activity->content;
645     $tableoptions = [
646         'border' => '0',
647         'cellpadding' => '3',
648         'cellspacing' => '0'
649     ];
650     $output = html_writer::start_tag('table', $tableoptions);
651     $output .= html_writer::start_tag('tr');
652     $output .= html_writer::tag('td', $content->image, ['class' => 'fp-icon', 'valign' => 'top']);
653     $output .= html_writer::start_tag('td');
654     $output .= html_writer::start_div('fp-filename');
655     $output .= html_writer::link($content->url, $content->filename);
656     $output .= html_writer::end_div();
658     // Show the uploader.
659     $fullname = fullname($activity->user, $viewfullnames);
660     $userurl = new moodle_url('/user/view.php');
661     $userurl->params(['id' => $activity->user->id, 'course' => $courseid]);
662     $by = new stdClass();
663     $by->name = html_writer::link($userurl, $fullname);
664     $by->date = userdate($activity->timestamp);
665     $authornamedate = get_string('bynameondate', 'folder', $by);
666     $output .= html_writer::div($authornamedate, 'user');
668     // Finish up the table.
669     $output .= html_writer::end_tag('tr');
670     $output .= html_writer::end_tag('table');
672     echo $output;
675 /**
676  * Gets recent file uploads in a given folder. Does not perform security checks.
677  *
678  * @param object $context
679  * @param int $timestart
680  * @param int $userid
681  *
682  * @return array
683  */
684 function folder_get_recent_activity($context, $timestart, $userid=0) {
685     $newfiles = array();
686     $fs = get_file_storage();
687     $files = $fs->get_area_files($context->id, 'mod_folder', 'content');
688     foreach ($files as $file) {
689         if ($file->get_timemodified() <= $timestart) {
690             continue;
691         }
692         if ($file->get_filename() === '.') {
693             continue;
694         }
695         if (!empty($userid) && $userid !== $file->get_userid()) {
696             continue;
697         }
698         $newfiles[] = $file;
699     }
700     return $newfiles;
703 /**
704  * Given a course and a date, prints a summary of all the new
705  * files posted in folder resources since that date
706  *
707  * @uses CONTEXT_MODULE
708  * @param object $course
709  * @param bool $viewfullnames capability
710  * @param int $timestart
711  * @return bool success
712  */
713 function folder_print_recent_activity($course, $viewfullnames, $timestart) {
714     global $OUTPUT;
716     $folders = get_all_instances_in_course('folder', $course);
718     if (empty($folders)) {
719         return false;
720     }
722     $newfiles = array();
724     $modinfo = get_fast_modinfo($course);
725     foreach ($folders as $folder) {
726         // Skip resources if the user can't view them.
727         $cm = $modinfo->cms[$folder->coursemodule];
728         $context = context_module::instance($cm->id);
729         if (!has_capability('mod/folder:view', $context)) {
730             continue;
731         }
733         // Get the files uploaded in the current time frame.
734         $newfiles = array_merge($newfiles, folder_get_recent_activity($context, $timestart));
735     }
737     if (empty($newfiles)) {
738         return false;
739     }
741     // Build list of files.
742     echo $OUTPUT->heading(get_string('newfoldercontent', 'folder').':', 3);
743     $list = html_writer::start_tag('ul', ['class' => 'unlist']);
744     foreach ($newfiles as $file) {
745         $filename = $file->get_filename();
746         $url = moodle_url::make_pluginfile_url($file->get_contextid(), 'mod_folder', 'content',
747             $file->get_itemid(), $file->get_filepath(), $filename);
749         $list .= html_writer::start_tag('li');
750         $list .= html_writer::start_div('head');
751         $list .= html_writer::div(userdate($file->get_timemodified(), get_string('strftimerecent')), 'date');
752         $list .= html_writer::div($file->get_author(), 'name');
753         $list .= html_writer::end_div(); // Head.
755         $list .= html_writer::start_div('info');
756         $list .= html_writer::link($url, $filename);
757         $list .= html_writer::end_div(); // Info.
758         $list .= html_writer::end_tag('li');
759     }
760     $list .= html_writer::end_tag('ul');
761     echo $list;
762     return true;
765 /**
766  * Check if the module has any update that affects the current user since a given time.
767  *
768  * @param  cm_info $cm course module data
769  * @param  int $from the time to check updates from
770  * @param  array $filter  if we need to check only specific updates
771  * @return stdClass an object with the different type of areas indicating if they were updated or not
772  * @since Moodle 3.2
773  */
774 function folder_check_updates_since(cm_info $cm, $from, $filter = array()) {
775     $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
776     return $updates;
779 /**
780  * Handles creating actions for events.
781  *
782  * @param calendar_event $event
783  * @param \core_calendar\action_factory $factory
784  * @return \core_calendar\local\event\value_objects\action|\core_calendar\local\interfaces\action_interface|null
785  */
786 function mod_folder_core_calendar_provide_event_action(calendar_event $event,
787                                                      \core_calendar\action_factory $factory) {
788     $cm = get_fast_modinfo($event->courseid)->instances['folder'][$event->instance];
790     $course = new stdClass();
791     $course->id = $event->courseid;
792     $completion = new \completion_info($course);
794     $completiondata = $completion->get_data($cm, false);
796     if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
797         return null;
798     }
800     return $factory->create_instance(
801         get_string('view'),
802         new \moodle_url('/mod/folder/view.php', ['id' => $cm->id]),
803         1,
804         true
805     );