ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ.. /** * Provides the {@link core_form\filetypes_util} class. * * @package core_form * @copyright 2017 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_form; use core_collator; use core_filetypes; use core_text; defined('MOODLE_INTERNAL') || die(); /** * Utility class for handling with file types in the forms. * * This class is supposed to serve as a helper class for {@link MoodleQuickForm_filetypes} * and {@link admin_setting_filetypes} classes. * * The file types can be specified in a syntax compatible with what filepicker * and filemanager support via the "accepted_types" option: a list of extensions * (e.g. ".doc"), mimetypes ("image/png") or groups ("audio"). * * @copyright 2017 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class filetypes_util { /** @var array Cache of all file type groups for the {@link self::get_groups_info()}. */ protected $cachegroups = null; /** * Converts the argument into an array (list) of file types. * * The list can be separated by whitespace, end of lines, commas, colons and semicolons. * Empty values are not returned. Values are converted to lowercase. * Duplicates are removed. Glob evaluation is not supported. * * The return value can be used as the accepted_types option for the filepicker. * * @param string|array $types List of file extensions, groups or mimetypes * @return array of strings */ public function normalize_file_types($types) { if ($types === '' || $types === null) { return []; } // Turn string into a list. if (!is_array($types)) { $types = preg_split('/[\s,;:"\']+/', $types, -1, PREG_SPLIT_NO_EMPTY); } // Fix whitespace and normalize the syntax a bit. foreach ($types as $i => $type) { $type = str_replace('*.', '.', $type); $type = core_text::strtolower($type); $type = trim($type); if ($type === '*') { return ['*']; } $types[$i] = $type; } // Do not make the user think that globs (like ".doc?") would work. foreach ($types as $i => $type) { if (strpos($type, '*') !== false or strpos($type, '?') !== false) { unset($types[$i]); } } foreach ($types as $i => $type) { if (substr($type, 0, 1) === '.') { // It looks like an extension. $type = '.'.ltrim($type, '.'); $types[$i] = clean_param($type, PARAM_FILE); } else if ($this->looks_like_mimetype($type)) { // All good, it looks like a mimetype. continue; } else if ($this->is_filetype_group($type)) { // All good, it is a known type group. continue; } else { // We assume the user typed something like "png" so we consider // it an extension. $types[$i] = '.'.$type; } } $types = array_filter($types, 'strlen'); $types = array_keys(array_flip($types)); return $types; } /** * Does the given file type looks like a valid MIME type? * * This does not check of the MIME type is actually registered here/known. * * @param string $type * @return bool */ public function looks_like_mimetype($type) { return (bool)preg_match('~^[-\.a-z0-9]+/[a-z0-9]+([-\.\+][a-z0-9]+)*$~', $type); } /** * Is the given string a known filetype group? * * @param string $type * @return bool|object false or the group info */ public function is_filetype_group($type) { $info = $this->get_groups_info(); if (isset($info[$type])) { return $info[$type]; } else { return false; } } /** * Provides a list of all known file type groups and their properties. * * @return array */ public function get_groups_info() { if ($this->cachegroups !== null) { return $this->cachegroups; } $groups = []; foreach (core_filetypes::get_types() as $ext => $info) { if (isset($info['groups']) && is_array($info['groups'])) { foreach ($info['groups'] as $group) { if (!isset($groups[$group])) { $groups[$group] = (object) [ 'extensions' => [], 'mimetypes' => [], ]; } $groups[$group]->extensions['.'.$ext] = true; if (isset($info['type'])) { $groups[$group]->mimetypes[$info['type']] = true; } } } } foreach ($groups as $group => $info) { $info->extensions = array_keys($info->extensions); $info->mimetypes = array_keys($info->mimetypes); } $this->cachegroups = $groups; return $this->cachegroups; } /** * Return a human readable name of the filetype group. * * @param string $group * @return string */ public function get_group_description($group) { if (get_string_manager()->string_exists('group:'.$group, 'core_mimetypes')) { return get_string('group:'.$group, 'core_mimetypes'); } else { return s($group); } } /** * Describe the list of file types for human user. * * Given the list of file types, return a list of human readable * descriptive names of relevant groups, types or file formats. * * @param string|array $types * @return object */ public function describe_file_types($types) { $descriptions = []; $types = $this->normalize_file_types($types); foreach ($types as $type) { if ($type === '*') { $desc = get_string('filetypesany', 'core_form'); $descriptions[$desc] = []; } else if ($group = $this->is_filetype_group($type)) { $desc = $this->get_group_description($type); $descriptions[$desc] = $group->extensions; } else if ($this->looks_like_mimetype($type)) { $desc = get_mimetype_description($type); $descriptions[$desc] = file_get_typegroup('extension', [$type]); } else { $desc = get_mimetype_description(['filename' => 'fakefile'.$type]); if (isset($descriptions[$desc])) { $descriptions[$desc][] = $type; } else { $descriptions[$desc] = [$type]; } } } $data = []; foreach ($descriptions as $desc => $exts) { sort($exts); $data[] = (object)[ 'description' => $desc, 'extensions' => join(' ', $exts), ]; } core_collator::asort_objects_by_property($data, 'description', core_collator::SORT_NATURAL); return (object)[ 'hasdescriptions' => !empty($data), 'descriptions' => array_values($data), ]; } /** * Prepares data for the filetypes-browser.mustache * * @param string|array $onlytypes Allow selection from these file types only; for example 'web_image'. * @param bool $allowall Allow to select 'All file types'. Does not apply with onlytypes are set. * @param string|array $current Current values that should be selected. * @return array */ public function data_for_browser($onlytypes=null, $allowall=true, $current=null) { $groups = []; $current = $this->normalize_file_types($current); // Firstly populate the tree of extensions categorized into groups. foreach ($this->get_groups_info() as $groupkey => $groupinfo) { if (empty($groupinfo->extensions)) { continue; } $group = (object) [ 'key' => $groupkey, 'name' => $this->get_group_description($groupkey), 'selectable' => true, 'selected' => in_array($groupkey, $current), 'ext' => implode(' ', $groupinfo->extensions), 'expanded' => false, ]; $types = []; foreach ($groupinfo->extensions as $extension) { if ($onlytypes && !$this->is_listed($extension, $onlytypes)) { $group->selectable = false; $group->expanded = true; $group->ext = ''; continue; } $desc = get_mimetype_description(['filename' => 'fakefile'.$extension]); if ($selected = in_array($extension, $current)) { $group->expanded = true; } $types[] = (object) [ 'key' => $extension, 'name' => get_mimetype_description(['filename' => 'fakefile'.$extension]), 'selected' => $selected, 'ext' => $extension, ]; } if (empty($types)) { continue; } core_collator::asort_objects_by_property($types, 'name', core_collator::SORT_NATURAL); $group->types = array_values($types); $groups[] = $group; } core_collator::asort_objects_by_property($groups, 'name', core_collator::SORT_NATURAL); // Append all other uncategorized extensions. $others = []; foreach (core_filetypes::get_types() as $extension => $info) { // Reserved for unknown file types. Not available here. if ($extension === 'xxx') { continue; } $extension = '.'.$extension; if ($onlytypes && !$this->is_listed($extension, $onlytypes)) { continue; } if (!isset($info['groups']) || empty($info['groups'])) { $others[] = (object) [ 'key' => $extension, 'name' => get_mimetype_description(['filename' => 'fakefile'.$extension]), 'selected' => in_array($extension, $current), 'ext' => $extension, ]; } } core_collator::asort_objects_by_property($others, 'name', core_collator::SORT_NATURAL); if (!empty($others)) { $groups[] = (object) [ 'key' => '', 'name' => get_string('filetypesothers', 'core_form'), 'selectable' => false, 'selected' => false, 'ext' => '', 'types' => array_values($others), 'expanded' => true, ]; } if (empty($onlytypes) and $allowall) { array_unshift($groups, (object) [ 'key' => '*', 'name' => get_string('filetypesany', 'core_form'), 'selectable' => true, 'selected' => in_array('*', $current), 'ext' => null, 'types' => [], 'expanded' => false, ]); } $groups = array_values($groups); return $groups; } /** * Expands the file types into the list of file extensions. * * The groups and mimetypes are expanded into the list of their associated file * extensions. Depending on the $keepgroups and $keepmimetypes, the groups * and mimetypes themselves are either kept in the list or removed. * * @param string|array $types * @param bool $keepgroups Keep the group item in the list after expansion * @param bool $keepmimetypes Keep the mimetype item in the list after expansion * @return array list of extensions and eventually groups and types */ public function expand($types, $keepgroups=false, $keepmimetypes=false) { $expanded = []; foreach ($this->normalize_file_types($types) as $type) { if ($group = $this->is_filetype_group($type)) { foreach ($group->extensions as $ext) { $expanded[$ext] = true; } if ($keepgroups) { $expanded[$type] = true; } } else if ($this->looks_like_mimetype($type)) { // A mime type expands to the associated extensions. foreach (file_get_typegroup('extension', [$type]) as $ext) { $expanded[$ext] = true; } if ($keepmimetypes) { $expanded[$type] = true; } } else { // Single extension expands to itself. $expanded[$type] = true; } } return array_keys($expanded); } /** * Should the file type be considered as a part of the given list. * * If multiple types are provided, all of them must be part of the list. Empty type is part of any list. * Any type is part of an empty list. * * @param string|array $types File type or list of types to be checked. * @param string|array $list An array or string listing the types to check against. * @return boolean */ public function is_listed($types, $list) { return empty($this->get_not_listed($types, $list)); } /** * @deprecated since Moodle 3.10 MDL-69050 - please use {@see is_listed} instead. */ public function is_whitelisted() { throw new \coding_exception('\core_form\filetypes_util::is_whitelisted() has been removed.'); } /** * Returns all types that are not part of the given list. * * This is similar check to the {@see self::is_listed()} but this one actually returns the extra types. * * @param string|array $types File type or list of types to be checked. * @param string|array $list An array or string listing the types to check against. * @return array Types not present in the list. */ public function get_not_listed($types, $list) { $listedtypes = $this->expand($list, true, true); if (empty($listedtypes) || $listedtypes == ['*']) { return []; } $giventypes = $this->normalize_file_types($types); if (empty($giventypes)) { return []; } return array_diff($giventypes, $listedtypes); } /** * @deprecated since Moodle 3.10 MDL-69050 - please use {@see get_not_listed} instead. */ public function get_not_whitelisted() { throw new \coding_exception('\core_form\filetypes_util::get_not_whitelisted() has been removed.'); } /** * Is the given filename of an allowed file type? * * Empty allowlist is interpreted as "any file type is allowed" rather * than "no file can be uploaded". * * @param string $filename the file name * @param string|array $allowlist list of allowed file extensions * @return boolean True if the file type is allowed, false if not */ public function is_allowed_file_type($filename, $allowlist) { $allowedextensions = $this->expand($allowlist); if (empty($allowedextensions) || $allowedextensions == ['*']) { return true; } $haystack = strrev(trim(core_text::strtolower($filename))); foreach ($allowedextensions as $extension) { if (strpos($haystack, strrev($extension)) === 0) { // The file name ends with the extension. return true; } } return false; } /** * Returns file types from the list that are not recognized * * @param string|array $types list of user-defined file types * @return array A list of unknown file types. */ public function get_unknown_file_types($types) { $unknown = []; foreach ($this->normalize_file_types($types) as $type) { if ($type === '*') { // Any file is considered as a known type. continue; } else if ($type === '.xxx') { $unknown[$type] = true; } else if ($this->is_filetype_group($type)) { // The type is a group that exists. continue; } else if ($this->looks_like_mimetype($type)) { // If there's no extension associated with that mimetype, we consider it unknown. if (empty(file_get_typegroup('extension', [$type]))) { $unknown[$type] = true; } } else { $coretypes = core_filetypes::get_types(); $typecleaned = str_replace(".", "", $type); if (empty($coretypes[$typecleaned])) { // If there's no extension, it doesn't exist. $unknown[$type] = true; } } } return array_keys($unknown); } }