mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
Merge branch 'wip-MDL-35668-master' of git://github.com/marinaglancy/moodle
This commit is contained in:
commit
4dfc2d19e7
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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().')';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
*
|
||||
|
244
lib/filebrowser/tests/file_browser_test.php
Normal file
244
lib/filebrowser/tests/file_browser_test.php
Normal 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));
|
||||
|
||||
}
|
||||
}
|
@ -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']) &&
|
||||
|
Loading…
x
Reference in New Issue
Block a user