ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç file_info_context_user.php000064400000023066152265472420012031 0ustar00. /** * Utility class for browsing of user files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents a user context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_context_user extends file_info { /** @var stdClass User object */ protected $user; /** * Constructor * * @param file_browser $browser * @param stdClass $context * @param stdClass $user */ public function __construct($browser, $context, $user) { parent::__construct($browser, $context); $this->user = $user; } /** * Return information about this specific context level * * @param string $component componet * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ public function get_file_info($component, $filearea, $itemid, $filepath, $filename) { global $USER; if (!isloggedin() or isguestuser()) { return null; } if (empty($component)) { // access control: list areas only for myself if ($this->user->id != $USER->id) { // no list of areas for other users return null; } return $this; } $methodname = "get_area_{$component}_{$filearea}"; if (method_exists($this, $methodname)) { return $this->$methodname($itemid, $filepath, $filename); } return null; } /** * Get a file from user private area * * @todo MDL-31070 this method should respect $CFG->userquota * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_user_private($itemid, $filepath, $filename) { global $USER, $CFG; // access control: only my files, nobody else if ($this->user->id != $USER->id) { return null; } if (is_null($itemid)) { // go to parent, we do not use itemids here in private area return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'user', 'private', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { // root dir does not exist yet $storedfile = new virtual_root_file($this->context->id, 'user', 'private', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; //TODO: user quota from $CFG->userquota return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areauserpersonal', 'repository'), false, true, true, false); } /** * Get a file from user profile area * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_user_profile($itemid, $filepath, $filename) { global $CFG; $readaccess = has_capability('moodle/user:update', $this->context); $writeaccess = has_capability('moodle/user:viewalldetails', $this->context); if (!$readaccess and !$writeaccess) { // the idea here is that only admins should be able to list/modify files in user profile, the rest has to use profile page return null; } if (is_null($itemid)) { // go to parent, we do not use itemids here in profile area return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'user', 'profile', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'user', 'profile', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areauserprofile', 'repository'), false, $readaccess, $writeaccess, false); } /** * Get a file from user draft area * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_user_draft($itemid, $filepath, $filename) { global $USER, $CFG; // access control: only my files if ($this->user->id != $USER->id) { return null; } if (empty($itemid)) { // do not browse itemids - you must know the draftid to see what is there return null; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'user', 'draft', $itemid, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'user', 'draft', $itemid); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/draftfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areauserdraft', 'repository'), true, true, true, true); } /** * Get a file from user backup area * * @todo MDL-31091 maybe we need new caability for access control * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_user_backup($itemid, $filepath, $filename) { global $USER, $CFG; // access control: only my files, nobody else - TODO: maybe we need new capability here if ($this->context->instanceid != $USER->id) { return null; } if (is_null($itemid)) { // go to parent, we do not use itemids here in profile area return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'user', 'backup', $itemid, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'user', 'backup', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areauserbackup', 'repository'), false, true, true, false); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return fullname($this->user, true); } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { $children = array(); if ($child = $this->get_area_user_private(0, '/', '.')) { $children[] = $child; } /* if ($child = $this->get_area_user_profile(0, '/', '.')) { $children[] = $child; } */ if ($child = $this->get_area_user_backup(0, '/', '.')) { $children[] = $child; } // do not list draft area here - it is browsable only if you know the draft itemid ;-) return $children; } /** * Returns parent file_info instance * * @return file_info|null file_info instance or null for root */ public function get_parent() { return $this->browser->get_file_info(); } } virtual_root_file.php000064400000017024152265472420011022 0ustar00. /** * Class simulating empty directories. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents the root directory of an empty file area in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class virtual_root_file { /** @var int context id */ protected $contextid; /** @var string file component */ protected $component; /** @var string file area */ protected $filearea; /** @var int file itemid */ protected $itemid; /** * Constructor * * @param int $contextid context ID * @param string $component component * @param string $filearea file area * @param int $itemid item ID */ public function __construct($contextid, $component, $filearea, $itemid) { $this->contextid = $contextid; $this->component = $component; $this->filearea = $filearea; $this->itemid = $itemid; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Delete file * * @return success */ public function delete() { return true; } /** * adds this file path to a curl request (POST only) * * @param curl $curlrequest the curl request object * @param string $key what key to use in the POST request */ public function add_to_curl_request(&$curlrequest, $key) { return; } /** * Returns file handle - read only mode, no writing allowed into pool files! * * @return resource file handle */ public function get_content_file_handle() { return null; } /** * Dumps file content to page * * @return resource file handle */ public function readfile() { return; } /** * Returns file content as string * * @return string content */ public function get_content() { return ''; } /** * Copy content of file to given pathname * * @param string $pathname real path to new file * @return bool success */ public function copy_content_to($pathname) { return false; } /** * List contents of archive * * @param file_packer $packer file packer instance * @return array of file infos */ public function list_files(file_packer $packer) { return null; } /** * Extract file to given file path (real OS filesystem), existing files are overwrited * * @param file_packer $packer file packer instance * @param string $pathname target directory * @return mixed list of processed files; false if error */ public function extract_to_pathname(file_packer $packer, $pathname) { return false; } /** * Extract file to given file path (real OS filesystem), existing files are overwrited * * @param file_packer $packer file packer instance * @param int $contextid context ID * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $pathbase path base * @param int $userid user ID * @return mixed list of processed files; false if error */ public function extract_to_storage(file_packer $packer, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) { return false; } /** * Add file/directory into archive * * @param file_archive $filearch file archive instance * @param string $archivepath pathname in archive * @return bool success */ public function archive_file(file_archive $filearch, $archivepath) { return false; } /** * Returns parent directory * * @return stored_file */ public function get_parent_directory() { return null; } /** * Returns context ID * * @return int context ID */ public function get_contextid() { return $this->contextid; } /** * Returns file component * * @return string component */ public function get_component() { return $this->component; } /** * Returns file area * * @return string filearea */ public function get_filearea() { return $this->filearea; } /** * Returns file itemid * * @return int itemid */ public function get_itemid() { return $this->itemid; } /** * Returns file path * * @return string filepath */ public function get_filepath() { return '/'; } /** * Returns file name * * @return string filename */ public function get_filename() { return '.'; } /** * Returns user ID * * @return int userid */ public function get_userid() { return null; } /** * Returns file size * * @return int filesize */ public function get_filesize() { return 0; } /** * Returns mimetype * * @return string mimetype */ public function get_mimetype() { return null; } /** * Returns time created * * @return int */ public function get_timecreated() { return 0; } /** * Returns time modified * * @return int */ public function get_timemodified() { return 0; } /** * Returns status * * @return int */ public function get_status() { return 0; } /** * Returns ID * * @return int */ public function get_id() { return 0; } /** * Returns sha1 hash code * * @return string */ public function get_contenthash() { return sha1(''); } /** * Returns path name hash * * @return string */ public function get_pathnamehash() { return sha1('/'.$this->get_contextid().'/'.$this->get_component().'/'.$this->get_filearea().'/'.$this->get_itemid().$this->get_filepath().$this->get_filename()); } /** * Returns license * * @return string */ public function get_license() { return null; } /** * Returns file's author * * @return string */ public function get_author() { return null; } /** * Returns file source * * @return string */ public function get_source() { return null; } /** * Returns file sort order * * @return int */ public function get_sortorder() { return null; } } file_info.php000064400000033046152265472420007226 0ustar00. /** * Base for all file browsing classes. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Base class for things in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class file_info { /** @var context File context */ protected $context; /** @var file_browser File browser instance */ protected $browser; /** * Constructor * * @param file_browser $browser file_browser instance * @param stdClass $context */ public function __construct($browser, $context) { $this->browser = $browser; $this->context = $context; } /** * Returns list of standard virtual file/directory identification. * The difference from stored_file parameters is that null values * are allowed in all fields * * @return array with keys contextid, component, filearea, itemid, filepath and filename */ public function get_params() { return array('contextid' => $this->context->id, 'component' => null, 'filearea' => null, 'itemid' => null, 'filepath' => null, 'filename' => null); } /** * Returns localised visible name. * * @return string */ abstract public function get_visible_name(); /** * Whether or not this is a directory * * @return bool */ abstract public function is_directory(); /** * Returns list of children. * * @return array of file_info instances */ abstract public 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 ($prefix && 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 = core_text::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. * * We usually don't need the exact number of non empty children if it is >=2 (see param $limit) * 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') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { $list = $this->get_children(); $cnt = 0; // first loop through files foreach ($list as $fileinfo) { if (!$fileinfo->is_directory()) { if ($extensions !== '*') { $filename = $fileinfo->get_visible_name(); $extension = core_text::strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (empty($extension) || !in_array('.' . $extension, $extensions)) { continue; } } if ((++$cnt) >= $limit) { return $cnt; } } } // now loop through directories foreach ($list as $fileinfo) { if ($fileinfo->is_directory() && $fileinfo->count_non_empty_children($extensions)) { if ((++$cnt) >= $limit) { return $cnt; } } } return $cnt; } /** * Returns parent file_info instance * * @return file_info or null for root */ abstract public function get_parent(); /** * Returns array of url encoded params. * * @return array with numeric keys */ public function get_params_rawencoded() { $params = $this->get_params(); $encoded = array(); $encoded[] = 'contextid=' . $params['contextid']; $encoded[] = 'component=' . $params['component']; $encoded[] = 'filearea=' . $params['filearea']; $encoded[] = 'itemid=' . (is_null($params['itemid']) ? -1 : $params['itemid']); $encoded[] = 'filepath=' . (is_null($params['filepath']) ? '' : rawurlencode($params['filepath'])); $encoded[] = 'filename=' . ((is_null($params['filename']) or $params['filename'] === '.') ? '' : rawurlencode($params['filename'])); return $encoded; } /** * Returns file download url * * @param bool $forcedownload whether or not force download * @param bool $https whether or not force https * @return string url */ public function get_url($forcedownload=false, $https=false) { return null; } /** * Whether or not I can read content of this file or enter directory * * @return bool */ public function is_readable() { return true; } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return true; } /** * Is this info area and is it "empty"? Are there any files in subfolders? * * This is used mostly in repositories to reduce the * number of empty folders. This method may be very slow, * use with care. * * @return bool */ public function is_empty_area() { return false; } /** * Returns file size in bytes, null for directories * * @return int bytes or null if not known */ public function get_filesize() { return null; } /** * Returns mimetype * * @return string mimetype or null if not known */ public function get_mimetype() { return null; } /** * Returns time created unix timestamp if known * * @return int timestamp or null */ public function get_timecreated() { return null; } /** * Returns time modified unix timestamp if known * * @return int timestamp or null */ public function get_timemodified() { return null; } /** * Returns the license type of the file * @return string license short name or null */ public function get_license() { return null; } /** * Returns the author name of the file * * @return string author name or null */ public function get_author() { return null; } /** * Returns the source of the file * * @return string a source url or null */ public function get_source() { return null; } /** * Returns the sort order of the file * * @return int */ public function get_sortorder() { return 0; } /** * Whether or not this is a external resource * * @return bool */ public function is_external_file() { return false; } /** * Returns file status flag. * * @return int 0 means file OK, anything else is a problem and file can not be used */ public function get_status() { return 0; } /** * Returns the localised human-readable name of the file together with virtual path * * @see file_info_stored::get_readable_fullname() * @return string */ public function get_readable_fullname() { return null; } /** * Create new directory, may throw exception - make sure * params are valid. * * @param string $newdirname name of new directory * @param int $userid id of author, default $USER->id * @return file_info new directory */ public function create_directory($newdirname, $userid = NULL) { return null; } /** * Create new file from string - make sure * params are valid. * * @param string $newfilename name of new file * @param string $content of file * @param int $userid id of author, default $USER->id * @return file_info new file */ public function create_file_from_string($newfilename, $content, $userid = NULL) { return null; } /** * Create new file from pathname - make sure * params are valid. * * @param string $newfilename name of new file * @param string $pathname location of file * @param int $userid id of author, default $USER->id * @return file_info new file */ public function create_file_from_pathname($newfilename, $pathname, $userid = NULL) { return null; } /** * Create new file from stored file - make sure * params are valid. * * @param string $newfilename name of new file * @param int|stored_file $fid id or stored_file of file * @param int $userid id of author, default $USER->id * @return file_info new file */ public function create_file_from_storedfile($newfilename, $fid, $userid = NULL) { return null; } /** * Delete file, make sure file is deletable first. * * @return bool success */ public function delete() { return false; } /** * Copy content of this file to local storage, overriding current file if needed. * * @param array|stdClass $filerecord contains contextid, component, filearea, * itemid, filepath, filename and optionally other attributes of the new file * @return bool success */ public function copy_to_storage($filerecord) { return false; } /** * Copy content of this file to local storage, overriding current file if needed. * * @todo MDL-31068 implement move() rename() unzip() zip() * @param string $pathname real local full file name * @return boolean success */ public function copy_to_pathname($pathname) { return false; } //TODO: following methods are not implemented yet ;-) //public abstract function move(location params); //public abstract function rename(new name); //public abstract function unzip(location params); //public abstract function zip(zip file, file info); } file_info_context_coursecat.php000064400000026371152265472420013045 0ustar00. /** * Utility class for browsing of curse category files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents a course category context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_context_coursecat extends file_info { /** @var stdClass Category object */ protected $category; /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stdClass $category category object */ public function __construct($browser, $context, $category) { parent::__construct($browser, $context); $this->category = $category; } /** * Return information about this specific context level * * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return fileinfo|null */ public function get_file_info($component, $filearea, $itemid, $filepath, $filename) { global $DB; if (!core_course_category::can_view_category($this->category)) { if (empty($component)) { // we can not list the category contents, so try parent, or top system if ($this->category->parent and $pc = $DB->get_record('course_categories', array('id'=>$this->category->parent))) { $parent = context_coursecat::instance($pc->id, IGNORE_MISSING); return $this->browser->get_file_info($parent); } else { return $this->browser->get_file_info(); } } return null; } if (empty($component)) { return $this; } $methodname = "get_area_{$component}_{$filearea}"; if (method_exists($this, $methodname)) { return $this->$methodname($itemid, $filepath, $filename); } return null; } /** * Return a file from course category description area * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return fileinfo|null */ protected function get_area_coursecat_description($itemid, $filepath, $filename) { global $CFG; if (!$this->category->id) { // No coursecat description area for "system". return null; } if (!core_course_category::can_view_category($this->category)) { return null; } if (!has_capability('moodle/category:manage', $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; $urlbase = $CFG->wwwroot.'/pluginfile.php'; if (!$storedfile = $fs->get_file($this->context->id, 'coursecat', 'description', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'coursecat', 'description', 0); } else { // not found return null; } } return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areacategoryintro', 'repository'), false, true, true, false); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return format_string($this->category->name, true, array('context'=>$this->context)); } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { $children = array(); if ($child = $this->get_area_coursecat_description(0, '/', '.')) { $children[] = $child; } list($coursecats, $hiddencats) = $this->get_categories(); foreach ($coursecats as $category) { $context = context_coursecat::instance($category->id); $children[] = new self($this->browser, $context, $category); } $courses = $this->get_courses($hiddencats); foreach ($courses as $course) { $children[] = $this->get_child_course($course); } return array_filter($children); } /** * List of courses in this category and in hidden subcategories * * @param array $hiddencats list of categories that are hidden from current user and returned by {@link get_categories()} * @return array list of courses */ protected function get_courses($hiddencats) { global $DB, $CFG; require_once($CFG->libdir.'/modinfolib.php'); // Let's retrieve only minimum number of fields from course table - // what is needed to check access or call get_fast_modinfo(). $coursefields = array_merge(['id', 'visible', 'sortorder'], \course_modinfo::$cachedfields); $fields = 'c.' . join(',c.', $coursefields) . ', ' . \context_helper::get_preload_record_columns_sql('ctx'); // First statement uses only category. $sql1 = "SELECT $fields FROM {course} c JOIN {context} ctx ON (ctx.instanceid = c.id) AND (ctx.contextlevel = :contextlevel1) WHERE c.category = :categoryid"; $params = ['categoryid' => $this->category->id, 'contextlevel1' => CONTEXT_COURSE]; if (empty($hiddencats)) { return $DB->get_records_sql($sql1, $params); } // Second statement uses only context paths. $orcond = []; foreach ($hiddencats as $category) { $catcontext = context_coursecat::instance($category->id); // Case- and accent-sensitive search is not necessary for paths. // If we do without it, this will lead to an enormous performance boost on large scale tables. $orcond[] = $DB->sql_like('path', ':path' . $category->id, false, false); $params['path' . $category->id] = $catcontext->path . '/%'; } $sql2 = "SELECT $fields FROM {course} c JOIN {context} ctx ON (ctx.instanceid = c.id) AND (ctx.contextlevel = :contextlevel2) WHERE (" . implode(' OR ', $orcond) . ")"; $params['contextlevel2'] = CONTEXT_COURSE; // Combine with UNION. $sql = "SELECT * FROM (($sql1) UNION ($sql2)) d ORDER BY d.sortorder"; return $DB->get_records_sql($sql, $params); } /** * Finds accessible and non-accessible direct subcategories * * @return array [$coursecats, $hiddencats] - child categories that are visible to the current user and not visible */ protected function get_categories() { global $DB; $fields = 'c.*, ' . context_helper::get_preload_record_columns_sql('ctx'); $coursecats = $DB->get_records_sql('SELECT ' . $fields . ' FROM {course_categories} c LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel) WHERE c.parent = :parent ORDER BY c.sortorder', array('parent' => $this->category->id, 'contextlevel' => CONTEXT_COURSECAT)); $hiddencats = []; foreach ($coursecats as $id => &$category) { context_helper::preload_from_record($category); if (!core_course_category::can_view_category($category)) { $hiddencats[$id] = $coursecats[$id]; unset($coursecats[$id]); } } return [$coursecats, $hiddencats]; } /** * Returns the file info element for a given course or null if course is not accessible * * @param stdClass $course may contain context fields for preloading * @return file_info_context_course|null */ protected function get_child_course($course) { context_helper::preload_from_record($course); $context = context_course::instance($course->id); $child = new file_info_context_course($this->browser, $context, $course); return $child->get_file_info(null, null, null, null, null); } /** * Returns the number of children which are either files matching the specified extensions * or folders containing at least one such file. * * @param string|array $extensions, for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { $cnt = 0; if ($child = $this->get_area_coursecat_description(0, '/', '.')) { $cnt += $child->count_non_empty_children($extensions) ? 1 : 0; if ($cnt >= $limit) { return $cnt; } } list($coursecats, $hiddencats) = $this->get_categories(); foreach ($coursecats as $category) { $context = context_coursecat::instance($category->id); $child = new file_info_context_coursecat($this->browser, $context, $category); $cnt += $child->count_non_empty_children($extensions) ? 1 : 0; if ($cnt >= $limit) { return $cnt; } } $courses = $this->get_courses($hiddencats); foreach ($courses as $course) { if ($child = $this->get_child_course($course)) { $cnt += $child->count_non_empty_children($extensions) ? 1 : 0; if ($cnt >= $limit) { return $cnt; } } } return $cnt; } /** * Returns parent file_info instance * * @return file_info|null fileinfo instance or null for root directory */ public function get_parent() { $parent = $this->context->get_parent_context(); return $this->browser->get_file_info($parent); } } file_browser.php000064400000022755152265472420007763 0ustar00. /** * Utility class for browsing of files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once("$CFG->libdir/filebrowser/file_info.php"); // general area types require_once("$CFG->libdir/filebrowser/file_info_stored.php"); require_once("$CFG->libdir/filebrowser/virtual_root_file.php"); // description of available areas in each context level require_once("$CFG->libdir/filebrowser/file_info_context_system.php"); require_once("$CFG->libdir/filebrowser/file_info_context_user.php"); require_once("$CFG->libdir/filebrowser/file_info_context_coursecat.php"); require_once("$CFG->libdir/filebrowser/file_info_context_course.php"); require_once("$CFG->libdir/filebrowser/file_info_context_module.php"); /** * This class provides the main entry point for other code wishing to get information about files. * * The whole file storage for a Moodle site can be seen as a huge virtual tree. * The spine of the tree is the tree of contexts (system, course-categories, * courses, modules, also users). Then, within each context, there may be any number of * file areas, and a file area contains folders and files. The various file_info * subclasses return info about the things in this tree. They should be obtained * from an instance of this class. * * This virtual tree is different for each user depending of his/her current permissions. * Some branches such as draft areas are hidden, but accessible. * * Always use this abstraction when you need to access module files from core code. * * @package core_files * @category files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_browser { /** @var array cached list of enrolled courses. */ protected $enrolledcourses = null; /** * Looks up file_info instance * * @param stdClass $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ public function get_file_info($context = NULL, $component = NULL, $filearea = NULL, $itemid = NULL, $filepath = NULL, $filename = NULL) { if (!$context) { $context = context_system::instance(); } switch ($context->contextlevel) { case CONTEXT_SYSTEM: return $this->get_file_info_context_system($context, $component, $filearea, $itemid, $filepath, $filename); case CONTEXT_USER: return $this->get_file_info_context_user($context, $component, $filearea, $itemid, $filepath, $filename); case CONTEXT_COURSECAT: return $this->get_file_info_context_coursecat($context, $component, $filearea, $itemid, $filepath, $filename); case CONTEXT_COURSE: return $this->get_file_info_context_course($context, $component, $filearea, $itemid, $filepath, $filename); case CONTEXT_MODULE: return $this->get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename); } return null; } /** * Returns info about the files at System context * @todo MDL-33372 - Provide a way of displaying recent files for blog entries. * * @param object $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info instance or null if not found or access not allowed */ private function get_file_info_context_system($context, $component, $filearea, $itemid, $filepath, $filename) { $level = new file_info_context_system($this, $context); return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); // nothing supported at this context yet } /** * Returns info about the files at User context * * @param stdClass $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ private function get_file_info_context_user($context, $component, $filearea, $itemid, $filepath, $filename) { global $DB, $USER; if ($context->instanceid == $USER->id) { $user = $USER; } else { $user = $DB->get_record('user', array('id'=>$context->instanceid)); } if (isguestuser($user)) { // guests do not have any files return null; } if ($user->deleted) { return null; } $level = new file_info_context_user($this, $context, $user); return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); } /** * Returns info about the files at Course category context * * @param stdClass $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ private function get_file_info_context_coursecat($context, $component, $filearea, $itemid, $filepath, $filename) { global $DB; if (!$category = $DB->get_record('course_categories', array('id'=>$context->instanceid))) { return null; } $level = new file_info_context_coursecat($this, $context, $category); return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); } /** * Returns info about the files at Course category context * * @param stdClass $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ private function get_file_info_context_course($context, $component, $filearea, $itemid, $filepath, $filename) { global $DB, $COURSE; if ($context->instanceid == $COURSE->id) { $course = $COURSE; } else if (!$course = $DB->get_record('course', array('id'=>$context->instanceid))) { return null; } $level = new file_info_context_course($this, $context, $course); return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); } /** * Returns info about the files at Course category context * * @param context $context context object * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ private function get_file_info_context_module($context, $component, $filearea, $itemid, $filepath, $filename) { if (!($context instanceof context_module)) { return null; } $coursecontext = $context->get_course_context(); $modinfo = get_fast_modinfo($coursecontext->instanceid); $cm = $modinfo->get_cm($context->instanceid); if (empty($cm->uservisible)) { return null; } $level = new file_info_context_module($this, $context, $cm->get_course(), $cm, $cm->modname); return $level->get_file_info($component, $filearea, $itemid, $filepath, $filename); } /** * Check if user is enrolled into the course * * This function keeps a cache of enrolled courses because it may be called multiple times for many courses in one request * * @param int $courseid * @return bool */ public function is_enrolled($courseid) { if ($this->enrolledcourses === null || PHPUNIT_TEST) { // Since get_file_browser() returns a statically cached object we can't rely on cache // inside the file_browser class in the unittests. // TODO MDL-59964 remove this caching when it's implemented inside enrol_get_my_courses(). $this->enrolledcourses = enrol_get_my_courses(['id']); } return array_key_exists($courseid, $this->enrolledcourses); } } file_info_stored.php000064400000060603152265472420010605 0ustar00. /** * Utility class for browsing of stored files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents an actual file or folder - a row in the file table in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_stored extends file_info { /** @var stored_file|virtual_root_file stored_file or virtual_root_file instance */ protected $lf; /** @var string the serving script */ protected $urlbase; /** @var string the human readable name of this area */ protected $topvisiblename; /** @var int|bool it's false if itemid is 0 and not included in URL */ protected $itemidused; /** @var bool allow file reading */ protected $readaccess; /** @var bool allow file write, delee */ protected $writeaccess; /** @var string do not show links to parent context/area */ protected $areaonly; /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stored_file|virtual_root_file $storedfile stored_file instance * @param string $urlbase the serving script - usually the $CFG->wwwroot/.'pluginfile.php' * @param string $topvisiblename the human readable name of this area * @param int|bool $itemidused false if itemid always 0 and not included in URL * @param bool $readaccess allow file reading * @param bool $writeaccess allow file write, delete * @param string $areaonly do not show links to parent context/area */ public function __construct(file_browser $browser, $context, $storedfile, $urlbase, $topvisiblename, $itemidused, $readaccess, $writeaccess, $areaonly) { parent::__construct($browser, $context); $this->lf = $storedfile; $this->urlbase = $urlbase; $this->topvisiblename = $topvisiblename; $this->itemidused = $itemidused; $this->readaccess = $readaccess; $this->writeaccess = $writeaccess; $this->areaonly = $areaonly; } /** * Returns list of standard virtual file/directory identification. * The difference from stored_file parameters is that null values * are allowed in all fields * * @return array with keys contextid, component, filearea, itemid, filepath and filename */ public function get_params() { return array('contextid'=>$this->context->id, 'component'=>$this->lf->get_component(), 'filearea' =>$this->lf->get_filearea(), 'itemid' =>$this->lf->get_itemid(), 'filepath' =>$this->lf->get_filepath(), 'filename' =>$this->lf->get_filename()); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { $filename = $this->lf->get_filename(); $filepath = $this->lf->get_filepath(); if ($filename !== '.') { return $filename; } else { $dir = trim($filepath, '/'); $dir = explode('/', $dir); $dir = array_pop($dir); if ($dir === '') { return $this->topvisiblename; } else { return $dir; } } } /** * Returns the localised human-readable name of the file together with virtual path * * @return string */ public function get_readable_fullname() { global $CFG; // retrieve the readable path with all parents (excluding the top most 'System') $fpath = array(); for ($parent = $this; $parent && $parent->get_parent(); $parent = $parent->get_parent()) { array_unshift($fpath, $parent->get_visible_name()); } if ($this->lf->get_component() == 'user' && $this->lf->get_filearea() == 'private') { // use the special syntax for user private files - 'USERNAME Private files: PATH' $username = array_shift($fpath); array_shift($fpath); // get rid of "Private Files/" in the beginning of the path return get_string('privatefilesof', 'repository', $username). ': '. join('/', $fpath); } else { // for all other files (except user private files) return 'Server files: PATH' // first, get and cache the name of the repository_local (will be used as prefix for file names): static $replocalname = null; if ($replocalname === null) { require_once($CFG->dirroot . "/repository/lib.php"); $instances = repository::get_instances(array('type' => 'local')); if (count($instances)) { $firstinstance = reset($instances); $replocalname = $firstinstance->get_name(); } else if (get_string_manager()->string_exists('pluginname', 'repository_local')) { $replocalname = get_string('pluginname', 'repository_local'); } else { $replocalname = get_string('arearoot', 'repository'); } } return $replocalname. ': '. join('/', $fpath); } } /** * Returns file download url * * @param bool $forcedownload Whether or not force download * @param bool $https whether or not force https * @return string url */ public function get_url($forcedownload=false, $https=false) { if (!$this->is_readable()) { return null; } if ($this->is_directory()) { return null; } $this->urlbase; $contextid = $this->lf->get_contextid(); $component = $this->lf->get_component(); $filearea = $this->lf->get_filearea(); $itemid = $this->lf->get_itemid(); $filepath = $this->lf->get_filepath(); $filename = $this->lf->get_filename(); if ($this->itemidused) { $path = '/'.$contextid.'/'.$component.'/'.$filearea.'/'.$itemid.$filepath.$filename; } else { $path = '/'.$contextid.'/'.$component.'/'.$filearea.$filepath.$filename; } return file_encode_url($this->urlbase, $path, $forcedownload, $https); } /** * Whether or not I can read content of this file or enter directory * * @return bool */ public function is_readable() { return $this->readaccess; } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return $this->writeaccess; } /** * Whether or not this is an empty area * * @return bool */ public function is_empty_area() { if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { // test the emptiness only in the top most level, it does not make sense at lower levels $fs = get_file_storage(); return $fs->is_area_empty($this->lf->get_contextid(), $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid()); } else { return false; } } /** * Returns file size in bytes, null for directories * * @return int bytes or null if not known */ public function get_filesize() { return $this->lf->get_filesize(); } /** * Returns width, height and mimetype of the stored image, or false * * @see stored_file::get_imageinfo() * @return array|false */ public function get_imageinfo() { return $this->lf->get_imageinfo(); } /** * Returns mimetype * * @return string mimetype or null if not known */ public function get_mimetype() { return $this->lf->get_mimetype(); } /** * Returns time created unix timestamp if known * * @return int timestamp or null */ public function get_timecreated() { return $this->lf->get_timecreated(); } /** * Returns time modified unix timestamp if known * * @return int timestamp or null */ public function get_timemodified() { return $this->lf->get_timemodified(); } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return $this->lf->is_directory(); } /** * Returns the license type of the file * * @return string license short name or null */ public function get_license() { return $this->lf->get_license(); } /** * Returns the author name of the file * * @return string author name or null */ public function get_author() { return $this->lf->get_author(); } /** * Returns the source of the file * * @return string a source url or null */ public function get_source() { return $this->lf->get_source(); } /** * Returns the sort order of the file * * @return int */ public function get_sortorder() { return $this->lf->get_sortorder(); } /** * Whether or not this is a external resource * * @return bool */ public function is_external_file() { return $this->lf->is_external_file(); } /** * Returns file status flag. * * @return int 0 means file OK, anything else is a problem and file can not be used */ public function get_status() { return $this->lf->get_status(); } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { if (!$this->lf->is_directory()) { return array(); } $result = array(); $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) { $result[] = new file_info_stored($this->browser, $this->context, $file, $this->urlbase, $this->topvisiblename, $this->itemidused, $this->readaccess, $this->writeaccess, false); } 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 = core_text::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. * * @param string|array $extensions, for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { global $DB; if (!$this->lf->is_directory()) { return 0; } $filepath = $this->lf->get_filepath(); $length = core_text::strlen($filepath); $sql = "SELECT filepath, filename 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); $rs = $DB->get_recordset_sql($sql.' '.$sql2, array_merge($params, $params2)); $children = array(); foreach ($rs as $record) { // we don't need to check access to individual files here, since the user can access parent if ($record->filepath === $filepath) { $children[] = $record->filename; } else { $path = explode('/', core_text::substr($record->filepath, $length)); if (!in_array($path[0], $children)) { $children[] = $path[0]; } } if (count($children) >= $limit) { break; } } $rs->close(); return count($children); } /** * Returns parent file_info instance * * @return file_info|null file_info instance or null for root */ public function get_parent() { if ($this->lf->get_filepath() === '/' and $this->lf->is_directory()) { if ($this->areaonly) { return null; } else if ($this->itemidused) { return $this->browser->get_file_info($this->context, $this->lf->get_component(), $this->lf->get_filearea()); } else { return $this->browser->get_file_info($this->context); } } if (!$this->lf->is_directory()) { return $this->browser->get_file_info($this->context, $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(), $this->lf->get_filepath(), '.'); } $filepath = $this->lf->get_filepath(); $filepath = trim($filepath, '/'); $dirs = explode('/', $filepath); array_pop($dirs); $filepath = implode('/', $dirs); $filepath = ($filepath === '') ? '/' : "/$filepath/"; return $this->browser->get_file_info($this->context, $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(), $filepath, '.'); } /** * Create new directory, may throw exception - make sure * params are valid. * * @param string $newdirname name of new directory * @param int $userid id of author, default $USER->id * @return file_info|null new directory's file_info instance or null if failed */ public function create_directory($newdirname, $userid = NULL) { if (!$this->is_writable() or !$this->lf->is_directory()) { return null; } $newdirname = clean_param($newdirname, PARAM_FILE); if ($newdirname === '') { return null; } $filepath = $this->lf->get_filepath().'/'.$newdirname.'/'; $fs = get_file_storage(); if ($file = $fs->create_directory($this->lf->get_contextid(), $this->lf->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(), $filepath, $userid)) { return $this->browser->get_file_info($this->context, $this->lf->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); } return null; } /** * Create new file from string - make sure * params are valid. * * @param string $newfilename name of new file * @param string $content of file * @param int $userid id of author, default $USER->id * @return file_info|null new file's file_info instance or null if failed */ public function create_file_from_string($newfilename, $content, $userid = NULL) { if (!$this->is_writable() or !$this->lf->is_directory()) { return null; } $newfilename = clean_param($newfilename, PARAM_FILE); if ($newfilename === '') { return null; } $fs = get_file_storage(); $now = time(); $newrecord = new stdClass(); $newrecord->contextid = $this->lf->get_contextid(); $newrecord->component = $this->lf->get_component(); $newrecord->filearea = $this->lf->get_filearea(); $newrecord->itemid = $this->lf->get_itemid(); $newrecord->filepath = $this->lf->get_filepath(); $newrecord->filename = $newfilename; if ($fs->file_exists($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename)) { // file already exists, sorry return null; } $newrecord->timecreated = $now; $newrecord->timemodified = $now; $newrecord->mimetype = mimeinfo('type', $newfilename); $newrecord->userid = $userid; if ($file = $fs->create_file_from_string($newrecord, $content)) { return $this->browser->get_file_info($this->context, $file->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); } return null; } /** * Create new file from pathname - make sure * params are valid. * * @param string $newfilename name of new file * @param string $pathname location of file * @param int $userid id of author, default $USER->id * @return file_info|null new file's file_info instance or null if failed */ public function create_file_from_pathname($newfilename, $pathname, $userid = NULL) { if (!$this->is_writable() or !$this->lf->is_directory()) { return null; } $newfilename = clean_param($newfilename, PARAM_FILE); if ($newfilename === '') { return null; } $fs = get_file_storage(); $now = time(); $newrecord = new stdClass(); $newrecord->contextid = $this->lf->get_contextid(); $newrecord->component = $this->lf->get_component(); $newrecord->filearea = $this->lf->get_filearea(); $newrecord->itemid = $this->lf->get_itemid(); $newrecord->filepath = $this->lf->get_filepath(); $newrecord->filename = $newfilename; if ($fs->file_exists($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename)) { // file already exists, sorry return null; } $newrecord->timecreated = $now; $newrecord->timemodified = $now; $newrecord->mimetype = mimeinfo('type', $newfilename); $newrecord->userid = $userid; if ($file = $fs->create_file_from_pathname($newrecord, $pathname)) { return $this->browser->get_file_info($this->context, $file->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); } return null; } /** * Create new file from stored file - make sure * params are valid. * * @param string $newfilename name of new file * @param int|stored_file $fid file id or stored_file of file * @param int $userid id of author, default $USER->id * @return file_info|null new file's file_info instance or null if failed */ public function create_file_from_storedfile($newfilename, $fid, $userid = NULL) { if (!$this->is_writable() or $this->lf->get_filename() !== '.') { return null; } $newfilename = clean_param($newfilename, PARAM_FILE); if ($newfilename === '') { return null; } $fs = get_file_storage(); $now = time(); $newrecord = new stdClass(); $newrecord->contextid = $this->lf->get_contextid(); $newrecord->component = $this->lf->get_component(); $newrecord->filearea = $this->lf->get_filearea(); $newrecord->itemid = $this->lf->get_itemid(); $newrecord->filepath = $this->lf->get_filepath(); $newrecord->filename = $newfilename; if ($fs->file_exists($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename)) { // file already exists, sorry return null; } $newrecord->timecreated = $now; $newrecord->timemodified = $now; $newrecord->mimetype = mimeinfo('type', $newfilename); $newrecord->userid = $userid; if ($file = $fs->create_file_from_storedfile($newrecord, $fid)) { return $this->browser->get_file_info($this->context, $file->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename()); } return null; } /** * Delete file, make sure file is deletable first. * * @return bool success */ public function delete() { if (!$this->is_writable()) { return false; } if ($this->is_directory()) { $filepath = $this->lf->get_filepath(); $fs = get_file_storage(); $storedfiles = $fs->get_area_files($this->context->id, $this->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid()); foreach ($storedfiles as $file) { if (strpos($file->get_filepath(), $filepath) === 0) { $file->delete(); } } } return $this->lf->delete(); } /** * Copy content of this file to local storage, overriding current file if needed. * * @param array|stdClass $filerecord contains contextid, component, filearea, * itemid, filepath, filename and optionally other attributes of the new file * @return bool success */ public function copy_to_storage($filerecord) { if (!$this->is_readable() or $this->is_directory()) { return false; } $filerecord = (array)$filerecord; $fs = get_file_storage(); if ($existing = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename'])) { $existing->delete(); } $fs->create_file_from_storedfile($filerecord, $this->lf); return true; } /** * Copy content of this file to local storage, overriding current file if needed. * * @param string $pathname real local full file name * @return bool success */ public function copy_to_pathname($pathname) { if (!$this->is_readable() or $this->is_directory()) { return false; } if (file_exists($pathname)) { if (!unlink($pathname)) { return false; } } $this->lf->copy_content_to($pathname); return true; } } file_info_context_module.php000064400000027751152265472420012345 0ustar00. /** * Utility class for browsing of module files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents a module context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_context_module extends file_info { /** @var stdClass Course object */ protected $course; /** @var cm_info Course module object */ protected $cm; /** @var string Module name */ 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 * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stdClass $course course object * @param stdClass $cm course module object * @param string $modname module name */ public function __construct($browser, $context, $course, $cm, $modname) { global $CFG; parent::__construct($browser, $context); $this->course = $course; $this->cm = cm_info::create($cm); $this->modname = $this->cm->modname; $this->nonemptychildren = null; if ($functionname = component_callback_exists('mod_'.$modname, 'get_file_areas')) { $cm = $this->cm->get_course_module_record(); $this->areas = $functionname($course, $cm, $context); } else { $this->areas = array(); } unset($this->areas['intro']); // hardcoded, ignore attempts to override it } /** * Return information about this specific context level * * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ public function get_file_info($component, $filearea, $itemid, $filepath, $filename) { // try to emulate require_login() tests here if (!isloggedin()) { return null; } $coursecontext = $this->context->get_course_context(true); if (!$this->course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) { return null; } if (!is_viewing($this->context) and !$this->browser->is_enrolled($this->course->id)) { // no peaking here if not enrolled or inspector return null; } if (!$this->cm->uservisible) { // activity hidden sorry return null; } if (empty($component)) { return $this; } if ($component == 'mod_'.$this->modname and $filearea === 'intro') { return $this->get_area_intro($itemid, $filepath, $filename); } else if ($component == 'backup' and $filearea === 'activity') { return $this->get_area_backup($itemid, $filepath, $filename); } if ($functionname = component_callback_exists('mod_'.$this->modname, 'get_file_info')) { $cm = $this->cm->get_course_module_record(); return $functionname($this->browser, $this->areas, $this->course, $cm, $this->context, $filearea, $itemid, $filepath, $filename); } return null; } /** * Get a file from module intro area * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_intro($itemid, $filepath, $filename) { global $CFG; if (!plugin_supports('mod', $this->modname, FEATURE_MOD_INTRO, true) or !has_capability('moodle/course:managefiles', $this->context)) { return null; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'mod_'.$this->modname, 'intro', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'mod_'.$this->modname, 'intro', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('moduleintro'), false, true, true, false); } /** * Get a file from module backup area * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_backup($itemid, $filepath, $filename) { global $CFG; if (!has_capability('moodle/backup:backupactivity', $this->context)) { return null; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'activity', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'backup', 'activity', 0); } else { // not found return null; } } $downloadable = has_capability('moodle/backup:downloadfile', $this->context); $uploadable = has_capability('moodle/restore:uploadfile', $this->context); $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('activitybackup', 'repository'), false, $downloadable, $uploadable, false); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return $this->cm->get_formatted_name().' ('.$this->cm->get_module_type_name().')'; } /** * Whether or not files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is an emtpy area * * @return bool */ public function is_empty_area() { if ($child = $this->get_area_backup(0, '/', '.')) { if (!$child->is_empty_area()) { return false; } } if ($child = $this->get_area_intro(0, '/', '.')) { if (!$child->is_empty_area()) { return false; } } foreach ($this->areas as $area=>$desctiption) { if ($child = $this->get_file_info('mod_'.$this->modname, $area, null, null, null)) { if (!$child->is_empty_area()) { return false; } } } return true; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @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|int $countonly if false returns the children, if an int returns just the * count of children but stops counting when $countonly number of children is reached * @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) { $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 !== false && count($children) >= $countonly) { break; } } } } if ($countonly !== false) { 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 = '*') { 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. * * @param string|array $extensions for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { if ($this->nonemptychildren !== null) { return count($this->nonemptychildren); } return $this->get_filtered_children($extensions, $limit); } /** * Returns parent file_info instance * * @return file_info|null file_info or null for root */ public function get_parent() { $parent = $this->context->get_parent_context(); return $this->browser->get_file_info($parent); } } file_info_context_system.php000064400000011155152265472420012373 0ustar00. /** * Utility class for browsing of system files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/filebrowser/file_info_context_coursecat.php'); /** * Represents the system context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_context_system extends file_info_context_coursecat { /** * Constructor * * @param file_browser $browser file_browser instance * @param stdClass $context context object */ public function __construct($browser, $context) { parent::__construct($browser, $context, (object)['id' => 0, 'parent' => 0, 'visible' => 1]); } /** * Return information about this specific part of context level * * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ public function get_file_info($component, $filearea, $itemid, $filepath, $filename) { if (empty($component)) { return $this; } $methodname = "get_area_{$component}_{$filearea}"; if (method_exists($this, $methodname)) { return $this->$methodname($itemid, $filepath, $filename); } return null; } /** * Gets a stored file for the backup course filearea directory. * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_backup_course($itemid, $filepath, $filename) { global $CFG; if (!isloggedin()) { return null; } if (!has_any_capability(array('moodle/backup:backupcourse', 'moodle/restore:restorecourse'), $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'course', 0, $filepath, $filename)) { if ($filepath === '/' && $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'backup', 'course', 0); } else { // Not found. return null; } } $downloadable = has_capability('moodle/backup:downloadfile', $this->context); $uploadable = has_capability('moodle/restore:uploadfile', $this->context); $urlbase = $CFG->wwwroot . '/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('coursebackup', 'repository'), false, $downloadable, $uploadable, false); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return get_string('arearoot', 'repository'); } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns parent file_info instance * * @return file_info|null file_info instance or null for root */ public function get_parent() { return null; } } tests/file_browser_test.php000064400000026304152265472420012156 0ustar00. namespace core; use file_info_context_course; use file_info_context_coursecat; use file_info_context_module; use file_info_stored; use stdClass; /** * Unit tests for file browser * * @package core * @copyright 2017 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class file_browser_test extends \advanced_testcase { /** @var int */ protected $initialnonempty; /** @var int */ protected $initialcategories; /** @var int */ protected $initialcourses; /** @var int */ protected $initialjpg; /** @var stdClass */ protected $course1; /** @var stdClass */ protected $course2; /** @var stdClass */ protected $module1; /** @var stdClass */ protected $module2; /** @var array */ protected $course1filerecord; /** @var stdClass */ protected $teacher; /** @var stdClass */ protected $teacherrole; /** * Set up */ public function setUp(): void { global $DB; parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_system::instance()); $this->initialnonempty = $fileinfo->count_non_empty_children(); $this->initialcategories = count(array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_coursecat; })); $this->initialcourses = count(array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_course; })); $this->initialcourses -= 1; // This includes the site course by default. $this->initialjpg = $fileinfo->count_non_empty_children(['.jpg']); $this->getDataGenerator()->create_category(); // Empty category. $this->course1 = $this->getDataGenerator()->create_course(); // Empty course. $this->course2 = $this->getDataGenerator()->create_course(); // Add a file to course1 summary. $coursecontext1 = \context_course::instance($this->course1->id); $this->course1filerecord = array('contextid' => $coursecontext1->id, 'component' => 'course', 'filearea' => 'summary', 'itemid' => '0', 'filepath' => '/', 'filename' => 'summaryfile.jpg'); $fs = get_file_storage(); $fs->create_file_from_string($this->course1filerecord, 'IMG'); $this->module1 = $this->getDataGenerator()->create_module('resource', ['course' => $this->course2->id]); // Contains 1 file. $this->module2 = $this->getDataGenerator()->create_module('assign', ['course' => $this->course2->id]); // Contains no files. $this->teacher = $this->getDataGenerator()->create_user(); $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); // Make sure we're testing what should be the default capabilities. assign_capability('moodle/restore:viewautomatedfilearea', CAP_ALLOW, $this->teacherrole->id, $coursecontext1); $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course1->id, $this->teacherrole->id); $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course2->id, $this->teacherrole->id); $this->setUser($this->teacher); } /** * Test "Server files" from the system context */ public function test_file_info_context_system(): void { // There is one non-empty category child and two category children. $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children()); $this->assertEquals($this->initialnonempty + 1, count($fileinfo->get_non_empty_children())); $categorychildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_coursecat; }); $this->assertEquals($this->initialcategories + 1, count($categorychildren)); } /** * Test "Server files" from the system context, hide Misc category */ public function test_file_info_context_system_hidden(): void { // Hide the course category that contains our two courses. Teacher does not have cap to view hidden categories. \core_course_category::get($this->course1->category)->update(['visible' => 0]); // We should have two non-empty children in system context (courses). $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children()); $this->assertEquals($this->initialnonempty + 2, count($fileinfo->get_non_empty_children())); // Should be 1 category children (empty category). $categorychildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_coursecat; }); $this->assertEquals($this->initialcategories, count($categorychildren)); // Should be 2 course children - courses that belonged to hidden subcategory are now direct children of "System". $coursechildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_course; }); $this->assertEquals($this->initialcourses + 2, count($coursechildren)); } /** * Test "Server files" from the course category context */ public function test_file_info_context_coursecat(): void { // There are two non-empty courses. $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_coursecat::instance($this->course2->category)); $this->assertNotEmpty($fileinfo->count_non_empty_children()); $this->assertEquals(2, count($fileinfo->get_non_empty_children())); $coursechildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_course; }); $this->assertEquals($this->initialcourses + 2, count($coursechildren)); } /** * Test "Server files" from the course category context, only look for .jpg */ public function test_file_info_context_coursecat_jpg(): void { // There is one non-empty category child and two category children. $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_system::instance()); $this->assertNotEmpty($fileinfo->count_non_empty_children(['.jpg'])); $this->assertEquals($this->initialjpg + 1, count($fileinfo->get_non_empty_children(['.jpg']))); } /** * Test "Server files" from the course context (course1) */ public function test_file_info_context_course_1(): void { $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_course::instance($this->course1->id)); // Fileinfo element has only one non-empty child - "Course summary" file area. $this->assertNotEmpty($fileinfo->count_non_empty_children()); $nonemptychildren = $fileinfo->get_non_empty_children(); $this->assertEquals(1, count($nonemptychildren)); $child = reset($nonemptychildren); $this->assertTrue($child instanceof file_info_stored); $this->assertEquals(['filename' => '.'] + $this->course1filerecord, $child->get_params()); // Filearea "Course summary" has a child that is the actual image file. $this->assertEquals($this->course1filerecord, $child->get_children()[0]->get_params()); // There are seven course-level file areas available to teachers with default caps and no modules in this course. $allchildren = $fileinfo->get_children(); $this->assertEquals(7, count($allchildren)); $modulechildren = array_filter($allchildren, function($a) { return $a instanceof file_info_context_module; }); $this->assertEquals(0, count($modulechildren)); // Admin can see seven course-level file areas. $this->setAdminUser(); $fileinfo = $browser->get_file_info(\context_course::instance($this->course1->id)); $this->assertEquals(7, count($fileinfo->get_children())); } /** * Test "Server files" from the course context (course1) */ public function test_file_info_context_course_2(): void { // 2. Start from the course level. $browser = get_file_browser(); $fileinfo = $browser->get_file_info(\context_course::instance($this->course2->id)); $this->assertNotEmpty($fileinfo->count_non_empty_children()); $nonemptychildren = $fileinfo->get_non_empty_children(); $this->assertEquals(1, count($nonemptychildren)); $child = reset($nonemptychildren); $this->assertTrue($child instanceof file_info_context_module); $this->assertEquals($this->module1->name.' (File)', $child->get_visible_name()); $this->assertEquals(1, count($child->get_non_empty_children())); $this->assertEquals(1, $child->count_non_empty_children()); $modulechildren = array_filter($fileinfo->get_children(), function($a) { return $a instanceof file_info_context_module; }); $this->assertEquals(2, count($modulechildren)); } /** * Test "Server files" from the course context (module1) */ public function test_file_info_context_module_1(): void { $module1context = \context_module::instance($this->module1->cmid); $browser = get_file_browser(); $fileinfo = $browser->get_file_info($module1context); $this->assertEquals($this->module1->name . ' (File)', $fileinfo->get_visible_name()); $this->assertNotEmpty($fileinfo->count_non_empty_children()); $nonemptychildren = $fileinfo->get_non_empty_children(); $this->assertEquals(1, count($nonemptychildren)); $child = reset($nonemptychildren); $this->assertTrue($child instanceof file_info_stored); } /** * Test "Server files" from the course context (module1) */ public function test_file_info_context_module_2(): void { $module2context = \context_module::instance($this->module2->cmid); $browser = get_file_browser(); $fileinfo = $browser->get_file_info($module2context); $this->assertEquals($this->module2->name.' (Assignment)', $fileinfo->get_visible_name()); $this->assertEmpty($fileinfo->count_non_empty_children()); $nonemptychildren = $fileinfo->get_non_empty_children(); $this->assertEquals(0, count($nonemptychildren)); } } file_info_context_course.php000064400000106117152265472420012352 0ustar00. /** * Utility class for browsing of course files. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Represents a course context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_context_course extends file_info { /** @var stdClass course object */ protected $course; /** @var file_info_context_module[] cached child modules. See {@link get_child_module()} */ protected $childrenmodules = []; /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stdClass $course course object */ public function __construct($browser, $context, $course) { parent::__construct($browser, $context); $this->course = $course; } /** * Return information about this specific context level * * @param string $component component * @param string $filearea file area * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ public function get_file_info($component, $filearea, $itemid, $filepath, $filename) { // try to emulate require_login() tests here if (!isloggedin()) { return null; } if (!$this->course->visible and !has_capability('moodle/course:viewhiddencourses', $this->context)) { return null; } if (!is_viewing($this->context) and !$this->browser->is_enrolled($this->course->id)) { // no peaking here if not enrolled or inspector return null; } if (empty($component)) { return $this; } $methodname = "get_area_{$component}_{$filearea}"; if (method_exists($this, $methodname)) { return $this->$methodname($itemid, $filepath, $filename); } return null; } /** * Returns list of areas inside this course * * @param string $extensions Only return areas that have files with these extensions * @param bool $returnemptyfolders return all areas always, if true it will ignore the previous argument * @return array */ protected function get_course_areas($extensions = '*', $returnemptyfolders = false) { global $DB; $allareas = [ 'course_summary', 'course_overviewfiles', 'course_section', 'backup_section', 'backup_course', 'backup_automated', 'course_legacy' ]; if ($returnemptyfolders) { return $allareas; } $params1 = ['contextid' => $this->context->id, 'emptyfilename' => '.']; $sql1 = "SELECT " . $DB->sql_concat('f.component', "'_'", 'f.filearea') . " FROM {files} f WHERE f.filename <> :emptyfilename AND f.contextid = :contextid "; $sql3 = ' GROUP BY f.component, f.filearea'; list($sql2, $params2) = $this->build_search_files_sql($extensions); $areaswithfiles = $DB->get_fieldset_sql($sql1 . $sql2 . $sql3, array_merge($params1, $params2)); return array_intersect($allareas, $areaswithfiles); } /** * Gets a stored file for the course summary filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_course_summary($itemid, $filepath, $filename) { global $CFG; if (!has_capability('moodle/course:update', $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'course', 'summary', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'course', 'summary', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areacourseintro', 'repository'), false, true, true, false); } /** * Gets a stored file for the course images filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_course_overviewfiles($itemid, $filepath, $filename) { global $CFG; if (!has_capability('moodle/course:update', $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'course', 'overviewfiles', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'course', 'overviewfiles', 0); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('areacourseoverviewfiles', 'repository'), false, true, true, false); } /** * Gets a stored file for the course section filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_course_section($itemid, $filepath, $filename) { global $CFG, $DB; if (!has_capability('moodle/course:update', $this->context)) { return null; } if (empty($itemid)) { // list all sections return new file_info_area_course_section($this->browser, $this->context, $this->course, $this); } if (!$section = $DB->get_record('course_sections', array('course'=>$this->course->id, 'id'=>$itemid))) { return null; // does not exist } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'course', 'section', $itemid, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'course', 'section', $itemid); } else { // not found return null; } } $urlbase = $CFG->wwwroot.'/pluginfile.php'; require_once($CFG->dirroot.'/course/lib.php'); $sectionname = get_section_name($this->course, $section); return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $sectionname, true, true, true, false); } /** * Gets a stored file for the course legacy filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_course_legacy($itemid, $filepath, $filename) { if (!has_capability('moodle/course:managefiles', $this->context)) { return null; } if ($this->course->id != SITEID and $this->course->legacyfiles != 2) { // bad luck, legacy course files not used any more } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'course', 'legacy', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'course', 'legacy', 0); } else { // not found return null; } } return new file_info_area_course_legacy($this->browser, $this->context, $storedfile); } /** * Gets a stored file for the backup course filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_backup_course($itemid, $filepath, $filename) { global $CFG; if (!has_capability('moodle/backup:backupcourse', $this->context) and !has_capability('moodle/restore:restorecourse', $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'course', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'backup', 'course', 0); } else { // not found return null; } } $downloadable = has_capability('moodle/backup:downloadfile', $this->context); $uploadable = has_capability('moodle/restore:uploadfile', $this->context); $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('coursebackup', 'repository'), false, $downloadable, $uploadable, false); } /** * Gets a stored file for the automated backup filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null */ protected function get_area_backup_automated($itemid, $filepath, $filename) { global $CFG; if (!has_capability('moodle/restore:viewautomatedfilearea', $this->context)) { return null; } if (is_null($itemid)) { return $this; } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'automated', 0, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'backup', 'automated', 0); } else { // not found return null; } } // Automated backup files are only downloadable if the user has both 'backup:downloadfile and 'restore:userinfo'. $downloadable = has_capability('moodle/backup:downloadfile', $this->context) && has_capability('moodle/restore:userinfo', $this->context); $uploadable = false; $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('automatedbackup', 'repository'), true, $downloadable, $uploadable, false); } /** * Gets a stored file for the backup section filearea directory * * @param int $itemid item ID * @param string $filepath file path * @param string $filename file name * @return file_info|null file_info instance or null if not found or access not allowed */ protected function get_area_backup_section($itemid, $filepath, $filename) { global $CFG, $DB; if (!has_capability('moodle/backup:backupcourse', $this->context) and !has_capability('moodle/restore:restorecourse', $this->context)) { return null; } if (empty($itemid)) { // list all sections return new file_info_area_backup_section($this->browser, $this->context, $this->course, $this); } if (!$section = $DB->get_record('course_sections', array('course'=>$this->course->id, 'id'=>$itemid))) { return null; // does not exist } $fs = get_file_storage(); $filepath = is_null($filepath) ? '/' : $filepath; $filename = is_null($filename) ? '.' : $filename; if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'section', $itemid, $filepath, $filename)) { if ($filepath === '/' and $filename === '.') { $storedfile = new virtual_root_file($this->context->id, 'backup', 'section', $itemid); } else { // not found return null; } } $downloadable = has_capability('moodle/backup:downloadfile', $this->context); $uploadable = has_capability('moodle/restore:uploadfile', $this->context); $urlbase = $CFG->wwwroot.'/pluginfile.php'; return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, $section->id, true, $downloadable, $uploadable, false); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return ($this->course->id == SITEID) ? get_string('frontpage', 'admin') : format_string(get_course_display_name_for_list($this->course), true, array('context'=>$this->context)); } /** * Whether or not new files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { return $this->get_filtered_children('*', false, true); } /** * Returns the child module if it is accessible by the current user * * @param cm_info|int $cm * @return file_info_context_module|null */ protected function get_child_module($cm) { $cmid = is_object($cm) ? $cm->id : $cm; if (!array_key_exists($cmid, $this->childrenmodules)) { $this->childrenmodules[$cmid] = null; if (!($cm instanceof cm_info)) { $cms = get_fast_modinfo($this->course)->cms; $cm = array_key_exists($cmid, $cms) ? $cms[$cmid] : null; } if ($cm && $cm->uservisible) { $this->childrenmodules[$cmid] = new file_info_context_module($this->browser, $cm->context, $this->course, $cm, $cm->modname); } } return $this->childrenmodules[$cmid]; } /** * 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|int $countonly if false returns the children, if an int returns just the * count of children but stops counting when $countonly number of children is reached * @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) { $children = array(); $courseareas = $this->get_course_areas($extensions, $returnemptyfolders); foreach ($courseareas as $areaname) { $area = explode('_', $areaname, 2); if ($child = $this->get_file_info($area[0], $area[1], 0, '/', '.')) { $children[] = $child; if (($countonly !== false) && count($children) >= $countonly) { return $countonly; } } } $cnt = count($children); 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 } else { if ($returnemptyfolders) { $modinfo = get_fast_modinfo($this->course); foreach ($modinfo->cms as $cminfo) { if ($child = $this->get_child_module($cminfo)) { $children[] = $child; $cnt++; } } } else if ($moduleareas = $this->get_module_areas_with_files($extensions)) { // We found files in some of the modules. // Create array of children modules ordered with the same way as cms in modinfo. $modulechildren = array_fill_keys(array_keys(get_fast_modinfo($this->course)->get_cms()), null); foreach ($moduleareas as $area) { if ($modulechildren[$area->cmid]) { // We already found non-empty area within the same module, do not analyse other areas. continue; } if ($child = $this->get_child_module($area->cmid)) { if ($child->get_file_info($area->component, $area->filearea, $area->itemid, null, null)) { $modulechildren[$area->cmid] = $child; $cnt++; if (($countonly !== false) && $cnt >= $countonly) { return $cnt; } } } } $children = array_merge($children, array_values(array_filter($modulechildren))); } } if ($countonly !== false) { return count($children); } return $children; } /** * Returns list of areas inside the course modules that have files with the given extension * * @param string $extensions * @return array */ protected function get_module_areas_with_files($extensions = '*') { global $DB; $params1 = ['contextid' => $this->context->id, 'emptyfilename' => '.', 'contextlevel' => CONTEXT_MODULE, 'course' => $this->course->id]; $ctxfieldsas = context_helper::get_preload_record_columns_sql('ctx'); $ctxfields = implode(', ', array_keys(context_helper::get_preload_record_columns('ctx'))); $sql1 = "SELECT ctx.id AS contextid, f.component, f.filearea, f.itemid, ctx.instanceid AS cmid, {$ctxfieldsas} FROM {files} f INNER JOIN {context} ctx ON ctx.id = f.contextid INNER JOIN {course_modules} cm ON cm.id = ctx.instanceid WHERE f.filename <> :emptyfilename AND cm.course = :course AND ctx.contextlevel = :contextlevel"; $sql3 = " GROUP BY ctx.id, f.component, f.filearea, f.itemid, {$ctxfields} ORDER BY ctx.id, f.component, f.filearea, f.itemid"; list($sql2, $params2) = $this->build_search_files_sql($extensions); $areas = []; if ($rs = $DB->get_recordset_sql($sql1. $sql2 . $sql3, array_merge($params1, $params2))) { foreach ($rs as $record) { context_helper::preload_from_record($record); $areas[] = $record; } $rs->close(); } // Sort areas so 'backup' and 'intro' are in the beginning of the list, they are the easiest to check access to. usort($areas, function($a, $b) { $aeasy = ($a->filearea === 'intro' && substr($a->component, 0, 4) === 'mod_') || ($a->filearea === 'activity' && $a->component === 'backup'); $beasy = ($b->filearea === 'intro' && substr($b->component, 0, 4) === 'mod_') || ($b->filearea === 'activity' && $b->component === 'backup'); return $aeasy == $beasy ? 0 : ($aeasy ? -1 : 1); }); return $areas; } /** * 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. * * @param string|array $extensions, for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { return $this->get_filtered_children($extensions, $limit); } /** * Returns parent file_info instance * * @return file_info or null for root */ public function get_parent() { $parent = $this->context->get_parent_context(); return $this->browser->get_file_info($parent); } } /** * Subclass of file_info_stored for files in the course files area. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_area_course_legacy extends file_info_stored { /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stored_file $storedfile stored_file instance */ public function __construct($browser, $context, $storedfile) { global $CFG; $urlbase = $CFG->wwwroot.'/file.php'; parent::__construct($browser, $context, $storedfile, $urlbase, get_string('coursefiles'), false, true, true, false); } /** * Returns file download url * * @param bool $forcedownload whether or not force download * @param bool $https whether or not force https * @return string url */ public function get_url($forcedownload=false, $https=false) { if (!$this->is_readable()) { return null; } if ($this->lf->is_directory()) { return null; } $filepath = $this->lf->get_filepath(); $filename = $this->lf->get_filename(); $courseid = $this->context->instanceid; $path = '/'.$courseid.$filepath.$filename; return file_encode_url($this->urlbase, $path, $forcedownload, $https); } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { 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 ASC, filename ASC"); foreach ($storedfiles as $file) { $result[] = new file_info_area_course_legacy($this->browser, $this->context, $file); } 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 = core_text::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; } } /** * Represents a course category context in the tree navigated by {@link file_browser}. * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_area_course_section extends file_info { /** @var stdClass course object */ protected $course; /** @var file_info_context_course course file info object */ protected $courseinfo; /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stdClass $course course object * @param file_info_context_course $courseinfo file info instance */ public function __construct($browser, $context, $course, file_info_context_course $courseinfo) { parent::__construct($browser, $context); $this->course = $course; $this->courseinfo = $courseinfo; } /** * Returns list of standard virtual file/directory identification. * The difference from stored_file parameters is that null values * are allowed in all fields * * @return array with keys contextid, filearea, itemid, filepath and filename */ public function get_params() { return array('contextid' => $this->context->id, 'component' => 'course', 'filearea' => 'section', 'itemid' => null, 'filepath' => null, 'filename' => null); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { //$format = $this->course->format; $sectionsname = get_string("coursesectionsummaries"); return $sectionsname; } /** * Return whether or not new files or directories can be added * * @return bool */ public function is_writable() { return false; } /** * Return whether or not this is a empty area * * @return bool */ public function is_empty_area() { $fs = get_file_storage(); return $fs->is_area_empty($this->context->id, 'course', 'section'); } /** * Return whether or not this is a empty area * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { global $DB; $children = array(); $course_sections = $DB->get_records('course_sections', array('course'=>$this->course->id), 'section'); foreach ($course_sections as $section) { if ($child = $this->courseinfo->get_file_info('course', 'section', $section->id, '/', '.')) { $children[] = $child; } } return $children; } /** * Returns the number of children which are either files matching the specified extensions * or folders containing at least one such file. * * @param string|array $extensions, for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { global $DB; $params1 = array( 'courseid' => $this->course->id, 'contextid' => $this->context->id, 'component' => 'course', 'filearea' => 'section', 'emptyfilename' => '.'); $sql1 = "SELECT DISTINCT cs.id 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); $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2)); $cnt = 0; foreach ($rs as $record) { if ((++$cnt) >= $limit) { break; } } $rs->close(); return $cnt; } /** * Returns parent file_info instance * * @return file_info|null file_info or null for root */ public function get_parent() { return $this->courseinfo; } } /** * Implementation of course section backup area * * @package core_files * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class file_info_area_backup_section extends file_info { /** @var stdClass course object */ protected $course; /** @var file_info_context_course course file info object */ protected $courseinfo; /** * Constructor * * @param file_browser $browser file browser instance * @param stdClass $context context object * @param stdClass $course course object * @param file_info_context_course $courseinfo file info instance */ public function __construct($browser, $context, $course, file_info_context_course $courseinfo) { parent::__construct($browser, $context); $this->course = $course; $this->courseinfo = $courseinfo; } /** * Returns list of standard virtual file/directory identification. * The difference from stored_file parameters is that null values * are allowed in all fields * * @return array with keys contextid, component, filearea, itemid, filepath and filename */ public function get_params() { return array('contextid' => $this->context->id, 'component' => 'backup', 'filearea' => 'section', 'itemid' => null, 'filepath' => null, 'filename' => null); } /** * Returns localised visible name. * * @return string */ public function get_visible_name() { return get_string('sectionbackup', 'repository'); } /** * Return whether or not new files and directories can be added * * @return bool */ public function is_writable() { return false; } /** * Whether or not this is an empty area * * @return bool */ public function is_empty_area() { $fs = get_file_storage(); return $fs->is_area_empty($this->context->id, 'backup', 'section'); } /** * Return whether or not this is a directory * * @return bool */ public function is_directory() { return true; } /** * Returns list of children. * * @return array of file_info instances */ public function get_children() { global $DB; $children = array(); $course_sections = $DB->get_records('course_sections', array('course'=>$this->course->id), 'section'); foreach ($course_sections as $section) { if ($child = $this->courseinfo->get_file_info('backup', 'section', $section->id, '/', '.')) { $children[] = $child; } } return $children; } /** * Returns the number of children which are either files matching the specified extensions * or folders containing at least one such file. * * @param string|array $extensions, for example '*' or array('.gif','.jpg') * @param int $limit stop counting after at least $limit non-empty children are found * @return int */ public function count_non_empty_children($extensions = '*', $limit = 1) { global $DB; $params1 = array( 'courseid' => $this->course->id, 'contextid' => $this->context->id, 'component' => 'backup', 'filearea' => 'section', 'emptyfilename' => '.'); $sql1 = "SELECT DISTINCT cs.id AS sectionid 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); $rs = $DB->get_recordset_sql($sql1. ' '. $sql2, array_merge($params1, $params2)); $cnt = 0; foreach ($rs as $record) { if ((++$cnt) >= $limit) { break; } } $rs->close(); return $cnt; } /** * Returns parent file_info instance * * @return file_info or null for root */ public function get_parent() { return $this->browser->get_file_info($this->context); } }