Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
/mod/folder/ -> lib.php (source)

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 402 and 403]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * 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   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  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);
  32  
  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 or string for the module purpose.
  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;
  49          case FEATURE_MOD_PURPOSE:             return MOD_PURPOSE_CONTENT;
  50  
  51          default: return null;
  52      }
  53  }
  54  
  55  /**
  56   * This function is used by the reset_course_userdata function in moodlelib.
  57   * @param $data the data submitted from the reset course.
  58   * @return array status array
  59   */
  60  function folder_reset_userdata($data) {
  61  
  62      // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
  63      // See MDL-9367.
  64  
  65      return array();
  66  }
  67  
  68  /**
  69   * List the actions that correspond to a view of this module.
  70   * This is used by the participation report.
  71   *
  72   * Note: This is not used by new logging system. Event with
  73   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
  74   *       be considered as view action.
  75   *
  76   * @return array
  77   */
  78  function folder_get_view_actions() {
  79      return array('view', 'view all');
  80  }
  81  
  82  /**
  83   * List the actions that correspond to a post of this module.
  84   * This is used by the participation report.
  85   *
  86   * Note: This is not used by new logging system. Event with
  87   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
  88   *       will be considered as post action.
  89   *
  90   * @return array
  91   */
  92  function folder_get_post_actions() {
  93      return array('update', 'add');
  94  }
  95  
  96  /**
  97   * Add folder instance.
  98   * @param object $data
  99   * @param object $mform
 100   * @return int new folder instance id
 101   */
 102  function folder_add_instance($data, $mform) {
 103      global $DB;
 104  
 105      $cmid        = $data->coursemodule;
 106      $draftitemid = $data->files;
 107  
 108      $data->timemodified = time();
 109      // If 'showexpanded' is not set, apply the site config.
 110      if (!isset($data->showexpanded)) {
 111          $data->showexpanded = get_config('folder', 'showexpanded');
 112      }
 113      $data->id = $DB->insert_record('folder', $data);
 114  
 115      // we need to use context now, so we need to make sure all needed info is already in db
 116      $DB->set_field('course_modules', 'instance', $data->id, array('id'=>$cmid));
 117      $context = context_module::instance($cmid);
 118  
 119      if ($draftitemid) {
 120          file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
 121      }
 122  
 123      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
 124      \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
 125  
 126      return $data->id;
 127  }
 128  
 129  /**
 130   * Update folder instance.
 131   * @param object $data
 132   * @param object $mform
 133   * @return bool true
 134   */
 135  function folder_update_instance($data, $mform) {
 136      global $CFG, $DB;
 137  
 138      $cmid        = $data->coursemodule;
 139      $draftitemid = $data->files;
 140  
 141      $data->timemodified = time();
 142      $data->id           = $data->instance;
 143      $data->revision++;
 144  
 145      $DB->update_record('folder', $data);
 146  
 147      $context = context_module::instance($cmid);
 148      if ($draftitemid = file_get_submitted_draft_itemid('files')) {
 149          file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
 150      }
 151  
 152      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
 153      \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
 154  
 155      return true;
 156  }
 157  
 158  /**
 159   * Delete folder instance.
 160   * @param int $id
 161   * @return bool true
 162   */
 163  function folder_delete_instance($id) {
 164      global $DB;
 165  
 166      if (!$folder = $DB->get_record('folder', array('id'=>$id))) {
 167          return false;
 168      }
 169  
 170      $cm = get_coursemodule_from_instance('folder', $id);
 171      \core_completion\api::update_completion_date_event($cm->id, 'folder', $folder->id, null);
 172  
 173      // note: all context files are deleted automatically
 174  
 175      $DB->delete_records('folder', array('id'=>$folder->id));
 176  
 177      return true;
 178  }
 179  
 180  /**
 181   * Lists all browsable file areas
 182   *
 183   * @package  mod_folder
 184   * @category files
 185   * @param stdClass $course course object
 186   * @param stdClass $cm course module object
 187   * @param stdClass $context context object
 188   * @return array
 189   */
 190  function folder_get_file_areas($course, $cm, $context) {
 191      $areas = array();
 192      $areas['content'] = get_string('foldercontent', 'folder');
 193  
 194      return $areas;
 195  }
 196  
 197  /**
 198   * File browsing support for folder module content area.
 199   *
 200   * @package  mod_folder
 201   * @category files
 202   * @param file_browser $browser file browser instance
 203   * @param array $areas file areas
 204   * @param stdClass $course course object
 205   * @param stdClass $cm course module object
 206   * @param stdClass $context context object
 207   * @param string $filearea file area
 208   * @param int $itemid item ID
 209   * @param string $filepath file path
 210   * @param string $filename file name
 211   * @return file_info instance or null if not found
 212   */
 213  function folder_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
 214      global $CFG;
 215  
 216  
 217      if ($filearea === 'content') {
 218          if (!has_capability('mod/folder:view', $context)) {
 219              return NULL;
 220          }
 221          $fs = get_file_storage();
 222  
 223          $filepath = is_null($filepath) ? '/' : $filepath;
 224          $filename = is_null($filename) ? '.' : $filename;
 225          if (!$storedfile = $fs->get_file($context->id, 'mod_folder', 'content', 0, $filepath, $filename)) {
 226              if ($filepath === '/' and $filename === '.') {
 227                  $storedfile = new virtual_root_file($context->id, 'mod_folder', 'content', 0);
 228              } else {
 229                  // not found
 230                  return null;
 231              }
 232          }
 233  
 234          require_once("$CFG->dirroot/mod/folder/locallib.php");
 235          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 236  
 237          // students may read files here
 238          $canwrite = has_capability('mod/folder:managefiles', $context);
 239          return new folder_content_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, $canwrite, false);
 240      }
 241  
 242      // note: folder_intro handled in file_browser automatically
 243  
 244      return null;
 245  }
 246  
 247  /**
 248   * Serves the folder files.
 249   *
 250   * @package  mod_folder
 251   * @category files
 252   * @param stdClass $course course object
 253   * @param stdClass $cm course module
 254   * @param stdClass $context context object
 255   * @param string $filearea file area
 256   * @param array $args extra arguments
 257   * @param bool $forcedownload whether or not force download
 258   * @param array $options additional options affecting the file serving
 259   * @return bool false if file not found, does not return if found - just send the file
 260   */
 261  function folder_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
 262      global $CFG, $DB;
 263  
 264      if ($context->contextlevel != CONTEXT_MODULE) {
 265          return false;
 266      }
 267  
 268      require_course_login($course, true, $cm);
 269      if (!has_capability('mod/folder:view', $context)) {
 270          return false;
 271      }
 272  
 273      if ($filearea !== 'content') {
 274          // intro is handled automatically in pluginfile.php
 275          return false;
 276      }
 277  
 278      array_shift($args); // ignore revision - designed to prevent caching problems only
 279  
 280      $fs = get_file_storage();
 281      $relativepath = implode('/', $args);
 282      $fullpath = "/$context->id/mod_folder/content/0/$relativepath";
 283      if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
 284          return false;
 285      }
 286  
 287      // Set security posture for in-browser display.
 288      if (!$forcedownload) {
 289          header("Content-Security-Policy: default-src 'none'; img-src 'self'; media-src 'self'");
 290      }
 291  
 292      // Finally send the file.
 293      send_stored_file($file, 0, 0, $forcedownload, $options);
 294  }
 295  
 296  /**
 297   * Return a list of page types
 298   * @param string $pagetype current page type
 299   * @param stdClass $parentcontext Block's parent context
 300   * @param stdClass $currentcontext Current context of block
 301   */
 302  function folder_page_type_list($pagetype, $parentcontext, $currentcontext) {
 303      $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder'));
 304      return $module_pagetype;
 305  }
 306  
 307  /**
 308   * Export folder resource contents
 309   *
 310   * @return array of file content
 311   */
 312  function folder_export_contents($cm, $baseurl) {
 313      global $CFG, $DB;
 314      $contents = array();
 315      $context = context_module::instance($cm->id);
 316      $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
 317  
 318      $fs = get_file_storage();
 319      $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
 320  
 321      foreach ($files as $fileinfo) {
 322          $file = array();
 323          $file['type'] = 'file';
 324          $file['filename']     = $fileinfo->get_filename();
 325          $file['filepath']     = $fileinfo->get_filepath();
 326          $file['filesize']     = $fileinfo->get_filesize();
 327          $file['fileurl']      = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_folder/content/'.$folder->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true);
 328          $file['timecreated']  = $fileinfo->get_timecreated();
 329          $file['timemodified'] = $fileinfo->get_timemodified();
 330          $file['sortorder']    = $fileinfo->get_sortorder();
 331          $file['userid']       = $fileinfo->get_userid();
 332          $file['author']       = $fileinfo->get_author();
 333          $file['license']      = $fileinfo->get_license();
 334          $file['mimetype']     = $fileinfo->get_mimetype();
 335          $file['isexternalfile'] = $fileinfo->is_external_file();
 336          if ($file['isexternalfile']) {
 337              $file['repositorytype'] = $fileinfo->get_repository_type();
 338          }
 339          $contents[] = $file;
 340      }
 341  
 342      return $contents;
 343  }
 344  
 345  /**
 346   * Register the ability to handle drag and drop file uploads
 347   * @return array containing details of the files / types the mod can handle
 348   */
 349  function folder_dndupload_register() {
 350      return array('files' => array(
 351                       array('extension' => 'zip', 'message' => get_string('dnduploadmakefolder', 'mod_folder'))
 352                   ));
 353  }
 354  
 355  /**
 356   * Handle a file that has been uploaded
 357   * @param object $uploadinfo details of the file / content that has been uploaded
 358   * @return int instance id of the newly created mod
 359   */
 360  function folder_dndupload_handle($uploadinfo) {
 361      global $DB, $USER;
 362  
 363      // Gather the required info.
 364      $data = new stdClass();
 365      $data->course = $uploadinfo->course->id;
 366      $data->name = $uploadinfo->displayname;
 367      $data->intro = '<p>'.$uploadinfo->displayname.'</p>';
 368      $data->introformat = FORMAT_HTML;
 369      $data->coursemodule = $uploadinfo->coursemodule;
 370      $data->files = null; // We will unzip the file and sort out the contents below.
 371  
 372      $data->id = folder_add_instance($data, null);
 373  
 374      // Retrieve the file from the draft file area.
 375      $context = context_module::instance($uploadinfo->coursemodule);
 376      file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_folder', 'temp', 0, array('subdirs'=>true));
 377      $fs = get_file_storage();
 378      $files = $fs->get_area_files($context->id, 'mod_folder', 'temp', 0, 'sortorder', false);
 379      // Only ever one file - extract the contents.
 380      $file = reset($files);
 381  
 382      $success = $file->extract_to_storage(new zip_packer(), $context->id, 'mod_folder', 'content', 0, '/', $USER->id);
 383      $fs->delete_area_files($context->id, 'mod_folder', 'temp', 0);
 384  
 385      if ($success) {
 386          return $data->id;
 387      }
 388  
 389      $DB->delete_records('folder', array('id' => $data->id));
 390      return false;
 391  }
 392  
 393  /**
 394   * Given a coursemodule object, this function returns the extra
 395   * information needed to print this activity in various places.
 396   *
 397   * If folder needs to be displayed inline we store additional information
 398   * in customdata, so functions {@link folder_cm_info_dynamic()} and
 399   * {@link folder_cm_info_view()} do not need to do DB queries
 400   *
 401   * @param cm_info $cm
 402   * @return cached_cm_info info
 403   */
 404  function folder_get_coursemodule_info($cm) {
 405      global $DB;
 406      if (!($folder = $DB->get_record('folder', array('id' => $cm->instance),
 407              'id, name, display, showexpanded, showdownloadfolder, forcedownload, intro, introformat'))) {
 408          return NULL;
 409      }
 410      $cminfo = new cached_cm_info();
 411      $cminfo->name = $folder->name;
 412      if ($folder->display == FOLDER_DISPLAY_INLINE) {
 413          // prepare folder object to store in customdata
 414          $fdata = new stdClass();
 415          $fdata->showexpanded = $folder->showexpanded;
 416          $fdata->showdownloadfolder = $folder->showdownloadfolder;
 417          $fdata->forcedownload = $folder->forcedownload;
 418          if ($cm->showdescription && strlen(trim($folder->intro))) {
 419              $fdata->intro = $folder->intro;
 420              if ($folder->introformat != FORMAT_MOODLE) {
 421                  $fdata->introformat = $folder->introformat;
 422              }
 423          }
 424          $cminfo->customdata = $fdata;
 425      } else {
 426          if ($cm->showdescription) {
 427              // Convert intro to html. Do not filter cached version, filters run at display time.
 428              $cminfo->content = format_module_intro('folder', $folder, $cm->id, false);
 429          }
 430      }
 431      return $cminfo;
 432  }
 433  
 434  /**
 435   * Sets dynamic information about a course module
 436   *
 437   * This function is called from cm_info when displaying the module
 438   * mod_folder can be displayed inline on course page and therefore have no course link
 439   *
 440   * @param cm_info $cm
 441   */
 442  function folder_cm_info_dynamic(cm_info $cm) {
 443      if ($cm->get_custom_data()) {
 444          // the field 'customdata' is not empty IF AND ONLY IF we display contens inline
 445          $cm->set_no_view_link();
 446      }
 447  }
 448  
 449  /**
 450   * Overwrites the content in the course-module object with the folder files list
 451   * if folder.display == FOLDER_DISPLAY_INLINE
 452   *
 453   * @param cm_info $cm
 454   */
 455  function folder_cm_info_view(cm_info $cm) {
 456      global $PAGE;
 457      if ($cm->uservisible && $cm->customdata &&
 458              has_capability('mod/folder:view', $cm->context)) {
 459          // Restore folder object from customdata.
 460          // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline.
 461          // Otherwise the content is default.
 462          $folder = $cm->customdata;
 463          $folder->id = (int)$cm->instance;
 464          $folder->course = (int)$cm->course;
 465          $folder->display = FOLDER_DISPLAY_INLINE;
 466          $folder->name = $cm->name;
 467          if (empty($folder->intro)) {
 468              $folder->intro = '';
 469          }
 470          if (empty($folder->introformat)) {
 471              $folder->introformat = FORMAT_MOODLE;
 472          }
 473          // display folder
 474          $renderer = $PAGE->get_renderer('mod_folder');
 475          $cm->set_content($renderer->display_folder($folder), true);
 476      }
 477  }
 478  
 479  /**
 480   * Mark the activity completed (if required) and trigger the course_module_viewed event.
 481   *
 482   * @param  stdClass $folder     folder object
 483   * @param  stdClass $course     course object
 484   * @param  stdClass $cm         course module object
 485   * @param  stdClass $context    context object
 486   * @since Moodle 3.0
 487   */
 488  function folder_view($folder, $course, $cm, $context) {
 489  
 490      // Trigger course_module_viewed event.
 491      $params = array(
 492          'context' => $context,
 493          'objectid' => $folder->id
 494      );
 495  
 496      $event = \mod_folder\event\course_module_viewed::create($params);
 497      $event->add_record_snapshot('course_modules', $cm);
 498      $event->add_record_snapshot('course', $course);
 499      $event->add_record_snapshot('folder', $folder);
 500      $event->trigger();
 501  
 502      // Completion.
 503      $completion = new completion_info($course);
 504      $completion->set_module_viewed($cm);
 505  }
 506  
 507  /**
 508   * Check if the folder can be zipped and downloaded.
 509   * @param stdClass $folder
 510   * @param context_module $cm
 511   * @return bool True if the folder can be zipped and downloaded.
 512   * @throws \dml_exception
 513   */
 514  function folder_archive_available($folder, $cm) {
 515      if (!$folder->showdownloadfolder) {
 516          return false;
 517      }
 518  
 519      $context = context_module::instance($cm->id);
 520      $fs = get_file_storage();
 521      $dir = $fs->get_area_tree($context->id, 'mod_folder', 'content', 0);
 522  
 523      $size = folder_get_directory_size($dir);
 524      $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
 525  
 526      if ($size == 0) {
 527          return false;
 528      }
 529  
 530      if (!empty($maxsize) && $size > $maxsize) {
 531          return false;
 532      }
 533  
 534      return true;
 535  }
 536  
 537  /**
 538   * Recursively measure the size of the files in a directory.
 539   * @param array $directory
 540   * @return int size of directory contents in bytes
 541   */
 542  function folder_get_directory_size($directory) {
 543      $size = 0;
 544  
 545      foreach ($directory['files'] as $file) {
 546          $size += $file->get_filesize();
 547      }
 548  
 549      foreach ($directory['subdirs'] as $subdirectory) {
 550          $size += folder_get_directory_size($subdirectory);
 551      }
 552  
 553      return $size;
 554  }
 555  
 556  /**
 557   * Mark the activity completed (if required) and trigger the all_files_downloaded event.
 558   *
 559   * @param  stdClass $folder     folder object
 560   * @param  stdClass $course     course object
 561   * @param  stdClass $cm         course module object
 562   * @param  stdClass $context    context object
 563   * @since Moodle 3.1
 564   */
 565  function folder_downloaded($folder, $course, $cm, $context) {
 566      $params = array(
 567          'context' => $context,
 568          'objectid' => $folder->id
 569      );
 570      $event = \mod_folder\event\all_files_downloaded::create($params);
 571      $event->add_record_snapshot('course_modules', $cm);
 572      $event->add_record_snapshot('course', $course);
 573      $event->add_record_snapshot('folder', $folder);
 574      $event->trigger();
 575  
 576      // Completion.
 577      $completion = new completion_info($course);
 578      $completion->set_module_viewed($cm);
 579  }
 580  
 581  /**
 582   * Returns all uploads since a given time in specified folder.
 583   *
 584   * @param array $activities
 585   * @param int $index
 586   * @param int $timestart
 587   * @param int $courseid
 588   * @param int $cmid
 589   * @param int $userid
 590   * @param int $groupid not used, but required for compatibilty with other modules
 591   */
 592  function folder_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
 593      global $DB, $OUTPUT;
 594  
 595      $modinfo = get_fast_modinfo($courseid);
 596      $cm = $modinfo->cms[$cmid];
 597  
 598      $context = context_module::instance($cm->id);
 599      if (!has_capability('mod/folder:view', $context)) {
 600          return;
 601      }
 602      $instance = $DB->get_record('folder', ['id' => $cm->instance], '*', MUST_EXIST);
 603  
 604      $files = folder_get_recent_activity($context, $timestart, $userid);
 605      foreach ($files as $file) {
 606          $tmpactivity = (object) [
 607              'type' => 'folder',
 608              'cmid' => $cm->id,
 609              'sectionnum' => $cm->sectionnum,
 610              'timestamp' => $file->get_timemodified(),
 611              'user' => core_user::get_user($file->get_userid()),
 612          ];
 613  
 614          $url = moodle_url::make_pluginfile_url(
 615              $file->get_contextid(),
 616              'mod_folder',
 617              'content',
 618              $file->get_itemid(),
 619              $file->get_filepath(),
 620              $file->get_filename(),
 621              !empty($instance->forcedownload)
 622          );
 623  
 624          if (file_extension_in_typegroup($file->get_filename(), 'web_image')) {
 625              $image = $url->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
 626              $image = html_writer::empty_tag('img', array('src' => $image));
 627          } else {
 628              $image = $OUTPUT->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
 629          }
 630  
 631          $tmpactivity->content = (object) [
 632              'image' => $image,
 633              'filename' => $file->get_filename(),
 634              'url' => $url,
 635          ];
 636  
 637          $activities[$index++] = $tmpactivity;
 638      }
 639  }
 640  
 641  /**
 642   * Outputs the folder uploads indicated by $activity.
 643   *
 644   * @param object $activity      the activity object the folder resides in
 645   * @param int    $courseid      the id of the course the folder resides in
 646   * @param bool   $detail        not used, but required for compatibilty with other modules
 647   * @param int    $modnames      not used, but required for compatibilty with other modules
 648   * @param bool   $viewfullnames not used, but required for compatibilty with other modules
 649   */
 650  function folder_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
 651      global $OUTPUT;
 652  
 653      $content = $activity->content;
 654      $tableoptions = [
 655          'border' => '0',
 656          'cellpadding' => '3',
 657          'cellspacing' => '0'
 658      ];
 659      $output = html_writer::start_tag('table', $tableoptions);
 660      $output .= html_writer::start_tag('tr');
 661      $output .= html_writer::tag('td', $content->image, ['class' => 'fp-icon', 'valign' => 'top']);
 662      $output .= html_writer::start_tag('td');
 663      $output .= html_writer::start_div('fp-filename');
 664      $output .= html_writer::link($content->url, $content->filename);
 665      $output .= html_writer::end_div();
 666  
 667      // Show the uploader.
 668      $fullname = fullname($activity->user, $viewfullnames);
 669      $userurl = new moodle_url('/user/view.php');
 670      $userurl->params(['id' => $activity->user->id, 'course' => $courseid]);
 671      $by = new stdClass();
 672      $by->name = html_writer::link($userurl, $fullname);
 673      $by->date = userdate($activity->timestamp);
 674      $authornamedate = get_string('bynameondate', 'folder', $by);
 675      $output .= html_writer::div($authornamedate, 'user');
 676  
 677      // Finish up the table.
 678      $output .= html_writer::end_tag('tr');
 679      $output .= html_writer::end_tag('table');
 680  
 681      echo $output;
 682  }
 683  
 684  /**
 685   * Gets recent file uploads in a given folder. Does not perform security checks.
 686   *
 687   * @param object $context
 688   * @param int $timestart
 689   * @param int $userid
 690   *
 691   * @return array
 692   */
 693  function folder_get_recent_activity($context, $timestart, $userid=0) {
 694      $newfiles = array();
 695      $fs = get_file_storage();
 696      $files = $fs->get_area_files($context->id, 'mod_folder', 'content');
 697      foreach ($files as $file) {
 698          if ($file->get_timemodified() <= $timestart) {
 699              continue;
 700          }
 701          if ($file->get_filename() === '.') {
 702              continue;
 703          }
 704          if (!empty($userid) && $userid !== $file->get_userid()) {
 705              continue;
 706          }
 707          $newfiles[] = $file;
 708      }
 709      return $newfiles;
 710  }
 711  
 712  /**
 713   * Given a course and a date, prints a summary of all the new
 714   * files posted in folder resources since that date
 715   *
 716   * @uses CONTEXT_MODULE
 717   * @param object $course
 718   * @param bool $viewfullnames capability
 719   * @param int $timestart
 720   * @return bool success
 721   */
 722  function folder_print_recent_activity($course, $viewfullnames, $timestart) {
 723      global $OUTPUT;
 724  
 725      $folders = get_all_instances_in_course('folder', $course);
 726  
 727      if (empty($folders)) {
 728          return false;
 729      }
 730  
 731      // The list of all new files.
 732      $newfiles = [];
 733      // Save the force download setting of all instances with files indexed by context.
 734      $forcedownloads = [];
 735  
 736      $modinfo = get_fast_modinfo($course);
 737      foreach ($folders as $folder) {
 738          // Skip resources if the user can't view them.
 739          $cm = $modinfo->cms[$folder->coursemodule];
 740          $context = context_module::instance($cm->id);
 741          if (!has_capability('mod/folder:view', $context)) {
 742              continue;
 743          }
 744  
 745          // Get the files uploaded in the current time frame.
 746          $newfiles = array_merge($newfiles, folder_get_recent_activity($context, $timestart));
 747          if (!isset($forcedownloads[$context->id])) {
 748              $forcedownloads[$context->id] = !empty($folder->forcedownload);
 749          }
 750      }
 751  
 752      if (empty($newfiles)) {
 753          return false;
 754      }
 755  
 756      // Build list of files.
 757      echo $OUTPUT->heading(get_string('newfoldercontent', 'folder') . ':', 6);
 758      $list = html_writer::start_tag('ul', ['class' => 'unlist']);
 759      foreach ($newfiles as $file) {
 760          $filename = $file->get_filename();
 761          $contextid = $file->get_contextid();
 762          $url = moodle_url::make_pluginfile_url(
 763              $contextid,
 764              'mod_folder',
 765              'content',
 766              $file->get_itemid(),
 767              $file->get_filepath(), $filename,
 768              $forcedownloads[$contextid] ?? false
 769          );
 770  
 771          $list .= html_writer::start_tag('li');
 772          $list .= html_writer::start_div('head');
 773          $list .= html_writer::div(userdate($file->get_timemodified(), get_string('strftimerecent')), 'date');
 774          $list .= html_writer::div($file->get_author(), 'name');
 775          $list .= html_writer::end_div(); // Head.
 776  
 777          $list .= html_writer::start_div('info');
 778          $list .= html_writer::link($url, $filename);
 779          $list .= html_writer::end_div(); // Info.
 780          $list .= html_writer::end_tag('li');
 781      }
 782      $list .= html_writer::end_tag('ul');
 783      echo $list;
 784      return true;
 785  }
 786  
 787  /**
 788   * Check if the module has any update that affects the current user since a given time.
 789   *
 790   * @param  cm_info $cm course module data
 791   * @param  int $from the time to check updates from
 792   * @param  array $filter  if we need to check only specific updates
 793   * @return stdClass an object with the different type of areas indicating if they were updated or not
 794   * @since Moodle 3.2
 795   */
 796  function folder_check_updates_since(cm_info $cm, $from, $filter = array()) {
 797      $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
 798      return $updates;
 799  }
 800  
 801  /**
 802   * This function receives a calendar event and returns the action associated with it, or null if there is none.
 803   *
 804   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 805   * is not displayed on the block.
 806   *
 807   * @param calendar_event $event
 808   * @param \core_calendar\action_factory $factory
 809   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
 810   * @return \core_calendar\local\event\entities\action_interface|null
 811   */
 812  function mod_folder_core_calendar_provide_event_action(calendar_event $event,
 813                                                         \core_calendar\action_factory $factory,
 814                                                         int $userid = 0) {
 815      global $USER;
 816  
 817      if (!$userid) {
 818          $userid = $USER->id;
 819      }
 820  
 821      $cm = get_fast_modinfo($event->courseid, $userid)->instances['folder'][$event->instance];
 822  
 823      if (!$cm->uservisible) {
 824          // The module is not visible to the user for any reason.
 825          return null;
 826      }
 827  
 828      $completion = new \completion_info($cm->get_course());
 829  
 830      $completiondata = $completion->get_data($cm, false, $userid);
 831  
 832      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
 833          return null;
 834      }
 835  
 836      return $factory->create_instance(
 837          get_string('view'),
 838          new \moodle_url('/mod/folder/view.php', ['id' => $cm->id]),
 839          1,
 840          true
 841      );
 842  }
 843  
 844  /**
 845   * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
 846   *
 847   * @param  string $filearea The filearea.
 848   * @param  array  $args The path (the part after the filearea and before the filename).
 849   * @return array The itemid and the filepath inside the $args path, for the defined filearea.
 850   */
 851  function mod_folder_get_path_from_pluginfile(string $filearea, array $args) : array {
 852      // Folder never has an itemid (the number represents the revision but it's not stored in database).
 853      array_shift($args);
 854  
 855      // Get the filepath.
 856      if (empty($args)) {
 857          $filepath = '/';
 858      } else {
 859          $filepath = '/' . implode('/', $args) . '/';
 860      }
 861  
 862      return [
 863          'itemid' => 0,
 864          'filepath' => $filepath,
 865      ];
 866  }