From 8cf36e9c81a704c8dee90d0fef9402aec0eaf80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Wed, 5 Apr 2017 01:21:48 +0200 Subject: [PATCH] MDL-53240 filetypes: Introduce admin_setting_filetypes class This new type of admin settings makes use of the filetypes browser but for the admin settings. --- admin/templates/setting_filetypes.mustache | 52 +++++++++ lang/en/form.php | 1 + lib/adminlib.php | 118 +++++++++++++++++++++ lib/form/classes/filetypes_util.php | 29 +++-- lib/form/tests/filetypes_util_test.php | 36 +++++++ lib/upgrade.txt | 7 ++ 6 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 admin/templates/setting_filetypes.mustache diff --git a/admin/templates/setting_filetypes.mustache b/admin/templates/setting_filetypes.mustache new file mode 100644 index 00000000000..7075ea25a4b --- /dev/null +++ b/admin/templates/setting_filetypes.mustache @@ -0,0 +1,52 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_admin/setting_filetypes + + Renders the admin_setting_filetypes setting element. + + Context variables required for this template: + * id - element id + * name - form element name + * value - element value + * descriptions - data for the core_form/filetypes-descriptions template + + Example context (json): + { + "id": "test0", + "name": "test", + "value": ".jpg,.gif", + "descriptions": { + "hasdescriptions": true, + "descriptions": [ + { + "description": "Image (JPEG)", + "extensions": ".jpeg .jpe .jpg" + }, + { + "description": "Image (GIF)", + "extensions": ".gif" + } + ] + } + } +}} +
+ + +
{{#descriptions}}{{>core_form/filetypes-descriptions}}{{/descriptions}}
+
diff --git a/lang/en/form.php b/lang/en/form.php index 79ea736f429..2ed6cbf733c 100644 --- a/lang/en/form.php +++ b/lang/en/form.php @@ -42,6 +42,7 @@ $string['err_numeric'] = 'You must enter a number here.'; $string['err_rangelength'] = 'You must enter between {$a->format[0]} and {$a->format[1]} characters here.'; $string['err_required'] = 'You must supply a value here.'; $string['filetypesany'] = 'All file types'; +$string['filetypesnotwhitelisted'] = 'These file types are not allowed here: {$a}'; $string['filetypesothers'] = 'Other files'; $string['general'] = 'General'; $string['hideadvanced'] = 'Hide advanced'; diff --git a/lib/adminlib.php b/lib/adminlib.php index 74a92b359be..7fc7d302fca 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -10519,3 +10519,121 @@ class admin_setting_scsscode extends admin_setting_configtextarea { return true; } } + + +/** + * Administration setting to define a list of file types. + * + * @copyright 2016 Jonathon Fowler + * @copyright 2017 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class admin_setting_filetypes extends admin_setting_configtext { + + /** @var array Allow selection from these file types only. */ + protected $onlytypes = []; + + /** @var bool Allow selection of 'All file types' (will be stored as '*'). */ + protected $allowall = true; + + /** @var core_form\filetypes_util instance to use as a helper. */ + protected $util = null; + + /** + * Constructor. + * + * @param string $name Unique ascii name like 'mycoresetting' or 'myplugin/mysetting' + * @param string $visiblename Localised label of the setting + * @param string $description Localised description of the setting + * @param string $defaultsetting Default setting value. + * @param array $options Setting widget options, an array with optional keys: + * 'onlytypes' => array Allow selection from these file types only; for example ['onlytypes' => ['web_image']]. + * 'allowall' => bool Allow to select 'All file types', defaults to true. Does not apply if onlytypes are set. + */ + public function __construct($name, $visiblename, $description, $defaultsetting = '', array $options = []) { + + parent::__construct($name, $visiblename, $description, $defaultsetting, PARAM_RAW); + + if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) { + $this->onlytypes = $options['onlytypes']; + } + + if (!$this->onlytypes && array_key_exists('allowall', $options)) { + $this->allowall = (bool)$options['allowall']; + } + + $this->util = new \core_form\filetypes_util(); + } + + /** + * Normalize the user's input and write it to the database as comma separated list. + * + * Comma separated list as a text representation of the array was chosen to + * make this compatible with how the $CFG->courseoverviewfilesext values are stored. + * + * @param string $data Value submitted by the admin. + * @return string Epty string if all good, error message otherwise. + */ + public function write_setting($data) { + return parent::write_setting(implode(',', $this->util->normalize_file_types($data))); + } + + /** + * Validate data before storage + * + * @param string $data The setting values provided by the admin + * @return bool|string True if ok, the string if error found + */ + public function validate($data) { + + // No need to call parent's validation here as we are PARAM_RAW. + + if ($this->util->is_whitelisted($data, $this->onlytypes)) { + return true; + + } else { + $troublemakers = $this->util->get_not_whitelisted($data, $this->onlytypes); + return get_string('filetypesnotwhitelisted', 'core_form', implode(' ', $troublemakers)); + } + } + + /** + * Return an HTML string for the setting element. + * + * @param string $data The current setting value + * @param string $query Admin search query to be highlighted + * @return string HTML to be displayed + */ + public function output_html($data, $query='') { + global $OUTPUT, $PAGE; + + $default = $this->get_defaultsetting(); + $context = (object) [ + 'id' => $this->get_id(), + 'name' => $this->get_full_name(), + 'value' => $data, + 'descriptions' => $this->util->describe_file_types($data), + ]; + $element = $OUTPUT->render_from_template('core_admin/setting_filetypes', $context); + + $PAGE->requires->js_call_amd('core_form/filetypes', 'init', [ + $this->get_id(), + $this->visiblename, + $this->onlytypes, + $this->allowall, + ]); + + return format_admin_setting($this, $this->visiblename, $element, $this->description, true, '', $default, $query); + } + + /** + * Should the values be always displayed in LTR mode? + * + * We always return true here because these values are not RTL compatible. + * + * @return bool True because these values are not RTL compatible. + */ + public function get_force_ltr() { + return true; + } +} diff --git a/lib/form/classes/filetypes_util.php b/lib/form/classes/filetypes_util.php index dc686e2902c..51276edb56f 100644 --- a/lib/form/classes/filetypes_util.php +++ b/lib/form/classes/filetypes_util.php @@ -414,25 +414,42 @@ class filetypes_util { * Should the given file type be considered as a part of the given whitelist. * * If multiple types are provided, all of them must be part of the - * whitelist. + * whitelist. Empty type is part of any whitelist. Any type is part of an + * empty whitelist. * - * @param string $types One or more types in a string (space , or ; separated) - * @param string|array $whitelist an array or string of whitelisted types + * @param string|array $types File types to be checked + * @param string|array $whitelist An array or string of whitelisted types * @return boolean */ public function is_whitelisted($types, $whitelist) { + return empty($this->get_not_whitelisted($types, $whitelist)); + } + + /** + * Returns all types that are not part of the give whitelist. + * + * This is similar check to the {@link self::is_whitelisted()} but this one + * actually returns the extra types. + * + * @param string|array $types File types to be checked + * @param string|array $whitelist An array or string of whitelisted types + * @return array Types not present in the whitelist + */ + public function get_not_whitelisted($types, $whitelist) { $whitelistedtypes = $this->expand($whitelist, true, true); if (empty($whitelistedtypes) || $whitelistedtypes == ['*']) { - return true; + return []; } $giventypes = $this->normalize_file_types($types); - $intersection = array_intersect($giventypes, $whitelistedtypes); + if (empty($giventypes)) { + return []; + } - return !empty($intersection); + return array_diff($giventypes, $whitelistedtypes); } /** diff --git a/lib/form/tests/filetypes_util_test.php b/lib/form/tests/filetypes_util_test.php index 45dec2f9500..d5419cd519a 100644 --- a/lib/form/tests/filetypes_util_test.php +++ b/lib/form/tests/filetypes_util_test.php @@ -219,6 +219,7 @@ class filetypes_util_testcase extends advanced_testcase { $this->assertTrue($util->is_whitelisted('audio', 'text/plain audio video')); $this->assertTrue($util->is_whitelisted('text/plain', 'text/plain audio video')); $this->assertTrue($util->is_whitelisted('jpg jpe jpeg', 'image/jpeg')); + $this->assertTrue($util->is_whitelisted(['jpg', 'jpe', '.png'], 'image')); // These should be intuitively false. $this->assertFalse($util->is_whitelisted('.gif', 'text/plain')); @@ -229,9 +230,44 @@ class filetypes_util_testcase extends advanced_testcase { // Not all documents (and also the group itself) is not a plain text. $this->assertFalse($util->is_whitelisted('document', 'text/plain')); + // This may look wrong at the first sight as you might expect that the + // mimetype should simply map to an extension ... + $this->assertFalse($util->is_whitelisted('image/jpeg', '.jpg')); + + // But it is principally same situation as this (there is no 1:1 mapping). + $this->assertFalse($util->is_whitelisted('.c', '.txt')); + $this->assertTrue($util->is_whitelisted('.txt .c', 'text/plain')); + $this->assertFalse($util->is_whitelisted('text/plain', '.c')); + // Any type is included if the filter is empty. $this->assertTrue($util->is_whitelisted('txt', '')); $this->assertTrue($util->is_whitelisted('txt', '*')); + + // Empty value is part of any whitelist. + $this->assertTrue($util->is_whitelisted('', '.txt')); + } + + /** + * Test getting types not present in a whitelist. + */ + public function test_get_not_whitelisted() { + + $this->resetAfterTest(true); + $util = new filetypes_util(); + + $this->assertEmpty($util->get_not_whitelisted('txt', 'text/plain')); + $this->assertEmpty($util->get_not_whitelisted('txt', '.doc .txt .rtf')); + $this->assertEmpty($util->get_not_whitelisted('txt', 'text/plain')); + $this->assertEmpty($util->get_not_whitelisted(['jpg', 'jpe', 'jpeg'], 'image/jpeg')); + $this->assertEmpty($util->get_not_whitelisted('', 'foo/bar')); + $this->assertEmpty($util->get_not_whitelisted('.foobar', '')); + $this->assertEmpty($util->get_not_whitelisted('.foobar', '*')); + + // Returned list is normalized so extensions have the dot added. + $this->assertContains('.exe', $util->get_not_whitelisted('exe', '.c .h')); + + // If this looks wrong to you, see {@link test_is_whitelisted()} for more details on this behaviour. + $this->assertContains('image/jpeg', $util->get_not_whitelisted('image/jpeg', '.jpg .jpeg')); } /** diff --git a/lib/upgrade.txt b/lib/upgrade.txt index c923bfb4056..23520d7ee8f 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -1,6 +1,13 @@ This files describes API changes in core libraries and APIs, information provided here is intended especially for developers. +=== 3.4 === + +* Added new moodleform element 'filetypes' and new admin setting widget 'admin_setting_filetypes'. These new widgets + allow users to define a list of file types; either by typing them manually or selecting them from a list. The widgets + directly support the syntax used to feed the 'accepted_types' option of the filemanager and filepicker elements. File + types can be specified as extensions (.jpg or just jpg), mime types (text/plain) or groups (image). + === 3.3.1 === * ldap_get_entries_moodle() now always returns lower-cased attribute names in the returned entries.