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 { class file_browser {
/** @var array cached list of enrolled courses. */
protected $enrolledcourses = null;
/** /**
* Looks up file_info instance * Looks up file_info instance
* *
@ -190,7 +193,7 @@ class file_browser {
/** /**
* Returns info about the files at Course category context * 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 $component component
* @param string $filearea file area * @param string $filearea file area
* @param int $itemid item ID * @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 * @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) { private function get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename) {
global $COURSE, $DB, $CFG; if (!($context instanceof context_module)) {
return null;
static $cachedmodules = array();
if (!array_key_exists($context->instanceid, $cachedmodules)) {
$cachedmodules[$context->instanceid] = get_coursemodule_from_id('', $context->instanceid);
} }
$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; return null;
} }
if ($cm->course == $COURSE->id) { $level = new file_info_context_module($this, $context, $cm->get_course(), $cm, $cm->modname);
$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);
return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); 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 */ /** @var stdClass course object */
protected $course; protected $course;
/** @var file_info_context_module[] cached child modules. See {@link get_child_module()} */
protected $childrenmodules = [];
/** /**
* Constructor * Constructor
* *
@ -68,7 +71,7 @@ class file_info_context_course extends file_info {
return null; 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 // no peaking here if not enrolled or inspector
return null; return null;
} }
@ -86,6 +89,41 @@ class file_info_context_course extends file_info {
return null; 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 * 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); 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 * 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 * @return array|int array of file_info instances or the count
*/ */
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 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(); $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 ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) { $children[] = $child;
$children[] = $child; if (($countonly !== false) && count($children) >= $countonly) {
if (($countonly !== false) && count($children) >= $countonly) { return $countonly;
return $countonly;
}
} }
} }
} }
$cnt = count($children);
if (!has_capability('moodle/course:managefiles', $this->context)) { if (!has_capability('moodle/course:managefiles', $this->context)) {
// 'managefiles' capability is checked in every activity module callback. // '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 // Don't even waste time on retrieving the modules if we can't browse the files anyway
} else { } else {
// now list all modules if ($returnemptyfolders) {
$modinfo = get_fast_modinfo($this->course); $modinfo = get_fast_modinfo($this->course);
foreach ($modinfo->cms as $cminfo) { foreach ($modinfo->cms as $cminfo) {
if (empty($cminfo->uservisible)) { if ($child = $this->get_child_module($cminfo)) {
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)) {
$children[] = $child; $children[] = $child;
if (($countonly !== false) && count($children) >= $countonly) { $cnt++;
return $countonly; }
}
} 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; 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 * Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file. * 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) { protected function get_area_coursecat_description($itemid, $filepath, $filename) {
global $CFG; 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)) { if (!$this->category->visible and !has_capability('moodle/category:viewhiddencategories', $this->context)) {
return null; return null;
} }
@ -158,37 +162,92 @@ class file_info_context_coursecat extends file_info {
* @return array of file_info instances * @return array of file_info instances
*/ */
public function get_children() { public function get_children() {
global $DB;
$children = array(); $children = array();
if ($child = $this->get_area_coursecat_description(0, '/', '.')) { if ($child = $this->get_area_coursecat_description(0, '/', '.')) {
$children[] = $child; $children[] = $child;
} }
$course_cats = $DB->get_records('course_categories', array('parent'=>$this->category->id), 'sortorder', 'id,visible'); list($coursecats, $hiddencats) = $this->get_categories();
foreach ($course_cats as $category) { foreach ($coursecats as $category) {
$context = context_coursecat::instance($category->id); $context = context_coursecat::instance($category->id);
if (!$category->visible and !has_capability('moodle/category:viewhiddencategories', $context)) { $children[] = new self($this->browser, $context, $category);
continue;
}
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
} }
$courses = $DB->get_records('course', array('category'=>$this->category->id), 'sortorder', 'id,visible'); $courses = $this->get_courses($hiddencats);
foreach ($courses as $course) { foreach ($courses as $course) {
$context = context_course::instance($course->id); $children[] = $this->get_child_course($course);
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
}
if ($child = $this->browser->get_file_info($context)) {
$children[] = $child;
}
} }
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 * @return int
*/ */
public function count_non_empty_children($extensions = '*', $limit = 1) { public function count_non_empty_children($extensions = '*', $limit = 1) {
global $DB;
$cnt = 0; $cnt = 0;
if (($child = $this->get_area_coursecat_description(0, '/', '.')) if ($child = $this->get_area_coursecat_description(0, '/', '.')) {
&& $child->count_non_empty_children($extensions) && (++$cnt) >= $limit) { $cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
return $cnt; if ($cnt >= $limit) {
return $cnt;
}
} }
$rs = $DB->get_recordset_sql('SELECT ctx.id AS contextid, c.visible list($coursecats, $hiddencats) = $this->get_categories();
FROM {context} ctx, {course} c foreach ($coursecats as $category) {
WHERE ctx.instanceid = c.id $context = context_coursecat::instance($category->id);
AND ctx.contextlevel = :courselevel $child = new file_info_context_coursecat($this->browser, $context, $category);
AND c.category = :categoryid $cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
ORDER BY c.visible DESC', // retrieve visible courses first if ($cnt >= $limit) {
array('categoryid' => $this->category->id, 'courselevel' => CONTEXT_COURSE)); return $cnt;
foreach ($rs as $record) {
$context = context::instance_by_id($record->contextid);
if (!$record->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
} }
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 $courses = $this->get_courses($hiddencats);
FROM {context} ctx, {course_categories} cat foreach ($courses as $course) {
WHERE ctx.instanceid = cat.id if ($child = $this->get_child_course($course)) {
AND ctx.contextlevel = :catlevel $cnt += $child->count_non_empty_children($extensions) ? 1 : 0;
AND cat.parent = :categoryid if ($cnt >= $limit) {
ORDER BY cat.visible DESC', // retrieve visible categories first return $cnt;
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;
} }
} }
$rs->close();
return $cnt; return $cnt;
} }

View File

@ -35,7 +35,7 @@ defined('MOODLE_INTERNAL') || die();
class file_info_context_module extends file_info { class file_info_context_module extends file_info {
/** @var stdClass Course object */ /** @var stdClass Course object */
protected $course; protected $course;
/** @var stdClass Course module object */ /** @var cm_info Course module object */
protected $cm; protected $cm;
/** @var string Module name */ /** @var string Module name */
protected $modname; protected $modname;
@ -58,23 +58,17 @@ class file_info_context_module extends file_info {
parent::__construct($browser, $context); parent::__construct($browser, $context);
$this->course = $course; $this->course = $course;
$this->cm = $cm; $this->cm = cm_info::create($cm);
$this->modname = $modname; $this->modname = $this->cm->modname;
$this->nonemptychildren = null; $this->nonemptychildren = null;
include_once("$CFG->dirroot/mod/$modname/lib.php"); if ($functionname = component_callback_exists('mod_'.$modname, 'get_file_areas')) {
$cm = $this->cm->get_course_module_record();
//find out all supported areas
$functionname = 'mod_'.$modname.'_get_file_areas';
$functionname_old = $modname.'_get_file_areas';
if (function_exists($functionname)) {
$this->areas = $functionname($course, $cm, $context); $this->areas = $functionname($course, $cm, $context);
} else if (function_exists($functionname_old)) {
$this->areas = $functionname_old($course, $cm, $context);
} else { } else {
$this->areas = array(); $this->areas = array();
} }
unset($this->areas['intro']); // hardcoded, ignore attempts to override it unset($this->areas['intro']); // hardcoded, ignore attempts to override it
} }
@ -99,14 +93,12 @@ class file_info_context_module extends file_info {
return null; 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 // no peaking here if not enrolled or inspector
return null; return null;
} }
$modinfo = get_fast_modinfo($this->course); if (!$this->cm->uservisible) {
$cminfo = $modinfo->get_cm($this->cm->id);
if (!$cminfo->uservisible) {
// activity hidden sorry // activity hidden sorry
return null; return null;
} }
@ -121,13 +113,10 @@ class file_info_context_module extends file_info {
return $this->get_area_backup($itemid, $filepath, $filename); return $this->get_area_backup($itemid, $filepath, $filename);
} }
$functionname = 'mod_'.$this->modname.'_get_file_info'; if ($functionname = component_callback_exists('mod_'.$this->modname, 'get_file_info')) {
$functionname_old = $this->modname.'_get_file_info'; $cm = $this->cm->get_course_module_record();
return $functionname($this->browser, $this->areas, $this->course, $cm,
if (function_exists($functionname)) { $this->context, $filearea, $itemid, $filepath, $filename);
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);
} }
return null; return null;
@ -206,7 +195,7 @@ class file_info_context_module extends file_info {
* @return string * @return string
*/ */
public function get_visible_name() { 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(); 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}. * 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) * @copyright 2008 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @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 * Constructor
@ -41,7 +43,7 @@ class file_info_context_system extends file_info {
* @param stdClass $context context object * @param stdClass $context context object
*/ */
public function __construct($browser, $context) { 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; 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 * 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 // do not skip files
return false; 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 // This is a course category. For non-admins we do not display categories
return empty($CFG->navshowmycoursecategories) && return empty($CFG->navshowmycoursecategories) &&
!has_capability('moodle/course:update', context_system::instance()); !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 { } else {
$params = $fileinfo->get_params(); $params = $fileinfo->get_params();
if (strlen($params['filearea']) && if (strlen($params['filearea']) &&