mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
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:
parent
ccd90e765e
commit
b8de262139
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user