Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/mod/folder/ -> lib.php (source)

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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
  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  
  50          default: return null;
  51      }
  52  }
  53  
  54  /**
  55   * This function is used by the reset_course_userdata function in moodlelib.
  56   * @param $data the data submitted from the reset course.
  57   * @return array status array
  58   */
  59  function folder_reset_userdata($data) {
  60  
  61      // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
  62      // See MDL-9367.
  63  
  64      return array();
  65  }
  66  
  67  /**
  68   * List the actions that correspond to a view of this module.
  69   * This is used by the participation report.
  70   *
  71   * Note: This is not used by new logging system. Event with
  72   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
  73   *       be considered as view action.
  74   *
  75   * @return array
  76   */
  77  function folder_get_view_actions() {
  78      return array('view', 'view all');
  79  }
  80  
  81  /**
  82   * List the actions that correspond to a post of this module.
  83   * This is used by the participation report.
  84   *
  85   * Note: This is not used by new logging system. Event with
  86   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
  87   *       will be considered as post action.
  88   *
  89   * @return array
  90   */
  91  function folder_get_post_actions() {
  92      return array('update', 'add');
  93  }
  94  
  95  /**
  96   * Add folder instance.
  97   * @param object $data
  98   * @param object $mform
  99   * @return int new folder instance id
 100   */
 101  function folder_add_instance($data, $mform) {
 102      global $DB;
 103  
 104      $cmid        = $data->coursemodule;
 105      $draftitemid = $data->files;
 106  
 107      $data->timemodified = time();
 108      // If 'showexpanded' is not set, apply the site config.
 109      if (!isset($data->showexpanded)) {
 110          $data->showexpanded = get_config('folder', 'showexpanded');
 111      }
 112      $data->id = $DB->insert_record('folder', $data);
 113  
 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);
 117  
 118      if ($draftitemid) {
 119          file_save_draft_area_files($draftitemid, $context->id, 'mod_folder', 'content', 0, array('subdirs'=>true));
 120      }
 121  
 122      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
 123      \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
 124  
 125      return $data->id;
 126  }
 127  
 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;
 136  
 137      $cmid        = $data->coursemodule;
 138      $draftitemid = $data->files;
 139  
 140      $data->timemodified = time();
 141      $data->id           = $data->instance;
 142      $data->revision++;
 143  
 144      $DB->update_record('folder', $data);
 145  
 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      }
 150  
 151      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
 152      \core_completion\api::update_completion_date_event($data->coursemodule, 'folder', $data->id, $completiontimeexpected);
 153  
 154      return true;
 155  }
 156  
 157  /**
 158   * Delete folder instance.
 159   * @param int $id
 160   * @return bool true
 161   */
 162  function folder_delete_instance($id) {
 163      global $DB;
 164  
 165      if (!$folder = $DB->get_record('folder', array('id'=>$id))) {
 166          return false;
 167      }
 168  
 169      $cm = get_coursemodule_from_instance('folder', $id);
 170      \core_completion\api::update_completion_date_event($cm->id, 'folder', $folder->id, null);
 171  
 172      // note: all context files are deleted automatically
 173  
 174      $DB->delete_records('folder', array('id'=>$folder->id));
 175  
 176      return true;
 177  }
 178  
 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');
 192  
 193      return $areas;
 194  }
 195  
 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;
 214  
 215  
 216      if ($filearea === 'content') {
 217          if (!has_capability('mod/folder:view', $context)) {
 218              return NULL;
 219          }
 220          $fs = get_file_storage();
 221  
 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          }
 232  
 233          require_once("$CFG->dirroot/mod/folder/locallib.php");
 234          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 235  
 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      }
 240  
 241      // note: folder_intro handled in file_browser automatically
 242  
 243      return null;
 244  }
 245  
 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;
 262  
 263      if ($context->contextlevel != CONTEXT_MODULE) {
 264          return false;
 265      }
 266  
 267      require_course_login($course, true, $cm);
 268      if (!has_capability('mod/folder:view', $context)) {
 269          return false;
 270      }
 271  
 272      if ($filearea !== 'content') {
 273          // intro is handled automatically in pluginfile.php
 274          return false;
 275      }
 276  
 277      array_shift($args); // ignore revision - designed to prevent caching problems only
 278  
 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      }
 285  
 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);
 289  }
 290  
 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;
 300  }
 301  
 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);
 312  
 313      $fs = get_file_storage();
 314      $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
 315  
 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      }
 336  
 337      return $contents;
 338  }
 339  
 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                   ));
 348  }
 349  
 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;
 357  
 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.
 366  
 367      $data->id = folder_add_instance($data, null);
 368  
 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);
 376  
 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);
 379  
 380      if ($success) {
 381          return $data->id;
 382      }
 383  
 384      $DB->delete_records('folder', array('id' => $data->id));
 385      return false;
 386  }
 387  
 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;
 426  }
 427  
 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      }
 441  }
 442  
 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), true);
 470      }
 471  }
 472  
 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) {
 483  
 484      // Trigger course_module_viewed event.
 485      $params = array(
 486          'context' => $context,
 487          'objectid' => $folder->id
 488      );
 489  
 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();
 495  
 496      // Completion.
 497      $completion = new completion_info($course);
 498      $completion->set_module_viewed($cm);
 499  }
 500  
 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      }
 512  
 513      $context = context_module::instance($cm->id);
 514      $fs = get_file_storage();
 515      $dir = $fs->get_area_tree($context->id, 'mod_folder', 'content', 0);
 516  
 517      $size = folder_get_directory_size($dir);
 518      $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
 519  
 520      if ($size == 0) {
 521          return false;
 522      }
 523  
 524      if (!empty($maxsize) && $size > $maxsize) {
 525          return false;
 526      }
 527  
 528      return true;
 529  }
 530  
 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;
 538  
 539      foreach ($directory['files'] as $file) {
 540          $size += $file->get_filesize();
 541      }
 542  
 543      foreach ($directory['subdirs'] as $subdirectory) {
 544          $size += folder_get_directory_size($subdirectory);
 545      }
 546  
 547      return $size;
 548  }
 549  
 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();
 569  
 570      // Completion.
 571      $completion = new completion_info($course);
 572      $completion->set_module_viewed($cm);
 573  }
 574  
 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;
 588  
 589      if ($COURSE->id == $courseid) {
 590          $course = $COURSE;
 591      } else {
 592          $course = $DB->get_record('course', array('id' => $courseid));
 593      }
 594  
 595      $modinfo = get_fast_modinfo($course);
 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      $files = folder_get_recent_activity($context, $timestart, $userid);
 603  
 604      foreach ($files as $file) {
 605          $tmpactivity = new stdClass();
 606  
 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());
 612  
 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());
 616  
 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          }
 623  
 624          $tmpactivity->content->image    = $image;
 625          $tmpactivity->content->filename = $file->get_filename();
 626  
 627          $activities[$index++] = $tmpactivity;
 628      }
 629  
 630  }
 631  
 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;
 643  
 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();
 657  
 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');
 667  
 668      // Finish up the table.
 669      $output .= html_writer::end_tag('tr');
 670      $output .= html_writer::end_tag('table');
 671  
 672      echo $output;
 673  }
 674  
 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;
 701  }
 702  
 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;
 715  
 716      $folders = get_all_instances_in_course('folder', $course);
 717  
 718      if (empty($folders)) {
 719          return false;
 720      }
 721  
 722      $newfiles = array();
 723  
 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          }
 732  
 733          // Get the files uploaded in the current time frame.
 734          $newfiles = array_merge($newfiles, folder_get_recent_activity($context, $timestart));
 735      }
 736  
 737      if (empty($newfiles)) {
 738          return false;
 739      }
 740  
 741      // Build list of files.
 742      echo $OUTPUT->heading(get_string('newfoldercontent', 'folder') . ':', 6);
 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);
 748  
 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.
 754  
 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;
 763  }
 764  
 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;
 777  }
 778  
 779  /**
 780   * This function receives a calendar event and returns the action associated with it, or null if there is none.
 781   *
 782   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 783   * is not displayed on the block.
 784   *
 785   * @param calendar_event $event
 786   * @param \core_calendar\action_factory $factory
 787   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
 788   * @return \core_calendar\local\event\entities\action_interface|null
 789   */
 790  function mod_folder_core_calendar_provide_event_action(calendar_event $event,
 791                                                         \core_calendar\action_factory $factory,
 792                                                         int $userid = 0) {
 793      global $USER;
 794  
 795      if (!$userid) {
 796          $userid = $USER->id;
 797      }
 798  
 799      $cm = get_fast_modinfo($event->courseid, $userid)->instances['folder'][$event->instance];
 800  
 801      if (!$cm->uservisible) {
 802          // The module is not visible to the user for any reason.
 803          return null;
 804      }
 805  
 806      $completion = new \completion_info($cm->get_course());
 807  
 808      $completiondata = $completion->get_data($cm, false, $userid);
 809  
 810      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
 811          return null;
 812      }
 813  
 814      return $factory->create_instance(
 815          get_string('view'),
 816          new \moodle_url('/mod/folder/view.php', ['id' => $cm->id]),
 817          1,
 818          true
 819      );
 820  }
 821  
 822  /**
 823   * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
 824   *
 825   * @param  string $filearea The filearea.
 826   * @param  array  $args The path (the part after the filearea and before the filename).
 827   * @return array The itemid and the filepath inside the $args path, for the defined filearea.
 828   */
 829  function mod_folder_get_path_from_pluginfile(string $filearea, array $args) : array {
 830      // Folder never has an itemid (the number represents the revision but it's not stored in database).
 831      array_shift($args);
 832  
 833      // Get the filepath.
 834      if (empty($args)) {
 835          $filepath = '/';
 836      } else {
 837          $filepath = '/' . implode('/', $args) . '/';
 838      }
 839  
 840      return [
 841          'itemid' => 0,
 842          'filepath' => $filepath,
 843      ];
 844  }