diff --git a/lib/filebrowser/file_browser.php b/lib/filebrowser/file_browser.php index 4b86c66f4e6..8f99d9dfb39 100644 --- a/lib/filebrowser/file_browser.php +++ b/lib/filebrowser/file_browser.php @@ -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; } diff --git a/lib/filebrowser/file_info.php b/lib/filebrowser/file_info.php index d5bcadecc7b..9ad03640947 100644 --- a/lib/filebrowser/file_info.php +++ b/lib/filebrowser/file_info.php @@ -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 * diff --git a/lib/filebrowser/file_info_context_course.php b/lib/filebrowser/file_info_context_course.php index 0f0816b15e9..a82adec8ca4 100644 --- a/lib/filebrowser/file_info_context_course.php +++ b/lib/filebrowser/file_info_context_course.php @@ -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 * diff --git a/lib/filebrowser/file_info_context_coursecat.php b/lib/filebrowser/file_info_context_coursecat.php index 9cab4502f25..15bf718925e 100644 --- a/lib/filebrowser/file_info_context_coursecat.php +++ b/lib/filebrowser/file_info_context_coursecat.php @@ -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 * diff --git a/lib/filebrowser/file_info_context_module.php b/lib/filebrowser/file_info_context_module.php index 7e8ac7722ac..7010fadfc81 100644 --- a/lib/filebrowser/file_info_context_module.php +++ b/lib/filebrowser/file_info_context_module.php @@ -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 * diff --git a/lib/filebrowser/file_info_stored.php b/lib/filebrowser/file_info_stored.php index 44fa566517b..1c4d2d2a86c 100644 --- a/lib/filebrowser/file_info_stored.php +++ b/lib/filebrowser/file_info_stored.php @@ -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 * diff --git a/mod/book/locallib.php b/mod/book/locallib.php index 26d0e4069a8..eba2e52e9ea 100644 --- a/mod/book/locallib.php +++ b/mod/book/locallib.php @@ -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 diff --git a/mod/data/locallib.php b/mod/data/locallib.php index 0662614d35a..280a2375873 100644 --- a/mod/data/locallib.php +++ b/mod/data/locallib.php @@ -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 * diff --git a/mod/forum/locallib.php b/mod/forum/locallib.php index 263af852c3a..f9d6616231e 100644 --- a/mod/forum/locallib.php +++ b/mod/forum/locallib.php @@ -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 * diff --git a/mod/glossary/locallib.php b/mod/glossary/locallib.php index f9374b735af..fdd211fa4a7 100644 --- a/mod/glossary/locallib.php +++ b/mod/glossary/locallib.php @@ -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 * diff --git a/mod/imscp/locallib.php b/mod/imscp/locallib.php index 34e760ec5a8..169522eaab3 100644 --- a/mod/imscp/locallib.php +++ b/mod/imscp/locallib.php @@ -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 diff --git a/mod/workshop/fileinfolib.php b/mod/workshop/fileinfolib.php index 5a8b4a2cb7c..eb1a3bfdbc1 100644 --- a/mod/workshop/fileinfolib.php +++ b/mod/workshop/fileinfolib.php @@ -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 diff --git a/repository/local/lib.php b/repository/local/lib.php index 37d214a57a8..9e5b743f7ec 100644 --- a/repository/local/lib.php +++ b/repository/local/lib.php @@ -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; $ican_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; $ichildren[] = 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; - } }