Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
/mod/folder/ -> lib.php (source)

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

   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      // Set security posture for in-browser display.
 287      if (!$forcedownload) {
 288          header("Content-Security-Policy: default-src 'none'; img-src 'self'");
 289      }
 290  
 291      // Finally send the file.
 292      send_stored_file($file, 0, 0, $forcedownload, $options);
 293  }
 294  
 295  /**
 296   * Return a list of page types
 297   * @param string $pagetype current page type
 298   * @param stdClass $parentcontext Block's parent context
 299   * @param stdClass $currentcontext Current context of block
 300   */
 301  function folder_page_type_list($pagetype, $parentcontext, $currentcontext) {
 302      $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder'));
 303      return $module_pagetype;
 304  }
 305  
 306  /**
 307   * Export folder resource contents
 308   *
 309   * @return array of file content
 310   */
 311  function folder_export_contents($cm, $baseurl) {
 312      global $CFG, $DB;
 313      $contents = array();
 314      $context = context_module::instance($cm->id);
 315      $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST);
 316  
 317      $fs = get_file_storage();
 318      $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false);
 319  
 320      foreach ($files as $fileinfo) {
 321          $file = array();
 322          $file['type'] = 'file';
 323          $file['filename']     = $fileinfo->get_filename();
 324          $file['filepath']     = $fileinfo->get_filepath();
 325          $file['filesize']     = $fileinfo->get_filesize();
 326          $file['fileurl']      = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_folder/content/'.$folder->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true);
 327          $file['timecreated']  = $fileinfo->get_timecreated();
 328          $file['timemodified'] = $fileinfo->get_timemodified();
 329          $file['sortorder']    = $fileinfo->get_sortorder();
 330          $file['userid']       = $fileinfo->get_userid();
 331          $file['author']       = $fileinfo->get_author();
 332          $file['license']      = $fileinfo->get_license();
 333          $file['mimetype']     = $fileinfo->get_mimetype();
 334          $file['isexternalfile'] = $fileinfo->is_external_file();
 335          if ($file['isexternalfile']) {
 336              $file['repositorytype'] = $fileinfo->get_repository_type();
 337          }
 338          $contents[] = $file;
 339      }
 340  
 341      return $contents;
 342  }
 343  
 344  /**
 345   * Register the ability to handle drag and drop file uploads
 346   * @return array containing details of the files / types the mod can handle
 347   */
 348  function folder_dndupload_register() {
 349      return array('files' => array(
 350                       array('extension' => 'zip', 'message' => get_string('dnduploadmakefolder', 'mod_folder'))
 351                   ));
 352  }
 353  
 354  /**
 355   * Handle a file that has been uploaded
 356   * @param object $uploadinfo details of the file / content that has been uploaded
 357   * @return int instance id of the newly created mod
 358   */
 359  function folder_dndupload_handle($uploadinfo) {
 360      global $DB, $USER;
 361  
 362      // Gather the required info.
 363      $data = new stdClass();
 364      $data->course = $uploadinfo->course->id;
 365      $data->name = $uploadinfo->displayname;
 366      $data->intro = '<p>'.$uploadinfo->displayname.'</p>';
 367      $data->introformat = FORMAT_HTML;
 368      $data->coursemodule = $uploadinfo->coursemodule;
 369      $data->files = null; // We will unzip the file and sort out the contents below.
 370  
 371      $data->id = folder_add_instance($data, null);
 372  
 373      // Retrieve the file from the draft file area.
 374      $context = context_module::instance($uploadinfo->coursemodule);
 375      file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_folder', 'temp', 0, array('subdirs'=>true));
 376      $fs = get_file_storage();
 377      $files = $fs->get_area_files($context->id, 'mod_folder', 'temp', 0, 'sortorder', false);
 378      // Only ever one file - extract the contents.
 379      $file = reset($files);
 380  
 381      $success = $file->extract_to_storage(new zip_packer(), $context->id, 'mod_folder', 'content', 0, '/', $USER->id);
 382      $fs->delete_area_files($context->id, 'mod_folder', 'temp', 0);
 383  
 384      if ($success) {
 385          return $data->id;
 386      }
 387  
 388      $DB->delete_records('folder', array('id' => $data->id));
 389      return false;
 390  }
 391  
 392  /**
 393   * Given a coursemodule object, this function returns the extra
 394   * information needed to print this activity in various places.
 395   *
 396   * If folder needs to be displayed inline we store additional information
 397   * in customdata, so functions {@link folder_cm_info_dynamic()} and
 398   * {@link folder_cm_info_view()} do not need to do DB queries
 399   *
 400   * @param cm_info $cm
 401   * @return cached_cm_info info
 402   */
 403  function folder_get_coursemodule_info($cm) {
 404      global $DB;
 405      if (!($folder = $DB->get_record('folder', array('id' => $cm->instance),
 406              'id, name, display, showexpanded, showdownloadfolder, forcedownload, intro, introformat'))) {
 407          return NULL;
 408      }
 409      $cminfo = new cached_cm_info();
 410      $cminfo->name = $folder->name;
 411      if ($folder->display == FOLDER_DISPLAY_INLINE) {
 412          // prepare folder object to store in customdata
 413          $fdata = new stdClass();
 414          $fdata->showexpanded = $folder->showexpanded;
 415          $fdata->showdownloadfolder = $folder->showdownloadfolder;
 416          $fdata->forcedownload = $folder->forcedownload;
 417          if ($cm->showdescription && strlen(trim($folder->intro))) {
 418              $fdata->intro = $folder->intro;
 419              if ($folder->introformat != FORMAT_MOODLE) {
 420                  $fdata->introformat = $folder->introformat;
 421              }
 422          }
 423          $cminfo->customdata = $fdata;
 424      } else {
 425          if ($cm->showdescription) {
 426              // Convert intro to html. Do not filter cached version, filters run at display time.
 427              $cminfo->content = format_module_intro('folder', $folder, $cm->id, false);
 428          }
 429      }
 430      return $cminfo;
 431  }
 432  
 433  /**
 434   * Sets dynamic information about a course module
 435   *
 436   * This function is called from cm_info when displaying the module
 437   * mod_folder can be displayed inline on course page and therefore have no course link
 438   *
 439   * @param cm_info $cm
 440   */
 441  function folder_cm_info_dynamic(cm_info $cm) {
 442      if ($cm->get_custom_data()) {
 443          // the field 'customdata' is not empty IF AND ONLY IF we display contens inline
 444          $cm->set_no_view_link();
 445      }
 446  }
 447  
 448  /**
 449   * Overwrites the content in the course-module object with the folder files list
 450   * if folder.display == FOLDER_DISPLAY_INLINE
 451   *
 452   * @param cm_info $cm
 453   */
 454  function folder_cm_info_view(cm_info $cm) {
 455      global $PAGE;
 456      if ($cm->uservisible && $cm->customdata &&
 457              has_capability('mod/folder:view', $cm->context)) {
 458          // Restore folder object from customdata.
 459          // Note the field 'customdata' is not empty IF AND ONLY IF we display contens inline.
 460          // Otherwise the content is default.
 461          $folder = $cm->customdata;
 462          $folder->id = (int)$cm->instance;
 463          $folder->course = (int)$cm->course;
 464          $folder->display = FOLDER_DISPLAY_INLINE;
 465          $folder->name = $cm->name;
 466          if (empty($folder->intro)) {
 467              $folder->intro = '';
 468          }
 469          if (empty($folder->introformat)) {
 470              $folder->introformat = FORMAT_MOODLE;
 471          }
 472          // display folder
 473          $renderer = $PAGE->get_renderer('mod_folder');
 474          $cm->set_content($renderer->display_folder($folder), true);
 475      }
 476  }
 477  
 478  /**
 479   * Mark the activity completed (if required) and trigger the course_module_viewed event.
 480   *
 481   * @param  stdClass $folder     folder object
 482   * @param  stdClass $course     course object
 483   * @param  stdClass $cm         course module object
 484   * @param  stdClass $context    context object
 485   * @since Moodle 3.0
 486   */
 487  function folder_view($folder, $course, $cm, $context) {
 488  
 489      // Trigger course_module_viewed event.
 490      $params = array(
 491          'context' => $context,
 492          'objectid' => $folder->id
 493      );
 494  
 495      $event = \mod_folder\event\course_module_viewed::create($params);
 496      $event->add_record_snapshot('course_modules', $cm);
 497      $event->add_record_snapshot('course', $course);
 498      $event->add_record_snapshot('folder', $folder);
 499      $event->trigger();
 500  
 501      // Completion.
 502      $completion = new completion_info($course);
 503      $completion->set_module_viewed($cm);
 504  }
 505  
 506  /**
 507   * Check if the folder can be zipped and downloaded.
 508   * @param stdClass $folder
 509   * @param context_module $cm
 510   * @return bool True if the folder can be zipped and downloaded.
 511   * @throws \dml_exception
 512   */
 513  function folder_archive_available($folder, $cm) {
 514      if (!$folder->showdownloadfolder) {
 515          return false;
 516      }
 517  
 518      $context = context_module::instance($cm->id);
 519      $fs = get_file_storage();
 520      $dir = $fs->get_area_tree($context->id, 'mod_folder', 'content', 0);
 521  
 522      $size = folder_get_directory_size($dir);
 523      $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
 524  
 525      if ($size == 0) {
 526          return false;
 527      }
 528  
 529      if (!empty($maxsize) && $size > $maxsize) {
 530          return false;
 531      }
 532  
 533      return true;
 534  }
 535  
 536  /**
 537   * Recursively measure the size of the files in a directory.
 538   * @param array $directory
 539   * @return int size of directory contents in bytes
 540   */
 541  function folder_get_directory_size($directory) {
 542      $size = 0;
 543  
 544      foreach ($directory['files'] as $file) {
 545          $size += $file->get_filesize();
 546      }
 547  
 548      foreach ($directory['subdirs'] as $subdirectory) {
 549          $size += folder_get_directory_size($subdirectory);
 550      }
 551  
 552      return $size;
 553  }
 554  
 555  /**
 556   * Mark the activity completed (if required) and trigger the all_files_downloaded event.
 557   *
 558   * @param  stdClass $folder     folder object
 559   * @param  stdClass $course     course object
 560   * @param  stdClass $cm         course module object
 561   * @param  stdClass $context    context object
 562   * @since Moodle 3.1
 563   */
 564  function folder_downloaded($folder, $course, $cm, $context) {
 565      $params = array(
 566          'context' => $context,
 567          'objectid' => $folder->id
 568      );
 569      $event = \mod_folder\event\all_files_downloaded::create($params);
 570      $event->add_record_snapshot('course_modules', $cm);
 571      $event->add_record_snapshot('course', $course);
 572      $event->add_record_snapshot('folder', $folder);
 573      $event->trigger();
 574  
 575      // Completion.
 576      $completion = new completion_info($course);
 577      $completion->set_module_viewed($cm);
 578  }
 579  
 580  /**
 581   * Returns all uploads since a given time in specified folder.
 582   *
 583   * @param array $activities
 584   * @param int $index
 585   * @param int $timestart
 586   * @param int $courseid
 587   * @param int $cmid
 588   * @param int $userid
 589   * @param int $groupid not used, but required for compatibilty with other modules
 590   */
 591  function folder_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
 592      global $DB, $OUTPUT;
 593  
 594      $modinfo = get_fast_modinfo($courseid);
 595      $cm = $modinfo->cms[$cmid];
 596  
 597      $context = context_module::instance($cm->id);
 598      if (!has_capability('mod/folder:view', $context)) {
 599          return;
 600      }
 601      $instance = $DB->get_record('folder', ['id' => $cm->instance], '*', MUST_EXIST);
 602  
 603      $files = folder_get_recent_activity($context, $timestart, $userid);
 604      foreach ($files as $file) {
 605          $tmpactivity = (object) [
 606              'type' => 'folder',
 607              'cmid' => $cm->id,
 608              'sectionnum' => $cm->sectionnum,
 609              'timestamp' => $file->get_timemodified(),
 610              'user' => core_user::get_user($file->get_userid()),
 611          ];
 612  
 613          $url = moodle_url::make_pluginfile_url(
 614              $file->get_contextid(),
 615              'mod_folder',
 616              'content',
 617              $file->get_itemid(),
 618              $file->get_filepath(),
 619              $file->get_filename(),
 620              !empty($instance->forcedownload)
 621          );
 622  
 623          if (file_extension_in_typegroup($file->get_filename(), 'web_image')) {
 624              $image = $url->out(false, array('preview' => 'tinyicon', 'oid' => $file->get_timemodified()));
 625              $image = html_writer::empty_tag('img', array('src' => $image));
 626          } else {
 627              $image = $OUTPUT->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
 628          }
 629  
 630          $tmpactivity->content = (object) [
 631              'image' => $image,
 632              'filename' => $file->get_filename(),
 633              'url' => $url,
 634          ];
 635  
 636          $activities[$index++] = $tmpactivity;
 637      }
 638  }
 639  
 640  /**
 641   * Outputs the folder uploads indicated by $activity.
 642   *
 643   * @param object $activity      the activity object the folder resides in
 644   * @param int    $courseid      the id of the course the folder resides in
 645   * @param bool   $detail        not used, but required for compatibilty with other modules
 646   * @param int    $modnames      not used, but required for compatibilty with other modules
 647   * @param bool   $viewfullnames not used, but required for compatibilty with other modules
 648   */
 649  function folder_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
 650      global $OUTPUT;
 651  
 652      $content = $activity->content;
 653      $tableoptions = [
 654          'border' => '0',
 655          'cellpadding' => '3',
 656          'cellspacing' => '0'
 657      ];
 658      $output = html_writer::start_tag('table', $tableoptions);
 659      $output .= html_writer::start_tag('tr');
 660      $output .= html_writer::tag('td', $content->image, ['class' => 'fp-icon', 'valign' => 'top']);
 661      $output .= html_writer::start_tag('td');
 662      $output .= html_writer::start_div('fp-filename');
 663      $output .= html_writer::link($content->url, $content->filename);
 664      $output .= html_writer::end_div();
 665  
 666      // Show the uploader.
 667      $fullname = fullname($activity->user, $viewfullnames);
 668      $userurl = new moodle_url('/user/view.php');
 669      $userurl->params(['id' => $activity->user->id, 'course' => $courseid]);
 670      $by = new stdClass();
 671      $by->name = html_writer::link($userurl, $fullname);
 672      $by->date = userdate($activity->timestamp);
 673      $authornamedate = get_string('bynameondate', 'folder', $by);
 674      $output .= html_writer::div($authornamedate, 'user');
 675  
 676      // Finish up the table.
 677      $output .= html_writer::end_tag('tr');
 678      $output .= html_writer::end_tag('table');
 679  
 680      echo $output;
 681  }
 682  
 683  /**
 684   * Gets recent file uploads in a given folder. Does not perform security checks.
 685   *
 686   * @param object $context
 687   * @param int $timestart
 688   * @param int $userid
 689   *
 690   * @return array
 691   */
 692  function folder_get_recent_activity($context, $timestart, $userid=0) {
 693      $newfiles = array();
 694      $fs = get_file_storage();
 695      $files = $fs->get_area_files($context->id, 'mod_folder', 'content');
 696      foreach ($files as $file) {
 697          if ($file->get_timemodified() <= $timestart) {
 698              continue;
 699          }
 700          if ($file->get_filename() === '.') {
 701              continue;
 702          }
 703          if (!empty($userid) && $userid !== $file->get_userid()) {
 704              continue;
 705          }
 706          $newfiles[] = $file;
 707      }
 708      return $newfiles;
 709  }
 710  
 711  /**
 712   * Given a course and a date, prints a summary of all the new
 713   * files posted in folder resources since that date
 714   *
 715   * @uses CONTEXT_MODULE
 716   * @param object $course
 717   * @param bool $viewfullnames capability
 718   * @param int $timestart
 719   * @return bool success
 720   */
 721  function folder_print_recent_activity($course, $viewfullnames, $timestart) {
 722      global $OUTPUT;
 723  
 724      $folders = get_all_instances_in_course('folder', $course);
 725  
 726      if (empty($folders)) {
 727          return false;
 728      }
 729  
 730      // The list of all new files.
 731      $newfiles = [];
 732      // Save the force download setting of all instances with files indexed by context.
 733      $forcedownloads = [];
 734  
 735      $modinfo = get_fast_modinfo($course);
 736      foreach ($folders as $folder) {
 737          // Skip resources if the user can't view them.
 738          $cm = $modinfo->cms[$folder->coursemodule];
 739          $context = context_module::instance($cm->id);
 740          if (!has_capability('mod/folder:view', $context)) {
 741              continue;
 742          }
 743  
 744          // Get the files uploaded in the current time frame.
 745          $newfiles = array_merge($newfiles, folder_get_recent_activity($context, $timestart));
 746          if (!isset($forcedownloads[$context->id])) {
 747              $forcedownloads[$context->id] = !empty($folder->forcedownload);
 748          }
 749      }
 750  
 751      if (empty($newfiles)) {
 752          return false;
 753      }
 754  
 755      // Build list of files.
 756      echo $OUTPUT->heading(get_string('newfoldercontent', 'folder') . ':', 6);
 757      $list = html_writer::start_tag('ul', ['class' => 'unlist']);
 758      foreach ($newfiles as $file) {
 759          $filename = $file->get_filename();
 760          $contextid = $file->get_contextid();
 761          $url = moodle_url::make_pluginfile_url(
 762              $contextid,
 763              'mod_folder',
 764              'content',
 765              $file->get_itemid(),
 766              $file->get_filepath(), $filename,
 767              $forcedownloads[$contextid] ?? false
 768          );
 769  
 770          $list .= html_writer::start_tag('li');
 771          $list .= html_writer::start_div('head');
 772          $list .= html_writer::div(userdate($file->get_timemodified(), get_string('strftimerecent')), 'date');
 773          $list .= html_writer::div($file->get_author(), 'name');
 774          $list .= html_writer::end_div(); // Head.
 775  
 776          $list .= html_writer::start_div('info');
 777          $list .= html_writer::link($url, $filename);
 778          $list .= html_writer::end_div(); // Info.
 779          $list .= html_writer::end_tag('li');
 780      }
 781      $list .= html_writer::end_tag('ul');
 782      echo $list;
 783      return true;
 784  }
 785  
 786  /**
 787   * Check if the module has any update that affects the current user since a given time.
 788   *
 789   * @param  cm_info $cm course module data
 790   * @param  int $from the time to check updates from
 791   * @param  array $filter  if we need to check only specific updates
 792   * @return stdClass an object with the different type of areas indicating if they were updated or not
 793   * @since Moodle 3.2
 794   */
 795  function folder_check_updates_since(cm_info $cm, $from, $filter = array()) {
 796      $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
 797      return $updates;
 798  }
 799  
 800  /**
 801   * This function receives a calendar event and returns the action associated with it, or null if there is none.
 802   *
 803   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 804   * is not displayed on the block.
 805   *
 806   * @param calendar_event $event
 807   * @param \core_calendar\action_factory $factory
 808   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
 809   * @return \core_calendar\local\event\entities\action_interface|null
 810   */
 811  function mod_folder_core_calendar_provide_event_action(calendar_event $event,
 812                                                         \core_calendar\action_factory $factory,
 813                                                         int $userid = 0) {
 814      global $USER;
 815  
 816      if (!$userid) {
 817          $userid = $USER->id;
 818      }
 819  
 820      $cm = get_fast_modinfo($event->courseid, $userid)->instances['folder'][$event->instance];
 821  
 822      if (!$cm->uservisible) {
 823          // The module is not visible to the user for any reason.
 824          return null;
 825      }
 826  
 827      $completion = new \completion_info($cm->get_course());
 828  
 829      $completiondata = $completion->get_data($cm, false, $userid);
 830  
 831      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
 832          return null;
 833      }
 834  
 835      return $factory->create_instance(
 836          get_string('view'),
 837          new \moodle_url('/mod/folder/view.php', ['id' => $cm->id]),
 838          1,
 839          true
 840      );
 841  }
 842  
 843  /**
 844   * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
 845   *
 846   * @param  string $filearea The filearea.
 847   * @param  array  $args The path (the part after the filearea and before the filename).
 848   * @return array The itemid and the filepath inside the $args path, for the defined filearea.
 849   */
 850  function mod_folder_get_path_from_pluginfile(string $filearea, array $args) : array {
 851      // Folder never has an itemid (the number represents the revision but it's not stored in database).
 852      array_shift($args);
 853  
 854      // Get the filepath.
 855      if (empty($args)) {
 856          $filepath = '/';
 857      } else {
 858          $filepath = '/' . implode('/', $args) . '/';
 859      }
 860  
 861      return [
 862          'itemid' => 0,
 863          'filepath' => $filepath,
 864      ];
 865  }