Permalink
Browse files

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
  • Loading branch information...
1 parent a2b30aa commit e2652f1a3172469635d873e8615b2f2ea5c3f7ad @marinaglancy marinaglancy committed Mar 21, 2012
@@ -371,6 +371,12 @@ public function get_children() {
$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) {
@@ -99,10 +99,6 @@ public function get_children() {
$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);
@@ -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,82 +65,37 @@ public function get_listing($encodedpath = '') {
$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();
}
}
$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();
- }
- if (!empty($pathnodes) && is_array($pathnodes)) {
- $pathnodes = array_reverse($pathnodes);
- $ret['path'] = $pathnodes;
+ }
+ // 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());
}
- $list = array();
}
$ret['list'] = array_filter($list, array($this, 'filter'));
return $ret;
@@ -166,3 +119,250 @@ public function has_moodle_files() {
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;
+ }
+}

0 comments on commit e2652f1

Please sign in to comment.