MDL-33857 Increase performance of queries for Server files repository

- file_info class now has new methods get_non_empty_children and count_non_empty_children; added default implementation;
- all core classes extending file_info have their implementation of those methods;
- class repository_local_file used for caching get_children() results is removed;
- class repository_local rewritten to use new methods from file_info;
- added caching of retrieved modules in file_browser::get_file_info_context_module() - this query is slow and executed multiple times on big servers
This commit is contained in:
Marina Glancy 2012-09-03 16:44:49 +08:00
parent ccd90e765e
commit b8de262139
13 changed files with 997 additions and 342 deletions

View File

@ -201,8 +201,13 @@ class file_browser {
private function get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename) {
global $COURSE, $DB, $CFG;
static $cachedmodules = array();
if (!$cm = get_coursemodule_from_id('', $context->instanceid)) {
if (!array_key_exists($context->instanceid, $cachedmodules)) {
$cachedmodules[$context->instanceid] = get_coursemodule_from_id('', $context->instanceid);
}
if (!($cm = $cachedmodules[$context->instanceid])) {
return null;
}

View File

@ -88,6 +88,111 @@ abstract class file_info {
*/
public abstract function get_children();
/**
* Builds SQL sub query (WHERE clause) for selecting files with the specified extensions
*
* If $extensions == '*' (any file), the result is array('', array())
* otherwise the result is something like array('AND filename ...', array(...))
*
* @param string|array $extensions - either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param string $prefix prefix for DB table files in the query (empty by default)
* @return array of two elements: $sql - sql where clause and $params - array of parameters
*/
protected function build_search_files_sql($extensions, $prefix = null) {
global $DB;
if (strlen($prefix)) {
$prefix = $prefix.'.';
} else {
$prefix = '';
}
$sql = '';
$params = array();
if (is_array($extensions) && !in_array('*', $extensions)) {
$likes = array();
$cnt = 0;
foreach ($extensions as $ext) {
$cnt++;
$likes[] = $DB->sql_like($prefix.'filename', ':filename'.$cnt, false);
$params['filename'.$cnt] = '%'.$ext;
}
$sql .= ' AND ('.join(' OR ', $likes).')';
}
return array($sql, $params);
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* It is recommended to overwrite this function so it uses a proper SQL
* query and does not create unnecessary file_info objects (might require a lot of time
* and memory usage on big sites).
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
$list = $this->get_children();
$nonemptylist = array();
foreach ($list as $fileinfo) {
if ($fileinfo->is_directory()) {
if ($fileinfo->count_non_empty_children($extensions)) {
$nonemptylist[] = $fileinfo;
}
} else if ($extensions === '*') {
$nonemptylist[] = $fileinfo;
} else {
$filename = $fileinfo->get_visible_name();
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!empty($extension) && in_array('.'.$extension, $extensions)) {
$nonemptylist[] = $fileinfo;
}
}
}
return $nonemptylist;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* This function is used by repository_local to evaluate if the folder is empty. But
* it also can be used to check if folder has only one subfolder because in some cases
* this subfolder can be skipped.
*
* It is strongly recommended to overwrite this function so it uses a proper SQL
* query and does not create file_info objects (later might require a lot of time
* and memory usage on big sites).
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
$list = $this->get_children();
$cnt = 0;
foreach ($list as $fileinfo) {
if ($cnt > 1) {
// it only matters if it is 0, 1 or 2+
return $cnt;
}
if ($fileinfo->is_directory()) {
if ($fileinfo->count_non_empty_children($extensions)) {
$cnt++;
}
} else if ($extensions === '*') {
$cnt++;
} else {
$filename = $fileinfo->get_visible_name();
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!empty($extension) && in_array('.'.$extension, $extensions)) {
$cnt++;
}
}
}
return $cnt;
}
/**
* Returns parent file_info instance
*

View File

@ -350,48 +350,91 @@ class file_info_context_course extends file_info {
* @return array of file_info instances
*/
public function get_children() {
$children = array();
return $this->get_filtered_children('*', false, true);
}
if ($child = $this->get_area_course_summary(0, '/', '.')) {
$children[] = $child;
}
if ($child = $this->get_area_course_section(null, null, null)) {
$children[] = $child;
}
if ($child = $this->get_area_backup_section(null, null, null)) {
$children[] = $child;
}
if ($child = $this->get_area_backup_course(0, '/', '.')) {
$children[] = $child;
}
if ($child = $this->get_area_backup_automated(0, '/', '.')) {
$children[] = $child;
}
if ($child = $this->get_area_course_legacy(0, '/', '.')) {
$children[] = $child;
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @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', 'section'),
array('backup', 'section'),
array('backup', 'course'),
array('backup', 'automated'),
array('course', 'legacy')
);
$children = array();
foreach ($areas as $area) {
if ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
$children[] = $child;
if ($countonly && count($children)>1) {
return 2;
}
}
}
}
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) {
if (empty($cminfo->uservisible)) {
continue;
}
$modcontext = context_module::instance($cminfo->id, IGNORE_MISSING);
if ($child = $this->browser->get_file_info($modcontext)) {
$children[] = $child;
} 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)) {
$children[] = $child;
if ($countonly && count($children)>1) {
return 2;
}
}
}
}
}
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
*
@ -473,6 +516,37 @@ class file_info_area_course_legacy extends file_info_stored {
return $result;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
if (!$this->lf->is_directory()) {
return array();
}
$result = array();
$fs = get_file_storage();
$storedfiles = $fs->get_directory_files($this->context->id, 'course', 'legacy', 0,
$this->lf->get_filepath(), false, true, "filepath, filename");
foreach ($storedfiles as $file) {
$extension = strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
if ($file->is_directory() || $extensions === '*' || (!empty($extension) && in_array('.'.$extension, $extensions))) {
$fileinfo = new file_info_area_course_legacy($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename,
$this->itemidused, $this->readaccess, $this->writeaccess, false);
if (!$file->is_directory() || $fileinfo->count_non_empty_children($extensions)) {
$result[] = $fileinfo;
}
}
}
return $result;
}
}
/**
@ -578,6 +652,35 @@ class file_info_area_course_section extends file_info {
return $children;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
global $DB;
$params1 = array(
'courseid' => $this->course->id,
'contextid' => $this->context->id,
'component' => 'course',
'filearea' => 'section',
'emptyfilename' => '.');
$sql1 = "SELECT 1 from {files} f, {course_sections} cs
WHERE cs.course = :courseid
AND f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea
AND f.itemid = cs.id
AND f.filename <> :emptyfilename";
list($sql2, $params2) = $this->build_search_files_sql($extensions);
return $DB->record_exists_sql($sql1.' '.$sql2, array_merge($params1, $params2)) ? 2 : 0;
}
/**
* Returns parent file_info instance
*
@ -689,6 +792,35 @@ class file_info_area_backup_section extends file_info {
return $children;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
global $DB;
$params1 = array(
'courseid' => $this->course->id,
'contextid' => $this->context->id,
'component' => 'backup',
'filearea' => 'section',
'emptyfilename' => '.');
$sql1 = "SELECT 1 from {files} f, {course_sections} cs
WHERE cs.course = :courseid
AND f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea
AND f.itemid = cs.id
AND f.filename <> :emptyfilename";
list($sql2, $params2) = $this->build_search_files_sql($extensions);
return $DB->record_exists_sql($sql1.' '.$sql2, array_merge($params1, $params2)) ? 2 : 0;
}
/**
* Returns parent file_info instance
*

View File

@ -188,6 +188,50 @@ class file_info_context_coursecat extends file_info {
return $children;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
global $DB;
if (($child = $this->get_area_coursecat_description(0, '/', '.'))
&& $child->count_non_empty_children($extensions)) {
return 2;
}
$course_cats = $DB->get_records('course_categories', array('parent'=>$this->category->id), '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))
&& $child->count_non_empty_children($extensions)) {
return 2;
}
}
$courses = $DB->get_records('course', array('category'=>$this->category->id), 'sortorder', 'id,visible');
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))
&& $child->count_non_empty_children($extensions)) {
return 2;
}
}
return 0;
}
/**
* Returns parent file_info instance
*

View File

@ -41,6 +41,8 @@ class file_info_context_module extends file_info {
protected $modname;
/** @var array Available file areas */
protected $areas;
/** @var array caches the result of last call to get_non_empty_children() */
protected $nonemptychildren;
/**
* Constructor
@ -58,6 +60,7 @@ class file_info_context_module extends file_info {
$this->course = $course;
$this->cm = $cm;
$this->modname = $modname;
$this->nonemptychildren = null;
include_once("$CFG->dirroot/mod/$modname/lib.php");
@ -258,24 +261,93 @@ class file_info_context_module extends file_info {
* @return array of file_info instances
*/
public function get_children() {
$children = array();
if ($child = $this->get_area_backup(0, '/', '.')) {
$children[] = $child;
}
if ($child = $this->get_area_intro(0, '/', '.')) {
$children[] = $child;
}
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
// prepare list of areas including intro and backup
$areas = array(
array('mod_'.$this->modname, 'intro'),
array('backup', 'activity')
);
foreach ($this->areas as $area=>$desctiption) {
if ($child = $this->get_file_info('mod_'.$this->modname, $area, null, null, null)) {
$children[] = $child;
$areas[] = array('mod_'.$this->modname, $area);
}
$params1 = array('contextid' => $this->context->id, 'emptyfilename' => '.');
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$children = array();
foreach ($areas as $area) {
if (!$returnemptyfolders) {
// fast pre-check if there are any files in the filearea
$params1['component'] = $area[0];
$params1['filearea'] = $area[1];
if (!$DB->record_exists_sql('SELECT 1 from {files}
WHERE contextid = :contextid
AND filename <> :emptyfilename
AND component = :component
AND filearea = :filearea '.$sql2,
array_merge($params1, $params2))) {
continue;
}
}
if ($child = $this->get_file_info($area[0], $area[1], null, null, null)) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
$children[] = $child;
if ($countonly && count($children)>1) {
break;
}
}
}
}
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
global $DB;
if ($this->nonemptychildren !== null) {
return $this->nonemptychildren;
}
$this->nonemptychildren = $this->get_filtered_children($extensions);
return $this->nonemptychildren;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
global $DB;
if ($this->nonemptychildren !== null) {
return count($this->nonemptychildren);
}
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
*

View File

@ -350,6 +350,70 @@ class file_info_stored extends file_info {
return $result;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
$result = array();
if (!$this->lf->is_directory()) {
return $result;
}
$fs = get_file_storage();
$storedfiles = $fs->get_directory_files($this->context->id, $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(),
$this->lf->get_filepath(), false, true, "filepath, filename");
foreach ($storedfiles as $file) {
$extension = strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
if ($file->is_directory() || $extensions === '*' || (!empty($extension) && in_array('.'.$extension, $extensions))) {
$fileinfo = new file_info_stored($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename,
$this->itemidused, $this->readaccess, $this->writeaccess, false);
if (!$file->is_directory() || $fileinfo->count_non_empty_children($extensions)) {
$result[] = $fileinfo;
}
}
}
return $result;
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
global $DB;
if (!$this->lf->is_directory()) {
return 0;
}
$filepath = $this->lf->get_filepath();
$length = textlib::strlen($filepath);
$sql = "SELECT 1
FROM {files} f
WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea AND f.itemid = :itemid
AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
AND filename <> '.' ";
$params = array('contextid' => $this->context->id,
'component' => $this->lf->get_component(),
'filearea' => $this->lf->get_filearea(),
'itemid' => $this->lf->get_itemid(),
'filepath' => $filepath);
list($sql2, $params2) = $this->build_search_files_sql($extensions);
// we don't need to check access to individual files here, since the user can access parent
return $DB->record_exists_sql($sql.' '.$sql2, array_merge($params, $params2)) ? 2 : 0;
}
/**
* Returns parent file_info instance
*

View File

@ -448,18 +448,85 @@ class book_file_info extends file_info {
* @return array of file_info instances
*/
public function get_children() {
global $DB;
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$params = array('contextid' => $this->context->id,
'component' => 'mod_book',
'filearea' => $this->filearea,
'bookid' => $this->cm->instance);
$sql = 'SELECT DISTINCT bc.id, bc.pagenum
FROM {files} f, {book_chapters} bc
WHERE f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea
AND bc.bookid = :bookid
AND bc.id = f.itemid';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY bc.pagenum';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
$chapters = $DB->get_records('book_chapters', array('bookid'=>$this->cm->instance), 'pagenum', 'id, pagenum');
foreach ($chapters as $itemid => $unused) {
if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $itemid)) {
$children[] = $child;
foreach ($rs as $record) {
if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $record->id)) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
$children[] = $child;
}
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
* @return file_info or null for root

View File

@ -492,20 +492,80 @@ class data_file_info_container extends file_info {
* @return array of file_info instances
*/
public function get_children() {
global $DB;
return $this->get_filtered_children('*', false, true);
}
$children = array();
$itemids = $DB->get_records('files', array('contextid' => $this->context->id, 'component' => $this->component,
'filearea' => $this->filearea), 'itemid DESC', "DISTINCT itemid");
foreach ($itemids as $itemid => $unused) {
if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $itemid)) {
$children[] = $child;
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$params = array('contextid' => $this->context->id,
'component' => $this->component,
'filearea' => $this->filearea);
$sql = 'SELECT DISTINCT itemid
FROM {files}
WHERE contextid = :contextid
AND component = :component
AND filearea = :filearea';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY itemid DESC';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
foreach ($rs as $record) {
if ($child = $this->browser->get_file_info($this->context, 'mod_data', $this->filearea, $record->itemid)) {
$children[] = $child;
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
*

View File

@ -484,20 +484,80 @@ class forum_file_info_container extends file_info {
* @return array of file_info instances
*/
public function get_children() {
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$children = array();
$itemids = $DB->get_records('files', array('contextid' => $this->context->id, 'component' => $this->component,
'filearea' => $this->filearea), 'itemid DESC', "DISTINCT itemid");
foreach ($itemids as $itemid => $unused) {
if ($child = $this->browser->get_file_info($this->context, 'mod_forum', $this->filearea, $itemid)) {
$children[] = $child;
}
$params = array('contextid' => $this->context->id,
'component' => $this->component,
'filearea' => $this->filearea);
$sql = 'SELECT DISTINCT itemid
FROM {files}
WHERE contextid = :contextid
AND component = :component
AND filearea = :filearea';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY itemid DESC';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
foreach ($rs as $record) {
if (($child = $this->browser->get_file_info($this->context, 'mod_forum', $this->filearea, $record->itemid))
&& ($returnemptyfolders || $child->count_non_empty_children($extensions))) {
$children[] = $child;
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
*

View File

@ -546,30 +546,87 @@ class glossary_file_info_container extends file_info {
* @return array of file_info instances
*/
public function get_children() {
global $DB;
return $this->get_filtered_children('*', false, true);
}
$sql = "SELECT DISTINCT f.itemid, ge.concept
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$sql = 'SELECT DISTINCT f.itemid, ge.concept
FROM {files} f
JOIN {modules} m ON (m.name = 'glossary' AND m.visible = 1)
JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = ?)
JOIN {modules} m ON (m.name = :modulename AND m.visible = 1)
JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = :instanceid)
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON (ge.glossaryid = g.id AND ge.id = f.itemid)
WHERE f.contextid = ? AND f.component = ? AND f.filearea = ?
ORDER BY ge.concept, f.itemid";
$params = array($this->context->instanceid, $this->context->id, $this->component, $this->filearea);
WHERE f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea';
$params = array(
'modulename' => 'glossary',
'instanceid' => $this->context->instanceid,
'contextid' => $this->context->id,
'component' => $this->component,
'filearea' => $this->filearea);
if (!$returnemptyfolders) {
$sql .= ' AND f.filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY ge.concept, f.itemid';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
foreach ($rs as $file) {
if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $file->itemid)) {
foreach ($rs as $record) {
if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $record->itemid)) {
$children[] = $child;
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
*

View File

@ -273,18 +273,80 @@ class imscp_file_info extends file_info {
* @return array of file_info instances
*/
public function get_children() {
global $DB;
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$params = array('contextid' => $this->context->id,
'component' => 'mod_imscp',
'filearea' => $this->filearea);
$sql = 'SELECT DISTINCT itemid
FROM {files}
WHERE contextid = :contextid
AND component = :component
AND filearea = :filearea';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY itemid';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
$itemids = $DB->get_records('files', array('contextid'=>$this->context->id, 'component'=>'mod_imscp', 'filearea'=>$this->filearea), 'itemid', "DISTINCT itemid");
foreach ($itemids as $itemid=>$unused) {
if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $itemid)) {
foreach ($rs as $record) {
if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $record->itemid)) {
$children[] = $child;
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
* @return file_info or null for root

View File

@ -87,24 +87,87 @@ class workshop_file_info_submissions_container extends file_info {
return true;
}
/**
* Returns list of children.
* Returns list of children nodes
*
* @return array of file_info instances
*/
public function get_children() {
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool $countonly if true returns the count of children (0, 1 or 2 if more than 1)
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$params = array('contextid' => $this->context->id,
'component' => 'mod_workshop',
'filearea' => $this->filearea);
$sql = 'SELECT DISTINCT itemid
FROM {files}
WHERE contextid = :contextid
AND component = :component
AND filearea = :filearea';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions);
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if (!$countonly) {
$sql .= ' ORDER BY itemid DESC';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
$itemids = $DB->get_records('files', array('contextid' => $this->context->id, 'component' => 'mod_workshop', 'filearea' => $this->filearea),
'itemid', "DISTINCT itemid");
foreach ($itemids as $itemid => $unused) {
if ($child = $this->browser->get_file_info($this->context, 'mod_workshop', $this->filearea, $itemid)) {
foreach ($rs as $record) {
if (($child = $this->browser->get_file_info($this->context, 'mod_workshop', $this->filearea, $record->itemid))
&& ($returnemptyfolders || $child->count_non_empty_children($extensions))) {
$children[] = $child;
}
if ($countonly && count($children)>1) {
break;
}
}
$rs->close();
if ($countonly) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* NOTE: We don't need the exact number of non empty children if it is >=2
* In this function 1 is never returned to avoid skipping the single subfolder
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return int
*/
public function count_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, true);
}
/**
* Returns parent file_info instance
* @return file_info or null for root

View File

@ -29,22 +29,16 @@ require_once($CFG->dirroot . '/repository/lib.php');
*
* @since 2.0
* @package repository_local
* @copyright 2012 Marina Glancy
* @copyright 2009 Dongsheng Cai {@link http://dongsheng.org}
* @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
*/
public function print_login() {
return $this->get_listing();
}
/**
* Get file listing
*
* @param string $encodedpath
* @param string $page no paging is used in repository_local
* @return mixed
*/
public function get_listing($encodedpath = '', $page = '') {
@ -53,11 +47,17 @@ class repository_local extends repository {
$ret['dynload'] = true;
$ret['nosearch'] = true;
$ret['nologin'] = true;
$list = array();
$ret['list'] = array();
$itemid = null;
$filename = null;
$filearea = null;
$filepath = null;
$component = null;
if (!empty($encodedpath)) {
$params = unserialize(base64_decode($encodedpath));
if (is_array($params)) {
if (is_array($params) && isset($params['contextid'])) {
$component = is_null($params['component']) ? NULL : clean_param($params['component'], PARAM_COMPONENT);
$filearea = is_null($params['filearea']) ? NULL : clean_param($params['filearea'], PARAM_AREA);
$itemid = is_null($params['itemid']) ? NULL : clean_param($params['itemid'], PARAM_INT);
@ -65,52 +65,54 @@ class repository_local extends repository {
$filename = is_null($params['filename']) ? NULL : clean_param($params['filename'], PARAM_FILE);
$context = context::instance_by_id(clean_param($params['contextid'], PARAM_INT));
}
} else {
$itemid = null;
$filename = null;
$filearea = null;
$filepath = null;
$component = null;
if (!empty($this->context)) {
list($context, $course, $cm) = get_context_info_array($this->context->id);
if (is_object($course)) {
$context = context_course::instance($course->id);
} else {
$context = get_system_context();
}
} else {
$context = get_system_context();
}
if (empty($context) && !empty($this->context)) {
list($repositorycontext, $course, $cm) = get_context_info_array($this->context->id);
if (isset($course->id)) {
$context = context_course::instance($course->id);
}
}
if (empty($context)) {
$context = context_system::instance();
}
$browser = get_file_browser();
$list = array();
if ($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename)) {
// build file tree
$element = repository_local_file::retrieve_file_info($fileinfo, $this);
$nonemptychildren = $element->get_non_empty_children();
foreach ($nonemptychildren as $child) {
$list[] = (array)$child->get_node();
}
// prepare list of allowed extensions: $extensions is either string '*'
// or array of lowercase extensions, i.e. array('.gif','.jpg')
$extensions = optional_param_array('accepted_types', '', PARAM_RAW);
if (empty($extensions) || $extensions === '*' || (is_array($extensions) && in_array('*', $extensions))) {
$extensions = '*';
} else {
if (!is_array($extensions)) {
$extensions = array($extensions);
}
$extensions = array_map('strtolower', $extensions);
}
// build file tree
$browser = get_file_browser();
if (!($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename))) {
// if file doesn't exist, build path nodes root of current context
$fileinfo = $browser->get_file_info($context, null, null, null, null, null);
}
$ret['list'] = $this->get_non_empty_children($fileinfo, $extensions);
// build path navigation
$path = array();
for ($level = $fileinfo; $level; $level = $level->get_parent()) {
array_unshift($path, $level);
}
array_unshift($path, null);
$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());
for ($i=1; $i<count($path); $i++) {
if ($path[$i] == $fileinfo || !$this->can_skip($path[$i], $extensions, $path[$i-1])) {
$ret['path'][] = $this->get_node_path($path[$i]);
}
}
$ret['list'] = array_filter($list, array($this, 'filter'));
return $ret;
}
/**
* Local file don't support to link to external links
* Tells how the file can be picked from this repository
*
* @return int
*/
@ -137,111 +139,112 @@ class repository_local extends repository {
// this should be realtime
return 0;
}
}
/**
* 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
* Returns all children elements that have one of the specified extensions
*
* This function may skip subfolers and recursively add their children
* {@link repository_local::can_skip()}
*
* @param file_info $fileinfo
* @param repository $repository
* @param repository_local_file $parent
* @return repository_local_file
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @return array array of file_info elements
*/
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);
private function get_non_empty_children(file_info $fileinfo, $extensions) {
$nonemptychildren = $fileinfo->get_non_empty_children($extensions);
$list = array();
foreach ($nonemptychildren as $child) {
if ($this->can_skip($child, $extensions, $fileinfo)) {
$list = array_merge($list, $this->get_non_empty_children($child, $extensions));
} else {
$list[] = $this->get_node($child);
}
}
return self::$cachedfiles[$encodedpath];
return $list;
}
/**
* Creates an object
* Wether this folder may be skipped in folder hierarchy
*
* 1. Skip the name of a single filearea in a module
* 2. Skip course categories for non-admins who do not have navshowmycoursecategories setting
*
* @param file_info $fileinfo
* @param repository $repository
* @param repository_local_file $parent
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @param file_info|int $parent specify parent here if we know it to avoid creating extra objects
* @return bool
*/
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;
private function can_skip(file_info $fileinfo, $extensions, $parent = -1) {
global $CFG;
static $skipcategories = null;
if (!$fileinfo->is_directory()) {
// do not skip files
return false;
}
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']) &&
($params['filepath'] === '/' || empty($params['filepath'])) &&
($params['filename'] === '.' || empty($params['filename']))) {
if ($parent === -1) {
$parent = $fileinfo->get_parent();
}
// This is a filearea inside an activity, it can be skipped if it has no non-empty siblings
if ($parent && ($parent instanceof file_info_context_module)) {
if ($parent->count_non_empty_children($extensions) <= 1) {
return true;
}
}
}
}
return false;
}
/**
* Returns node for $ret['list']
* Converts file_info object to element of repository return list
*
* @param file_info $fileinfo
* @return array
*/
public function get_node() {
private function get_node(file_info $fileinfo) {
global $OUTPUT;
$encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
$node = array(
'title' => $this->fileinfo->get_visible_name(),
'datemodified' => $this->fileinfo->get_timemodified(),
'datecreated' => $this->fileinfo->get_timecreated()
'title' => $fileinfo->get_visible_name(),
'datemodified' => $fileinfo->get_timemodified(),
'datecreated' => $fileinfo->get_timecreated()
);
if ($this->isdir) {
if ($fileinfo->is_directory()) {
$node['path'] = $encodedpath;
$node['thumbnail'] = $OUTPUT->pix_url(file_folder_icon(90))->out(false);
$node['children'] = array();
} else {
$node['size'] = $this->fileinfo->get_filesize();
$node['author'] = $this->fileinfo->get_author();
$node['license'] = $this->fileinfo->get_license();
$node['isref'] = $this->fileinfo->is_external_file();
if ($this->fileinfo->get_status() == 666) {
$node['size'] = $fileinfo->get_filesize();
$node['author'] = $fileinfo->get_author();
$node['license'] = $fileinfo->get_license();
$node['isref'] = $fileinfo->is_external_file();
if ($fileinfo->get_status() == 666) {
$node['originalmissing'] = true;
}
$node['source'] = $encodedpath;
$node['thumbnail'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 90))->out(false);
$node['icon'] = $OUTPUT->pix_url(file_file_icon($this->fileinfo, 24))->out(false);
if ($imageinfo = $this->fileinfo->get_imageinfo()) {
$node['thumbnail'] = $OUTPUT->pix_url(file_file_icon($fileinfo, 90))->out(false);
$node['icon'] = $OUTPUT->pix_url(file_file_icon($fileinfo, 24))->out(false);
if ($imageinfo = $fileinfo->get_imageinfo()) {
// what a beautiful picture, isn't it
$fileurl = new moodle_url($this->fileinfo->get_url());
$node['realthumbnail'] = $fileurl->out(false, array('preview' => 'thumb', 'oid' => $this->fileinfo->get_timemodified()));
$node['realicon'] = $fileurl->out(false, array('preview' => 'tinyicon', 'oid' => $this->fileinfo->get_timemodified()));
$fileurl = new moodle_url($fileinfo->get_url());
$node['realthumbnail'] = $fileurl->out(false, array('preview' => 'thumb', 'oid' => $fileinfo->get_timemodified()));
$node['realicon'] = $fileurl->out(false, array('preview' => 'tinyicon', 'oid' => $fileinfo->get_timemodified()));
$node['image_width'] = $imageinfo['width'];
$node['image_height'] = $imageinfo['height'];
}
@ -250,155 +253,16 @@ class repository_local_file {
}
/**
* Returns node for $ret['path']
* Converts file_info object to element of repository return path
*
* @param file_info $fileinfo
* @return array
*/
public function get_node_path() {
$encodedpath = base64_encode(serialize($this->fileinfo->get_params()));
private function get_node_path(file_info $fileinfo) {
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
return array(
'path' => $encodedpath,
'name' => $this->fileinfo->get_visible_name()
'name' => $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', context_system::instance());
}
}
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;
}
}