Merge branch 'wip-MDL-35668-master' of git://github.com/marinaglancy/moodle

This commit is contained in:
Jun Pataleta 2017-09-11 11:01:41 +08:00
commit 4dfc2d19e7
7 changed files with 533 additions and 212 deletions

View File

@ -60,6 +60,9 @@ require_once("$CFG->libdir/filebrowser/file_info_context_module.php");
*/
class file_browser {
/** @var array cached list of enrolled courses. */
protected $enrolledcourses = null;
/**
* Looks up file_info instance
*
@ -190,7 +193,7 @@ class file_browser {
/**
* Returns info about the files at Course category context
*
* @param stdClass $context context object
* @param context $context context object
* @param string $component component
* @param string $filearea file area
* @param int $itemid item ID
@ -199,39 +202,36 @@ class file_browser {
* @return file_info|null file_info instance or null if not found or access not allowed
*/
private function get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename) {
global $COURSE, $DB, $CFG;
static $cachedmodules = array();
if (!array_key_exists($context->instanceid, $cachedmodules)) {
$cachedmodules[$context->instanceid] = get_coursemodule_from_id('', $context->instanceid);
if (!($context instanceof context_module)) {
return null;
}
$coursecontext = $context->get_course_context();
$modinfo = get_fast_modinfo($coursecontext->instanceid);
$cm = $modinfo->get_cm($context->instanceid);
if (!($cm = $cachedmodules[$context->instanceid])) {
if (empty($cm->uservisible)) {
return null;
}
if ($cm->course == $COURSE->id) {
$course = $COURSE;
} else if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
return null;
}
$modinfo = get_fast_modinfo($course);
if (empty($modinfo->cms[$cm->id]->uservisible)) {
return null;
}
$modname = $modinfo->cms[$cm->id]->modname;
if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
return null;
}
// ok, we know that module exists, and user may access it
$level = new file_info_context_module($this, $context, $course, $cm, $modname);
$level = new file_info_context_module($this, $context, $cm->get_course(), $cm, $cm->modname);
return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename);
}
/**
* Check if user is enrolled into the course
*
* This function keeps a cache of enrolled courses because it may be called multiple times for many courses in one request
*
* @param int $courseid
* @return bool
*/
public function is_enrolled($courseid) {
if ($this->enrolledcourses === null || PHPUNIT_TEST) {
// Since get_file_browser() returns a statically cached object we can't rely on cache
// inside the file_browser class in the unittests.
// TODO MDL-59964 remove this caching when it's implemented inside enrol_get_my_courses().
$this->enrolledcourses = enrol_get_my_courses(['id']);
}
return array_key_exists($courseid, $this->enrolledcourses);
}
}

View File

@ -36,6 +36,9 @@ class file_info_context_course extends file_info {
/** @var stdClass course object */
protected $course;
/** @var file_info_context_module[] cached child modules. See {@link get_child_module()} */
protected $childrenmodules = [];
/**
* Constructor
*
@ -68,7 +71,7 @@ class file_info_context_course extends file_info {
return null;
}
if (!is_viewing($this->context) and !is_enrolled($this->context)) {
if (!is_viewing($this->context) and !$this->browser->is_enrolled($this->course->id)) {
// no peaking here if not enrolled or inspector
return null;
}
@ -86,6 +89,41 @@ class file_info_context_course extends file_info {
return null;
}
/**
* Returns list of areas inside this course
*
* @param string $extensions Only return areas that have files with these extensions
* @param bool $returnemptyfolders return all areas always, if true it will ignore the previous argument
* @return array
*/
protected function get_course_areas($extensions = '*', $returnemptyfolders = false) {
global $DB;
$allareas = [
'course_summary',
'course_overviewfiles',
'course_section',
'backup_section',
'backup_course',
'backup_automated',
'course_legacy'
];
if ($returnemptyfolders) {
return $allareas;
}
$params1 = ['contextid' => $this->context->id, 'emptyfilename' => '.'];
$sql1 = "SELECT " . $DB->sql_concat('f.component', "'_'", 'f.filearea') . "
FROM {files} f
WHERE f.filename <> :emptyfilename AND f.contextid = :contextid ";
$sql3 = ' GROUP BY f.component, f.filearea';
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$areaswithfiles = $DB->get_fieldset_sql($sql1 . $sql2 . $sql3, array_merge($params1, $params2));
return array_intersect($allareas, $areaswithfiles);
}
/**
* Gets a stored file for the course summary filearea directory
*
@ -387,6 +425,28 @@ class file_info_context_course extends file_info {
return $this->get_filtered_children('*', false, true);
}
/**
* Returns the child module if it is accessible by the current user
*
* @param cm_info|int $cm
* @return file_info_context_module|null
*/
protected function get_child_module($cm) {
$cmid = is_object($cm) ? $cm->id : $cm;
if (!array_key_exists($cmid, $this->childrenmodules)) {
$this->childrenmodules[$cmid] = null;
if (!($cm instanceof cm_info)) {
$cms = get_fast_modinfo($this->course)->cms;
$cm = array_key_exists($cmid, $cms) ? $cms[$cmid] : null;
}
if ($cm && $cm->uservisible) {
$this->childrenmodules[$cmid] = new file_info_context_module($this->browser,
$cm->context, $this->course, $cm, $cm->modname);
}
}
return $this->childrenmodules[$cmid];
}
/**
* Help function to return files matching extensions or their count
*
@ -397,46 +457,52 @@ class file_info_context_course extends file_info {
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
$areas = array(
array('course', 'summary'),
array('course', 'overviewfiles'),
array('course', 'section'),
array('backup', 'section'),
array('backup', 'course'),
array('backup', 'automated'),
array('course', 'legacy')
);
$children = array();
foreach ($areas as $area) {
$courseareas = $this->get_course_areas($extensions, $returnemptyfolders);
foreach ($courseareas as $areaname) {
$area = explode('_', $areaname, 2);
if ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
$children[] = $child;
if (($countonly !== false) && count($children) >= $countonly) {
return $countonly;
}
$children[] = $child;
if (($countonly !== false) && count($children) >= $countonly) {
return $countonly;
}
}
}
$cnt = count($children);
if (!has_capability('moodle/course:managefiles', $this->context)) {
// 'managefiles' capability is checked in every activity module callback.
// Don't even waste time on retrieving the modules if we can't browse the files anyway
} else {
// now list all modules
$modinfo = get_fast_modinfo($this->course);
foreach ($modinfo->cms as $cminfo) {
if (empty($cminfo->uservisible)) {
continue;
}
$modcontext = context_module::instance($cminfo->id, IGNORE_MISSING);
if ($child = $this->browser->get_file_info($modcontext)) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
if ($returnemptyfolders) {
$modinfo = get_fast_modinfo($this->course);
foreach ($modinfo->cms as $cminfo) {
if ($child = $this->get_child_module($cminfo)) {
$children[] = $child;
if (($countonly !== false) && count($children) >= $countonly) {
return $countonly;
$cnt++;
}
}
} else if ($moduleareas = $this->get_module_areas_with_files($extensions)) {
// We found files in some of the modules.
// Create array of children modules ordered with the same way as cms in modinfo.
$modulechildren = array_fill_keys(array_keys(get_fast_modinfo($this->course)->get_cms()), null);
foreach ($moduleareas as $area) {
if ($modulechildren[$area->cmid]) {
// We already found non-empty area within the same module, do not analyse other areas.
continue;
}
if ($child = $this->get_child_module($area->cmid)) {
if ($child->get_file_info($area->component, $area->filearea, $area->itemid, null, null)) {
$modulechildren[$area->cmid] = $child;
$cnt++;
if (($countonly !== false) && $cnt >= $countonly) {
return $cnt;
}
}
}
}
$children = array_merge($children, array_values(array_filter($modulechildren)));
}
}
@ -446,6 +512,52 @@ class file_info_context_course extends file_info {
return $children;
}
/**
* Returns list of areas inside the course modules that have files with the given extension
*
* @param string $extensions
* @return array
*/
protected function get_module_areas_with_files($extensions = '*') {
global $DB;
$params1 = ['contextid' => $this->context->id,
'emptyfilename' => '.',
'contextlevel' => CONTEXT_MODULE,
'depth' => $this->context->depth + 1,
'pathmask' => $this->context->path . '/%'];
$sql1 = "SELECT ctx.id AS contextid, f.component, f.filearea, f.itemid, ctx.instanceid AS cmid, " .
context_helper::get_preload_record_columns_sql('ctx') . "
FROM {files} f
INNER JOIN {context} ctx ON ctx.id = f.contextid
WHERE f.filename <> :emptyfilename
AND ctx.contextlevel = :contextlevel
AND ctx.depth = :depth
AND " . $DB->sql_like('ctx.path', ':pathmask') . " ";
$sql3 = ' GROUP BY ctx.id, f.component, f.filearea, f.itemid, ctx.instanceid,
ctx.path, ctx.depth, ctx.contextlevel
ORDER BY ctx.id, f.component, f.filearea, f.itemid';
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$areas = [];
if ($rs = $DB->get_recordset_sql($sql1. $sql2 . $sql3, array_merge($params1, $params2))) {
foreach ($rs as $record) {
context_helper::preload_from_record($record);
$areas[] = $record;
}
$rs->close();
}
// Sort areas so 'backup' and 'intro' are in the beginning of the list, they are the easiest to check access to.
usort($areas, function($a, $b) {
$aeasy = ($a->filearea === 'intro' && substr($a->component, 0, 4) === 'mod_') ||
($a->filearea === 'activity' && $a->component === 'backup');
$beasy = ($b->filearea === 'intro' && substr($b->component, 0, 4) === 'mod_') ||
($b->filearea === 'activity' && $b->component === 'backup');
return $aeasy == $beasy ? 0 : ($aeasy ? -1 : 1);
});
return $areas;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.

View File

@ -97,6 +97,10 @@ class file_info_context_coursecat extends file_info {
protected function get_area_coursecat_description($itemid, $filepath, $filename) {
global $CFG;
if (!$this->category->id) {
// No coursecat description area for "system".
return null;
}
if (!$this->category->visible and !has_capability('moodle/category:viewhiddencategories', $this->context)) {
return null;
}
@ -158,37 +162,92 @@ class file_info_context_coursecat extends file_info {
* @return array of file_info instances
*/
public function get_children() {
global $DB;
$children = array();
if ($child = $this->get_area_coursecat_description(0, '/', '.')) {
$children[] = $child;
}
$course_cats = $DB->get_records('course_categories', array('parent'=>$this->category->id), 'sortorder', 'id,visible');
foreach ($course_cats as $category) {
list($coursecats, $hiddencats) = $this->get_categories();
foreach ($coursecats as $category) {
$context = context_coursecat::instance($category->id);
if (!$category->visible and !has_capability('moodle/category:viewhiddencategories', $context)) {
continue;
}
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
$children[] = new self($this->browser, $context, $category);
}
$courses = $DB->get_records('course', array('category'=>$this->category->id), 'sortorder', 'id,visible');
$courses = $this->get_courses($hiddencats);
foreach ($courses as $course) {
$context = context_course::instance($course->id);
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
}
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
$children[] = $this->get_child_course($course);
}
return $children;
return array_filter($children);
}
/**
* List of courses in this category and in hidden subcategories
*
* @param array $hiddencats list of categories that are hidden from current user and returned by {@link get_categories()}
* @return array list of courses
*/
protected function get_courses($hiddencats) {
global $DB, $CFG;
require_once($CFG->libdir.'/modinfolib.php');
$params = array('category' => $this->category->id, 'contextlevel' => CONTEXT_COURSE);
$sql = 'c.category = :category';
foreach ($hiddencats as $category) {
$catcontext = context_coursecat::instance($category->id);
$sql .= ' OR ' . $DB->sql_like('ctx.path', ':path' . $category->id);
$params['path' . $category->id] = $catcontext->path . '/%';
}
// Let's retrieve only minimum number of fields from course table -
// what is needed to check access or call get_fast_modinfo().
$coursefields = array_merge(['id', 'visible'], course_modinfo::$cachedfields);
$fields = 'c.' . join(',c.', $coursefields) . ', ' .
context_helper::get_preload_record_columns_sql('ctx');
return $DB->get_records_sql('SELECT ' . $fields . ' FROM {course} c
JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)
WHERE ('.$sql.') ORDER BY c.sortorder', $params);
}
/**
* Finds accessible and non-accessible direct subcategories
*
* @return array [$coursecats, $hiddencats] - child categories that are visible to the current user and not visible
*/
protected function get_categories() {
global $DB;
$fields = 'c.*, ' . context_helper::get_preload_record_columns_sql('ctx');
$coursecats = $DB->get_records_sql('SELECT ' . $fields . ' FROM {course_categories} c
LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)
WHERE c.parent = :parent ORDER BY c.sortorder',
array('parent' => $this->category->id, 'contextlevel' => CONTEXT_COURSECAT));
$hiddencats = [];
foreach ($coursecats as $id => &$category) {
context_helper::preload_from_record($category);
$context = context_coursecat::instance($category->id);
if (!$category->visible && !has_capability('moodle/category:viewhiddencategories', $context)) {
$hiddencats[$id] = $coursecats[$id];
unset($coursecats[$id]);
}
}
return [$coursecats, $hiddencats];
}
/**
* Returns the file info element for a given course or null if course is not accessible
*
* @param stdClass $course may contain context fields for preloading
* @return file_info_context_course|null
*/
protected function get_child_course($course) {
context_helper::preload_from_record($course);
$context = context_course::instance($course->id);
$child = new file_info_context_course($this->browser, $context, $course);
return $child->get_file_info(null, null, null, null, null);
}
/**
@ -200,53 +259,33 @@ class file_info_context_coursecat extends file_info {
* @return int
*/
public function count_non_empty_children($extensions = '*', $limit = 1) {
global $DB;
$cnt = 0;
if (($child = $this->get_area_coursecat_description(0, '/', '.'))
&& $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
return $cnt;
if ($child = $this->get_area_coursecat_description(0, '/', '.')) {
$cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
if ($cnt >= $limit) {
return $cnt;
}
}
$rs = $DB->get_recordset_sql('SELECT ctx.id AS contextid, c.visible
FROM {context} ctx, {course} c
WHERE ctx.instanceid = c.id
AND ctx.contextlevel = :courselevel
AND c.category = :categoryid
ORDER BY c.visible DESC', // retrieve visible courses first
array('categoryid' => $this->category->id, 'courselevel' => CONTEXT_COURSE));
foreach ($rs as $record) {
$context = context::instance_by_id($record->contextid);
if (!$record->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
list($coursecats, $hiddencats) = $this->get_categories();
foreach ($coursecats as $category) {
$context = context_coursecat::instance($category->id);
$child = new file_info_context_coursecat($this->browser, $context, $category);
$cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
if ($cnt >= $limit) {
return $cnt;
}
if (($child = $this->browser->get_file_info($context))
&& $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
break;
}
}
$rs->close();
if ($cnt >= $limit) {
return $cnt;
}
$rs = $DB->get_recordset_sql('SELECT ctx.id AS contextid, cat.visible
FROM {context} ctx, {course_categories} cat
WHERE ctx.instanceid = cat.id
AND ctx.contextlevel = :catlevel
AND cat.parent = :categoryid
ORDER BY cat.visible DESC', // retrieve visible categories first
array('categoryid' => $this->category->id, 'catlevel' => CONTEXT_COURSECAT));
foreach ($rs as $record) {
$context = context::instance_by_id($record->contextid);
if (!$record->visible and !has_capability('moodle/category:viewhiddencategories', $context)) {
continue;
}
if (($child = $this->browser->get_file_info($context))
&& $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) {
break;
$courses = $this->get_courses($hiddencats);
foreach ($courses as $course) {
if ($child = $this->get_child_course($course)) {
$cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
if ($cnt >= $limit) {
return $cnt;
}
}
}
$rs->close();
return $cnt;
}

View File

@ -35,7 +35,7 @@ defined('MOODLE_INTERNAL') || die();
class file_info_context_module extends file_info {
/** @var stdClass Course object */
protected $course;
/** @var stdClass Course module object */
/** @var cm_info Course module object */
protected $cm;
/** @var string Module name */
protected $modname;
@ -58,23 +58,17 @@ class file_info_context_module extends file_info {
parent::__construct($browser, $context);
$this->course = $course;
$this->cm = $cm;
$this->modname = $modname;
$this->cm = cm_info::create($cm);
$this->modname = $this->cm->modname;
$this->nonemptychildren = null;
include_once("$CFG->dirroot/mod/$modname/lib.php");
//find out all supported areas
$functionname = 'mod_'.$modname.'_get_file_areas';
$functionname_old = $modname.'_get_file_areas';
if (function_exists($functionname)) {
if ($functionname = component_callback_exists('mod_'.$modname, 'get_file_areas')) {
$cm = $this->cm->get_course_module_record();
$this->areas = $functionname($course, $cm, $context);
} else if (function_exists($functionname_old)) {
$this->areas = $functionname_old($course, $cm, $context);
} else {
$this->areas = array();
}
unset($this->areas['intro']); // hardcoded, ignore attempts to override it
}
@ -99,14 +93,12 @@ class file_info_context_module extends file_info {
return null;
}
if (!is_viewing($this->context) and !is_enrolled($this->context)) {
if (!is_viewing($this->context) and !$this->browser->is_enrolled($this->course->id)) {
// no peaking here if not enrolled or inspector
return null;
}
$modinfo = get_fast_modinfo($this->course);
$cminfo = $modinfo->get_cm($this->cm->id);
if (!$cminfo->uservisible) {
if (!$this->cm->uservisible) {
// activity hidden sorry
return null;
}
@ -121,13 +113,10 @@ class file_info_context_module extends file_info {
return $this->get_area_backup($itemid, $filepath, $filename);
}
$functionname = 'mod_'.$this->modname.'_get_file_info';
$functionname_old = $this->modname.'_get_file_info';
if (function_exists($functionname)) {
return $functionname($this->browser, $this->areas, $this->course, $this->cm, $this->context, $filearea, $itemid, $filepath, $filename);
} else if (function_exists($functionname_old)) {
return $functionname_old($this->browser, $this->areas, $this->course, $this->cm, $this->context, $filearea, $itemid, $filepath, $filename);
if ($functionname = component_callback_exists('mod_'.$this->modname, 'get_file_info')) {
$cm = $this->cm->get_course_module_record();
return $functionname($this->browser, $this->areas, $this->course, $cm,
$this->context, $filearea, $itemid, $filepath, $filename);
}
return null;
@ -206,7 +195,7 @@ class file_info_context_module extends file_info {
* @return string
*/
public function get_visible_name() {
return $this->cm->name.' ('.get_string('modulename', $this->cm->modname).')';
return $this->cm->get_formatted_name().' ('.$this->cm->get_module_type_name().')';
}
/**

View File

@ -25,6 +25,8 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/filebrowser/file_info_context_coursecat.php');
/**
* Represents the system context in the tree navigated by {@link file_browser}.
*
@ -32,7 +34,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2008 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class file_info_context_system extends file_info {
class file_info_context_system extends file_info_context_coursecat {
/**
* Constructor
@ -41,7 +43,7 @@ class file_info_context_system extends file_info {
* @param stdClass $context context object
*/
public function __construct($browser, $context) {
parent::__construct($browser, $context);
parent::__construct($browser, $context, (object)['id' => 0, 'parent' => 0, 'visible' => 1]);
}
/**
@ -139,71 +141,6 @@ class file_info_context_system extends file_info {
return true;
}
/**
* Returns list of children.
*
* @return array of file_info instances
*/
public function get_children() {
global $DB;
$children = array();
// Add course categories on the top level that are either visible or user is able to view hidden categories.
$course_cats = $DB->get_records('course_categories', array('parent'=>0), 'sortorder', 'id,visible');
foreach ($course_cats as $category) {
$context = context_coursecat::instance($category->id);
if (!$category->visible and !has_capability('moodle/category:viewhiddencategories', $context)) {
continue;
}
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
}
// Add courses where user is enrolled that are located in hidden course categories because they would not
// be present in the above tree but user may still be able to access files in them.
if ($hiddencontexts = $this->get_inaccessible_coursecat_contexts()) {
$courses = enrol_get_my_courses();
foreach ($courses as $course) {
$context = context_course::instance($course->id);
$parents = $context->get_parent_context_ids();
if (array_intersect($hiddencontexts, $parents)) {
// This course has hidden parent category.
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
}
}
}
return $children;
}
/**
* Returns list of course categories contexts that current user can not see
*
* @return array array of course categories contexts ids
*/
protected function get_inaccessible_coursecat_contexts() {
global $DB;
$sql = context_helper::get_preload_record_columns_sql('ctx');
$records = $DB->get_records_sql("SELECT ctx.id, $sql
FROM {course_categories} c
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = ?
WHERE c.visible = ?", [CONTEXT_COURSECAT, 0]);
$hiddencontexts = [];
foreach ($records as $record) {
context_helper::preload_from_record($record);
$context = context::instance_by_id($record->id);
if (!has_capability('moodle/category:viewhiddencategories', $context)) {
$hiddencontexts[] = $record->id;
}
}
return $hiddencontexts;
}
/**
* Returns parent file_info instance
*

View File

@ -0,0 +1,244 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for file browser
*
* @package core_files
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for file browser
*
* @package core_files
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class file_browser_testcase extends advanced_testcase {
/** @var stdClass */
protected $course1;
/** @var stdClass */
protected $course2;
/** @var stdClass */
protected $module1;
/** @var stdClass */
protected $module2;
/** @var stdClass */
protected $course1filerecord;
/** @var stdClass */
protected $teacher;
/** @var stdClass */
protected $teacherrole;
/**
* Set up
*/
public function setUp() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$this->getDataGenerator()->create_category(); // Empty category.
$this->course1 = $this->getDataGenerator()->create_course(); // Empty course.
$this->course2 = $this->getDataGenerator()->create_course();
// Add a file to course1 summary.
$coursecontext1 = context_course::instance($this->course1->id);
$this->course1filerecord = array('contextid' => $coursecontext1->id,
'component' => 'course',
'filearea' => 'summary',
'itemid' => '0',
'filepath' => '/',
'filename' => 'summaryfile.jpg');
$fs = get_file_storage();
$fs->create_file_from_string($this->course1filerecord, 'IMG');
$this->module1 = $this->getDataGenerator()->create_module('resource', ['course' => $this->course2->id]); // Contains 1 file.
$this->module2 = $this->getDataGenerator()->create_module('assign', ['course' => $this->course2->id]); // Contains no files.
$this->teacher = $this->getDataGenerator()->create_user();
$this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($this->teacher->id, $this->course1->id, $this->teacherrole->id);
$this->getDataGenerator()->enrol_user($this->teacher->id, $this->course2->id, $this->teacherrole->id);
$this->setUser($this->teacher);
}
/**
* Test "Server files" from the system context
*/
public function test_file_info_context_system() {
// There is one non-empty category child and two category children.
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_system::instance());
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$this->assertEquals(1, count($fileinfo->get_non_empty_children()));
$categorychildren = array_filter($fileinfo->get_children(), function($a) {
return $a instanceof file_info_context_coursecat;
});
$this->assertEquals(2, count($categorychildren));
}
/**
* Test "Server files" from the system context, hide Misc category
*/
public function test_file_info_context_system_hidden() {
// Hide the course category that contains our two courses. Teacher does not have cap to view hidden categories.
coursecat::get($this->course1->category)->update(['visible' => 0]);
// We should have two non-empty children in system context (courses).
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_system::instance());
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$this->assertEquals(2, count($fileinfo->get_non_empty_children()));
// Should be 1 category children (empty category).
$categorychildren = array_filter($fileinfo->get_children(), function($a) {
return $a instanceof file_info_context_coursecat;
});
$this->assertEquals(1, count($categorychildren));
// Should be 2 course children - courses that belonged to hidden subcategory are now direct children of "System".
$coursechildren = array_filter($fileinfo->get_children(), function($a) {
return $a instanceof file_info_context_course;
});
$this->assertEquals(2, count($coursechildren));
}
/**
* Test "Server files" from the course category context
*/
public function test_file_info_context_coursecat() {
// There are two non-empty courses.
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_coursecat::instance($this->course2->category));
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$this->assertEquals(2, count($fileinfo->get_non_empty_children()));
$coursechildren = array_filter($fileinfo->get_children(), function($a) {
return $a instanceof file_info_context_course;
});
$this->assertEquals(2, count($coursechildren));
}
/**
* Test "Server files" from the course category context, only look for .jpg
*/
public function test_file_info_context_coursecat_jpg() {
// There is one non-empty category child and two category children.
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_system::instance());
$this->assertNotEmpty($fileinfo->count_non_empty_children(['.jpg']));
$this->assertEquals(1, count($fileinfo->get_non_empty_children(['.jpg'])));
}
/**
* Test "Server files" from the course context (course1)
*/
public function test_file_info_context_course_1() {
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_course::instance($this->course1->id));
// Fileinfo element has only one non-empty child - "Course summary" file area.
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$nonemptychildren = $fileinfo->get_non_empty_children();
$this->assertEquals(1, count($nonemptychildren));
$child = reset($nonemptychildren);
$this->assertTrue($child instanceof file_info_stored);
$this->assertEquals(['filename' => '.'] + $this->course1filerecord, $child->get_params());
// Filearea "Course summary" has a child that is the actual image file.
$this->assertEquals($this->course1filerecord, $child->get_children()[0]->get_params());
// There are six course-level file areas and no modules in this course.
$allchildren = $fileinfo->get_children();
$this->assertEquals(6, count($allchildren));
$modulechildren = array_filter($allchildren, function($a) {
return $a instanceof file_info_context_module;
});
$this->assertEquals(0, count($modulechildren));
// Admin can see seven course-level file areas.
$this->setAdminUser();
$fileinfo = $browser->get_file_info(context_course::instance($this->course1->id));
$this->assertEquals(7, count($fileinfo->get_children()));
}
/**
* Test "Server files" from the course context (course1)
*/
public function test_file_info_context_course_2() {
// 2. Start from the course level.
$browser = get_file_browser();
$fileinfo = $browser->get_file_info(context_course::instance($this->course2->id));
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$nonemptychildren = $fileinfo->get_non_empty_children();
$this->assertEquals(1, count($nonemptychildren));
$child = reset($nonemptychildren);
$this->assertTrue($child instanceof file_info_context_module);
$this->assertEquals($this->module1->name.' (File)', $child->get_visible_name());
$this->assertEquals(1, count($child->get_non_empty_children()));
$this->assertEquals(1, $child->count_non_empty_children());
$modulechildren = array_filter($fileinfo->get_children(), function($a) {
return $a instanceof file_info_context_module;
});
$this->assertEquals(2, count($modulechildren));
}
/**
* Test "Server files" from the course context (module1)
*/
public function test_file_info_context_module_1() {
$module1context = context_module::instance($this->module1->cmid);
$browser = get_file_browser();
$fileinfo = $browser->get_file_info($module1context);
$this->assertEquals($this->module1->name . ' (File)', $fileinfo->get_visible_name());
$this->assertNotEmpty($fileinfo->count_non_empty_children());
$nonemptychildren = $fileinfo->get_non_empty_children();
$this->assertEquals(1, count($nonemptychildren));
$child = reset($nonemptychildren);
$this->assertTrue($child instanceof file_info_stored);
}
/**
* Test "Server files" from the course context (module1)
*/
public function test_file_info_context_module_2() {
$module2context = context_module::instance($this->module2->cmid);
$browser = get_file_browser();
$fileinfo = $browser->get_file_info($module2context);
$this->assertEquals($this->module2->name.' (Assignment)', $fileinfo->get_visible_name());
$this->assertEmpty($fileinfo->count_non_empty_children());
$nonemptychildren = $fileinfo->get_non_empty_children();
$this->assertEquals(0, count($nonemptychildren));
}
}

View File

@ -166,17 +166,17 @@ class repository_local extends repository {
// do not skip files
return false;
}
if ($fileinfo instanceof file_info_context_coursecat) {
if ($fileinfo instanceof file_info_context_course ||
$fileinfo instanceof file_info_context_user ||
$fileinfo instanceof file_info_area_course_legacy ||
$fileinfo instanceof file_info_context_module ||
$fileinfo instanceof file_info_context_system) {
// These instances can never be filearea inside an activity, they will never be skipped.
return false;
} else if ($fileinfo instanceof file_info_context_coursecat) {
// This is a course category. For non-admins we do not display categories
return empty($CFG->navshowmycoursecategories) &&
!has_capability('moodle/course:update', context_system::instance());
} else if ($fileinfo instanceof file_info_context_course ||
$fileinfo instanceof file_info_context_user ||
$fileinfo instanceof file_info_area_course_legacy ||
$fileinfo instanceof file_info_context_module ||
$fileinfo instanceof file_info_context_system) {
// these instances can never be filearea inside an activity, they will never be skipped
return false;
} else {
$params = $fileinfo->get_params();
if (strlen($params['filearea']) &&