MDL-27236: Server files, improve tree view

- Removed 'Private files' from 'Server files' repository;
- Show only non-empty directories (taking into account filetype filter);
- If there is only one non-empty filearea within a module, do not show it and skip the extra subdirectory level;
- If the user is not admin (does not have 'moodle/course:update' capability in system context), do not show course categories, just list available courses;
- Also when retrieving the course files capability to managefiles is checked before retrieving the modules list for performance tuning on sites with a lot of courses
This commit is contained in:
Marina Glancy 2012-03-21 13:55:22 +08:00
parent a2b30aa852
commit e2652f1a31
3 changed files with 272 additions and 70 deletions

View File

@ -371,6 +371,12 @@ class file_info_context_course extends file_info {
$children[] = $child;
}
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
return $children;
}
// now list all modules
$modinfo = get_fast_modinfo($this->course);
foreach ($modinfo->cms as $cminfo) {

View File

@ -99,10 +99,6 @@ class file_info_context_system extends file_info {
$children = array();
if ($child = $this->browser->get_file_info(get_context_instance(CONTEXT_USER, $USER->id))) {
$children[] = $child;
}
$course_cats = $DB->get_records('course_categories', array('parent'=>0), 'sortorder', 'id,visible');
foreach ($course_cats as $category) {
$context = get_context_instance(CONTEXT_COURSECAT, $category->id);

View File

@ -18,15 +18,13 @@
/**
* repository_local class is used to browse moodle files
*
* @since 2.0
* @package repository
* @subpackage local
* @since 2.0
* @package repository_local
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repository_local extends repository {
/**
* local plugin doesn't require login, so list all files
* @return mixed
@ -67,8 +65,11 @@ class repository_local extends repository {
$component = null;
if (!empty($this->context)) {
list($context, $course, $cm) = get_context_info_array($this->context->id);
$courseid = is_object($course) ? $course->id : SITEID;
$context = get_context_instance(CONTEXT_COURSE, $courseid);
if (is_object($course)) {
$context = get_context_instance(CONTEXT_COURSE, $course->id);
} else {
$context = get_system_context();
}
} else {
$context = get_system_context();
}
@ -76,73 +77,25 @@ class repository_local extends repository {
$browser = get_file_browser();
$list = array();
if ($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename)) {
// build path navigation
$pathnodes = array();
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
$pathnodes[] = array('name'=>$fileinfo->get_visible_name(), 'path'=>$encodedpath);
$level = $fileinfo->get_parent();
while ($level) {
$encodedpath = base64_encode(serialize($level->get_params()));
$pathnodes[] = array('name'=>$level->get_visible_name(), 'path'=>$encodedpath);
$level = $level->get_parent();
}
if (!empty($pathnodes) && is_array($pathnodes)) {
$pathnodes = array_reverse($pathnodes);
$ret['path'] = $pathnodes;
}
// build file tree
$children = $fileinfo->get_children();
foreach ($children as $child) {
if ($child->is_directory()) {
if ($child->is_empty_area()) {
continue;
}
$params = $child->get_params();
$encodedpath = base64_encode(serialize($params));
// hide user_private area from local plugin, user should
// use private file plugin to access private files
//if ($params['filearea'] == 'user_private') {
//continue;
//}
$node = array(
'title' => $child->get_visible_name(),
'size' => 0,
'date' => '',
'path' => $encodedpath,
'children'=>array(),
'thumbnail' => $OUTPUT->pix_url('f/folder-32')->out(false)
);
$list[] = $node;
} else {
$encodedpath = base64_encode(serialize($child->get_params()));
$node = array(
'title' => $child->get_visible_name(),
'size' => 0,
'date' => '',
'source'=> $encodedpath,
'thumbnail' => $OUTPUT->pix_url(file_extension_icon($child->get_visible_name(), 32))->out(false)
);
$list[] = $node;
}
$element = repository_local_file::retrieve_file_info($fileinfo, $this);
$nonemptychildren = $element->get_non_empty_children();
foreach ($nonemptychildren as $child) {
$list[] = (array)$child->get_node();
}
} else {
// if file doesn't exist, build path nodes root of current context
$pathnodes = array();
$fileinfo = $browser->get_file_info($context, null, null, null, null, null);
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
$pathnodes[] = array('name'=>$fileinfo->get_visible_name(), 'path'=>$encodedpath);
$level = $fileinfo->get_parent();
while ($level) {
$encodedpath = base64_encode(serialize($level->get_params()));
$pathnodes[] = array('name'=>$level->get_visible_name(), 'path'=>$encodedpath);
$level = $level->get_parent();
}
// build path navigation
$ret['path'] = array();
$element = repository_local_file::retrieve_file_info($fileinfo, $this);
for ($level = $element; $level; $level = $level->get_parent()) {
if ($level == $element || !$level->can_skip()) {
array_unshift($ret['path'], $level->get_node_path());
}
if (!empty($pathnodes) && is_array($pathnodes)) {
$pathnodes = array_reverse($pathnodes);
$ret['path'] = $pathnodes;
}
$list = array();
}
$ret['list'] = array_filter($list, array($this, 'filter'));
return $ret;
@ -166,3 +119,250 @@ class repository_local extends repository {
return true;
}
}
/**
* Class to cache some information about file
*
* This class is a wrapper to instances of file_info. It caches such information as
* parent and list of children. It also stores an array of already retrieved elements.
*
* It also implements more comprehensive algorithm for checking if folder is empty
* (taking into account the filtering of the files). To decrease number of levels
* we check if some subfolders can be skipped from the tree.
*
* As a result we display in Server files repository only non-empty folders and skip
* filearea folders if this is the only filearea in the module.
* For non-admin the course categories are not shown as well (courses are shown as a list)
*
* @package repository_local
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repository_local_file {
/** @var array stores already retrieved files */
private static $cachedfiles = array();
/** @var file_info Stores the original file */
public $fileinfo;
/** @var bool whether this file is directory */
private $isdir;
/** @var array caches retrieved children */
private $children = null;
/** @var array caches retrieved information whether this file is an empty directory */
protected $isempty = null;
/** @var repository link to the container repository (for filtering the results) */
private $repository;
/** @var repository_local_file link to parent directory */
protected $parent;
/** @var bool caches calculated information on whether this directory must be skipped in the tree */
private $skip = null;
/**
* Creates (or retrieves from cache) the repository_local_file object for $file_info
*
* @param file_info $fileinfo
* @param repository $repository
* @param repository_local_file $parent
* @return repository_local_file
*/
public static function retrieve_file_info(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
if (!isset(self::$cachedfiles[$encodedpath])) {
self::$cachedfiles[$encodedpath] = new repository_local_file($fileinfo, $repository, $parent);
}
return self::$cachedfiles[$encodedpath];
}
/**
* Creates an object
*
* @param file_info $fileinfo
* @param repository $repository
* @param repository_local_file $parent
*/
private function __construct(file_info $fileinfo, repository $repository, repository_local_file $parent = null) {
$this->repository = $repository;
$this->fileinfo = $fileinfo;
$this->isdir = $fileinfo->is_directory();
if (!$this->isdir) {
$node = array('title' => $this->fileinfo->get_visible_name());
$this->isempty = !$repository->filter($node);
$this->skip = false;
}
}
/**
* Returns node for $ret['list']
*
* @return array
*/
public function get_node() {
global $OUTPUT;
$encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
$node = array(
'title' => $this->fileinfo->get_visible_name(),
'size' => 0,
'date' => '');
if ($this->isdir) {
$node['path'] = $encodedpath;
$node['thumbnail'] = $OUTPUT->pix_url('f/folder-32')->out(false);
$node['children'] = array();
} else {
$node['source'] = $encodedpath;
$node['thumbnail'] = $OUTPUT->pix_url(file_extension_icon($node['title'], 32))->out(false);
}
return $node;
}
/**
* Returns node for $ret['path']
*
* @return array
*/
public function get_node_path() {
$encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
return array(
'path' => $encodedpath,
'name' => $this->fileinfo->get_visible_name()
);
}
/**
* Checks if this is a directory
*
* @return bool
*/
public function is_dir() {
return $this->isdir;
}
/**
* Returns children of this element
*
* @return array
*/
public function get_children() {
if (!$this->isdir) {
return array();
}
if ($this->children === null) {
$this->children = array();
$children = $this->fileinfo->get_children();
for ($i=0; $i<count($children); $i++) {
$this->children[] = self::retrieve_file_info($children[$i], $this->repository, $this);
}
}
return $this->children;
}
/**
* Checks if this folder is empty (contains no non-empty children)
*
* @return bool
*/
public function is_empty() {
if ($this->isempty === null) {
$this->isempty = true;
if (!$this->fileinfo->is_empty_area()) {
// even if is_empty_area() returns false, element still may be empty
$children = $this->get_children();
if (!empty($children)) {
// 1. Let's look at already retrieved children
foreach ($children as $childnode) {
if ($childnode->isempty === false) {
// we already calculated isempty for a child, and it is not empty
$this->isempty = false;
break;
}
}
if ($this->isempty) {
// 2. now we know that this directory contains children that are either empty or we don't know
foreach ($children as $childnode) {
if (!$childnode->is_empty()) {
$this->isempty = false;
break;
}
}
}
}
}
}
return $this->isempty;
}
/**
* Returns the parent element
*
* @return repository_local_file
*/
public function get_parent() {
if ($this->parent === null) {
if ($parent = $this->fileinfo->get_parent()) {
$this->parent = self::retrieve_file_info($parent, $this->repository);
} else {
$this->parent = false;
}
}
return $this->parent;
}
/**
* Wether this folder may be skipped in tree view
*
* @return bool
*/
public function can_skip() {
global $CFG;
if ($this->skip === null) {
$this->skip = false;
if ($this->fileinfo instanceof file_info_stored) {
$params = $this->fileinfo->get_params();
if (strlen($params['filearea']) && $params['filepath'] == '/' && $params['filename'] == '.') {
// This is a filearea inside an activity, it can be skipped if it has no non-empty siblings
if ($parent = $this->get_parent()) {
$siblings = $parent->get_children();
$countnonempty = 0;
foreach ($siblings as $sibling) {
if (!$sibling->is_empty()) {
$countnonempty++;
if ($countnonempty > 1) {
break;
}
}
}
if ($countnonempty <= 1) {
$this->skip = true;
}
}
}
} else if ($this->fileinfo instanceof file_info_context_coursecat) {
// This is a course category. For non-admins we do not display categories
$this->skip = empty($CFG->navshowmycoursecategories) &&
!has_capability('moodle/course:update', get_context_instance(CONTEXT_SYSTEM));
}
}
return $this->skip;
}
/**
* Returns array of children who have any elmenets
*
* If a subfolder can be skipped - list children of subfolder instead
* (recursive function)
*
* @return array
*/
public function get_non_empty_children() {
$children = $this->get_children();
$nonemptychildren = array();
foreach ($children as $child) {
if (!$child->is_empty()) {
if ($child->can_skip()) {
$nonemptychildren = array_merge($nonemptychildren, $child->get_non_empty_children());
} else {
$nonemptychildren[] = $child;
}
}
}
return $nonemptychildren;
}
}