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.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  
  18  /**
  19   * Utility class for browsing of course files.
  20   *
  21   * @package    core_files
  22   * @copyright  2008 Petr Skoda (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  /**
  29   * Represents a course context in the tree navigated by {@link file_browser}.
  30   *
  31   * @package    core_files
  32   * @copyright  2008 Petr Skoda (http://skodak.org)
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class file_info_context_course extends file_info {
  36      /** @var stdClass course object */
  37      protected $course;
  38  
  39      /** @var file_info_context_module[] cached child modules. See {@link get_child_module()} */
  40      protected $childrenmodules = [];
  41  
  42      /**
  43       * Constructor
  44       *
  45       * @param file_browser $browser file browser instance
  46       * @param stdClass $context context object
  47       * @param stdClass $course course object
  48       */
  49      public function __construct($browser, $context, $course) {
  50          parent::__construct($browser, $context);
  51          $this->course   = $course;
  52      }
  53  
  54      /**
  55       * Return information about this specific context level
  56       *
  57       * @param string $component component
  58       * @param string $filearea file area
  59       * @param int $itemid item ID
  60       * @param string $filepath file path
  61       * @param string $filename file name
  62       * @return file_info|null file_info instance or null if not found or access not allowed
  63       */
  64      public function get_file_info($component, $filearea, $itemid, $filepath, $filename) {
  65          // try to emulate require_login() tests here
  66          if (!isloggedin()) {
  67              return null;
  68          }
  69  
  70          if (!$this->course->visible and !has_capability('moodle/course:viewhiddencourses', $this->context)) {
  71              return null;
  72          }
  73  
  74          if (!is_viewing($this->context) and !$this->browser->is_enrolled($this->course->id)) {
  75              // no peaking here if not enrolled or inspector
  76              return null;
  77          }
  78  
  79          if (empty($component)) {
  80              return $this;
  81          }
  82  
  83          $methodname = "get_area_{$component}_{$filearea}";
  84  
  85          if (method_exists($this, $methodname)) {
  86              return $this->$methodname($itemid, $filepath, $filename);
  87          }
  88  
  89          return null;
  90      }
  91  
  92      /**
  93       * Returns list of areas inside this course
  94       *
  95       * @param string $extensions Only return areas that have files with these extensions
  96       * @param bool $returnemptyfolders return all areas always, if true it will ignore the previous argument
  97       * @return array
  98       */
  99      protected function get_course_areas($extensions = '*', $returnemptyfolders = false) {
 100          global $DB;
 101  
 102          $allareas = [
 103              'course_summary',
 104              'course_overviewfiles',
 105              'course_section',
 106              'backup_section',
 107              'backup_course',
 108              'backup_automated',
 109              'course_legacy'
 110          ];
 111  
 112          if ($returnemptyfolders) {
 113              return $allareas;
 114          }
 115  
 116          $params1 = ['contextid' => $this->context->id, 'emptyfilename' => '.'];
 117          $sql1 = "SELECT " . $DB->sql_concat('f.component', "'_'", 'f.filearea') . "
 118              FROM {files} f
 119              WHERE f.filename <> :emptyfilename AND f.contextid = :contextid ";
 120          $sql3 = ' GROUP BY f.component, f.filearea';
 121          list($sql2, $params2) = $this->build_search_files_sql($extensions);
 122          $areaswithfiles = $DB->get_fieldset_sql($sql1 . $sql2 . $sql3, array_merge($params1, $params2));
 123  
 124          return array_intersect($allareas, $areaswithfiles);
 125      }
 126  
 127      /**
 128       * Gets a stored file for the course summary filearea directory
 129       *
 130       * @param int $itemid item ID
 131       * @param string $filepath file path
 132       * @param string $filename file name
 133       * @return file_info|null file_info instance or null if not found or access not allowed
 134       */
 135      protected function get_area_course_summary($itemid, $filepath, $filename) {
 136          global $CFG;
 137  
 138          if (!has_capability('moodle/course:update', $this->context)) {
 139              return null;
 140          }
 141          if (is_null($itemid)) {
 142              return $this;
 143          }
 144  
 145          $fs = get_file_storage();
 146  
 147          $filepath = is_null($filepath) ? '/' : $filepath;
 148          $filename = is_null($filename) ? '.' : $filename;
 149          if (!$storedfile = $fs->get_file($this->context->id, 'course', 'summary', 0, $filepath, $filename)) {
 150              if ($filepath === '/' and $filename === '.') {
 151                  $storedfile = new virtual_root_file($this->context->id, 'course', 'summary', 0);
 152              } else {
 153                  // not found
 154                  return null;
 155              }
 156          }
 157          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 158          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areacourseintro', 'repository'), false, true, true, false);
 159      }
 160  
 161      /**
 162       * Gets a stored file for the course images filearea directory
 163       *
 164       * @param int $itemid item ID
 165       * @param string $filepath file path
 166       * @param string $filename file name
 167       * @return file_info|null file_info instance or null if not found or access not allowed
 168       */
 169      protected function get_area_course_overviewfiles($itemid, $filepath, $filename) {
 170          global $CFG;
 171  
 172          if (!has_capability('moodle/course:update', $this->context)) {
 173              return null;
 174          }
 175          if (is_null($itemid)) {
 176              return $this;
 177          }
 178  
 179          $fs = get_file_storage();
 180  
 181          $filepath = is_null($filepath) ? '/' : $filepath;
 182          $filename = is_null($filename) ? '.' : $filename;
 183          if (!$storedfile = $fs->get_file($this->context->id, 'course', 'overviewfiles', 0, $filepath, $filename)) {
 184              if ($filepath === '/' and $filename === '.') {
 185                  $storedfile = new virtual_root_file($this->context->id, 'course', 'overviewfiles', 0);
 186              } else {
 187                  // not found
 188                  return null;
 189              }
 190          }
 191          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 192          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areacourseoverviewfiles', 'repository'), false, true, true, false);
 193      }
 194  
 195      /**
 196       * Gets a stored file for the course section filearea directory
 197       *
 198       * @param int $itemid item ID
 199       * @param string $filepath file path
 200       * @param string $filename file name
 201       * @return file_info|null file_info instance or null if not found or access not allowed
 202       */
 203      protected function get_area_course_section($itemid, $filepath, $filename) {
 204          global $CFG, $DB;
 205  
 206          if (!has_capability('moodle/course:update', $this->context)) {
 207              return null;
 208          }
 209  
 210          if (empty($itemid)) {
 211              // list all sections
 212              return new file_info_area_course_section($this->browser, $this->context, $this->course, $this);
 213          }
 214  
 215          if (!$section = $DB->get_record('course_sections', array('course'=>$this->course->id, 'id'=>$itemid))) {
 216              return null; // does not exist
 217          }
 218  
 219          $fs = get_file_storage();
 220  
 221          $filepath = is_null($filepath) ? '/' : $filepath;
 222          $filename = is_null($filename) ? '.' : $filename;
 223          if (!$storedfile = $fs->get_file($this->context->id, 'course', 'section', $itemid, $filepath, $filename)) {
 224              if ($filepath === '/' and $filename === '.') {
 225                  $storedfile = new virtual_root_file($this->context->id, 'course', 'section', $itemid);
 226              } else {
 227                  // not found
 228                  return null;
 229              }
 230          }
 231          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 232          require_once($CFG->dirroot.'/course/lib.php');
 233          $sectionname = get_section_name($this->course, $section);
 234          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $sectionname, true, true, true, false);
 235      }
 236  
 237      /**
 238       * Gets a stored file for the course legacy filearea directory
 239       *
 240       * @param int $itemid item ID
 241       * @param string $filepath file path
 242       * @param string $filename file name
 243       * @return file_info|null file_info instance or null if not found or access not allowed
 244       */
 245      protected function get_area_course_legacy($itemid, $filepath, $filename) {
 246          if (!has_capability('moodle/course:managefiles', $this->context)) {
 247              return null;
 248          }
 249  
 250          if ($this->course->id != SITEID and $this->course->legacyfiles != 2) {
 251              // bad luck, legacy course files not used any more
 252          }
 253  
 254          if (is_null($itemid)) {
 255              return $this;
 256          }
 257  
 258          $fs = get_file_storage();
 259  
 260          $filepath = is_null($filepath) ? '/' : $filepath;
 261          $filename = is_null($filename) ? '.' : $filename;
 262          if (!$storedfile = $fs->get_file($this->context->id, 'course', 'legacy', 0, $filepath, $filename)) {
 263              if ($filepath === '/' and $filename === '.') {
 264                  $storedfile = new virtual_root_file($this->context->id, 'course', 'legacy', 0);
 265              } else {
 266                  // not found
 267                  return null;
 268              }
 269          }
 270  
 271          return new file_info_area_course_legacy($this->browser, $this->context, $storedfile);
 272      }
 273  
 274      /**
 275       * Gets a stored file for the backup course filearea directory
 276       *
 277       * @param int $itemid item ID
 278       * @param string $filepath file path
 279       * @param string $filename file name
 280       * @return file_info|null file_info instance or null if not found or access not allowed
 281       */
 282      protected function get_area_backup_course($itemid, $filepath, $filename) {
 283          global $CFG;
 284  
 285          if (!has_capability('moodle/backup:backupcourse', $this->context) and !has_capability('moodle/restore:restorecourse', $this->context)) {
 286              return null;
 287          }
 288          if (is_null($itemid)) {
 289              return $this;
 290          }
 291  
 292          $fs = get_file_storage();
 293  
 294          $filepath = is_null($filepath) ? '/' : $filepath;
 295          $filename = is_null($filename) ? '.' : $filename;
 296          if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'course', 0, $filepath, $filename)) {
 297              if ($filepath === '/' and $filename === '.') {
 298                  $storedfile = new virtual_root_file($this->context->id, 'backup', 'course', 0);
 299              } else {
 300                  // not found
 301                  return null;
 302              }
 303          }
 304  
 305          $downloadable = has_capability('moodle/backup:downloadfile', $this->context);
 306          $uploadable   = has_capability('moodle/restore:uploadfile', $this->context);
 307  
 308          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 309          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('coursebackup', 'repository'), false, $downloadable, $uploadable, false);
 310      }
 311  
 312      /**
 313       * Gets a stored file for the automated backup filearea directory
 314       *
 315       * @param int $itemid item ID
 316       * @param string $filepath file path
 317       * @param string $filename file name
 318       * @return file_info|null
 319       */
 320      protected function get_area_backup_automated($itemid, $filepath, $filename) {
 321          global $CFG;
 322  
 323          if (!has_capability('moodle/restore:viewautomatedfilearea', $this->context)) {
 324              return null;
 325          }
 326          if (is_null($itemid)) {
 327              return $this;
 328          }
 329  
 330          $fs = get_file_storage();
 331  
 332          $filepath = is_null($filepath) ? '/' : $filepath;
 333          $filename = is_null($filename) ? '.' : $filename;
 334          if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'automated', 0, $filepath, $filename)) {
 335              if ($filepath === '/' and $filename === '.') {
 336                  $storedfile = new virtual_root_file($this->context->id, 'backup', 'automated', 0);
 337              } else {
 338                  // not found
 339                  return null;
 340              }
 341          }
 342  
 343          // Automated backup files are only downloadable if the user has both 'backup:downloadfile and 'restore:userinfo'.
 344          $downloadable = has_capability('moodle/backup:downloadfile', $this->context) &&
 345                          has_capability('moodle/restore:userinfo', $this->context);
 346          $uploadable   = false;
 347  
 348          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 349          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('automatedbackup', 'repository'), true, $downloadable, $uploadable, false);
 350      }
 351  
 352      /**
 353       * Gets a stored file for the backup section filearea directory
 354       *
 355       * @param int $itemid item ID
 356       * @param string $filepath file path
 357       * @param string $filename file name
 358       * @return file_info|null file_info instance or null if not found or access not allowed
 359       */
 360      protected function get_area_backup_section($itemid, $filepath, $filename) {
 361          global $CFG, $DB;
 362  
 363          if (!has_capability('moodle/backup:backupcourse', $this->context) and !has_capability('moodle/restore:restorecourse', $this->context)) {
 364              return null;
 365          }
 366  
 367          if (empty($itemid)) {
 368              // list all sections
 369              return new file_info_area_backup_section($this->browser, $this->context, $this->course, $this);
 370          }
 371  
 372          if (!$section = $DB->get_record('course_sections', array('course'=>$this->course->id, 'id'=>$itemid))) {
 373              return null; // does not exist
 374          }
 375  
 376          $fs = get_file_storage();
 377  
 378          $filepath = is_null($filepath) ? '/' : $filepath;
 379          $filename = is_null($filename) ? '.' : $filename;
 380          if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'section', $itemid, $filepath, $filename)) {
 381              if ($filepath === '/' and $filename === '.') {
 382                  $storedfile = new virtual_root_file($this->context->id, 'backup', 'section', $itemid);
 383              } else {
 384                  // not found
 385                  return null;
 386              }
 387          }
 388  
 389          $downloadable = has_capability('moodle/backup:downloadfile', $this->context);
 390          $uploadable   = has_capability('moodle/restore:uploadfile', $this->context);
 391  
 392          $urlbase = $CFG->wwwroot.'/pluginfile.php';
 393          return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $section->id, true, $downloadable, $uploadable, false);
 394      }
 395  
 396      /**
 397       * Returns localised visible name.
 398       *
 399       * @return string
 400       */
 401      public function get_visible_name() {
 402          return ($this->course->id == SITEID) ? get_string('frontpage', 'admin') : format_string(get_course_display_name_for_list($this->course), true, array('context'=>$this->context));
 403      }
 404  
 405      /**
 406       * Whether or not new files or directories can be added
 407       *
 408       * @return bool
 409       */
 410      public function is_writable() {
 411          return false;
 412      }
 413  
 414      /**
 415       * Whether or not this is a directory
 416       *
 417       * @return bool
 418       */
 419      public function is_directory() {
 420          return true;
 421      }
 422  
 423      /**
 424       * Returns list of children.
 425       *
 426       * @return array of file_info instances
 427       */
 428      public function get_children() {
 429          return $this->get_filtered_children('*', false, true);
 430      }
 431  
 432      /**
 433       * Returns the child module if it is accessible by the current user
 434       *
 435       * @param cm_info|int $cm
 436       * @return file_info_context_module|null
 437       */
 438      protected function get_child_module($cm) {
 439          $cmid = is_object($cm) ? $cm->id : $cm;
 440          if (!array_key_exists($cmid, $this->childrenmodules)) {
 441              $this->childrenmodules[$cmid] = null;
 442              if (!($cm instanceof cm_info)) {
 443                  $cms = get_fast_modinfo($this->course)->cms;
 444                  $cm = array_key_exists($cmid, $cms) ? $cms[$cmid] : null;
 445              }
 446              if ($cm && $cm->uservisible) {
 447                  $this->childrenmodules[$cmid] = new file_info_context_module($this->browser,
 448                      $cm->context, $this->course, $cm, $cm->modname);
 449              }
 450          }
 451          return $this->childrenmodules[$cmid];
 452      }
 453  
 454      /**
 455       * Help function to return files matching extensions or their count
 456       *
 457       * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
 458       * @param bool|int $countonly if false returns the children, if an int returns just the
 459       *    count of children but stops counting when $countonly number of children is reached
 460       * @param bool $returnemptyfolders if true returns items that don't have matching files inside
 461       * @return array|int array of file_info instances or the count
 462       */
 463      private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
 464          $children = array();
 465  
 466          $courseareas = $this->get_course_areas($extensions, $returnemptyfolders);
 467          foreach ($courseareas as $areaname) {
 468              $area = explode('_', $areaname, 2);
 469              if ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) {
 470                  $children[] = $child;
 471                  if (($countonly !== false) && count($children) >= $countonly) {
 472                      return $countonly;
 473                  }
 474              }
 475          }
 476  
 477          $cnt = count($children);
 478          if (!has_capability('moodle/course:managefiles', $this->context)) {
 479              // 'managefiles' capability is checked in every activity module callback.
 480              // Don't even waste time on retrieving the modules if we can't browse the files anyway
 481          } else {
 482              if ($returnemptyfolders) {
 483                  $modinfo = get_fast_modinfo($this->course);
 484                  foreach ($modinfo->cms as $cminfo) {
 485                      if ($child = $this->get_child_module($cminfo)) {
 486                          $children[] = $child;
 487                          $cnt++;
 488                      }
 489                  }
 490              } else if ($moduleareas = $this->get_module_areas_with_files($extensions)) {
 491                  // We found files in some of the modules.
 492                  // Create array of children modules ordered with the same way as cms in modinfo.
 493                  $modulechildren = array_fill_keys(array_keys(get_fast_modinfo($this->course)->get_cms()), null);
 494                  foreach ($moduleareas as $area) {
 495                      if ($modulechildren[$area->cmid]) {
 496                          // We already found non-empty area within the same module, do not analyse other areas.
 497                          continue;
 498                      }
 499                      if ($child = $this->get_child_module($area->cmid)) {
 500                          if ($child->get_file_info($area->component, $area->filearea, $area->itemid, null, null)) {
 501                              $modulechildren[$area->cmid] = $child;
 502                              $cnt++;
 503                              if (($countonly !== false) && $cnt >= $countonly) {
 504                                  return $cnt;
 505                              }
 506                          }
 507                      }
 508                  }
 509                  $children = array_merge($children, array_values(array_filter($modulechildren)));
 510              }
 511          }
 512  
 513          if ($countonly !== false) {
 514              return count($children);
 515          }
 516          return $children;
 517      }
 518  
 519      /**
 520       * Returns list of areas inside the course modules that have files with the given extension
 521       *
 522       * @param string $extensions
 523       * @return array
 524       */
 525      protected function get_module_areas_with_files($extensions = '*') {
 526          global $DB;
 527  
 528          $params1 = ['contextid' => $this->context->id,
 529              'emptyfilename' => '.',
 530              'contextlevel' => CONTEXT_MODULE,
 531              'course' => $this->course->id];
 532          $ctxfieldsas = context_helper::get_preload_record_columns_sql('ctx');
 533          $ctxfields = implode(', ', array_keys(context_helper::get_preload_record_columns('ctx')));
 534          $sql1 = "SELECT
 535                      ctx.id AS contextid,
 536                      f.component,
 537                      f.filearea,
 538                      f.itemid,
 539                      ctx.instanceid AS cmid,
 540                      {$ctxfieldsas}
 541              FROM {files} f
 542              INNER JOIN {context} ctx ON ctx.id = f.contextid
 543              INNER JOIN {course_modules} cm ON cm.id = ctx.instanceid
 544              WHERE f.filename <> :emptyfilename
 545                AND cm.course = :course
 546                AND ctx.contextlevel = :contextlevel";
 547          $sql3 = "
 548              GROUP BY ctx.id, f.component, f.filearea, f.itemid, {$ctxfields}
 549              ORDER BY ctx.id, f.component, f.filearea, f.itemid";
 550          list($sql2, $params2) = $this->build_search_files_sql($extensions);
 551          $areas = [];
 552          if ($rs = $DB->get_recordset_sql($sql1. $sql2 . $sql3, array_merge($params1, $params2))) {
 553              foreach ($rs as $record) {
 554                  context_helper::preload_from_record($record);
 555                  $areas[] = $record;
 556              }
 557              $rs->close();
 558          }
 559  
 560          // Sort areas so 'backup' and 'intro' are in the beginning of the list, they are the easiest to check access to.
 561          usort($areas, function($a, $b) {
 562              $aeasy = ($a->filearea === 'intro' && substr($a->component, 0, 4) === 'mod_') ||
 563                  ($a->filearea === 'activity' && $a->component === 'backup');
 564              $beasy = ($b->filearea === 'intro' && substr($b->component, 0, 4) === 'mod_') ||
 565                  ($b->filearea === 'activity' && $b->component === 'backup');
 566              return $aeasy == $beasy ? 0 : ($aeasy ? -1 : 1);
 567          });
 568          return $areas;
 569      }
 570  
 571      /**
 572       * Returns list of children which are either files matching the specified extensions
 573       * or folders that contain at least one such file.
 574       *
 575       * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
 576       * @return array of file_info instances
 577       */
 578      public function get_non_empty_children($extensions = '*') {
 579          return $this->get_filtered_children($extensions, false);
 580      }
 581  
 582      /**
 583       * Returns the number of children which are either files matching the specified extensions
 584       * or folders containing at least one such file.
 585       *
 586       * @param string|array $extensions, for example '*' or array('.gif','.jpg')
 587       * @param int $limit stop counting after at least $limit non-empty children are found
 588       * @return int
 589       */
 590      public function count_non_empty_children($extensions = '*', $limit = 1) {
 591          return $this->get_filtered_children($extensions, $limit);
 592      }
 593  
 594      /**
 595       * Returns parent file_info instance
 596       *
 597       * @return file_info or null for root
 598       */
 599      public function get_parent() {
 600          $parent = $this->context->get_parent_context();
 601          return $this->browser->get_file_info($parent);
 602      }
 603  }
 604  
 605  
 606  /**
 607   * Subclass of file_info_stored for files in the course files area.
 608   *
 609   * @package   core_files
 610   * @copyright 2008 Petr Skoda (http://skodak.org)
 611   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 612   */
 613  class file_info_area_course_legacy extends file_info_stored {
 614      /**
 615       * Constructor
 616       *
 617       * @param file_browser $browser file browser instance
 618       * @param stdClass $context context object
 619       * @param stored_file $storedfile stored_file instance
 620       */
 621      public function __construct($browser, $context, $storedfile) {
 622          global $CFG;
 623          $urlbase = $CFG->wwwroot.'/file.php';
 624          parent::__construct($browser, $context, $storedfile, $urlbase, get_string('coursefiles'), false, true, true, false);
 625      }
 626  
 627      /**
 628       * Returns file download url
 629       *
 630       * @param bool $forcedownload whether or not force download
 631       * @param bool $https whether or not force https
 632       * @return string url
 633       */
 634      public function get_url($forcedownload=false, $https=false) {
 635          if (!$this->is_readable()) {
 636              return null;
 637          }
 638  
 639          if ($this->lf->is_directory()) {
 640              return null;
 641          }
 642  
 643          $filepath = $this->lf->get_filepath();
 644          $filename = $this->lf->get_filename();
 645          $courseid = $this->context->instanceid;
 646  
 647          $path = '/'.$courseid.$filepath.$filename;
 648  
 649          return file_encode_url($this->urlbase, $path, $forcedownload, $https);
 650      }
 651  
 652      /**
 653       * Returns list of children.
 654       *
 655       * @return array of file_info instances
 656       */
 657      public function get_children() {
 658          if (!$this->lf->is_directory()) {
 659              return array();
 660          }
 661  
 662          $result = array();
 663          $fs = get_file_storage();
 664  
 665          $storedfiles = $fs->get_directory_files($this->context->id, 'course', 'legacy', 0, $this->lf->get_filepath(), false, true, "filepath ASC, filename ASC");
 666          foreach ($storedfiles as $file) {
 667              $result[] = new file_info_area_course_legacy($this->browser, $this->context, $file);
 668          }
 669  
 670          return $result;
 671      }
 672  
 673      /**
 674       * Returns list of children which are either files matching the specified extensions
 675       * or folders that contain at least one such file.
 676       *
 677       * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
 678       * @return array of file_info instances
 679       */
 680      public function get_non_empty_children($extensions = '*') {
 681          if (!$this->lf->is_directory()) {
 682              return array();
 683          }
 684  
 685          $result = array();
 686          $fs = get_file_storage();
 687  
 688          $storedfiles = $fs->get_directory_files($this->context->id, 'course', 'legacy', 0,
 689                                                  $this->lf->get_filepath(), false, true, "filepath, filename");
 690          foreach ($storedfiles as $file) {
 691              $extension = core_text::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
 692              if ($file->is_directory() || $extensions === '*' || (!empty($extension) && in_array('.'.$extension, $extensions))) {
 693                  $fileinfo = new file_info_area_course_legacy($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename,
 694                                                   $this->itemidused, $this->readaccess, $this->writeaccess, false);
 695                  if (!$file->is_directory() || $fileinfo->count_non_empty_children($extensions)) {
 696                      $result[] = $fileinfo;
 697                  }
 698              }
 699          }
 700  
 701          return $result;
 702      }
 703  }
 704  
 705  /**
 706   * Represents a course category context in the tree navigated by {@link file_browser}.
 707   *
 708   * @package    core_files
 709   * @copyright  2008 Petr Skoda (http://skodak.org)
 710   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 711   */
 712  class file_info_area_course_section extends file_info {
 713      /** @var stdClass course object */
 714      protected $course;
 715      /** @var file_info_context_course course file info object */
 716      protected $courseinfo;
 717  
 718      /**
 719       * Constructor
 720       *
 721       * @param file_browser $browser file browser instance
 722       * @param stdClass $context context object
 723       * @param stdClass $course course object
 724       * @param file_info_context_course $courseinfo file info instance
 725       */
 726      public function __construct($browser, $context, $course, file_info_context_course $courseinfo) {
 727          parent::__construct($browser, $context);
 728          $this->course     = $course;
 729          $this->courseinfo = $courseinfo;
 730      }
 731  
 732      /**
 733       * Returns list of standard virtual file/directory identification.
 734       * The difference from stored_file parameters is that null values
 735       * are allowed in all fields
 736       *
 737       * @return array with keys contextid, filearea, itemid, filepath and filename
 738       */
 739      public function get_params() {
 740          return array('contextid' => $this->context->id,
 741                       'component' => 'course',
 742                       'filearea'  => 'section',
 743                       'itemid'    => null,
 744                       'filepath'  => null,
 745                       'filename'  => null);
 746      }
 747  
 748      /**
 749       * Returns localised visible name.
 750       *
 751       * @return string
 752       */
 753      public function get_visible_name() {
 754          //$format = $this->course->format;
 755          $sectionsname = get_string("coursesectionsummaries");
 756  
 757          return $sectionsname;
 758      }
 759  
 760      /**
 761       * Return whether or not new files or directories can be added
 762       *
 763       * @return bool
 764       */
 765      public function is_writable() {
 766          return false;
 767      }
 768  
 769      /**
 770       * Return whether or not this is a empty area
 771       *
 772       * @return bool
 773       */
 774      public function is_empty_area() {
 775          $fs = get_file_storage();
 776          return $fs->is_area_empty($this->context->id, 'course', 'section');
 777      }
 778  
 779      /**
 780       * Return whether or not this is a empty area
 781       *
 782       * @return bool
 783       */
 784      public function is_directory() {
 785          return true;
 786      }
 787  
 788      /**
 789       * Returns list of children.
 790       *
 791       * @return array of file_info instances
 792       */
 793      public function get_children() {
 794          global $DB;
 795  
 796          $children = array();
 797  
 798          $course_sections = $DB->get_records('course_sections', array('course'=>$this->course->id), 'section');
 799          foreach ($course_sections as $section) {
 800              if ($child = $this->courseinfo->get_file_info('course', 'section', $section->id, '/', '.')) {
 801                  $children[] = $child;
 802              }
 803          }
 804  
 805          return $children;
 806      }
 807  
 808      /**
 809       * Returns the number of children which are either files matching the specified extensions
 810       * or folders containing at least one such file.
 811       *
 812       * @param string|array $extensions, for example '*' or array('.gif','.jpg')
 813       * @param int $limit stop counting after at least $limit non-empty children are found
 814       * @return int
 815       */
 816      public function count_non_empty_children($extensions = '*', $limit = 1) {
 817          global $DB;
 818          $params1 = array(
 819              'courseid' => $this->course->id,
 820              'contextid' => $this->context->id,
 821              'component' => 'course',
 822              'filearea' => 'section',
 823              'emptyfilename' => '.');
 824          $sql1 = "SELECT DISTINCT cs.id FROM {files} f, {course_sections} cs
 825              WHERE cs.course = :courseid
 826              AND f.contextid = :contextid
 827              AND f.component = :component
 828              AND f.filearea = :filearea
 829              AND f.itemid = cs.id
 830              AND f.filename <> :emptyfilename";
 831          list($sql2, $params2) = $this->build_search_files_sql($extensions);
 832          $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2));
 833          $cnt = 0;
 834          foreach ($rs as $record) {
 835              if ((++$cnt) >= $limit) {
 836                  break;
 837              }
 838          }
 839          $rs->close();
 840          return $cnt;
 841      }
 842  
 843      /**
 844       * Returns parent file_info instance
 845       *
 846       * @return file_info|null file_info or null for root
 847       */
 848      public function get_parent() {
 849          return $this->courseinfo;
 850      }
 851  }
 852  
 853  
 854  /**
 855   * Implementation of course section backup area
 856   *
 857   * @package    core_files
 858   * @copyright  2008 Petr Skoda (http://skodak.org)
 859   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 860   */
 861  class file_info_area_backup_section extends file_info {
 862      /** @var stdClass course object */
 863      protected $course;
 864      /** @var file_info_context_course course file info object */
 865      protected $courseinfo;
 866  
 867      /**
 868       * Constructor
 869       *
 870       * @param file_browser $browser file browser instance
 871       * @param stdClass $context context object
 872       * @param stdClass $course course object
 873       * @param file_info_context_course $courseinfo file info instance
 874       */
 875      public function __construct($browser, $context, $course, file_info_context_course $courseinfo) {
 876          parent::__construct($browser, $context);
 877          $this->course     = $course;
 878          $this->courseinfo = $courseinfo;
 879      }
 880  
 881      /**
 882       * Returns list of standard virtual file/directory identification.
 883       * The difference from stored_file parameters is that null values
 884       * are allowed in all fields
 885       *
 886       * @return array with keys contextid, component, filearea, itemid, filepath and filename
 887       */
 888      public function get_params() {
 889          return array('contextid' => $this->context->id,
 890                       'component' => 'backup',
 891                       'filearea'  => 'section',
 892                       'itemid'    => null,
 893                       'filepath'  => null,
 894                       'filename'  => null);
 895      }
 896  
 897      /**
 898       * Returns localised visible name.
 899       *
 900       * @return string
 901       */
 902      public function get_visible_name() {
 903          return get_string('sectionbackup', 'repository');
 904      }
 905  
 906      /**
 907       * Return whether or not new files and directories can be added
 908       *
 909       * @return bool
 910       */
 911      public function is_writable() {
 912          return false;
 913      }
 914  
 915      /**
 916       * Whether or not this is an empty area
 917       *
 918       * @return bool
 919       */
 920      public function is_empty_area() {
 921          $fs = get_file_storage();
 922          return $fs->is_area_empty($this->context->id, 'backup', 'section');
 923      }
 924  
 925      /**
 926       * Return whether or not this is a directory
 927       *
 928       * @return bool
 929       */
 930      public function is_directory() {
 931          return true;
 932      }
 933  
 934      /**
 935       * Returns list of children.
 936       *
 937       * @return array of file_info instances
 938       */
 939      public function get_children() {
 940          global $DB;
 941  
 942          $children = array();
 943  
 944          $course_sections = $DB->get_records('course_sections', array('course'=>$this->course->id), 'section');
 945          foreach ($course_sections as $section) {
 946              if ($child = $this->courseinfo->get_file_info('backup', 'section', $section->id, '/', '.')) {
 947                  $children[] = $child;
 948              }
 949          }
 950  
 951          return $children;
 952      }
 953  
 954      /**
 955       * Returns the number of children which are either files matching the specified extensions
 956       * or folders containing at least one such file.
 957       *
 958       * @param string|array $extensions, for example '*' or array('.gif','.jpg')
 959       * @param int $limit stop counting after at least $limit non-empty children are found
 960       * @return int
 961       */
 962      public function count_non_empty_children($extensions = '*', $limit = 1) {
 963          global $DB;
 964          $params1 = array(
 965              'courseid' => $this->course->id,
 966              'contextid' => $this->context->id,
 967              'component' => 'backup',
 968              'filearea' => 'section',
 969              'emptyfilename' => '.');
 970          $sql1 = "SELECT DISTINCT cs.id AS sectionid FROM {files} f, {course_sections} cs
 971              WHERE cs.course = :courseid
 972              AND f.contextid = :contextid
 973              AND f.component = :component
 974              AND f.filearea = :filearea
 975              AND f.itemid = cs.id
 976              AND f.filename <> :emptyfilename";
 977          list($sql2, $params2) = $this->build_search_files_sql($extensions);
 978          $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2));
 979          $cnt = 0;
 980          foreach ($rs as $record) {
 981              if ((++$cnt) >= $limit) {
 982                  break;
 983              }
 984          }
 985          $rs->close();
 986          return $cnt;
 987      }
 988  
 989      /**
 990       * Returns parent file_info instance
 991       *
 992       * @return file_info or null for root
 993       */
 994      public function get_parent() {
 995          return $this->browser->get_file_info($this->context);
 996      }
 997  }