$temp = new admin_settingpage('langsettings', new lang_string('languagesettings', 'admin'));
$temp->add(new admin_setting_configcheckbox('autolang', new lang_string('autolang', 'admin'), new lang_string('configautolang', 'admin'), 1));
$temp->add(new admin_setting_configselect('lang', new lang_string('lang', 'admin'), new lang_string('configlang', 'admin'), current_language(), get_string_manager()->get_list_of_translations())); // $CFG->lang might be set in installer already, default en is in setup.php
+ $temp->add(new admin_setting_configcheckbox('autolangusercreation', new lang_string('autolangusercreation', 'admin'),
+ new lang_string('configautolangusercreation', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('langmenu', new lang_string('langmenu', 'admin'), new lang_string('configlangmenu', 'admin'), 1));
$temp->add(new admin_setting_langlist());
$temp->add(new admin_setting_configcheckbox('langcache', new lang_string('langcache', 'admin'), new lang_string('langcache_desc', 'admin'), 1));
<?php
+// This file is part of Moodle - https://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Defines settingpages and externalpages under the "server" category.
+ *
+ * @package core
+ * @category admin
+ * @copyright 2006 Martin Dougiamas
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+ // System paths.
+ $temp = new admin_settingpage('systempaths', new lang_string('systempaths', 'admin'));
+ $temp->add(new admin_setting_configexecutable('pathtophp', new lang_string('pathtophp', 'admin'),
+ new lang_string('configpathtophp', 'admin'), ''));
+ $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'),
+ new lang_string('configpathtodu', 'admin'), ''));
+ $temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'),
+ new lang_string('edhelpaspellpath'), ''));
+ $temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'),
+ new lang_string('pathtodot_help', 'admin'), ''));
+ $temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'),
+ new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
+ $temp->add(new admin_setting_configexecutable('pathtopython', new lang_string('pathtopython', 'admin'),
+ new lang_string('pathtopythondesc', 'admin'), ''));
+ $ADMIN->add('server', $temp);
-// This file defines settingpages and externalpages under the "server" category
-
-if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
-
-// "systempaths" settingpage
-$temp = new admin_settingpage('systempaths', new lang_string('systempaths','admin'));
-$temp->add(new admin_setting_configexecutable('pathtophp', new lang_string('pathtophp', 'admin'),
- new lang_string('configpathtophp', 'admin'), ''));
-$temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'), new lang_string('configpathtodu', 'admin'), ''));
-$temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
-$temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
-$temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'), new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
-$temp->add(new admin_setting_configexecutable('pathtopython', new lang_string('pathtopython', 'admin'),
- new lang_string('pathtopythondesc', 'admin'), ''));
-$ADMIN->add('server', $temp);
-
-
-
-// "supportcontact" settingpage
-$temp = new admin_settingpage('supportcontact', new lang_string('supportcontact','admin'));
-$primaryadmin = get_admin();
-if ($primaryadmin) {
- $primaryadminemail = $primaryadmin->email;
- $primaryadminname = fullname($primaryadmin, true);
-} else {
- // no defaults during installation - admin user must be created first
- $primaryadminemail = NULL;
- $primaryadminname = NULL;
-}
-$temp->add(new admin_setting_configtext('supportname', new lang_string('supportname', 'admin'),
- new lang_string('configsupportname', 'admin'), $primaryadminname, PARAM_NOTAGS));
-$setting = new admin_setting_configtext('supportemail', new lang_string('supportemail', 'admin'),
- new lang_string('configsupportemail', 'admin'), $primaryadminemail, PARAM_EMAIL);
-$setting->set_force_ltr(true);
-$temp->add($setting);
-$temp->add(new admin_setting_configtext('supportpage', new lang_string('supportpage', 'admin'), new lang_string('configsupportpage', 'admin'), '', PARAM_URL));
-$ADMIN->add('server', $temp);
-
-
-// "sessionhandling" settingpage
-$temp = new admin_settingpage('sessionhandling', new lang_string('sessionhandling', 'admin'));
-if (empty($CFG->session_handler_class) and $DB->session_lock_supported()) {
- $temp->add(new admin_setting_configcheckbox('dbsessions', new lang_string('dbsessions', 'admin'), new lang_string('configdbsessions', 'admin'), 0));
-}
+ // Support contact.
+ $temp = new admin_settingpage('supportcontact', new lang_string('supportcontact', 'admin'));
+ $primaryadmin = get_admin();
+ if ($primaryadmin) {
+ $primaryadminemail = $primaryadmin->email;
+ $primaryadminname = fullname($primaryadmin, true);
+ } else {
+ // No defaults during installation - admin user must be created first.
+ $primaryadminemail = null;
+ $primaryadminname = null;
+ }
+ $temp->add(new admin_setting_configtext('supportname', new lang_string('supportname', 'admin'),
+ new lang_string('configsupportname', 'admin'), $primaryadminname, PARAM_NOTAGS));
+ $setting = new admin_setting_configtext('supportemail', new lang_string('supportemail', 'admin'),
+ new lang_string('configsupportemail', 'admin'), $primaryadminemail, PARAM_EMAIL);
+ $setting->set_force_ltr(true);
+ $temp->add($setting);
+ $temp->add(new admin_setting_configtext('supportpage', new lang_string('supportpage', 'admin'),
+ new lang_string('configsupportpage', 'admin'), '', PARAM_URL));
+ $ADMIN->add('server', $temp);
-$temp->add(new admin_setting_configduration('sessiontimeout', new lang_string('sessiontimeout', 'admin'),
- new lang_string('configsessiontimeout', 'admin'), 8 * 60 * 60));
-
-$temp->add(new admin_setting_configtext('sessioncookie', new lang_string('sessioncookie', 'admin'), new lang_string('configsessioncookie', 'admin'), '', PARAM_ALPHANUM));
-$temp->add(new admin_setting_configtext('sessioncookiepath', new lang_string('sessioncookiepath', 'admin'), new lang_string('configsessioncookiepath', 'admin'), '', PARAM_RAW));
-$temp->add(new admin_setting_configtext('sessioncookiedomain', new lang_string('sessioncookiedomain', 'admin'), new lang_string('configsessioncookiedomain', 'admin'), '', PARAM_RAW, 50));
-$ADMIN->add('server', $temp);
-
-
-// "stats" settingpage
-$temp = new admin_settingpage('stats', new lang_string('stats'), 'moodle/site:config', empty($CFG->enablestats));
-$temp->add(new admin_setting_configselect('statsfirstrun', new lang_string('statsfirstrun', 'admin'), new lang_string('configstatsfirstrun', 'admin'), 'none', array('none' => new lang_string('none'),
- 60*60*24*7 => new lang_string('numweeks','moodle',1),
- 60*60*24*14 => new lang_string('numweeks','moodle',2),
- 60*60*24*21 => new lang_string('numweeks','moodle',3),
- 60*60*24*28 => new lang_string('nummonths','moodle',1),
- 60*60*24*56 => new lang_string('nummonths','moodle',2),
- 60*60*24*84 => new lang_string('nummonths','moodle',3),
- 60*60*24*112 => new lang_string('nummonths','moodle',4),
- 60*60*24*140 => new lang_string('nummonths','moodle',5),
- 60*60*24*168 => new lang_string('nummonths','moodle',6),
- 'all' => new lang_string('all') )));
-$temp->add(new admin_setting_configselect('statsmaxruntime', new lang_string('statsmaxruntime', 'admin'), new lang_string('configstatsmaxruntime3', 'admin'), 0, array(0 => new lang_string('untilcomplete'),
- 60*30 => '10 '.new lang_string('minutes'),
- 60*30 => '30 '.new lang_string('minutes'),
- 60*60 => '1 '.new lang_string('hour'),
- 60*60*2 => '2 '.new lang_string('hours'),
- 60*60*3 => '3 '.new lang_string('hours'),
- 60*60*4 => '4 '.new lang_string('hours'),
- 60*60*5 => '5 '.new lang_string('hours'),
- 60*60*6 => '6 '.new lang_string('hours'),
- 60*60*7 => '7 '.new lang_string('hours'),
- 60*60*8 => '8 '.new lang_string('hours') )));
-$temp->add(new admin_setting_configtext('statsruntimedays', new lang_string('statsruntimedays', 'admin'), new lang_string('configstatsruntimedays', 'admin'), 31, PARAM_INT));
-$temp->add(new admin_setting_configtext('statsuserthreshold', new lang_string('statsuserthreshold', 'admin'), new lang_string('configstatsuserthreshold', 'admin'), 0, PARAM_INT));
-$ADMIN->add('server', $temp);
-
-
-// "http" settingpage
-$temp = new admin_settingpage('http', new lang_string('http', 'admin'));
-$temp->add(new admin_setting_configcheckbox('slasharguments', new lang_string('slasharguments', 'admin'), new lang_string('configslasharguments', 'admin'), 1));
-$temp->add(new admin_setting_heading('reverseproxy', new lang_string('reverseproxy', 'admin'), '', ''));
-$options = array(
- 0 => 'HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
- GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
- GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR => 'HTTP_CLIENT, REMOTE_ADDR',
- GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
-$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
- new lang_string('configgetremoteaddrconf', 'admin'),
- GETREMOTEADDR_SKIP_DEFAULT, $options));
-$temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'), new lang_string('configreverseproxyignore', 'admin'), ''));
-
-$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
-$temp->add(new admin_setting_configtext('proxyhost', new lang_string('proxyhost', 'admin'), new lang_string('configproxyhost', 'admin'), '', PARAM_HOST));
-$temp->add(new admin_setting_configtext('proxyport', new lang_string('proxyport', 'admin'), new lang_string('configproxyport', 'admin'), 0, PARAM_INT));
-$options = array('HTTP'=>'HTTP');
-if (defined('CURLPROXY_SOCKS5')) {
- $options['SOCKS5'] = 'SOCKS5';
-}
-$temp->add(new admin_setting_configselect('proxytype', new lang_string('proxytype', 'admin'), new lang_string('configproxytype','admin'), 'HTTP', $options));
-$temp->add(new admin_setting_configtext('proxyuser', new lang_string('proxyuser', 'admin'), new lang_string('configproxyuser', 'admin'), ''));
-$temp->add(new admin_setting_configpasswordunmask('proxypassword', new lang_string('proxypassword', 'admin'), new lang_string('configproxypassword', 'admin'), ''));
-$temp->add(new admin_setting_configtext('proxybypass', new lang_string('proxybypass', 'admin'), new lang_string('configproxybypass', 'admin'), 'localhost, 127.0.0.1'));
-$ADMIN->add('server', $temp);
-
-$temp = new admin_settingpage('maintenancemode', new lang_string('sitemaintenancemode', 'admin'));
-$options = array(0=>new lang_string('disable'), 1=>new lang_string('enable'));
-$temp->add(new admin_setting_configselect('maintenance_enabled', new lang_string('sitemaintenancemode', 'admin'),
- new lang_string('helpsitemaintenance', 'admin'), 0, $options));
-$temp->add(new admin_setting_confightmleditor('maintenance_message', new lang_string('optionalmaintenancemessage', 'admin'),
- '', ''));
-$ADMIN->add('server', $temp);
-
-$temp = new admin_settingpage('cleanup', new lang_string('cleanup', 'admin'));
-$temp->add(new admin_setting_configselect('deleteunconfirmed', new lang_string('deleteunconfirmed', 'admin'), new lang_string('configdeleteunconfirmed', 'admin'), 168, array(0 => new lang_string('never'),
- 168 => new lang_string('numdays', '', 7),
- 144 => new lang_string('numdays', '', 6),
- 120 => new lang_string('numdays', '', 5),
- 96 => new lang_string('numdays', '', 4),
- 72 => new lang_string('numdays', '', 3),
- 48 => new lang_string('numdays', '', 2),
- 24 => new lang_string('numdays', '', 1),
- 12 => new lang_string('numhours', '', 12),
- 6 => new lang_string('numhours', '', 6),
- 1 => new lang_string('numhours', '', 1))));
-
-$temp->add(new admin_setting_configselect('deleteincompleteusers', new lang_string('deleteincompleteusers', 'admin'), new lang_string('configdeleteincompleteusers', 'admin'), 0, array(0 => new lang_string('never'),
- 168 => new lang_string('numdays', '', 7),
- 144 => new lang_string('numdays', '', 6),
- 120 => new lang_string('numdays', '', 5),
- 96 => new lang_string('numdays', '', 4),
- 72 => new lang_string('numdays', '', 3),
- 48 => new lang_string('numdays', '', 2),
- 24 => new lang_string('numdays', '', 1))));
-
-
-$temp->add(new admin_setting_configcheckbox('disablegradehistory', new lang_string('disablegradehistory', 'grades'),
- new lang_string('disablegradehistory_help', 'grades'), 0));
-
-$temp->add(new admin_setting_configselect('gradehistorylifetime', new lang_string('gradehistorylifetime', 'grades'),
- new lang_string('gradehistorylifetime_help', 'grades'), 0, array(0 => new lang_string('neverdeletehistory', 'grades'),
- 1000 => new lang_string('numdays', '', 1000),
- 365 => new lang_string('numdays', '', 365),
- 180 => new lang_string('numdays', '', 180),
- 150 => new lang_string('numdays', '', 150),
- 120 => new lang_string('numdays', '', 120),
- 90 => new lang_string('numdays', '', 90),
- 60 => new lang_string('numdays', '', 60),
- 30 => new lang_string('numdays', '', 30))));
-
-$temp->add(new admin_setting_configselect('tempdatafoldercleanup', new lang_string('tempdatafoldercleanup', 'admin'),
- new lang_string('configtempdatafoldercleanup', 'admin'), 168, array(
+ // Session handling.
+ $temp = new admin_settingpage('sessionhandling', new lang_string('sessionhandling', 'admin'));
+ if (empty($CFG->session_handler_class) and $DB->session_lock_supported()) {
+ $temp->add(new admin_setting_configcheckbox('dbsessions', new lang_string('dbsessions', 'admin'),
+ new lang_string('configdbsessions', 'admin'), 0));
+ }
+
+ $temp->add(new admin_setting_configduration('sessiontimeout', new lang_string('sessiontimeout', 'admin'),
+ new lang_string('configsessiontimeout', 'admin'), 8 * 60 * 60));
+
+ $temp->add(new admin_setting_configtext('sessioncookie', new lang_string('sessioncookie', 'admin'),
+ new lang_string('configsessioncookie', 'admin'), '', PARAM_ALPHANUM));
+ $temp->add(new admin_setting_configtext('sessioncookiepath', new lang_string('sessioncookiepath', 'admin'),
+ new lang_string('configsessioncookiepath', 'admin'), '', PARAM_RAW));
+ $temp->add(new admin_setting_configtext('sessioncookiedomain', new lang_string('sessioncookiedomain', 'admin'),
+ new lang_string('configsessioncookiedomain', 'admin'), '', PARAM_RAW, 50));
+ $ADMIN->add('server', $temp);
+
+ // Statistics.
+ $temp = new admin_settingpage('stats', new lang_string('stats'), 'moodle/site:config', empty($CFG->enablestats));
+ $temp->add(new admin_setting_configselect('statsfirstrun', new lang_string('statsfirstrun', 'admin'),
+ new lang_string('configstatsfirstrun', 'admin'), 'none',
+ [
+ 'none' => new lang_string('none'),
+ 60 * 60 * 24 * 7 => new lang_string('numweeks', 'moodle', 1),
+ 60 * 60 * 24 * 14 => new lang_string('numweeks', 'moodle', 2),
+ 60 * 60 * 24 * 21 => new lang_string('numweeks', 'moodle', 3),
+ 60 * 60 * 24 * 28 => new lang_string('nummonths', 'moodle', 1),
+ 60 * 60 * 24 * 56 => new lang_string('nummonths', 'moodle', 2),
+ 60 * 60 * 24 * 84 => new lang_string('nummonths', 'moodle', 3),
+ 60 * 60 * 24 * 112 => new lang_string('nummonths', 'moodle', 4),
+ 60 * 60 * 24 * 140 => new lang_string('nummonths', 'moodle', 5),
+ 60 * 60 * 24 * 168 => new lang_string('nummonths', 'moodle', 6),
+ 'all' => new lang_string('all')
+ ]
+ ));
+ $temp->add(new admin_setting_configselect('statsmaxruntime', new lang_string('statsmaxruntime', 'admin'),
+ new lang_string('configstatsmaxruntime3', 'admin'), 0,
+ [
+ 0 => new lang_string('untilcomplete'),
+ 60 * 30 => '10 ' . new lang_string('minutes'),
+ 60 * 30 => '30 ' . new lang_string('minutes'),
+ 60 * 60 => '1 ' . new lang_string('hour'),
+ 60 * 60 * 2 => '2 ' . new lang_string('hours'),
+ 60 * 60 * 3 => '3 ' . new lang_string('hours'),
+ 60 * 60 * 4 => '4 ' . new lang_string('hours'),
+ 60 * 60 * 5 => '5 ' . new lang_string('hours'),
+ 60 * 60 * 6 => '6 ' . new lang_string('hours'),
+ 60 * 60 * 7 => '7 ' . new lang_string('hours'),
+ 60 * 60 * 8 => '8 ' . new lang_string('hours'),
+ ]
+ ));
+ $temp->add(new admin_setting_configtext('statsruntimedays', new lang_string('statsruntimedays', 'admin'),
+ new lang_string('configstatsruntimedays', 'admin'), 31, PARAM_INT));
+ $temp->add(new admin_setting_configtext('statsuserthreshold', new lang_string('statsuserthreshold', 'admin'),
+ new lang_string('configstatsuserthreshold', 'admin'), 0, PARAM_INT));
+ $ADMIN->add('server', $temp);
+
+ // HTTP.
+ $temp = new admin_settingpage('http', new lang_string('http', 'admin'));
+ $temp->add(new admin_setting_configcheckbox('slasharguments', new lang_string('slasharguments', 'admin'),
+ new lang_string('configslasharguments', 'admin'), 1));
+ $temp->add(new admin_setting_heading('reverseproxy', new lang_string('reverseproxy', 'admin'), '', ''));
+ $options = [
+ 0 => 'HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
+ GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
+ GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR => 'HTTP_CLIENT, REMOTE_ADDR',
+ GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR | GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR'
+ ];
+ $temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
+ new lang_string('configgetremoteaddrconf', 'admin'),
+ GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR | GETREMOTEADDR_SKIP_HTTP_CLIENT_IP, $options));
+ $temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'),
+ new lang_string('configreverseproxyignore', 'admin'), ''));
+
+ $temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'),
+ new lang_string('webproxyinfo', 'admin')));
+ $temp->add(new admin_setting_configtext('proxyhost', new lang_string('proxyhost', 'admin'),
+ new lang_string('configproxyhost', 'admin'), '', PARAM_HOST));
+ $temp->add(new admin_setting_configtext('proxyport', new lang_string('proxyport', 'admin'),
+ new lang_string('configproxyport', 'admin'), 0, PARAM_INT));
+ $options = ['HTTP' => 'HTTP'];
+ if (defined('CURLPROXY_SOCKS5')) {
+ $options['SOCKS5'] = 'SOCKS5';
+ }
+ $temp->add(new admin_setting_configselect('proxytype', new lang_string('proxytype', 'admin'),
+ new lang_string('configproxytype', 'admin'), 'HTTP', $options));
+ $temp->add(new admin_setting_configtext('proxyuser', new lang_string('proxyuser', 'admin'),
+ new lang_string('configproxyuser', 'admin'), ''));
+ $temp->add(new admin_setting_configpasswordunmask('proxypassword', new lang_string('proxypassword', 'admin'),
+ new lang_string('configproxypassword', 'admin'), ''));
+ $temp->add(new admin_setting_configtext('proxybypass', new lang_string('proxybypass', 'admin'),
+ new lang_string('configproxybypass', 'admin'), 'localhost, 127.0.0.1'));
+ $ADMIN->add('server', $temp);
+
+ $temp = new admin_settingpage('maintenancemode', new lang_string('sitemaintenancemode', 'admin'));
+ $options = [0 => new lang_string('disable'), 1 => new lang_string('enable')];
+ $temp->add(new admin_setting_configselect('maintenance_enabled', new lang_string('sitemaintenancemode', 'admin'),
+ new lang_string('helpsitemaintenance', 'admin'), 0, $options));
+ $temp->add(new admin_setting_confightmleditor('maintenance_message', new lang_string('optionalmaintenancemessage', 'admin'),
+ '', ''));
+ $ADMIN->add('server', $temp);
+
+ // Cleanup.
+ $temp = new admin_settingpage('cleanup', new lang_string('cleanup', 'admin'));
+ $temp->add(new admin_setting_configselect('deleteunconfirmed', new lang_string('deleteunconfirmed', 'admin'),
+ new lang_string('configdeleteunconfirmed', 'admin'), 168,
+ [
+ 0 => new lang_string('never'),
+ 168 => new lang_string('numdays', '', 7),
+ 144 => new lang_string('numdays', '', 6),
+ 120 => new lang_string('numdays', '', 5),
+ 96 => new lang_string('numdays', '', 4),
+ 72 => new lang_string('numdays', '', 3),
+ 48 => new lang_string('numdays', '', 2),
+ 24 => new lang_string('numdays', '', 1),
+ 12 => new lang_string('numhours', '', 12),
+ 6 => new lang_string('numhours', '', 6),
+ 1 => new lang_string('numhours', '', 1),
+ ]
+ ));
+
+ $temp->add(new admin_setting_configselect('deleteincompleteusers', new lang_string('deleteincompleteusers', 'admin'),
+ new lang_string('configdeleteincompleteusers', 'admin'), 0,
+ [
+ 0 => new lang_string('never'),
+ 168 => new lang_string('numdays', '', 7),
+ 144 => new lang_string('numdays', '', 6),
+ 120 => new lang_string('numdays', '', 5),
+ 96 => new lang_string('numdays', '', 4),
+ 72 => new lang_string('numdays', '', 3),
+ 48 => new lang_string('numdays', '', 2),
+ 24 => new lang_string('numdays', '', 1),
+ ]
+ ));
+
+ $temp->add(new admin_setting_configcheckbox('disablegradehistory', new lang_string('disablegradehistory', 'grades'),
+ new lang_string('disablegradehistory_help', 'grades'), 0));
+
+ $temp->add(new admin_setting_configselect('gradehistorylifetime', new lang_string('gradehistorylifetime', 'grades'),
+ new lang_string('gradehistorylifetime_help', 'grades'), 0,
+ [
+ 0 => new lang_string('neverdeletehistory', 'grades'),
+ 1000 => new lang_string('numdays', '', 1000),
+ 365 => new lang_string('numdays', '', 365),
+ 180 => new lang_string('numdays', '', 180),
+ 150 => new lang_string('numdays', '', 150),
+ 120 => new lang_string('numdays', '', 120),
+ 90 => new lang_string('numdays', '', 90),
+ 60 => new lang_string('numdays', '', 60),
+ 30 => new lang_string('numdays', '', 30),
+ ]
+ ));
+
+ $temp->add(new admin_setting_configselect('tempdatafoldercleanup', new lang_string('tempdatafoldercleanup', 'admin'),
+ new lang_string('configtempdatafoldercleanup', 'admin'), 168,
+ [
1 => new lang_string('numhours', '', 1),
3 => new lang_string('numhours', '', 3),
6 => new lang_string('numhours', '', 6),
24 => new lang_string('numhours', '', 24),
48 => new lang_string('numdays', '', 2),
168 => new lang_string('numdays', '', 7),
-)));
+ ]
+ ));
-$ADMIN->add('server', $temp);
+ $ADMIN->add('server', $temp);
$temp->add(new admin_setting_configduration('filescleanupperiod',
new lang_string('filescleanupperiod', 'admin'),
new lang_string('filescleanupperiod_help', 'admin'),
86400));
-$ADMIN->add('server', new admin_externalpage('environment', new lang_string('environment','admin'), "$CFG->wwwroot/$CFG->admin/environment.php"));
-$ADMIN->add('server', new admin_externalpage('phpinfo', new lang_string('phpinfo'), "$CFG->wwwroot/$CFG->admin/phpinfo.php"));
-$ADMIN->add('server', new admin_externalpage('testoutgoingmailconf', new lang_string('testoutgoingmailconf', 'admin'),
- new moodle_url("$CFG->wwwroot/$CFG->admin/testoutgoingmailconf.php"), 'moodle/site:config', true));
+ // Environment.
+ $ADMIN->add('server', new admin_externalpage('environment', new lang_string('environment', 'admin'),
+ "{$CFG->wwwroot}/{$CFG->admin}/environment.php"));
+
+ // PHP info.
+ $ADMIN->add('server', new admin_externalpage('phpinfo', new lang_string('phpinfo'),
+ "{$CFG->wwwroot}/{$CFG->admin}/phpinfo.php"));
+
+ // Test outgoing mail configuration (hidden, accessed via direct link from the settings page).
+ $ADMIN->add('server', new admin_externalpage('testoutgoingmailconf', new lang_string('testoutgoingmailconf', 'admin'),
+ new moodle_url('/admin/testoutgoingmailconf.php'), 'moodle/site:config', true));
+
+ // Performance.
+ $temp = new admin_settingpage('performance', new lang_string('performance', 'admin'));
+
+ // Memory limit options for large administration tasks.
+ $memoryoptions = [
+ '64M' => '64M',
+ '128M' => '128M',
+ '256M' => '256M',
+ '512M' => '512M',
+ '1024M' => '1024M',
+ '2048M' => '2048M',
+ ];
+
+ // Allow larger memory usage for 64-bit sites only.
+ if (PHP_INT_SIZE === 8) {
+ $memoryoptions['3072M'] = '3072M';
+ $memoryoptions['4096M'] = '4096M';
+ }
+
+ $temp->add(new admin_setting_configselect('extramemorylimit', new lang_string('extramemorylimit', 'admin'),
+ new lang_string('configextramemorylimit', 'admin'), '512M', $memoryoptions));
+
+ $temp->add(new admin_setting_configtext('maxtimelimit', new lang_string('maxtimelimit', 'admin'),
+ new lang_string('maxtimelimit_desc', 'admin'), 0, PARAM_INT));
+ $temp->add(new admin_setting_configtext('curlcache', new lang_string('curlcache', 'admin'),
+ new lang_string('configcurlcache', 'admin'), 120, PARAM_INT));
-// "performance" settingpage
-$temp = new admin_settingpage('performance', new lang_string('performance', 'admin'));
+ $temp->add(new admin_setting_configtext('curltimeoutkbitrate', new lang_string('curltimeoutkbitrate', 'admin'),
+ new lang_string('curltimeoutkbitrate_help', 'admin'), 56, PARAM_INT));
-// Memory limit options for large administration tasks.
-$memoryoptions = array(
- '64M' => '64M',
- '128M' => '128M',
- '256M' => '256M',
- '512M' => '512M',
- '1024M' => '1024M',
- '2048M' => '2048M');
+ $ADMIN->add('server', $temp);
-// Allow larger memory usage for 64-bit sites only.
-if (PHP_INT_SIZE === 8) {
- $memoryoptions['3072M'] = '3072M';
- $memoryoptions['4096M'] = '4096M';
-}
+ // Tasks.
+ $ADMIN->add('server', new admin_category('taskconfig', new lang_string('taskadmintitle', 'admin')));
-$temp->add(new admin_setting_configselect('extramemorylimit', new lang_string('extramemorylimit', 'admin'),
- new lang_string('configextramemorylimit', 'admin'), '512M',
- $memoryoptions));
-$temp->add(new admin_setting_configtext('maxtimelimit', new lang_string('maxtimelimit', 'admin'),
- new lang_string('maxtimelimit_desc', 'admin'), 0, PARAM_INT));
+ // Task processing.
+ $temp = new admin_settingpage('taskprocessing', new lang_string('taskprocessing', 'admin'));
-$temp->add(new admin_setting_configtext('curlcache', new lang_string('curlcache', 'admin'),
- new lang_string('configcurlcache', 'admin'), 120, PARAM_INT));
-
-$temp->add(new admin_setting_configtext('curltimeoutkbitrate', new lang_string('curltimeoutkbitrate', 'admin'),
- new lang_string('curltimeoutkbitrate_help', 'admin'), 56, PARAM_INT));
-
-$ADMIN->add('server', $temp);
-
-
-$ADMIN->add('server', new admin_category('taskconfig', new lang_string('taskadmintitle', 'admin')));
-$temp = new admin_settingpage('taskprocessing', new lang_string('taskprocessing','admin'));
-
-$setting = new admin_setting_configcheckbox(
- 'cron_enabled',
- new lang_string('cron_enabled', 'admin'),
- new lang_string('cron_enabled_desc', 'admin'),
- 1
-);
-$setting->set_updatedcallback('theme_reset_static_caches');
-$temp->add($setting);
-
-$temp->add(
- new admin_setting_configtext(
- 'task_scheduled_concurrency_limit',
- new lang_string('task_scheduled_concurrency_limit', 'admin'),
- new lang_string('task_scheduled_concurrency_limit_desc', 'admin'),
- 3,
- PARAM_INT
- )
-);
-
-$temp->add(
- new admin_setting_configduration(
- 'task_scheduled_max_runtime',
- new lang_string('task_scheduled_max_runtime', 'admin'),
- new lang_string('task_scheduled_max_runtime_desc', 'admin'),
- 30 * MINSECS
- )
-);
-
-$temp->add(
- new admin_setting_configtext(
- 'task_adhoc_concurrency_limit',
- new lang_string('task_adhoc_concurrency_limit', 'admin'),
- new lang_string('task_adhoc_concurrency_limit_desc', 'admin'),
- 3,
- PARAM_INT
- )
-);
-
-$temp->add(
- new admin_setting_configduration(
- 'task_adhoc_max_runtime',
- new lang_string('task_adhoc_max_runtime', 'admin'),
- new lang_string('task_adhoc_max_runtime_desc', 'admin'),
- 30 * MINSECS
- )
-);
-$ADMIN->add('taskconfig', $temp);
-
-$temp = new admin_settingpage('tasklogging', new lang_string('tasklogging','admin'));
-$temp->add(
- new admin_setting_configselect(
- 'task_logmode',
- new lang_string('task_logmode', 'admin'),
- new lang_string('task_logmode_desc', 'admin'),
- \core\task\logmanager::MODE_ALL,
- [
- \core\task\logmanager::MODE_ALL => new lang_string('task_logmode_all', 'admin'),
- \core\task\logmanager::MODE_FAILONLY => new lang_string('task_logmode_failonly', 'admin'),
- \core\task\logmanager::MODE_NONE => new lang_string('task_logmode_none', 'admin'),
- ]
- )
-);
-$temp->add(
- new admin_setting_configcheckbox(
- 'task_logtostdout',
- new lang_string('task_logtostdout', 'admin'),
- new lang_string('task_logtostdout_desc', 'admin'),
+ $setting = new admin_setting_configcheckbox(
+ 'cron_enabled',
+ new lang_string('cron_enabled', 'admin'),
+ new lang_string('cron_enabled_desc', 'admin'),
1
- )
-);
+ );
+ $setting->set_updatedcallback('theme_reset_static_caches');
+ $temp->add($setting);
+
+ $temp->add(
+ new admin_setting_configtext(
+ 'task_scheduled_concurrency_limit',
+ new lang_string('task_scheduled_concurrency_limit', 'admin'),
+ new lang_string('task_scheduled_concurrency_limit_desc', 'admin'),
+ 3,
+ PARAM_INT
+ )
+ );
-if (\core\task\logmanager::uses_standard_settings()) {
$temp->add(
new admin_setting_configduration(
- 'task_logretention',
- new \lang_string('task_logretention', 'admin'),
- new \lang_string('task_logretention_desc', 'admin'),
- 28 * DAYSECS
+ 'task_scheduled_max_runtime',
+ new lang_string('task_scheduled_max_runtime', 'admin'),
+ new lang_string('task_scheduled_max_runtime_desc', 'admin'),
+ 30 * MINSECS
)
);
$temp->add(
new admin_setting_configtext(
- 'task_logretainruns',
- new \lang_string('task_logretainruns', 'admin'),
- new \lang_string('task_logretainruns_desc', 'admin'),
- 20,
+ 'task_adhoc_concurrency_limit',
+ new lang_string('task_adhoc_concurrency_limit', 'admin'),
+ new lang_string('task_adhoc_concurrency_limit_desc', 'admin'),
+ 3,
PARAM_INT
)
);
-}
-$ADMIN->add('taskconfig', $temp);
-if (\core\task\logmanager::uses_standard_settings()) {
- $ADMIN->add('taskconfig', new admin_externalpage(
- 'tasklogs',
- new lang_string('tasklogs','admin'),
- "{$CFG->wwwroot}/{$CFG->admin}/tasklogs.php"
- ));
-}
+ $temp->add(
+ new admin_setting_configduration(
+ 'task_adhoc_max_runtime',
+ new lang_string('task_adhoc_max_runtime', 'admin'),
+ new lang_string('task_adhoc_max_runtime_desc', 'admin'),
+ 30 * MINSECS
+ )
+ );
+ $ADMIN->add('taskconfig', $temp);
-// E-mail settings.
-$ADMIN->add('server', new admin_category('email', new lang_string('categoryemail', 'admin')));
-
-$temp = new admin_settingpage('outgoingmailconfig', new lang_string('outgoingmailconfig', 'admin'));
-
-$temp->add(new admin_setting_heading('smtpheading', new lang_string('smtp', 'admin'),
- new lang_string('smtpdetail', 'admin')));
-$temp->add(new admin_setting_configtext('smtphosts', new lang_string('smtphosts', 'admin'),
- new lang_string('configsmtphosts', 'admin'), '', PARAM_RAW));
-$options = array('' => new lang_string('none', 'admin'), 'ssl' => 'SSL', 'tls' => 'TLS');
-$temp->add(new admin_setting_configselect('smtpsecure', new lang_string('smtpsecure', 'admin'),
- new lang_string('configsmtpsecure', 'admin'), '', $options));
-$authtypeoptions = array('LOGIN' => 'LOGIN', 'PLAIN' => 'PLAIN', 'NTLM' => 'NTLM', 'CRAM-MD5' => 'CRAM-MD5');
-$temp->add(new admin_setting_configselect('smtpauthtype', new lang_string('smtpauthtype', 'admin'),
- new lang_string('configsmtpauthtype', 'admin'), 'LOGIN', $authtypeoptions));
-$temp->add(new admin_setting_configtext('smtpuser', new lang_string('smtpuser', 'admin'),
- new lang_string('configsmtpuser', 'admin'), '', PARAM_NOTAGS));
-$temp->add(new admin_setting_configpasswordunmask('smtppass', new lang_string('smtppass', 'admin'),
- new lang_string('configsmtpuser', 'admin'), ''));
-$temp->add(new admin_setting_configtext('smtpmaxbulk', new lang_string('smtpmaxbulk', 'admin'),
- new lang_string('configsmtpmaxbulk', 'admin'), 1, PARAM_INT));
-$temp->add(new admin_setting_heading('noreplydomainheading', new lang_string('noreplydomain', 'admin'),
+ // Task log configuration.
+ $temp = new admin_settingpage('tasklogging', new lang_string('tasklogging', 'admin'));
+ $temp->add(
+ new admin_setting_configselect(
+ 'task_logmode',
+ new lang_string('task_logmode', 'admin'),
+ new lang_string('task_logmode_desc', 'admin'),
+ \core\task\logmanager::MODE_ALL,
+ [
+ \core\task\logmanager::MODE_ALL => new lang_string('task_logmode_all', 'admin'),
+ \core\task\logmanager::MODE_FAILONLY => new lang_string('task_logmode_failonly', 'admin'),
+ \core\task\logmanager::MODE_NONE => new lang_string('task_logmode_none', 'admin'),
+ ]
+ )
+ );
+ $temp->add(
+ new admin_setting_configcheckbox(
+ 'task_logtostdout',
+ new lang_string('task_logtostdout', 'admin'),
+ new lang_string('task_logtostdout_desc', 'admin'),
+ 1
+ )
+ );
+
+ if (\core\task\logmanager::uses_standard_settings()) {
+ $temp->add(
+ new admin_setting_configduration(
+ 'task_logretention',
+ new \lang_string('task_logretention', 'admin'),
+ new \lang_string('task_logretention_desc', 'admin'),
+ 28 * DAYSECS
+ )
+ );
+
+ $temp->add(
+ new admin_setting_configtext(
+ 'task_logretainruns',
+ new \lang_string('task_logretainruns', 'admin'),
+ new \lang_string('task_logretainruns_desc', 'admin'),
+ 20,
+ PARAM_INT
+ )
+ );
+ }
+ $ADMIN->add('taskconfig', $temp);
+
+ // Task logs.
+ if (\core\task\logmanager::uses_standard_settings()) {
+ $ADMIN->add('taskconfig', new admin_externalpage(
+ 'tasklogs',
+ new lang_string('tasklogs', 'admin'),
+ "{$CFG->wwwroot}/{$CFG->admin}/tasklogs.php"
+ ));
+ }
+
+ // Email.
+ $ADMIN->add('server', new admin_category('email', new lang_string('categoryemail', 'admin')));
+
+ // Outgoing mail configuration.
+ $temp = new admin_settingpage('outgoingmailconfig', new lang_string('outgoingmailconfig', 'admin'));
+
+ $temp->add(new admin_setting_heading('smtpheading', new lang_string('smtp', 'admin'),
+ new lang_string('smtpdetail', 'admin')));
+
+ $temp->add(new admin_setting_configtext('smtphosts', new lang_string('smtphosts', 'admin'),
+ new lang_string('configsmtphosts', 'admin'), '', PARAM_RAW));
+
+ $options = [
+ '' => new lang_string('none', 'admin'),
+ 'ssl' => 'SSL',
+ 'tls' => 'TLS',
+ ];
+
+ $temp->add(new admin_setting_configselect('smtpsecure', new lang_string('smtpsecure', 'admin'),
+ new lang_string('configsmtpsecure', 'admin'), '', $options));
+
+ $authtypeoptions = [
+ 'LOGIN' => 'LOGIN',
+ 'PLAIN' => 'PLAIN',
+ 'NTLM' => 'NTLM',
+ 'CRAM-MD5' => 'CRAM-MD5',
+ ];
+
+ $temp->add(new admin_setting_configselect('smtpauthtype', new lang_string('smtpauthtype', 'admin'),
+ new lang_string('configsmtpauthtype', 'admin'), 'LOGIN', $authtypeoptions));
+
+ $temp->add(new admin_setting_configtext('smtpuser', new lang_string('smtpuser', 'admin'),
+ new lang_string('configsmtpuser', 'admin'), '', PARAM_NOTAGS));
+
+ $temp->add(new admin_setting_configpasswordunmask('smtppass', new lang_string('smtppass', 'admin'),
+ new lang_string('configsmtpuser', 'admin'), ''));
+
+ $temp->add(new admin_setting_configtext('smtpmaxbulk', new lang_string('smtpmaxbulk', 'admin'),
+ new lang_string('configsmtpmaxbulk', 'admin'), 1, PARAM_INT));
+
+ $temp->add(new admin_setting_heading('noreplydomainheading', new lang_string('noreplydomain', 'admin'),
new lang_string('noreplydomaindetail', 'admin')));
-$temp->add(new admin_setting_configtext('noreplyaddress', new lang_string('noreplyaddress', 'admin'),
- new lang_string('confignoreplyaddress', 'admin'), 'noreply@' . get_host_from_url($CFG->wwwroot), PARAM_EMAIL));
-$temp->add(new admin_setting_configtextarea('allowedemaildomains',
+
+ $temp->add(new admin_setting_configtext('noreplyaddress', new lang_string('noreplyaddress', 'admin'),
+ new lang_string('confignoreplyaddress', 'admin'), 'noreply@' . get_host_from_url($CFG->wwwroot), PARAM_EMAIL));
+
+ $temp->add(new admin_setting_configtextarea('allowedemaildomains',
new lang_string('allowedemaildomains', 'admin'),
new lang_string('configallowedemaildomains', 'admin'),
''));
-$url = new moodle_url('/admin/testoutgoingmailconf.php');
-$link = html_writer::link($url, get_string('testoutgoingmailconf', 'admin'));
-$temp->add(new admin_setting_heading('testoutgoinmailc', new lang_string('testoutgoingmailconf', 'admin'),
+
+ $url = new moodle_url('/admin/testoutgoingmailconf.php');
+ $link = html_writer::link($url, get_string('testoutgoingmailconf', 'admin'));
+ $temp->add(new admin_setting_heading('testoutgoinmailc', new lang_string('testoutgoingmailconf', 'admin'),
new lang_string('testoutgoingmaildetail', 'admin', $link)));
-$temp->add(new admin_setting_heading('emaildoesnotfit', new lang_string('doesnotfit', 'admin'),
+
+ $temp->add(new admin_setting_heading('emaildoesnotfit', new lang_string('doesnotfit', 'admin'),
new lang_string('doesnotfitdetail', 'admin')));
-$charsets = get_list_of_charsets();
-unset($charsets['UTF-8']); // Not needed here.
-$options = array();
-$options['0'] = 'UTF-8';
-$options = array_merge($options, $charsets);
-$temp->add(new admin_setting_configselect('sitemailcharset', new lang_string('sitemailcharset', 'admin'),
- new lang_string('configsitemailcharset','admin'), '0', $options));
-$temp->add(new admin_setting_configcheckbox('allowusermailcharset', new lang_string('allowusermailcharset', 'admin'),
- new lang_string('configallowusermailcharset', 'admin'), 0));
-$temp->add(new admin_setting_configcheckbox('allowattachments', new lang_string('allowattachments', 'admin'),
- new lang_string('configallowattachments', 'admin'), 1));
-$options = array('LF' => 'LF', 'CRLF' => 'CRLF');
-$temp->add(new admin_setting_configselect('mailnewline', new lang_string('mailnewline', 'admin'),
- new lang_string('configmailnewline', 'admin'), 'LF', $options));
-
-$choices = array(new lang_string('never', 'admin'),
- new lang_string('always', 'admin'),
- new lang_string('onlynoreply', 'admin'));
-$temp->add(new admin_setting_configselect('emailfromvia', new lang_string('emailfromvia', 'admin'),
- new lang_string('configemailfromvia', 'admin'), 1, $choices));
-
-$temp->add(new admin_setting_configtext('emailsubjectprefix', new lang_string('emailsubjectprefix', 'admin'),
+
+ $charsets = get_list_of_charsets();
+ unset($charsets['UTF-8']);
+ $options = [
+ '0' => 'UTF-8',
+ ];
+ $options = array_merge($options, $charsets);
+ $temp->add(new admin_setting_configselect('sitemailcharset', new lang_string('sitemailcharset', 'admin'),
+ new lang_string('configsitemailcharset', 'admin'), '0', $options));
+
+ $temp->add(new admin_setting_configcheckbox('allowusermailcharset', new lang_string('allowusermailcharset', 'admin'),
+ new lang_string('configallowusermailcharset', 'admin'), 0));
+
+ $temp->add(new admin_setting_configcheckbox('allowattachments', new lang_string('allowattachments', 'admin'),
+ new lang_string('configallowattachments', 'admin'), 1));
+
+ $options = [
+ 'LF' => 'LF',
+ 'CRLF' => 'CRLF',
+ ];
+ $temp->add(new admin_setting_configselect('mailnewline', new lang_string('mailnewline', 'admin'),
+ new lang_string('configmailnewline', 'admin'), 'LF', $options));
+
+ $choices = [
+ new lang_string('never', 'admin'),
+ new lang_string('always', 'admin'),
+ new lang_string('onlynoreply', 'admin'),
+ ];
+ $temp->add(new admin_setting_configselect('emailfromvia', new lang_string('emailfromvia', 'admin'),
+ new lang_string('configemailfromvia', 'admin'), 1, $choices));
+
+ $temp->add(new admin_setting_configtext('emailsubjectprefix', new lang_string('emailsubjectprefix', 'admin'),
new lang_string('configemailsubjectprefix', 'admin'), '', PARAM_RAW));
-$temp->add(new admin_setting_configtextarea('emailheaders', new lang_string('emailheaders', 'admin'),
+
+ $temp->add(new admin_setting_configtextarea('emailheaders', new lang_string('emailheaders', 'admin'),
new lang_string('configemailheaders', 'admin'), '', PARAM_RAW, '50', '3'));
-$ADMIN->add('email', $temp);
-
-// "update notifications" settingpage
-if (empty($CFG->disableupdatenotifications)) {
- $temp = new admin_settingpage('updatenotifications', new lang_string('updatenotifications', 'core_admin'));
- $temp->add(new admin_setting_configcheckbox('updateautocheck', new lang_string('updateautocheck', 'core_admin'),
- new lang_string('updateautocheck_desc', 'core_admin'), 1));
- $temp->add(new admin_setting_configselect('updateminmaturity', new lang_string('updateminmaturity', 'core_admin'),
- new lang_string('updateminmaturity_desc', 'core_admin'), MATURITY_STABLE,
- array(
- MATURITY_ALPHA => new lang_string('maturity'.MATURITY_ALPHA, 'core_admin'),
- MATURITY_BETA => new lang_string('maturity'.MATURITY_BETA, 'core_admin'),
- MATURITY_RC => new lang_string('maturity'.MATURITY_RC, 'core_admin'),
- MATURITY_STABLE => new lang_string('maturity'.MATURITY_STABLE, 'core_admin'),
- )));
- $temp->add(new admin_setting_configcheckbox('updatenotifybuilds', new lang_string('updatenotifybuilds', 'core_admin'),
- new lang_string('updatenotifybuilds_desc', 'core_admin'), 0));
- $ADMIN->add('server', $temp);
+ $ADMIN->add('email', $temp);
+
+ // Update notifications.
+ if (empty($CFG->disableupdatenotifications)) {
+ $temp = new admin_settingpage('updatenotifications', new lang_string('updatenotifications', 'core_admin'));
+ $temp->add(new admin_setting_configcheckbox('updateautocheck', new lang_string('updateautocheck', 'core_admin'),
+ new lang_string('updateautocheck_desc', 'core_admin'), 1));
+ $temp->add(new admin_setting_configselect('updateminmaturity', new lang_string('updateminmaturity', 'core_admin'),
+ new lang_string('updateminmaturity_desc', 'core_admin'), MATURITY_STABLE,
+ [
+ MATURITY_ALPHA => new lang_string('maturity'.MATURITY_ALPHA, 'core_admin'),
+ MATURITY_BETA => new lang_string('maturity'.MATURITY_BETA, 'core_admin'),
+ MATURITY_RC => new lang_string('maturity'.MATURITY_RC, 'core_admin'),
+ MATURITY_STABLE => new lang_string('maturity'.MATURITY_STABLE, 'core_admin'),
+ ]
+ ));
+ $temp->add(new admin_setting_configcheckbox('updatenotifybuilds', new lang_string('updatenotifybuilds', 'core_admin'),
+ new lang_string('updatenotifybuilds_desc', 'core_admin'), 0));
+ $ADMIN->add('server', $temp);
+ }
}
-
-} // end of speedup
+++ /dev/null
-statuspreprocessing,tool_dataprivacy
$string['duplicaterole'] = 'Role already specified';
$string['purposeoverview'] = 'A purpose describes the intended use and retention policy for stored data. The basis for storing and retaining that data is also described in the purpose.';
$string['roleoverrideoverview'] = 'The default retention policy can be overridden for specific user roles, allowing you to specify a longer, or a shorter, retention policy. A user is only expired when all of their roles have expired.';
-
-// Deprecated since Moodle 3.6.
-$string['statuspreprocessing'] = 'Pre-processing';
public static function verify_webfinger_parameters() {
return new external_function_parameters(
array(
- 'profileurl' => new external_value(PARAM_RAW, 'The profile url that the user has given us', VALUE_REQUIRED),
+ 'profileurl' => new external_value(PARAM_NOTAGS, 'The profile url that the user has given us', VALUE_REQUIRED),
'course' => new external_value(PARAM_INT, 'The course we are adding to', VALUE_REQUIRED),
'section' => new external_value(PARAM_INT, 'The section within the course we are adding to', VALUE_REQUIRED),
)
$user = \core_user::get_user($userid, 'moodlenetprofile');
try {
$userprofile = $user->moodlenetprofile ? $user->moodlenetprofile : '';
- return (isset($user)) ? new moodlenet_user_profile($userprofile, $userid) : null;
+ return (isset($user)) ? new moodlenet_user_profile(s($userprofile), $userid) : null;
} catch (\moodle_exception $e) {
// If an exception is thrown, means there isn't a valid profile set. No need to log exception.
return null;
if ($field->get_category_name() == self::get_category_name()
&& $field->inputname == 'profile_field_mnetprofile') {
try {
- return new moodlenet_user_profile($field->display_data(), $userid);
+ return new moodlenet_user_profile(s($field->display_data()), $userid);
} catch (\moodle_exception $e) {
// If an exception is thrown, means there isn't a valid profile set. No need to log exception.
return null;
// Automatically generated Moodle v3.9.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2021052501) {
+
+ // Find out if there are users with MoodleNet profiles set.
+ $sql = "SELECT u.*
+ FROM {user} u
+ WHERE u.moodlenetprofile IS NOT NULL";
+
+ $records = $DB->get_records_sql($sql);
+
+ foreach ($records as $record) {
+ // Force clean user value just incase there is something malicious.
+ $record->moodlenetprofile = clean_text($record->moodlenetprofile, PARAM_NOTAGS);
+ $DB->update_record('user', $record);
+ }
+
+ upgrade_plugin_savepoint(true, 2021052501, 'tool', 'moodlenet');
+ }
+
return true;
}
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_moodlenet';
-$plugin->version = 2021052500;
+$plugin->version = 2021052501;
$plugin->requires = 2021052500;
$plugin->maturity = MATURITY_ALPHA;
$mform->setExpanded('defaultheader', true);
$displaylist = core_course_category::make_categories_list('moodle/course:create');
- $mform->addElement('select', 'defaults[category]', get_string('coursecategory'), $displaylist);
+ $mform->addElement('autocomplete', 'defaults[category]', get_string('coursecategory'), $displaylist);
$mform->addHelpButton('defaults[category]', 'coursecategory');
$choices = array();
*/
const CONFIG_SHIPPED_VERSION = 'shipped_version';
+ /**
+ * Helper method to initialize admin page, setting appropriate extra URL parameters
+ *
+ * @param string $action
+ */
+ protected function setup_admin_externalpage(string $action): void {
+ admin_externalpage_setup('tool_usertours/tours', '', array_filter([
+ 'action' => $action,
+ 'id' => optional_param('id', 0, PARAM_INT),
+ 'tourid' => optional_param('tourid', 0, PARAM_INT),
+ 'direction' => optional_param('direction', 0, PARAM_INT),
+ ]));
+ }
+
/**
* This is the entry point for this controller class.
*
* @param string $action The action to perform.
*/
public function execute($action) {
- admin_externalpage_setup('tool_usertours/tours');
+ $this->setup_admin_externalpage($action);
+
// Add the main content.
switch($action) {
case self::ACTION_NEWTOUR:
// Automatically generated Moodle v3.9.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2021052501) {
+ // Normalize the memberattribute_isdn plugin config.
+ set_config('memberattribute_isdn',
+ !empty(get_config('auth_cas', 'memberattribute_isdn')), 'auth_cas');
+
+ upgrade_plugin_savepoint(true, 2021052501, 'auth', 'cas');
+ }
+
return true;
}
if ($ADMIN->fulltree) {
if (!function_exists('ldap_connect')) {
- $settings->add(new admin_setting_heading('auth_casnotinstalled', '', get_string('auth_casnotinstalled', 'auth_cas')));
+ $notify = new \core\output\notification(get_string('auth_casnotinstalled', 'auth_cas'),
+ \core\output\notification::NOTIFY_WARNING);
+ $settings->add(new admin_setting_heading('auth_casnotinstalled', '', $OUTPUT->render($notify)));
} else {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_lowercase_configtext.php');
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
- $settings->add(new admin_setting_configtext('auth_cas/memberattribute_isdn',
+ $settings->add(new admin_setting_configselect('auth_cas/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
- get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
+ get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_cas/objectclass',
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2021052500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2021052501; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2021052500; // Requires this Moodle version
$plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics)
$user->confirmed = 1;
$user->auth = $this->authtype;
$user->mnethostid = $CFG->mnet_localhost_id;
- if (empty($user->lang)) {
- $user->lang = $CFG->lang;
- }
+
if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
$trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
continue;
//
// The cast to int is a workaround for MDL-53959.
$user->suspended = (int)$this->is_user_suspended($user);
- if (empty($user->lang)) {
- $user->lang = $CFG->lang;
- }
+
if (empty($user->calendartype)) {
$user->calendartype = $CFG->calendartype;
}
// Automatically generated Moodle v3.9.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2021052501) {
+ // Normalize the memberattribute_isdn plugin config.
+ set_config('memberattribute_isdn',
+ !empty(get_config('auth_ldap', 'memberattribute_isdn')), 'auth_ldap');
+
+ upgrade_plugin_savepoint(true, 2021052501, 'auth', 'ldap');
+ }
+
return true;
}
$string['auth_ldap_ldap_encoding_key'] = 'LDAP encoding';
$string['auth_ldap_login_settings'] = 'Login settings';
$string['auth_ldap_memberattribute'] = 'Optional: Overrides user member attribute, when users belongs to a group. Usually \'member\'';
-$string['auth_ldap_memberattribute_isdn'] = 'Optional: Overrides handling of member attribute values, either 0 or 1';
+$string['auth_ldap_memberattribute_isdn'] = 'Overrides handling of member attribute values';
$string['auth_ldap_memberattribute_isdn_key'] = 'Member attribute uses dn';
$string['auth_ldap_memberattribute_key'] = 'Member attribute';
$string['auth_ldap_noconnect'] = 'LDAP-module cannot connect to server: {$a}';
$string['auth_ldap_noconnect_all'] = 'LDAP-module cannot connect to any servers: {$a}';
-$string['auth_ldap_noextension'] = '<em>The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.</em>';
+$string['auth_ldap_noextension'] = 'The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.';
$string['auth_ldap_no_mbstring'] = 'You need the mbstring extension to create users in Active Directory.';
$string['auth_ldapnotinstalled'] = 'Cannot use LDAP authentication. The PHP LDAP module is not installed.';
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you don\'t need to change this.';
if ($ADMIN->fulltree) {
if (!function_exists('ldap_connect')) {
- $settings->add(new admin_setting_heading('auth_ldap_noextension', '', get_string('auth_ldap_noextension', 'auth_ldap')));
+ $notify = new \core\output\notification(get_string('auth_ldap_noextension', 'auth_ldap'),
+ \core\output\notification::NOTIFY_WARNING);
+ $settings->add(new admin_setting_heading('auth_ldap_noextension', '', $OUTPUT->render($notify)));
} else {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
- $settings->add(new admin_setting_configtext('auth_ldap/memberattribute_isdn',
+ $settings->add(new admin_setting_configselect('auth_ldap/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
- get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
+ get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_ldap/objectclass',
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2021052500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2021052501; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2021052500; // Requires this Moodle version
$plugin->component = 'auth_ldap'; // Full name of the plugin (used for diagnostics)
if (isset($remoteuser->lang)) {
$remoteuser->lang = clean_param(str_replace('_utf8', '', $remoteuser->lang), PARAM_LANG);
}
- if (empty($remoteuser->lang)) {
- if (!empty($CFG->lang)) {
- $remoteuser->lang = $CFG->lang;
- } else {
- $remoteuser->lang = 'en';
- }
- }
+
$firsttime = false;
// get the local record for the remote user
And the page should meet accessibility standards
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
+
+ @javascript @accessibility
+ Scenario: The login page must have sufficient colour contrast
+ Given the following config values are set as admin:
+ | custommenuitems | -This is a custom item\|/customurl/ |
+ When I am on site homepage
+ Then the page should meet "wcag143" accessibility standards
+ And the page should meet accessibility standards with "wcag143" extra tests
'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
'needsupdate', 'timecreated', 'timemodified'));
+ $this->add_plugin_structure('local', $grade_item, true);
+
$grade_grades = new backup_nested_element('grade_grades');
$grade_grade = new backup_nested_element('grade_grade', array('id'), array(
'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
$paths[] = new restore_path_element('attributes', '/gradebook/attributes');
$paths[] = new restore_path_element('grade_category', '/gradebook/grade_categories/grade_category');
- $paths[] = new restore_path_element('grade_item', '/gradebook/grade_items/grade_item');
+
+ $gradeitem = new restore_path_element('grade_item', '/gradebook/grade_items/grade_item');
+ $paths[] = $gradeitem;
+ $this->add_plugin_structure('local', $gradeitem);
+
if ($userinfo) {
$paths[] = new restore_path_element('grade_grade', '/gradebook/grade_items/grade_item/grade_grades/grade_grade');
}
This files describes API changes in /backup/*,
information provided here is intended especially for developers.
+=== 4.0 ===
+ * Local plugins can now hook into a backup and restore process of grade items by
+ using define_grade_item_plugin_structure method (See MDL-69418).
+
=== 3.1 ===
* New close() method added to loggers so they can close any open resource. Previously
// if user lang doesn't exist here, use site default
if (!array_key_exists($user->lang, $languages)) {
- $user->lang = $CFG->lang;
+ $user->lang = get_newuser_language();
}
// if user theme isn't available on target site or they are disabled, reset theme
* Take the validated form data and extract the required information for copy operations.
*
* @param \stdClass $formdata Data from the validated course copy form.
- * @throws \moodle_exception
* @return \stdClass $copydata Data required for course copy operations.
+ * @throws \moodle_exception If one of the required copy fields is missing
*/
private final function get_copy_data(\stdClass $formdata): \stdClass {
$copydata = new \stdClass();
if (isset($formdata->{$field})) {
$copydata->{$field} = $formdata->{$field};
} else {
- throw new \moodle_exception('copy_class_field_not_found');
+ throw new \moodle_exception('copyfieldnotfound', 'backup', '', null, $field);
}
}
// Always keep current category.
$displaylist[$course->category] = \core_course_category::get($course->category, MUST_EXIST, true)->get_formatted_name();
}
- $mform->addElement('select', 'category', get_string('coursecategory'), $displaylist);
+ $mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addHelpButton('category', 'coursecategory');
// Course visibility.
}
// Keep source course user data.
+ $mform->addElement('select', 'userdata', get_string('userdata', 'backup'),
+ [0 => get_string('no'), 1 => get_string('yes')]);
+ $mform->setDefault('userdata', 0);
+ $mform->addHelpButton('userdata', 'userdata', 'backup');
+
$requiredcapabilities = array(
'moodle/restore:createuser', 'moodle/backup:userinfo', 'moodle/restore:userinfo'
);
- if (has_all_capabilities($requiredcapabilities, $coursecontext)) {
- $dataarray = array();
- $dataarray[] = $mform->createElement('advcheckbox', 'userdata',
- get_string('enable'), '', array('group' => 1), array(0, 1));
- $mform->addGroup($dataarray, 'dataarray', get_string('userdata', 'backup'), ' ', false);
- $mform->addHelpButton('dataarray', 'userdata', 'backup');
+ if (!has_all_capabilities($requiredcapabilities, $coursecontext)) {
+ $mform->hardFreeze('userdata');
+ $mform->setConstants('userdata', 0);
}
// Keep manual enrolments.
$rec = $DB->get_record('role', array('id' => $rid));
if ($rec) {
- return role_get_name($rec, $PAGE->context, ROLENAME_ALIAS);
+ return role_get_name($rec, $PAGE->context, ROLENAME_BOTH);
} else {
return null;
}
$string['show'] = 'Restore to view';
$string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\'';
-// Deprecated since Moodle 3.6.
-$string['defaulttab'] = 'Default tab';
-$string['defaulttab_desc'] = 'The tab that will be displayed when a user first views their course overview. When returning to their course overview, the user\'s active tab is remembered.';
-$string['morecourses'] = 'More courses';
-$string['nocoursesinprogress'] = 'No in progress courses';
-$string['nocoursesfuture'] = 'No future courses';
-$string['nocoursespast'] = 'No past courses';
-$string['noevents'] = 'No upcoming activities due';
-$string['next30days'] = 'Next 30 days';
-$string['next7days'] = 'Next 7 days';
-$string['recentlyoverdue'] = 'Recently overdue';
-$string['sortbycourses'] = 'Sort by courses';
-$string['sortbydates'] = 'Sort by dates';
-$string['timeline'] = 'Timeline';
-$string['viewcoursename'] = 'View course {$a}';
-$string['privacy:metadata:overviewlasttab'] = 'This stores the last tab selected by the user on the overview block.';
-$string['viewcourse'] = 'View course';
-
// Deprecated since Moodle 3.7.
$string['complete'] = 'complete';
$string['nocourses'] = 'No courses';
-defaulttab,block_myoverview
-defaulttab_desc,block_myoverview
-morecourses,block_myoverview
-nocoursesinprogress,block_myoverview
-nocoursesfuture,block_myoverview
-nocoursespast,block_myoverview
-noevents,block_myoverview
-next30days,block_myoverview
-next7days,block_myoverview
-recentlyoverdue,block_myoverview
-sortbycourses,block_myoverview
-sortbydates,block_myoverview
-timeline,block_myoverview
-viewcoursename,block_myoverview
-privacy:metadata:overviewlasttab,block_myoverview
nocourses,block_myoverview
-complete,block_myoverview
\ No newline at end of file
+complete,block_myoverview
$categoryoptions[$id] = $category;
}
- $mform->addElement('select', 'categoryid', get_string('category'), $categoryoptions);
+ $mform->addElement('autocomplete', 'categoryid', get_string('category'), $categoryoptions);
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
}
* @param string|null $view preference view options (eg: day, month, upcoming)
*/
public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
+ global $PAGE;
+
+ if (!has_capability('moodle/block:view', $PAGE->context) ) {
+ return;
+ }
+
if ($showfilters) {
$filters = new block_contents();
$filters->content = $renderer->event_filter();
And I am viewing site calendar
And I click on "New event" "button"
And I set the field "Type of event" to "Course"
- When I open the autocomplete suggestions list
- Then I should see "Course 1" in the ".form-autocomplete-suggestions" "css_element"
- And I should not see "Course 2" in the ".form-autocomplete-suggestions" "css_element"
- And I should not see "Course 3" in the ".form-autocomplete-suggestions" "css_element"
+ When I expand the "Course" autocomplete
+ Then "Course 1" "autocomplete_suggestions" should exist
+ And "Course 2" "autocomplete_suggestions" should not exist
+ And "Course 3" "autocomplete_suggestions" should not exist
And I click on "Close" "button"
And I am on site homepage
And I navigate to "Appearance > Calendar" in site administration
And I am viewing site calendar
And I click on "New event" "button"
And I set the field "Type of event" to "Course"
- When I open the autocomplete suggestions list
- Then I should see "Course 1" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Course 2" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Course 3" in the ".form-autocomplete-suggestions" "css_element"
+ When I expand the "Course" autocomplete
+ Then "Course 1" "autocomplete_suggestions" should exist
+ And "Course 2" "autocomplete_suggestions" should exist
+ And "Course 3" "autocomplete_suggestions" should exist
@javascript
Scenario: Students can only see user event type by default.
And I follow "This month"
When I click on "New event" "button"
Then I should see "User" in the "div#fitem_id_staticeventtype" "css_element"
+
+ @javascript @accessibility
+ Scenario: The calendar page must be accessible
+ Given I log in as "student1"
+ And I am on "Course 1" course homepage
+ When I follow "This month"
+ Then the page should meet accessibility standards
+ And the page should meet "wcag131, wcag143, wcag412" accessibility standards
+ And the page should meet accessibility standards with "wcag131, wcag143, wcag412" extra tests
$mform->setType('name', PARAM_TEXT);
$options = $this->get_category_options($cohort->contextid);
- $mform->addElement('select', 'contextid', get_string('context', 'role'), $options);
+ $mform->addElement('autocomplete', 'contextid', get_string('context', 'role'), $options);
$mform->addElement('text', 'idnumber', get_string('idnumber', 'cohort'), 'maxlength="254" size="50"');
$mform->setType('idnumber', PARAM_RAW); // Idnumbers are plain text, must not be changed.
| Description | Test cohort description |
And I press "Save changes"
+ @javascript
Scenario: Add a cohort
When I follow "Cohorts"
Then I should see "Test cohort name"
And I should see "Test cohort description"
And I should see "Created manually"
+ @javascript
Scenario: Add users to a cohort selecting them from the system users list
When I add "First User (first@example.com)" user to "333" cohort members
And I add "Second User (second@example.com)" user to "333" cohort members
And the "Current users" select box should contain "Second User (second@example.com)"
And the "Current users" select box should not contain "Forth User (forth@example.com)"
+ @javascript
Scenario: Add users to a cohort using a bulk user action
When I follow "Accounts"
And I follow "Bulk user actions"
$args->area = $commentrecord->commentarea;
$manager = new comment($args);
- if ($commentrecord->userid != $USER->id && !$manager->can_delete($commentrecord->id)) {
+ if (!$manager->can_delete($commentrecord)) {
throw new comment_exception('nopermissiontodelentry');
}
}
break;
case 'delete':
- $comment_record = $DB->get_record('comments', array('id'=>$commentid));
- if ($manager->can_delete($commentid) || $comment_record->userid == $USER->id) {
+ $comment = $DB->get_record('comments', ['id' => $commentid]);
+ if ($manager->can_delete($comment)) {
if ($manager->delete($commentid)) {
$result = array(
'client_id' => $client_id,
$c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
$c->userid = $u->id;
- $candelete = $this->can_delete($c->id);
- if (($USER->id == $u->id) || !empty($candelete)) {
+ if ($this->can_delete($c)) {
$c->delete = true;
}
$comments[] = $c;
/**
* Delete a comment
*
- * @param int $commentid
+ * @param int|stdClass $comment The id of a comment, or a comment record.
* @return bool
*/
- public function delete($commentid) {
- global $DB, $USER;
- $candelete = has_capability('moodle/comment:delete', $this->context);
- if (!$comment = $DB->get_record('comments', array('id'=>$commentid))) {
+ public function delete($comment) {
+ global $DB;
+ if (is_object($comment)) {
+ $commentid = $comment->id;
+ } else {
+ $commentid = $comment;
+ $comment = $DB->get_record('comments', ['id' => $commentid]);
+ }
+
+ if (!$comment) {
throw new comment_exception('dbupdatefailed');
}
- if (!($USER->id == $comment->userid || !empty($candelete))) {
+ if (!$this->can_delete($comment)) {
throw new comment_exception('nopermissiontocomment');
}
$DB->delete_records('comments', array('id'=>$commentid));
}
/**
- * Returns true if the user can delete this comment
- * @param int $commentid
+ * Returns true if the user can delete this comment.
+ *
+ * The user can delete comments if it is one they posted and they can still make posts,
+ * or they have the capability to delete comments.
+ *
+ * A database call is avoided if a comment record is passed.
+ *
+ * @param int|stdClass $comment The id of a comment, or a comment record.
* @return bool
*/
- public function can_delete($commentid) {
+ public function can_delete($comment) {
+ global $USER, $DB;
+ if (is_object($comment)) {
+ $commentid = $comment->id;
+ } else {
+ $commentid = $comment;
+ }
+
$this->validate(array('commentid'=>$commentid));
- return has_capability('moodle/comment:delete', $this->context);
+
+ if (!is_object($comment)) {
+ // Get the comment record from the database.
+ $comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST);
+ }
+
+ $hascapability = has_capability('moodle/comment:delete', $this->context);
+ $owncomment = $USER->id == $comment->userid;
+
+ return ($hascapability || ($owncomment && $this->can_post()));
}
/**
--- /dev/null
+<?php
+// 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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for comments when the context is frozen.
+ *
+ * @package core_comment
+ * @copyright 2019 University of Nottingham
+ * @author Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests for comments when the context is frozen.
+ *
+ * @package core_comment
+ * @copyright 2019 University of Nottingham
+ * @author Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class comment_context_freeze_testcase extends advanced_testcase {
+ /**
+ * Creates a comment by a student.
+ *
+ * Returns:
+ * - The comment object
+ * - The sudent that wrote the comment
+ * - The arguments used to create the comment
+ *
+ * @param stdClass $course Moodle course from the datagenerator
+ * @return array
+ */
+ protected function create_student_comment_and_freeze_course($course): array {
+ set_config('contextlocking', 1);
+
+ $context = context_course::instance($course->id);
+ $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+
+ $args = new stdClass;
+ $args->context = $context;
+ $args->course = $course;
+ $args->area = 'page_comments';
+ $args->itemid = 0;
+ $args->component = 'block_comments';
+ $args->linktext = get_string('showcomments');
+ $args->notoggle = true;
+ $args->autostart = true;
+ $args->displaycancel = false;
+
+ // Create a comment by the student.
+ $this->setUser($student);
+ $comment = new comment($args);
+ $newcomment = $comment->add('New comment');
+
+ // Freeze the context.
+ $this->setAdminUser();
+ $context->set_locked(true);
+
+ return [$newcomment, $student, $args];
+ }
+
+ /**
+ * Test that a student cannot delete their own comments in frozen contexts via the external service.
+ */
+ public function test_delete_student_external() {
+ global $CFG;
+ require_once($CFG->dirroot . '/comment/lib.php');
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
+
+ // Check that a student cannot delete their own comment.
+ $this->setUser($student);
+ $studentcomment = new comment($args);
+ $this->assertFalse($studentcomment->can_delete($newcomment->id));
+ $this->assertFalse($studentcomment->can_post());
+ $this->expectException(comment_exception::class);
+ $this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
+ core_comment_external::delete_comments([$newcomment->id]);
+ }
+
+ /**
+ * Test that a student cannot delete their own comments in frozen contexts.
+ */
+ public function test_delete_student() {
+ global $CFG;
+ require_once($CFG->dirroot . '/comment/lib.php');
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
+
+ // Check that a student cannot delete their own comment.
+ $this->setUser($student);
+ $studentcomment = new comment($args);
+ $this->assertFalse($studentcomment->can_delete($newcomment->id));
+ $this->assertFalse($studentcomment->can_post());
+ $this->expectException(comment_exception::class);
+ $this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
+ $studentcomment->delete($newcomment->id);
+ }
+
+ /**
+ * Test that an admin cannot delete comments in frozen contexts via the external service.
+ */
+ public function test_delete_admin_external() {
+ global $CFG;
+ require_once($CFG->dirroot . '/comment/lib.php');
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
+
+ // Check that the admin user cannot delete the comment.
+ $admincomment = new comment($args);
+ $this->assertFalse($admincomment->can_delete($newcomment->id));
+ $this->assertFalse($admincomment->can_post());
+ $this->expectException(comment_exception::class);
+ $this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
+ core_comment_external::delete_comments([$newcomment->id]);
+ }
+
+ /**
+ * Test that an admin cannot delete comments in frozen contexts.
+ */
+ public function test_delete_admin() {
+ global $CFG;
+ require_once($CFG->dirroot . '/comment/lib.php');
+
+ $this->resetAfterTest();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
+
+ // Check that the admin user cannot delete the comment.
+ $admincomment = new comment($args);
+ $this->assertFalse($admincomment->can_delete($newcomment->id));
+ $this->assertFalse($admincomment->can_post());
+ $this->expectException(comment_exception::class);
+ $this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
+ $admincomment->delete($newcomment->id);
+ }
+}
use stored_file;
use stdClass;
use coding_exception;
+use context;
use moodle_url;
use core\event\contentbank_content_updated;
return $this->content->contenttype;
}
+ /**
+ * Return the contenttype instance of this content.
+ *
+ * @return contenttype The content type instance
+ */
+ public function get_content_type_instance(): contenttype {
+ $context = context::instance_by_id($this->content->contextid);
+ $contenttypeclass = "\\{$this->content->contenttype}\\contenttype";
+ return new $contenttypeclass($context);
+ }
+
/**
* Returns $this->content->timemodified.
*
return $contenttypes;
}
+
+ /**
+ * Return a content class form a content id.
+ *
+ * @throws coding_exception if the ID is not valid or some class does no exists
+ * @param int $id the content id
+ * @return content the content class instance
+ */
+ public function get_content_from_id(int $id): content {
+ global $DB;
+ $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST);
+ $contentclass = "\\$record->contenttype\\content";
+ return new $contentclass($record);
+ }
}
*/
abstract class contenttype {
- /** Plugin implements uploading feature */
+ /** @var string Constant representing whether the plugin implements uploading feature */
const CAN_UPLOAD = 'upload';
- /** Plugin implements edition feature */
+ /** @var string Constant representing whether the plugin implements edition feature */
const CAN_EDIT = 'edit';
+ /**
+ * @var string Constant representing whether the plugin implements download feature
+ * @since Moodle 3.10
+ */
+ const CAN_DOWNLOAD = 'download';
+
/** @var \context This contenttype's context. **/
protected $context = null;
return $content;
}
+ /**
+ * Replace a content using an uploaded file.
+ *
+ * @throws file_exception If file operations fail
+ * @throws dml_exception if the content creation fails
+ * @param stored_file $file the uploaded file
+ * @param content $content the original content record
+ * @return content Object with the updated content bank information.
+ */
+ public function replace_content(stored_file $file, content $content): content {
+ $content->import_file($file);
+ $content->update_content();
+ return $content;
+ }
+
/**
* Delete this content from the content_bank.
* This method can be overwritten by the plugins if they need to delete specific information.
return '';
}
+ /**
+ * Returns the URL to download the content.
+ *
+ * @since Moodle 3.10
+ * @param content $content The content to be downloaded.
+ * @return string URL with the content to download.
+ */
+ public function get_download_url(content $content): string {
+ $downloadurl = '';
+ $file = $content->get_file();
+ if (!empty($file)) {
+ $url = \moodle_url::make_pluginfile_url(
+ $file->get_contextid(),
+ $file->get_component(),
+ $file->get_filearea(),
+ $file->get_itemid(),
+ $file->get_filepath(),
+ $file->get_filename()
+ );
+ $downloadurl = $url->out(false);
+ }
+
+ return $downloadurl;
+ }
+
/**
* Returns the HTML code to render the icon for content bank contents.
*
return true;
}
+ /**
+ * Returns whether or not the user has permission to download the content.
+ *
+ * @since Moodle 3.10
+ * @param content $content The content to be downloaded.
+ * @return bool True if the user can download the content. False otherwise.
+ */
+ final public function can_download(content $content): bool {
+ if (!$this->is_feature_supported(self::CAN_DOWNLOAD)) {
+ return false;
+ }
+
+ if (!$this->can_access()) {
+ return false;
+ }
+
+ $hascapability = has_capability('moodle/contentbank:downloadcontent', $this->context);
+ return $hascapability && $this->is_download_allowed($content);
+ }
+
+ /**
+ * Returns plugin allows downloading.
+ *
+ * @since Moodle 3.10
+ * @param content $content The content to be downloaed.
+ * @return bool True if plugin allows downloading. False otherwise.
+ */
+ protected function is_download_allowed(content $content): bool {
+ // Plugins can overwrite this function to add any check they need.
+ return true;
+ }
+
/**
* Returns the plugin supports the feature.
*
* @return array
*/
protected function get_implemented_features(): array {
- return [self::CAN_UPLOAD, self::CAN_EDIT];
+ return [self::CAN_UPLOAD, self::CAN_EDIT, self::CAN_DOWNLOAD];
}
/**
--- /dev/null
+@core @core_contentbank @contenttype_h5p @_file_upload @_switch_iframe @javascript
+Feature: Replace H5P file from an existing content
+ In order to replace an H5P content from the content bank
+ As an admin
+ I need to be able to replace the content with a new .h5p file
+
+ Background:
+ Given the following "contentbank content" exist:
+ | contextlevel | reference | contenttype | user | contentname | filepath |
+ | System | | contenttype_h5p | admin | filltheblanks.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
+ And I log in as "admin"
+ And I press "Customise this page"
+ And I add the "Navigation" block if not present
+ And I expand "Site pages" node
+ And I click on "Content bank" "link"
+
+ Scenario: Admins can replace the original .h5p file with a new one
+ Given I click on "filltheblanks.h5p" "link"
+ And I switch to "h5p-player" class iframe
+ And I switch to "h5p-iframe" class iframe
+ And I should see "Of which countries"
+ And I switch to the main frame
+ When I open the action menu in "region-main-settings-menu" "region"
+ And I choose "Replace with file" in the open action menu
+ And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Upload content" filemanager
+ And I click on "Save changes" "button"
+ Then I switch to "h5p-player" class iframe
+ And I switch to "h5p-iframe" class iframe
+ And I should see "Lorum ipsum"
+ And I switch to the main frame
--- /dev/null
+@core @core_contentbank @contenttype_h5p @_file_upload @_switch_iframe @javascript
+Feature: Replace H5P file from an existing content requires special capabilities
+ In order replace an H5P content from the content bank
+ As a teacher
+ I need to be able to replace the content only if certain capabilities are allowed
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "categories" exist:
+ | name | category | idnumber |
+ | Cat 1 | 0 | CAT1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | CAT1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "contentbank content" exist:
+ | contextlevel | reference | contenttype | user | contentname | filepath |
+ | Course | C1 | contenttype_h5p | admin | admincontent | /h5p/tests/fixtures/ipsums.h5p |
+ | Course | C1 | contenttype_h5p | teacher1 | teachercontent | /h5p/tests/fixtures/filltheblanks.h5p |
+ And I log in as "teacher1"
+ And I am on "Course 1" course homepage with editing mode on
+ And I add the "Navigation" block if not present
+ And I expand "Site pages" node
+ And I click on "Content bank" "link"
+ # Force the content deploy
+ And I click on "admincontent" "link"
+ And I click on "Content bank" "link"
+
+ Scenario: Teacher can replace its own H5P files
+ Given I click on "teachercontent" "link"
+ When I open the action menu in "region-main-settings-menu" "region"
+ And I choose "Replace with file" in the open action menu
+ And I upload "h5p/tests/fixtures/ipsums.h5p" file to "Upload content" filemanager
+ And I click on "Save changes" "button"
+ Then I switch to "h5p-player" class iframe
+ And I switch to "h5p-iframe" class iframe
+ And I should see "Lorum ipsum"
+ And I switch to the main frame
+
+ Scenario: Teacher cannot replace another user's H5P files
+ When I click on "admincontent" "link"
+ Then "region-main-settings-menu" "region" should not exist
+
+ Scenario: Teacher cannot replace a content without having upload capability
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/contentbank:upload | Prevent | editingteacher | Course | C1 |
+ When I click on "teachercontent" "link"
+ Then "region-main-settings-menu" "region" should not exist
+
+ Scenario: Teacher cannot replace a content without having the H5P upload capability
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | contenttype/h5p:upload | Prevent | editingteacher | Course | C1 |
+ When I click on "teachercontent" "link"
+ Then "region-main-settings-menu" "region" should not exist
+
+ Scenario: Teacher cannot replace a content without having the manage own content capability
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/contentbank:manageowncontent | Prevent | editingteacher | Course | C1 |
+ When I click on "teachercontent" "link"
+ Then "region-main-settings-menu" "region" should not exist
$this->assertNotEquals($defaulticon, $findicon);
$this->assertContains('find', $findicon, '', true);
}
+
+ /**
+ * Tests get_download_url result.
+ *
+ * @covers ::get_download_url
+ */
+ public function test_get_download_url() {
+ global $CFG;
+
+ $this->resetAfterTest();
+ $systemcontext = context_system::instance();
+ $this->setAdminUser();
+ $contenttype = new contenttype_h5p\contenttype($systemcontext);
+
+ // Add an H5P fill the blanks file to the content bank.
+ $filename = 'filltheblanks.h5p';
+ $filepath = $CFG->dirroot . '/h5p/tests/fixtures/' . $filename;
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ $contents = $generator->generate_contentbank_data('contenttype_h5p', 1, 0, $systemcontext, true, $filepath);
+ $filltheblanks = array_shift($contents);
+
+ // Check before deploying the URL is returned OK.
+ $url1 = $contenttype->get_download_url($filltheblanks);
+ $this->assertNotEmpty($url1);
+ $this->assertContains($filename, $url1);
+
+ // Deploy the contents though the player to create the H5P DB entries and know specific content type.
+ $h5pplayer = new \core_h5p\player($filltheblanks->get_file_url(), new \stdClass(), true);
+ $h5pplayer->add_assets_to_page();
+ $h5pplayer->output();
+
+ // Once the H5P has been deployed, the URL is still the same.
+ $url2 = $contenttype->get_download_url($filltheblanks);
+ $this->assertNotEmpty($url2);
+ $this->assertEquals($url1, $url2);
+ }
}
$mform->addElement('hidden', 'contextid', $this->_customdata['contextid']);
$mform->setType('contextid', PARAM_INT);
+ if (!empty($this->_customdata['id'])) {
+ $mform->addElement('hidden', 'id', $this->_customdata['id']);
+ $mform->setType('id', PARAM_INT);
+ }
+
$options = $this->_customdata['options'];
$mform->addElement('filepicker', 'file', get_string('file', 'core_contentbank'), null, $options);
$mform->addHelpButton('file', 'file', 'core_contentbank');
--- /dev/null
+@core @core_contentbank @contentbank_h5p @_file_upload @javascript
+Feature: Download H5P content from the content bank
+ In order export H5P content from the content bank
+ As an admin
+ I need to be able to download any H5P content from the content bank
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | manager | Max | Manager | man@example.com |
+ And the following "role assigns" exist:
+ | user | role | contextlevel | reference |
+ | manager | manager | System | |
+ And the following "contentbank content" exist:
+ | contextlevel | reference | contenttype | user | contentname | filepath |
+ | System | | contenttype_h5p | admin | filltheblanksadmin.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
+ | System | | contenttype_h5p | manager | filltheblanksmanager.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
+ And I log in as "admin"
+ And I am on site homepage
+ And I turn editing mode on
+ And I add the "Navigation" block if not present
+ And I configure the "Navigation" block
+ And I set the following fields to these values:
+ | Page contexts | Display throughout the entire site |
+ And I press "Save changes"
+
+ Scenario: Admins can download content from the content bank
+ Given I click on "Site pages" "list_item" in the "Navigation" "block"
+ And I click on "Content bank" "link" in the "Navigation" "block"
+ And I follow "filltheblanksmanager.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ And I should see "Download"
+ When I choose "Download" in the open action menu
+ Then I should see "filltheblanksmanager.h5p"
+
+ Scenario: Users can download content created by different users
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/contentbank:manageanycontent | Prohibit | manager | System | |
+ And I log out
+ And I log in as "manager"
+ When I click on "Site pages" "list_item" in the "Navigation" "block"
+ And I click on "Content bank" "link" in the "Navigation" "block"
+ And I should see "filltheblanksadmin.h5p"
+ And I follow "filltheblanksadmin.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ Then I should see "Download"
+ And I should not see "Rename"
+
+ Scenario: Users without the required capability cannot download content
+ Given the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/contentbank:downloadcontent | Prohibit | manager | System | |
+ And I log out
+ And I log in as "manager"
+ When I click on "Site pages" "list_item" in the "Navigation" "block"
+ And I click on "Content bank" "link" in the "Navigation" "block"
+ And I should see "filltheblanksmanager.h5p"
+ And I follow "filltheblanksmanager.h5p"
+ And I open the action menu in "region-main-settings-menu" "region"
+ Then I should not see "Download"
$contentfile = $content->get_file($file);
$this->assertEquals($importedfile->get_id(), $contentfile->get_id());
}
+
+ /**
+ * Tests for 'get_content_type_instance'
+ *
+ * @covers ::get_content_type_instance
+ */
+ public function test_get_content_type_instance(): void {
+ global $USER;
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $context = context_system::instance();
+
+ $type = new contenttype($context);
+ $record = (object)[
+ 'name' => 'content name',
+ 'usercreated' => $USER->id,
+ ];
+ $content = $type->create_content($record);
+
+ $contenttype = $content->get_content_type_instance();
+
+ $this->assertInstanceOf(get_class($type), $contenttype);
+ }
}
use context_course;
use context_coursecat;
use context_system;
+use Exception;
global $CFG;
require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
$actual = $cb->get_contenttypes_with_capability_feature('test2', null, $enabled);
$this->assertEquals($contenttypescanfeature, array_values($actual));
}
+
+ /**
+ * Test the behaviour of get_content_from_id()
+ *
+ * @covers ::get_content_from_id
+ */
+ public function test_get_content_from_id() {
+
+ $this->resetAfterTest();
+ $cb = new \core_contentbank\contentbank();
+
+ // Create a category and two courses.
+ $systemcontext = context_system::instance();
+
+ // Add some content to the content bank.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ $contents = $generator->generate_contentbank_data(null, 3, 0, $systemcontext);
+ $content = reset($contents);
+
+ // Get the content instance form id.
+ $newinstance = $cb->get_content_from_id($content->get_id());
+ $this->assertEquals($content->get_id(), $newinstance->get_id());
+
+ // Now produce and exception with an innexistent id.
+ $this->expectException(Exception::class);
+ $cb->get_content_from_id(0);
+ }
}
$this->assertEquals(1, $DB->count_records('files', ['contenthash' => $dummyfile->get_contenthash()]));
}
+ /**
+ * Tests for behaviour of replace_content() using a dummy file.
+ *
+ * @covers ::replace_content
+ */
+ public function test_replace_content(): void {
+ global $USER;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $context = context_system::instance();
+
+ // Add some content to the content bank.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
+ $content = reset($contents);
+
+ $dummy = [
+ 'contextid' => context_user::instance($USER->id)->id,
+ 'component' => 'user',
+ 'filearea' => 'draft',
+ 'itemid' => 1,
+ 'filepath' => '/',
+ 'filename' => 'file.h5p',
+ 'userid' => $USER->id,
+ ];
+ $fs = get_file_storage();
+ $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
+
+ $contenttype = new contenttype(context_system::instance());
+ $content = $contenttype->replace_content($dummyfile, $content);
+
+ $this->assertEquals('contenttype_testable', $content->get_content_type());
+ $this->assertInstanceOf('\\contenttype_testable\\content', $content);
+
+ $file = $content->get_file();
+ $this->assertEquals($dummyfile->get_userid(), $file->get_userid());
+ $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash());
+ $this->assertEquals('contentbank', $file->get_component());
+ $this->assertEquals('public', $file->get_filearea());
+ $this->assertEquals('/', $file->get_filepath());
+ }
+
+ /**
+ * Tests for behaviour of replace_content() using an error file.
+ *
+ * @covers ::replace_content
+ */
+ public function test_replace_content_exception(): void {
+ global $USER;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $context = context_system::instance();
+
+ // Add some content to the content bank.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
+ $content = reset($contents);
+
+ $dummy = [
+ 'contextid' => context_user::instance($USER->id)->id,
+ 'component' => 'user',
+ 'filearea' => 'draft',
+ 'itemid' => 1,
+ 'filepath' => '/',
+ 'filename' => 'error.txt',
+ 'userid' => $USER->id,
+ ];
+ $fs = get_file_storage();
+ $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
+
+ $contenttype = new contenttype(context_system::instance());
+
+ $this->expectException(Exception::class);
+ $content = $contenttype->replace_content($dummyfile, $content);
+ }
+
/**
* Test the behaviour of can_delete().
*/
/**
* Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user).
*/
- protected function contenttype_setup_scenario_data(): void {
+ protected function contenttype_setup_scenario_data(string $contenttype = 'contenttype_testable'): void {
global $DB;
$systemcontext = context_system::instance();
$this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
$this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id);
$this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id);
+ $editingteacherrolerid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
$this->user = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->role_assign($editingteacherrolerid, $this->user->id);
// Add some content to the content bank.
$generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
- $this->contents[$this->manager1->id] = $generator->generate_contentbank_data(null, 3, $this->manager1->id);
- $this->contents[$this->user->id] = $generator->generate_contentbank_data(null, 1, $this->user->id);
+ $this->contents[$this->manager1->id] = $generator->generate_contentbank_data($contenttype, 3, $this->manager1->id);
+ $this->contents[$this->user->id] = $generator->generate_contentbank_data($contenttype, 1, $this->user->id);
- $this->contenttype = new \contenttype_testable\contenttype($systemcontext);
+ $contenttypeclass = "\\$contenttype\\contenttype";
+ $this->contenttype = new $contenttypeclass($systemcontext);
}
/**
$this->assertFalse($contenttype->can_manage($contentbyteacher));
$this->assertFalse($contenttype->can_manage($contentbyadmin));
}
+
+ /**
+ * Test the behaviour of can_download().
+ *
+ * @covers ::can_download
+ */
+ public function test_can_download() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->contenttype_setup_scenario_data('contenttype_h5p');
+
+ $managercontent = array_shift($this->contents[$this->manager1->id]);
+ $usercontent = array_shift($this->contents[$this->user->id]);
+
+ // Check the content has been created as expected.
+ $records = $DB->count_records('contentbank_content');
+ $this->assertEquals(4, $records);
+
+ // Check user can download content created by anybody.
+ $this->setUser($this->user);
+ $this->assertTrue($this->contenttype->can_download($usercontent));
+ $this->assertTrue($this->contenttype->can_download($managercontent));
+
+ // Check manager can download all the content too.
+ $this->setUser($this->manager1);
+ $this->assertTrue($this->contenttype->can_download($managercontent));
+ $this->assertTrue($this->contenttype->can_download($usercontent));
+
+ // Unassign capability to manager role and check she cannot download content anymore.
+ unassign_capability('moodle/contentbank:downloadcontent', $this->managerroleid);
+ $this->assertFalse($this->contenttype->can_download($managercontent));
+ $this->assertFalse($this->contenttype->can_download($usercontent));
+ }
+
+ /**
+ * Tests get_download_url result.
+ *
+ * @covers ::get_download_url
+ */
+ public function test_get_download_url() {
+ global $CFG;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ $systemcontext = context_system::instance();
+
+ // Add some content to the content bank.
+ $filename = 'filltheblanks.h5p';
+ $filepath = $CFG->dirroot . '/h5p/tests/fixtures/' . $filename;
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ $contents = $generator->generate_contentbank_data('contenttype_testable', 1, 0, $systemcontext, true, $filepath);
+ $content = array_shift($contents);
+
+ // Check the URL is returned OK for a content with file.
+ $contenttype = new contenttype($systemcontext);
+ $url = $contenttype->get_download_url($content);
+ $this->assertNotEmpty($url);
+ $this->assertContains($filename, $url);
+
+ // Check the URL is empty when the content hasn't any file.
+ $record = new stdClass();
+ $content = $contenttype->create_content($record);
+ $url = $contenttype->get_download_url($content);
+ $this->assertEmpty($url);
+ }
}
require_capability('moodle/contentbank:upload', $context);
+$cb = new \core_contentbank\contentbank();
+
+$id = optional_param('id', null, PARAM_INT);
+if ($id) {
+ $content = $cb->get_content_from_id($id);
+ $contenttype = $content->get_content_type_instance();
+ if (!$contenttype->can_manage($content) || !$contenttype->can_upload()) {
+ print_error('nopermissions', 'error', $returnurl, get_string('replacecontent', 'contentbank'));
+ }
+}
+
$title = get_string('contentbank');
\core_contentbank\helper::get_page_ready($context, $title, true);
if ($PAGE->course) {
$maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED;
}
-$cb = new \core_contentbank\contentbank();
-$accepted = $cb->get_supported_extensions_as_string($context);
+if ($id) {
+ $extensions = $contenttype->get_manageable_extensions();
+ $accepted = implode(',', $extensions);
+} else {
+ $accepted = $cb->get_supported_extensions_as_string($context);
+}
$data = new stdClass();
$options = array(
);
file_prepare_standard_filemanager($data, 'files', $options, $context, 'contentbank', 'public', 0);
-$mform = new contentbank_files_form(null, ['contextid' => $contextid, 'data' => $data, 'options' => $options]);
+$mform = new contentbank_files_form(null, ['contextid' => $contextid, 'data' => $data, 'options' => $options, 'id' => $id]);
$error = '';
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $formdata->file, 'itemid, filepath, filename', false);
if (!empty($files)) {
$file = reset($files);
- $content = $cb->create_content_from_file($context, $USER->id, $file);
+ if ($id) {
+ $content = $contenttype->replace_content($file, $content);
+ } else {
+ $content = $cb->create_content_from_file($context, $USER->id, $file);
+ }
$viewurl = new \moodle_url('/contentbank/view.php', ['id' => $content->get_id(), 'contextid' => $contextid]);
redirect($viewurl);
} else {
false,
$attributes
));
+
+ if ($contenttype->can_upload()) {
+ $actionmenu->add_secondary_action(new action_menu_link(
+ new moodle_url('/contentbank/upload.php', ['contextid' => $context->id, 'id' => $content->get_id()]),
+ new pix_icon('i/upload', get_string('upload')),
+ get_string('replacecontent', 'contentbank'),
+ false
+ ));
+ }
+}
+if ($contenttype->can_download($content)) {
+ // Add the download content item to the menu.
+ $actionmenu->add_secondary_action(new action_menu_link(
+ new moodle_url($contenttype->get_download_url($content)),
+ new pix_icon('t/download', get_string('download')),
+ get_string('download'),
+ false
+ ));
}
if ($contenttype->can_delete($content)) {
// Add the delete content item to the menu.
}
if ($displaylist) {
- $mform->addElement('select', 'newparent', get_string('movecategorycontentto'), $displaylist);
+ $mform->addElement('autocomplete', 'newparent', get_string('movecategorycontentto'), $displaylist);
if (in_array($this->coursecat->parent, $displaylist)) {
$mform->setDefault('newparent', $this->coursecat->parent);
}
$strsubmit = get_string('createcategory');
}
- $mform->addElement('select', 'parent', get_string('parentcategory'), $options);
+ $mform->addElement('autocomplete', 'parent', get_string('parentcategory'), $options);
$mform->addElement('text', 'name', get_string('categoryname'), array('size' => '30'));
$mform->addRule('name', get_string('required'), 'required', null);
if (empty($course->id)) {
if (has_capability('moodle/course:create', $categorycontext)) {
$displaylist = core_course_category::make_categories_list('moodle/course:create');
- $mform->addElement('select', 'category', get_string('coursecategory'), $displaylist);
+ $mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addHelpButton('category', 'coursecategory');
$mform->setDefault('category', $category->id);
} else {
$displaylist[$course->category] = core_course_category::get($course->category, MUST_EXIST, true)
->get_formatted_name();
}
- $mform->addElement('select', 'category', get_string('coursecategory'), $displaylist);
+ $mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addHelpButton('category', 'coursecategory');
} else {
//keep current
if (empty($CFG->lockrequestcategory)) {
$displaylist = core_course_category::make_categories_list('moodle/course:request');
- $mform->addElement('select', 'category', get_string('coursecategory'), $displaylist);
+ $mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->setDefault('category', $CFG->defaultrequestcategory);
$mform->addHelpButton('category', 'coursecategory');
}
$roles[0] = get_string('switchrolereturn');
$assumedrole = $USER->access['rsw'][$context->path];
}
- $availableroles = get_switchable_roles($context);
+ $availableroles = get_switchable_roles($context, ROLENAME_BOTH);
if (is_array($availableroles)) {
foreach ($availableroles as $key => $role) {
if ($assumedrole == (int)$key) {
And I should see "Category 1 (edited)" in the "#category-listing" "css_element"
And I should see "Category 1 (edited)" in the "#course-listing h3" "css_element"
+ @javascript
Scenario: Test deleting a categories through the management interface.
Given the following "categories" exist:
| name | category | idnumber |
And I should see "Delete category: Cat 1"
And I should see "Contents of Cat 1"
And "What to do" "select" should exist
- And "Move into" "select" should exist
- And the "Move into" select box should not contain "Cat 2"
- And the "Move into" select box should contain "Miscellaneous"
+ And I expand the "Move into" autocomplete
+ And "Cat 2" "autocomplete_suggestions" should not exist
+ And "Miscellaneous" "autocomplete_selection" should be visible
And I set the field "What to do" to "Delete all - cannot be undone"
And "Move into" "select" should not be visible
And I press "Cancel"
And I am on course index
And I follow "English category"
And I press "Request a course"
- And the field "Course category" matches value "English category"
+ And the "Course category" select box should contain "English category"
And I set the following fields to these values:
| Course full name | My new course |
| Course short name | Mynewcourse |
And I am on "Course 1" course homepage
And I navigate to "Edit settings" in current page administration
And I expand all fieldsets
- Then I should see "Mathematics" in the ".form-autocomplete-selection" "css_element"
+ Then "Mathematics" "autocomplete_suggestions" should exist
And I set the following fields to these values:
| Tags | Algebra |
And I press "Save and display"
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
- And I should see "Tutor" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Learner" in the ".form-autocomplete-suggestions" "css_element"
- And I should not see "Student" in the ".form-autocomplete-suggestions" "css_element"
+ And I should see "Tutor (Non-editing teacher)" in the ".form-autocomplete-suggestions" "css_element"
+ And I should see "Learner (Student)" in the ".form-autocomplete-suggestions" "css_element"
+ And I click on "Student 1's role assignments" "link"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Student 1" "table_row"
+ And "Tutor (Non-editing teacher)" "autocomplete_suggestions" should exist
+ And I click on "Cancel" "link"
+ And I press "Enrol users"
+ And the "Assign role" select box should contain "Learner (Student)"
+ And I click on "Cancel" "button" in the "Enrol users" "dialogue"
And I am on "Course 1" course homepage
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
| Your word for 'Teacher' | Lecturer |
| Your word for 'Student' | Learner |
And I press "Save and display"
- And I navigate to "Users > Enrolled users" in current page administration
- Then I should see "Lecturer" in the "Teacher 1" "table_row"
+ And I navigate to course participants
+ Then I should see "Lecturer (Teacher)" in the "Teacher 1" "table_row"
+ And I should see "Learner (Student)" in the "Student 1" "table_row"
+ And I log out
+ And I log in as "student1"
+ And I am on "Course 1" course homepage
+ And I navigate to course participants
+ And I should see "Lecturer" in the "Teacher 1" "table_row"
And I should see "Learner" in the "Student 1" "table_row"
+ And I should not see "Lecturer (Teacher)" in the "Teacher 1" "table_row"
+ And I should not see "Learner (Student)" in the "Student 1" "table_row"
Scenario: Ability to rename roles can be prevented
Given I log in as "admin"
namespace customfield_select;
-use core_customfield\api;
-
defined('MOODLE_INTERNAL') || die;
/**
public function get_default_value() {
$defaultvalue = $this->get_field()->get_configdata_property('defaultvalue');
if ('' . $defaultvalue !== '') {
- $options = field_controller::get_options_array($this->get_field());
- $key = array_search($defaultvalue, $options);
+ $key = array_search($defaultvalue, $this->get_field()->get_options());
if ($key !== false) {
return $key;
}
public function instance_form_definition(\MoodleQuickForm $mform) {
$field = $this->get_field();
$config = $field->get('configdata');
- $options = field_controller::get_options_array($field);
+ $options = $field->get_options();
$formattedoptions = array();
$context = $this->get_field()->get_handler()->get_configuration_context();
foreach ($options as $key => $option) {
return null;
}
- $options = field_controller::get_options_array($this->get_field());
+ $options = $this->get_field()->get_options();
if (array_key_exists($value, $options)) {
return format_string($options[$value], true,
['context' => $this->get_field()->get_handler()->get_configuration_context()]);
*
* @param \core_customfield\field_controller $field
* @return array
+ *
+ * @deprecated since Moodle 3.10 - MDL-68569 please use $field->get_options
*/
public static function get_options_array(\core_customfield\field_controller $field) : array {
- if ($field->get_configdata_property('options')) {
- $options = preg_split("/\s*\n\s*/", trim($field->get_configdata_property('options')));
+ debugging('get_options_array() is deprecated, please use $field->get_options() instead', DEBUG_DEVELOPER);
+
+ return $field->get_options();
+ }
+
+ /**
+ * Return configured field options
+ *
+ * @return array
+ */
+ public function get_options(): array {
+ $optionconfig = $this->get_configdata_property('options');
+ if ($optionconfig) {
+ $options = preg_split("/\s*\n\s*/", trim($optionconfig));
} else {
$options = array();
}
* @return array
*/
public function course_grouping_format_values($values): array {
- $options = self::get_options_array($this);
+ $options = $this->get_options();
$ret = [];
foreach ($values as $value) {
if (isset($options[$value])) {
* @return int
*/
public function parse_value(string $value) {
- return (int) array_search($value, self::get_options_array($this));
+ return (int) array_search($value, $this->get_options());
}
}
\ No newline at end of file
}
$cohortname = format_string($cohort->name, true, array('context'=>context::instance_by_id($cohort->contextid)));
if ($role = $DB->get_record('role', array('id'=>$instance->roleid))) {
- $role = role_get_name($role, context_course::instance($instance->courseid, IGNORE_MISSING));
+ $role = role_get_name($role, context_course::instance($instance->courseid, IGNORE_MISSING), ROLENAME_BOTH);
return get_string('pluginname', 'enrol_'.$enrol) . ' (' . $cohortname . ' - ' . $role .')';
} else {
return get_string('pluginname', 'enrol_'.$enrol) . ' (' . $cohortname . ')';
protected function get_role_options($instance, $coursecontext) {
global $DB;
- $roles = get_assignable_roles($coursecontext);
+ $roles = get_assignable_roles($coursecontext, ROLENAME_BOTH);
$roles[0] = get_string('none');
$roles = array_reverse($roles, true); // Descending default sortorder.
+
+ // If the instance is already configured, but the configured role is no longer assignable in the course then add it back.
if ($instance->id and !isset($roles[$instance->roleid])) {
if ($role = $DB->get_record('role', array('id' => $instance->roleid))) {
- $roles = role_fix_names($roles, $coursecontext, ROLENAME_ALIAS, true);
- $roles[$instance->roleid] = role_get_name($role, $coursecontext);
+ $roles[$instance->roleid] = role_get_name($role, $coursecontext, ROLENAME_BOTH);
} else {
$roles[$instance->roleid] = get_string('error');
}
$string['ok'] = "OK!\n";
$string['opt_deref'] = 'If the group membership contains distinguished names, specify how aliases are handled during a search. Select one of the following values: \'No\' (LDAP_DEREF_NEVER) or \'Yes\' (LDAP_DEREF_ALWAYS).';
$string['opt_deref_key'] = 'Dereference aliases';
-$string['phpldap_noextension'] = '<em>The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this enrolment plugin.</em>';
+$string['phpldap_noextension'] = 'The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this enrolment plugin.';
$string['pluginname'] = 'LDAP enrolments';
$string['pluginname_desc'] = '<p>You can use an LDAP server to control your enrolments. It is assumed your LDAP tree contains groups that map to the courses, and that each of those groups/courses will have membership entries to map to students.</p><p>It is assumed that courses are defined as groups in LDAP, with each group having multiple membership fields (<em>member</em> or <em>memberUid</em>) that contain a uniqueidentification of the user.</p><p>To use LDAP enrolment, your users <strong>must</strong> to have a valid idnumber field. The LDAP groups must have that idnumber in the member fields for a user to be enrolled in the course. This will usually work well if you are already using LDAP Authentication.</p><p>Enrolments will be updated when the user logs in. You can also run a script to keep enrolments in synch. Look in <em>enrol/ldap/cli/sync.php</em>.</p><p>This plugin can also be set to automatically create new courses when new groups appear in LDAP.</p>';
$string['pluginnotenabled'] = 'Plugin not enabled!';
if ($ADMIN->fulltree) {
- //--- heading ---
- $settings->add(new admin_setting_heading('enrol_ldap_settings', '', get_string('pluginname_desc', 'enrol_ldap')));
-
if (!function_exists('ldap_connect')) {
- $settings->add(new admin_setting_heading('enrol_phpldap_noextension', '', get_string('phpldap_noextension', 'enrol_ldap')));
+ $notify = new \core\output\notification(get_string('phpldap_noextension', 'enrol_ldap'),
+ \core\output\notification::NOTIFY_WARNING);
+ $settings->add(new admin_setting_heading('enrol_phpldap_noextension', '', $OUTPUT->render($notify)));
+ $settings->add(new admin_setting_heading('enrol_ldap_settings', '', get_string('pluginname_desc', 'enrol_ldap')));
} else {
+
+ $settings->add(new admin_setting_heading('enrol_ldap_settings', '', get_string('pluginname_desc', 'enrol_ldap')));
+
require_once($CFG->dirroot.'/enrol/ldap/settingslib.php');
require_once($CFG->libdir.'/ldaplib.php');
}
}
- $roles = get_assignable_roles($context);
+ $roles = get_assignable_roles($context, ROLENAME_BOTH);
$mform->addElement('select', 'roletoassign', get_string('assignrole', 'enrol_manual'), $roles);
$mform->setDefault('roletoassign', $instance->roleid);
* Activity name filtering
*/
class filter_activitynames extends moodle_text_filter {
- // Trivial-cache - keyed on $cachedcourseid and $cacheduserid.
- static $activitylist = null;
- static $cachedcourseid;
- static $cacheduserid;
function filter($text, array $options = array()) {
- global $USER; // Since 2.7 we can finally start using globals in filters.
-
$coursectx = $this->context->get_course_context(false);
if (!$coursectx) {
return $text;
}
$courseid = $coursectx->instanceid;
- // Initialise/invalidate our trivial cache if dealing with a different course.
- if (!isset(self::$cachedcourseid) || self::$cachedcourseid !== (int)$courseid) {
- self::$activitylist = null;
- }
- self::$cachedcourseid = (int)$courseid;
- // And the same for user id.
- if (!isset(self::$cacheduserid) || self::$cacheduserid !== (int)$USER->id) {
- self::$activitylist = null;
- }
- self::$cacheduserid = (int)$USER->id;
-
- /// It may be cached
-
- if (is_null(self::$activitylist)) {
- self::$activitylist = array();
-
- $modinfo = get_fast_modinfo($courseid);
- if (!empty($modinfo->cms)) {
- self::$activitylist = array(); // We will store all the created filters here.
-
- // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
- $sortedactivities = array();
- foreach ($modinfo->cms as $cm) {
- // Use normal access control and visibility, but exclude labels and hidden activities.
- if ($cm->visible and $cm->has_view() and $cm->uservisible) {
- $sortedactivities[] = (object)array(
- 'name' => $cm->name,
- 'url' => $cm->url,
- 'id' => $cm->id,
- 'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
- );
- }
- }
- // Sort activities by the length of the activity name in reverse order.
- core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
-
- foreach ($sortedactivities as $cm) {
- $title = s(trim(strip_tags($cm->name)));
- $currentname = trim($cm->name);
- $entitisedname = s($currentname);
- // Avoid empty or unlinkable activity names.
- if (!empty($title)) {
- $href_tag_begin = html_writer::start_tag('a',
- array('class' => 'autolink', 'title' => $title,
- 'href' => $cm->url));
- self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
- if ($currentname != $entitisedname) {
- // If name has some entity (& " < >) add that filter too. MDL-17545.
- self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
- }
- }
- }
- }
- }
+ $activitylist = $this->get_cached_activity_list($courseid);
$filterslist = array();
- if (self::$activitylist) {
+ if (!empty($activitylist)) {
$cmid = $this->context->instanceid;
- if ($this->context->contextlevel == CONTEXT_MODULE && isset(self::$activitylist[$cmid])) {
+ if ($this->context->contextlevel == CONTEXT_MODULE && isset($activitylist[$cmid])) {
// remove filterobjects for the current module
- $filterslist = array_values(array_diff_key(self::$activitylist, array($cmid => 1, $cmid.'-e' => 1)));
+ $filterslist = array_values(array_diff_key($activitylist, array($cmid => 1, $cmid.'-e' => 1)));
} else {
- $filterslist = array_values(self::$activitylist);
+ $filterslist = array_values($activitylist);
}
}
return $text;
}
}
+
+ /**
+ * Get all the cached activity list for a course
+ *
+ * @param int $courseid id of the course
+ * @return filterobject[] the activities
+ */
+ protected function get_cached_activity_list($courseid) {
+ global $USER;
+ $cached = cache::make_from_params(cache_store::MODE_REQUEST, 'filter', 'activitynames');
+
+ // Return cached activity list.
+ if ($cached->get('cachecourseid') == $courseid && $cached->get('cacheuserid') == $USER->id) {
+ return $cached->get('activitylist');
+ }
+
+ // Not cached yet, get activity list and set cache.
+ $activitylist = $this->get_activity_list($courseid);
+ $cached->set('cacheuserid', $USER->id);
+ $cached->set('cachecourseid', $courseid);
+ $cached->set('activitylist', $activitylist);
+ return $activitylist;
+ }
+
+ /**
+ * Get all the activity list for a course
+ *
+ * @param int $courseid id of the course
+ * @return filterobject[] the activities
+ */
+ protected function get_activity_list($courseid) {
+ $activitylist = array();
+
+ $modinfo = get_fast_modinfo($courseid);
+ if (!empty($modinfo->cms)) {
+ $activitylist = array(); // We will store all the created filters here.
+
+ // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
+ $sortedactivities = array();
+ foreach ($modinfo->cms as $cm) {
+ // Use normal access control and visibility, but exclude labels and hidden activities.
+ if ($cm->visible and $cm->has_view() and $cm->uservisible) {
+ $sortedactivities[] = (object)array(
+ 'name' => $cm->name,
+ 'url' => $cm->url,
+ 'id' => $cm->id,
+ 'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
+ );
+ }
+ }
+ // Sort activities by the length of the activity name in reverse order.
+ core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
+
+ foreach ($sortedactivities as $cm) {
+ $title = s(trim(strip_tags($cm->name)));
+ $currentname = trim($cm->name);
+ $entitisedname = s($currentname);
+ // Avoid empty or unlinkable activity names.
+ if (!empty($title)) {
+ $hreftagbegin = html_writer::start_tag('a',
+ array('class' => 'autolink', 'title' => $title,
+ 'href' => $cm->url));
+ $activitylist[$cm->id] = new filterobject($currentname, $hreftagbegin, '</a>', false, true);
+ if ($currentname != $entitisedname) {
+ // If name has some entity (& " < >) add that filter too. MDL-17545.
+ $activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
+ }
+ }
+ }
+ }
+ return $activitylist;
+ }
}
$this->assertEquals($page->cmid, $matches[2][0]);
$this->assertEquals($page->name, $matches[3][0]);
}
+
+ public function test_cache() {
+ $this->resetAfterTest(true);
+
+ // Create a test courses.
+ $course1 = $this->getDataGenerator()->create_course();
+ $course2 = $this->getDataGenerator()->create_course();
+ $context1 = context_course::instance($course1->id);
+ $context2 = context_course::instance($course2->id);
+
+ // Create page 1.
+ $page1 = $this->getDataGenerator()->create_module('page',
+ ['course' => $course1->id, 'name' => 'Test 1']);
+ // Format text with page 1 in HTML.
+ $html = '<p>Please read the two pages Test 1 and Test 2.</p>';
+ $filtered1 = format_text($html, FORMAT_HTML, array('context' => $context1));
+ // Find all the activity links in the result.
+ $matches = [];
+ preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
+ $filtered1, $matches);
+ // There should be 1 link.
+ $this->assertCount(1, $matches[1]);
+ $this->assertEquals($page1->name, $matches[1][0]);
+
+ // Create page 2.
+ $page2 = $this->getDataGenerator()->create_module('page',
+ ['course' => $course1->id, 'name' => 'Test 2']);
+ // Filter the text again.
+ $filtered2 = format_text($html, FORMAT_HTML, array('context' => $context1));
+ // The filter result does not change due to caching.
+ $this->assertEquals($filtered1, $filtered2);
+
+ // Change context, so that cache for course 1 is cleared.
+ $filtered3 = format_text($html, FORMAT_HTML, array('context' => $context2));
+ $this->assertNotEquals($filtered1, $filtered3);
+ $matches = [];
+ preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
+ $filtered3, $matches);
+ // There should be no links.
+ $this->assertCount(0, $matches[1]);
+
+ // Filter the text for course 1.
+ $filtered4 = format_text($html, FORMAT_HTML, array('context' => $context1));
+ // Find all the activity links in the result.
+ $matches = [];
+ preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
+ $filtered4, $matches);
+ // There should be 2 links.
+ $this->assertCount(2, $matches[1]);
+ $this->assertEquals($page1->name, $matches[1][0]);
+ $this->assertEquals($page2->name, $matches[1][1]);
+ }
}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-require_once '../../../config.php';
-require_once $CFG->dirroot.'/grade/lib.php';
-require_once $CFG->dirroot.'/grade/report/lib.php';
-require_once 'category_form.php';
+require_once('../../../config.php');
+require_once($CFG->dirroot.'/grade/lib.php');
+require_once($CFG->dirroot.'/grade/edit/tree/lib.php');
+require_once($CFG->dirroot.'/grade/report/lib.php');
+require_once('category_form.php');
$courseid = required_param('courseid', PARAM_INT);
$id = optional_param('id', 0, PARAM_INT); // grade_category->id
redirect($returnurl);
} else if ($data = $mform->get_data(false)) {
- // If no fullname is entered for a course category, put ? in the DB
- if (!isset($data->fullname) || $data->fullname == '') {
- $data->fullname = '?';
- }
-
- if (!isset($data->aggregateonlygraded)) {
- $data->aggregateonlygraded = 0;
- }
- if (!isset($data->aggregateoutcomes)) {
- $data->aggregateoutcomes = 0;
- }
- grade_category::set_properties($grade_category, $data);
-
- /// CATEGORY
- if (empty($grade_category->id)) {
- $grade_category->insert();
-
- } else {
- $grade_category->update();
- }
-
- /// GRADE ITEM
- // grade item data saved with prefix "grade_item_"
- $itemdata = new stdClass();
- foreach ($data as $k => $v) {
- if (preg_match('/grade_item_(.*)/', $k, $matches)) {
- $itemdata->{$matches[1]} = $v;
- }
- }
-
- if (!isset($itemdata->aggregationcoef)) {
- $itemdata->aggregationcoef = 0;
- }
-
- if (!isset($itemdata->gradepass) || $itemdata->gradepass == '') {
- $itemdata->gradepass = 0;
- }
-
- if (!isset($itemdata->grademax) || $itemdata->grademax == '') {
- $itemdata->grademax = 0;
- }
-
- if (!isset($itemdata->grademin) || $itemdata->grademin == '') {
- $itemdata->grademin = 0;
- }
-
- $hidden = empty($itemdata->hidden) ? 0: $itemdata->hidden;
- $hiddenuntil = empty($itemdata->hiddenuntil) ? 0: $itemdata->hiddenuntil;
- unset($itemdata->hidden);
- unset($itemdata->hiddenuntil);
-
- $locked = empty($itemdata->locked) ? 0: $itemdata->locked;
- $locktime = empty($itemdata->locktime) ? 0: $itemdata->locktime;
- unset($itemdata->locked);
- unset($itemdata->locktime);
-
- $convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef', 'aggregationcoef2');
- foreach ($convert as $param) {
- if (property_exists($itemdata, $param)) {
- $itemdata->$param = unformat_float($itemdata->$param);
- }
- }
- if (isset($itemdata->aggregationcoef2)) {
- $itemdata->aggregationcoef2 = $itemdata->aggregationcoef2 / 100.0;
- }
-
- // When creating a new category, a number of grade item fields are filled out automatically, and are required.
- // If the user leaves these fields empty during creation of a category, we let the default values take effect
- // Otherwise, we let the user-entered grade item values take effect
- $grade_item = $grade_category->load_grade_item();
- $grade_item_copy = fullclone($grade_item);
- grade_item::set_properties($grade_item, $itemdata);
-
- if (empty($grade_item->id)) {
- $grade_item->id = $grade_item_copy->id;
- }
- if (empty($grade_item->grademax) && $grade_item->grademax != '0') {
- $grade_item->grademax = $grade_item_copy->grademax;
- }
- if (empty($grade_item->grademin) && $grade_item->grademin != '0') {
- $grade_item->grademin = $grade_item_copy->grademin;
- }
- if (empty($grade_item->gradepass) && $grade_item->gradepass != '0') {
- $grade_item->gradepass = $grade_item_copy->gradepass;
- }
- if (empty($grade_item->aggregationcoef) && $grade_item->aggregationcoef != '0') {
- $grade_item->aggregationcoef = $grade_item_copy->aggregationcoef;
- }
-
- // Handle null decimals value - must be done before update!
- if (!property_exists($itemdata, 'decimals') or $itemdata->decimals < 0) {
- $grade_item->decimals = null;
- }
-
- // Change weightoverride flag. Check if the value is set, because it is not when the checkbox is not ticked.
- $itemdata->weightoverride = isset($itemdata->weightoverride) ? $itemdata->weightoverride : 0;
- if ($grade_item->weightoverride != $itemdata->weightoverride && $grade_category->aggregation == GRADE_AGGREGATE_SUM) {
- // If we are using natural weight and the weight has been un-overriden, force parent category to recalculate weights.
- $grade_category->force_regrading();
- }
- $grade_item->weightoverride = $itemdata->weightoverride;
-
- $grade_item->outcomeid = null;
-
- if (!empty($data->grade_item_rescalegrades) && $data->grade_item_rescalegrades == 'yes') {
- $grade_item->rescale_grades_keep_percentage($grade_item_copy->grademin, $grade_item_copy->grademax, $grade_item->grademin,
- $grade_item->grademax, 'gradebook');
- }
-
- // update hiding flag
- if ($hiddenuntil) {
- $grade_item->set_hidden($hiddenuntil, false);
- } else {
- $grade_item->set_hidden($hidden, false);
- }
-
- $grade_item->set_locktime($locktime); // locktime first - it might be removed when unlocking
- $grade_item->set_locked($locked, false, true);
-
- $grade_item->update(); // We don't need to insert it, it's already created when the category is created
-
- // set parent if needed
- if (isset($data->parentcategory)) {
- $grade_category->set_parent($data->parentcategory, 'gradebook');
- }
-
+ grade_edit_tree::update_gradecategory($grade_category, $data);
redirect($returnurl);
}
return $deepest_level;
}
+
+ /**
+ * Updates the provided gradecategory item with the provided data.
+ *
+ * @param grade_category $gradecategory The category to update.
+ * @param stdClass $data the data to update the category with.
+ * @return void
+ */
+ public static function update_gradecategory(grade_category $gradecategory, stdClass $data) {
+ // If no fullname is entered for a course category, put ? in the DB.
+ if (!isset($data->fullname) || $data->fullname == '') {
+ $data->fullname = '?';
+ }
+
+ if (!isset($data->aggregateonlygraded)) {
+ $data->aggregateonlygraded = 0;
+ }
+ if (!isset($data->aggregateoutcomes)) {
+ $data->aggregateoutcomes = 0;
+ }
+ grade_category::set_properties($gradecategory, $data);
+
+ // CATEGORY.
+ if (empty($gradecategory->id)) {
+ $gradecategory->insert();
+
+ } else {
+ $gradecategory->update();
+ }
+
+ // GRADE ITEM.
+ // Grade item data saved with prefix "grade_item_".
+ $itemdata = new stdClass();
+ foreach ($data as $k => $v) {
+ if (preg_match('/grade_item_(.*)/', $k, $matches)) {
+ $itemdata->{$matches[1]} = $v;
+ }
+ }
+
+ if (!isset($itemdata->aggregationcoef)) {
+ $itemdata->aggregationcoef = 0;
+ }
+
+ if (!isset($itemdata->gradepass) || $itemdata->gradepass == '') {
+ $itemdata->gradepass = 0;
+ }
+
+ if (!isset($itemdata->grademax) || $itemdata->grademax == '') {
+ $itemdata->grademax = 0;
+ }
+
+ if (!isset($itemdata->grademin) || $itemdata->grademin == '') {
+ $itemdata->grademin = 0;
+ }
+
+ $hidden = empty($itemdata->hidden) ? 0 : $itemdata->hidden;
+ $hiddenuntil = empty($itemdata->hiddenuntil) ? 0 : $itemdata->hiddenuntil;
+ unset($itemdata->hidden);
+ unset($itemdata->hiddenuntil);
+
+ $locked = empty($itemdata->locked) ? 0 : $itemdata->locked;
+ $locktime = empty($itemdata->locktime) ? 0 : $itemdata->locktime;
+ unset($itemdata->locked);
+ unset($itemdata->locktime);
+
+ $convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef', 'aggregationcoef2');
+ foreach ($convert as $param) {
+ if (property_exists($itemdata, $param)) {
+ $itemdata->$param = unformat_float($itemdata->$param);
+ }
+ }
+ if (isset($itemdata->aggregationcoef2)) {
+ $itemdata->aggregationcoef2 = $itemdata->aggregationcoef2 / 100.0;
+ }
+
+ // When creating a new category, a number of grade item fields are filled out automatically, and are required.
+ // If the user leaves these fields empty during creation of a category, we let the default values take effect.
+ // Otherwise, we let the user-entered grade item values take effect.
+ $gradeitem = $gradecategory->load_grade_item();
+ $gradeitemcopy = fullclone($gradeitem);
+ grade_item::set_properties($gradeitem, $itemdata);
+
+ if (empty($gradeitem->id)) {
+ $gradeitem->id = $gradeitemcopy->id;
+ }
+ if (empty($gradeitem->grademax) && $gradeitem->grademax != '0') {
+ $gradeitem->grademax = $gradeitemcopy->grademax;
+ }
+ if (empty($gradeitem->grademin) && $gradeitem->grademin != '0') {
+ $gradeitem->grademin = $gradeitemcopy->grademin;
+ }
+ if (empty($gradeitem->gradepass) && $gradeitem->gradepass != '0') {
+ $gradeitem->gradepass = $gradeitemcopy->gradepass;
+ }
+ if (empty($gradeitem->aggregationcoef) && $gradeitem->aggregationcoef != '0') {
+ $gradeitem->aggregationcoef = $gradeitemcopy->aggregationcoef;
+ }
+
+ // Handle null decimals value - must be done before update!
+ if (!property_exists($itemdata, 'decimals') or $itemdata->decimals < 0) {
+ $gradeitem->decimals = null;
+ }
+
+ // Change weightoverride flag. Check if the value is set, because it is not when the checkbox is not ticked.
+ $itemdata->weightoverride = isset($itemdata->weightoverride) ? $itemdata->weightoverride : 0;
+ if ($gradeitem->weightoverride != $itemdata->weightoverride && $gradecategory->aggregation == GRADE_AGGREGATE_SUM) {
+ // If we are using natural weight and the weight has been un-overriden, force parent category to recalculate weights.
+ $gradecategory->force_regrading();
+ }
+ $gradeitem->weightoverride = $itemdata->weightoverride;
+
+ $gradeitem->outcomeid = null;
+
+ if (!empty($data->grade_item_rescalegrades) && $data->grade_item_rescalegrades == 'yes') {
+ $gradeitem->rescale_grades_keep_percentage($gradeitemcopy->grademin, $gradeitemcopy->grademax,
+ $gradeitem->grademin, $gradeitem->grademax, 'gradebook');
+ }
+
+ // Update hiding flag.
+ if ($hiddenuntil) {
+ $gradeitem->set_hidden($hiddenuntil, false);
+ } else {
+ $gradeitem->set_hidden($hidden, false);
+ }
+
+ $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking.
+ $gradeitem->set_locked($locked, false, true);
+
+ $gradeitem->update(); // We don't need to insert it, it's already created when the category is created.
+
+ // Set parent if needed.
+ if (isset($data->parentcategory)) {
+ $gradecategory->set_parent($data->parentcategory, 'gradebook');
+ }
+ }
}
/**
return $togglegroup;
}
-}
-
+}
\ No newline at end of file
// This query returns a count of ungraded grades (NULL finalgrade OR no matching record in grade_grades table)
$sql = "SELECT gi.id, COUNT(DISTINCT u.id) AS count
FROM {grade_items} gi
- CROSS JOIN {user} u
- JOIN ($enrolledsql) je
- ON je.id = u.id
+ CROSS JOIN ($enrolledsql) u
JOIN {role_assignments} ra
ON ra.userid = u.id
LEFT OUTER JOIN {grade_grades} g
WHERE gi.courseid = :courseid
AND ra.roleid $gradebookrolessql
AND ra.contextid $relatedctxsql
- AND u.deleted = 0
AND g.id IS NULL
$groupwheresql
GROUP BY gi.id";
public function original_headers() {
return array(
'', // For filter icon.
- get_string('firstname') . ' (' . get_string('alternatename') . ') ' . get_string('lastname'),
+ get_string('fullnameuser', 'core'),
get_string('range', 'grades'),
get_string('grade', 'grades'),
get_string('feedback', 'grades'),
$lockicon = $OUTPUT->pix_icon('t/locked', 'grade is locked') . ' ';
}
- if (!empty($item->alternatename)) {
- $fullname = $lockicon . $item->alternatename . ' (' . $item->firstname . ') ' . $item->lastname;
+ if (has_capability('moodle/site:viewfullnames', \context_course::instance($this->courseid))) {
+ $fullname = $lockicon . fullname($item, true);
} else {
$fullname = $lockicon . fullname($item);
}
}
if ($errorstr) {
- $user = $DB->get_record('user', array('id' => $userid), 'id, firstname, alternatename, lastname');
+ $user = get_complete_user_data('id', $userid);
$gradestr = new stdClass;
- if (!empty($user->alternatename)) {
- $gradestr->username = $user->alternatename . ' (' . $user->firstname . ') ' . $user->lastname;
+ if (has_capability('moodle/site:viewfullnames', \context_course::instance($gradeitem->courseid))) {
+ $gradestr->username = fullname($user, true);
} else {
- $gradestr->username = $user->firstname . ' ' . $user->lastname;
+ $gradestr->username = fullname($user);
}
$gradestr->itemname = $this->grade->grade_item->get_name();
$errorstr = get_string($errorstr, 'grades', $gradestr);
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I follow "Single view for Test assignment one"
- Then the field "Grade for james (Student) 1" matches value "50.00"
- And the field "Override for james (Student) 1" matches value "0"
+ Then the field "Grade for Student 1" matches value "50.00"
+ And the field "Override for Student 1" matches value "0"
And I set the field "Perform bulk insert" to "1"
And I set the field "Insert value" to "1.0"
And I press "Save"
And I press "Continue"
- And the field "Grade for james (Student) 1" matches value "50.00"
- And the field "Override for james (Student) 1" matches value "0"
- And the field "Grade for holly (Student) 2" matches value "1.00"
- And the field "Override for holly (Student) 2" matches value "1"
- And the field "Grade for anna (Student) 3" matches value "1.00"
- And the field "Override for anna (Student) 3" matches value "1"
- And the field "Grade for zac (Student) 4" matches value "1.00"
- And the field "Override for zac (Student) 4" matches value "1"
+ And the field "Grade for Student 1" matches value "50.00"
+ And the field "Override for Student 1" matches value "0"
+ And the field "Grade for Student 2" matches value "1.00"
+ And the field "Override for Student 2" matches value "1"
+ And the field "Grade for Student 3" matches value "1.00"
+ And the field "Override for Student 3" matches value "1"
+ And the field "Grade for Student 4" matches value "1.00"
+ And the field "Override for Student 4" matches value "1"
And I set the field "For" to "All grades"
And I set the field "Perform bulk insert" to "1"
And I set the field "Insert value" to "2.0"
And I press "Save"
And I press "Continue"
- And the field "Grade for james (Student) 1" matches value "2.00"
- And the field "Override for james (Student) 1" matches value "1"
- And the field "Grade for holly (Student) 2" matches value "2.00"
- And the field "Override for holly (Student) 2" matches value "1"
- And the field "Grade for anna (Student) 3" matches value "2.00"
- And the field "Override for anna (Student) 3" matches value "1"
- And the field "Grade for zac (Student) 4" matches value "2.00"
- And the field "Override for zac (Student) 4" matches value "1"
+ And the field "Grade for Student 1" matches value "2.00"
+ And the field "Override for Student 1" matches value "1"
+ And the field "Grade for Student 2" matches value "2.00"
+ And the field "Override for Student 2" matches value "1"
+ And the field "Grade for Student 3" matches value "2.00"
+ And the field "Override for Student 3" matches value "1"
+ And the field "Grade for Student 4" matches value "2.00"
+ And the field "Override for Student 4" matches value "1"
Scenario: I can bulk insert grades and check their override flags for user view.
Given I log in as "teacher1"
And I set the field "Perform bulk insert" to "1"
When I set the field "Insert value" to "-1"
And I press "Save"
- Then I should see "The grade entered for Test assignment one for james (Student) 1 is less than the minimum allowed"
- And I should see "The grade entered for Test assignment one for holly (Student) 2 is less than the minimum allowed"
- And I should see "The grade entered for Test assignment one for anna (Student) 3 is less than the minimum allowed"
- And I should see "The grade entered for Test assignment one for zac (Student) 4 is less than the minimum allowed"
+ Then I should see "The grade entered for Test assignment one for Student 1 is less than the minimum allowed"
+ And I should see "The grade entered for Test assignment one for Student 2 is less than the minimum allowed"
+ And I should see "The grade entered for Test assignment one for Student 3 is less than the minimum allowed"
+ And I should see "The grade entered for Test assignment one for Student 4 is less than the minimum allowed"
And I should see "Grades were set for 0 items"
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
- | username | firstname | lastname | email | idnumber | alternatename |
- | teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred |
- | teacher2 | No edit | 1 | teacher2@example.com | t2 | nick |
- | student1 | Student | 1 | student1@example.com | s1 | james |
- | student2 | Student | 2 | student1@example.com | s2 | holly |
- | student3 | Student | 3 | student1@example.com | s3 | anna |
- | student4 | Student | 4 | student1@example.com | s4 | zac |
+ | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic |
+ | teacher1 | Teacher | 1 | teacher1@example.com | t1 | | fred | | |
+ | teacher2 | No edit | 1 | teacher2@example.com | t2 | | nick | | |
+ | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham |
+ | student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee |
+ | student3 | Siobhan | Desforges | student3@example.com | s3 | Sarah | Sev | Shevon | De-forjay |
+ | student4 | Student | 4 | student4@example.com | s4 | | zac | | |
And the following "scales" exist:
| name | scale |
| Test Scale | Disappointing, Good, Very good, Excellent |
| capability | permission | role | contextlevel | reference |
| moodle/grade:edit | Allow | teacher | Course | C1 |
| gradereport/singleview:view | Allow | teacher | Course | C1 |
+ And the following config values are set as admin:
+ | fullnamedisplay | firstnamephonetic,lastnamephonetic |
+ | alternativefullnameformat | middlename, alternatename, firstname, lastname |
And I log in as "teacher1"
And I am on "Course 1" course homepage
Given I navigate to "View > Grader report" in the course gradebook
@javascript
Scenario: I can update grades, add feedback and exclude grades.
Given I navigate to "View > Single view" in the course gradebook
- And I select "Student 4" from the "Select user..." singleselect
+ And I select "Student" from the "Select user..." singleselect
And I set the field "Override for Test assignment one" to "1"
When I set the following fields to these values:
| Grade for Test assignment one | 10.00 |
And the field "Grade for Test grade item" matches value "45.00"
And the field "Grade for Course total" matches value "55.00"
And I click on "Show grades for Test assignment three" "link"
- And I click on "Override for james (Student) 1" "checkbox"
+ And I click on "Override for Ann, Jill, Grainne, Beauchamp" "checkbox"
And I set the following fields to these values:
- | Grade for james (Student) 1 | 12.05 |
- | Feedback for james (Student) 1 | test data2 |
- And I set the field "Exclude for holly (Student) 2" to "1"
+ | Grade for Ann, Jill, Grainne, Beauchamp | 12.05 |
+ | Feedback for Ann, Jill, Grainne, Beauchamp | test data2 |
+ And I set the field "Exclude for Jane, Nina, Niamh, Cholmondely" to "1"
And I press "Save"
Then I should see "Grades were set for 2 items"
And I press "Continue"
- And the field "Grade for james (Student) 1" matches value "12.05"
- And the field "Exclude for holly (Student) 2" matches value "1"
+ And the field "Grade for Ann, Jill, Grainne, Beauchamp" matches value "12.05"
+ And the field "Exclude for Jane, Nina, Niamh, Cholmondely" matches value "1"
And I select "new grade item 1" from the "Select grade item..." singleselect
- And I set the field "Grade for james (Student) 1" to "Very good"
+ And I set the field "Grade for Ann, Jill, Grainne, Beauchamp" to "Very good"
And I press "Save"
Then I should see "Grades were set for 1 items"
And I press "Continue"
And the following should exist in the "generaltable" table:
| First name (Alternate name) Surname | Grade |
- | james (Student) 1 | Very good |
+ | Ann, Jill, Grainne, Beauchamp | Very good |
And I log out
And I log in as "teacher2"
And I am on "Course 1" course homepage
Given I navigate to "View > Single view" in the course gradebook
- And I select "Student 4" from the "Select user..." singleselect
+ And I select "Student" from the "Select user..." singleselect
And the "Exclude for Test assignment one" "checkbox" should be disabled
And the "Override for Test assignment one" "checkbox" should be enabled
Given I follow "Single view for Test assignment one"
Then I should see "Test assignment one"
Then I navigate to "View > Grader report" in the course gradebook
- And I follow "Single view for Student 1"
- Then I should see "Student 1"
+ And I follow "Single view for Ann, Jill, Grainne, Beauchamp"
+ Then I should see "Gronya,Beecham"
Scenario: I can bulk update grades.
- Given I follow "Single view for Student 1"
- Then I should see "Student 1"
+ Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
+ Then I should see "Gronya,Beecham"
When I set the field "For" to "All grades"
And I set the field "Insert value" to "1.0"
And I set the field "Perform bulk insert" to "1"
Then I should see "Grades were set for 6 items"
Scenario: Navigation works in the Single view.
- Given I follow "Single view for Student 1"
- Then I should see "Student 1"
- And I follow "Student 2"
- Then I should see "Student 2"
- And I follow "Student 1"
- Then I should see "Student 1"
+ Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
+ Then I should see "Gronya,Beecham"
+ And I follow "Nee,Chumlee"
+ Then I should see "Nee,Chumlee"
+ And I follow "Gronya,Beecham"
+ Then I should see "Gronya,Beecham"
And I click on "Show grades for Test assignment four" "link"
Then I should see "Test assignment four"
And I follow "Test assignment three"
Scenario: Activities are clickable only when
it has a valid activity page.
- Given I follow "Single view for Student 1"
+ Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
And "new grade item 1" "link" should not exist in the "//tbody//tr[position()=1]//td[position()=2]" "xpath_element"
Then "Category total" "link" should not exist in the "//tbody//tr[position()=2]//td[position()=2]" "xpath_element"
And "Course total" "link" should not exist in the "//tbody//tr[position()=last()]//td[position()=2]" "xpath_element"
$error = '';
/// Get applicable roles - used in menus etc later on
-$rolenames = role_fix_names(get_profile_roles($context), $context, ROLENAME_ALIAS, true);
+$rolenames = role_fix_names(get_profile_roles($context), $context, ROLENAME_BOTH, true);
/// Create the form
$editform = new autogroup_form(null, array('roles' => $rolenames));
}
$template = new \stdClass();
- $template->embedurl = self::get_embed_url($url)->out();
+ $template->embedurl = self::get_embed_url($url, $this->component)->out(false);
return $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
}
/**
* Get the encoded URL for embeding this H5P content.
* @param string $url The URL of the .h5p file.
+ * @param string $component optional Moodle component to send xAPI tracking
*
* @return \moodle_url The embed URL.
*/
- public static function get_embed_url(string $url): \moodle_url {
- return new \moodle_url('/h5p/embed.php', ['url' => $url]);
+ public static function get_embed_url(string $url, string $component = ''): \moodle_url {
+ $params = ['url' => $url];
+ if (!empty($component)) {
+ // If component is not empty, it will be passed too, in order to allow tracking too.
+ $params['component'] = $component;
+ }
+
+ return new \moodle_url('/h5p/embed.php', $params);
}
/**
$string['authpreventaccountcreation_help'] = 'When a user authenticates, an account on the site is automatically created if it doesn\'t yet exist. If an external database, such as LDAP, is used for authentication, but you wish to restrict access to the site to users with an existing account only, then this option should be enabled. New accounts will need to be created manually or via the upload users feature. Note that this setting doesn\'t apply to MNet authentication.';
$string['authsettings'] = 'Manage authentication';
$string['autolang'] = 'Language autodetect';
+$string['autolangusercreation'] = 'Use language that is auto detected from users browser during user creation';
$string['autologinguests'] = 'Auto-login guests';
$string['searchareas'] = 'Search areas';
$string['availableto'] = 'Available to';
$string['configallusersaresitestudents'] = 'For activities on the front page of the site, should ALL users be considered as students? If you answer "Yes", then any confirmed user account will be allowed to participate as a student in those activities. If you answer "No", then only users who are already a participant in at least one course will be able to take part in those front page activities. Only admins and specially assigned teachers can act as teachers for these front page activities.';
$string['configauthenticationplugins'] = 'Please choose the authentication plugins you wish to use and arrange them in order of failthrough.';
$string['configautolang'] = 'Detect default language from browser setting, if disabled site default is used.';
+$string['configautolangusercreation'] = 'Use language from users browser during user creation';
$string['configautologinguests'] = 'Should visitors be logged in as guests automatically when entering courses with guest access?';
$string['configbloglevel'] = 'This setting allows you to restrict the level to which user blogs can be viewed on this site. Note that they specify the maximum context of the VIEWER not the poster or the types of blog posts. Blogs can also be disabled completely if you don\'t want them at all.';
$string['configcalendarcustomexport'] = 'Enable custom date range export of calendar';
$string['copydest'] = 'Destination';
$string['copyingcourse'] = 'Course copying in progress';
$string['copyingcourseshortname'] = 'copying';
+$string['copyfieldnotfound'] = 'A required field was not found';
$string['copyformfail'] = 'AJAX submission of course copy form has failed.';
$string['copyop'] = 'Current operation';
$string['copyprogressheading'] = 'Course copies in progress';
$string['warnexpired'] = ' (This badge has expired!)';
$string['year'] = 'Year(s)';
-// Deprecated since Moodle 3.6.
-$string['error:invalidbadgeurl'] = 'Invalid issuer URL format. The URL should have a prefix http:// or https://.';
-$string['backpackbadges'] = 'You have {$a->totalbadges} badge(s) displayed from {$a->totalcollections} collection(s). <a href="mybackpack.php">Change backpack settings</a>.';
-$string['error:nogroups'] = '<p>There are no public collections of badges available in your backpack. </p> <p>Only public collections are shown. <a href="https://backpack.openbadges.org">Visit your backpack</a> to create some public collections.</p>';
-$string['nobackpackbadges'] = 'There are no badges in the collections you have selected. <a href="mybackpack.php">Add more collections</a>.';
-$string['nobackpackcollections'] = 'No badge collections have been selected. <a href="mybackpack.php">Add collections</a>.';
-
// Deprecated since Moodle 3.9.
$string['editsettings'] = 'Edit settings';
$string['sitebackpackverify'] = 'Backpack connection';
$string['emptynamenotallowed'] = 'Empty name is not allowed';
$string['eventcontentcreated'] = 'Content created';
$string['eventcontentdeleted'] = 'Content deleted';
+$string['eventcontentreplaced'] = 'Content replaced with file';
$string['eventcontentupdated'] = 'Content updated';
$string['eventcontentuploaded'] = 'Content uploaded';
$string['eventcontentviewed'] = 'Content viewed';
$string['privacy:request:preference:set'] = 'The value of the setting \'{$a->name}\' was \'{$a->value}\'';
$string['rename'] = 'Rename';
$string['renamecontent'] = 'Rename content';
+$string['replacecontent'] = 'Replace with file';
$string['searchcontentbankbyname'] = 'Search for content by name';
$string['size'] = 'Size';
$string['timecreated'] = 'Time created';
mypreferences,core_grades
myprofile,core
viewallmyentries,core_blog
-formattexttype,core
-currentlyselectedusers,core
-emailuserhasnone,core
-emaildisplayhidden,core
-sitemessage,core
-coursemessage,core
-addedrecip,core
-addedrecips,core
messagecontactrequestsnotification,core_message
messagecontactrequestsnotificationsubject,core_message
-messagingdisabled,core_message
-messagedselectedcountusersfailed,core
-backtoparticipants,core
-keepsearching,core
-allfieldsrequired,core
-previewhtml,core
-messagedselecteduserfailed,core
-eventmessagecontactblocked,core_message
-eventmessagecontactunblocked,core_message
-userisblockingyou,core_message
-userisblockingyounoncontact,core_message
-error:invalidbadgeurl,core_badges
nomessages,core_message
searchallavailablecourses_desc,core_admin
search:mycourse,core_search
undockblock,core_block
undockitem,core_block
canceledit,core_message
-backpackbadges,core_badges
-nobackpackbadges,core_badges
-nobackpackcollections,core_badges
-error:nogroups,core_badges
purgedefinitionsuccess,core_cache
purgestoresuccess,core_cache
eventrolecapabilitiesupdated,core_role
$string['noparticipatorycms'] = 'Sorry, but you have no participatory course modules to report on';
$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a}).';
$string['nopermissiontocomment'] = 'You can\'t add comments';
-$string['nopermissiontodelentry'] = 'You can\'t delete other people\'s entries!';
+$string['nopermissiontodelentry'] = 'You can\'t delete this comment!';
$string['nopermissiontoeditcomment'] = 'You can\'t edit other people\'s comments!';
$string['nopermissiontohide'] = 'No permission to hide!';
$string['nopermissiontoimportact'] = 'You do not have the required permissions to import activities to this course';
$string['unreadnewmessage'] = 'New message from {$a}';
$string['useentertosend'] = 'Use enter to send';
$string['usercantbemessaged'] = 'You can\'t message {$a} due to their message preferences. Try adding them as a contact.';
-$string['userisblockingyou'] = 'This user has blocked you from sending messages to them';
-$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
$string['userwouldliketocontactyou'] = '{$a} would like to contact you';
$string['viewfullnotification'] = 'View full notification';
$string['viewmessageswith'] = 'View messages with {$a}';
$string['youhaveblockeduser'] = 'You have blocked this user.';
$string['yourcontactrequestpending'] = 'Your contact request is pending with {$a}';
-// Deprecated since Moodle 3.6.
-$string['eventmessagecontactblocked'] = 'Message contact blocked';
-$string['eventmessagecontactunblocked'] = 'Message contact unblocked';
-$string['messagingdisabled'] = 'Messaging is disabled on this site, emails will be sent instead';
-$string['userisblockingyou'] = 'This user has blocked you from sending messages to them.';
-$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
-
// Deprecated since Moodle 3.7.
$string['nomessages'] = 'No messages';
$string['outputdisabled'] = 'Output disabled';
$string['zippingbackup'] = 'Zipping backup';
$string['deprecatedeventname'] = '{$a} (no longer in use)';
-// Deprecated since Moodle 3.6.
-$string['addedrecip'] = 'Added {$a} new recipient';
-$string['addedrecips'] = 'Added {$a} new recipients';
-$string['allfieldsrequired'] = 'All fields are required';
-$string['backtoparticipants'] = 'Back to participants list';
-$string['currentlyselectedusers'] = 'Currently selected users';
-$string['coursemessage'] = 'Message course users';
-$string['emaildisplayhidden'] = 'Email hidden';
-$string['emailuserhasnone'] = 'There is no email address for the user.';
-$string['formattexttype'] = 'Formatting';
-$string['keepsearching'] = 'Keep searching';
-$string['messagedselectedcountusersfailed'] = 'A problem occurred and {$a} messages have not been sent.';
-$string['messagedselecteduserfailed'] = 'The message was not sent to user {$a->fullname}.';
-$string['previewhtml'] = 'HTML format preview';
-$string['sitemessage'] = 'Message users';
-
// Deprecated since Moodle 3.9.
$string['participantscount'] = 'Number of participants: {$a}';
$string['userfilterplaceholder'] = 'Search keyword or select filter';
$string['cannotdownload'] = 'Cannot download this file';
$string['cannotdownloaddir'] = 'Cannot download this folder';
$string['cannotinitplugin'] = 'Call plugin_init failed';
+$string['cannotunzipcontentunreadable'] = 'Cannot unzip this file because the contents of the file cannot be read.';
+$string['cannotunzipquotaexceeded'] = 'Cannot unzip this file because the maximum size allowed in this draft area will be exceeded.';
$string['cleancache'] = 'Clean my cache files';
$string['close'] = 'Close';
$string['commonrepositorysettings'] = 'Common repository settings';
$string['contentbank:access'] = 'Access the content bank';
$string['contentbank:deleteanycontent'] = 'Delete any content from the content bank';
$string['contentbank:deleteowncontent'] = 'Delete content from own content bank';
+$string['contentbank:downloadcontent'] = 'Download a content from the content bank';
$string['contentbank:manageanycontent'] = 'Manage any content from the content bank';
$string['contentbank:manageowncontent'] = 'Manage content from own content bank';
$string['contentbank:upload'] = 'Upload new content to the content bank';
$string['match'] = 'Match';
$string['matchofthefollowing'] = 'of the following:';
$string['moodlenetprofile'] = 'MoodleNet profile';
+$string['moodlenetprofile_help'] = 'This field is to link your MoodleNet profile to Moodle. It expects a WebFinger compliant URI';
$string['placeholdertypeorselect'] = 'Type or select...';
$string['placeholdertype'] = 'Type...';
$string['privacy:courserequestpath'] = 'Requested courses';
}
}
+ if (!empty($USER->loginascontext)) {
+ // The current user is logged in as another user and can assume their identity at or below the `loginascontext`
+ // defined in the USER session.
+ // The user may not assume their identity at any other location.
+ if (!$USER->loginascontext->is_parent_of($context, true)) {
+ // The context being checked is not the specified context, or one of its children.
+ return false;
+ }
+ }
+
// Find out if user is admin - it is not possible to override the doanything in any way
// and it is not possible to switch to admin role either.
if ($doanything) {
* test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
*
* @param context $context a context.
+ * @param int $rolenamedisplay the type of role name to display. One of the
+ * ROLENAME_X constants. Default ROLENAME_ALIAS.
* @return array an array $roleid => $rolename.
*/
-function get_switchable_roles(context $context) {
+function get_switchable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS) {
global $USER, $DB;
// You can't switch roles without this capability.
ORDER BY r.sortorder";
$roles = $DB->get_records_sql($query, $params);
- return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
+ return role_fix_names($roles, $context, $rolenamedisplay, true);
}
/**
*
* @param context $context a context.
* @param int $userid id of user.
+ * @param int $rolenamedisplay the type of role name to display. One of the
+ * ROLENAME_X constants. Default ROLENAME_ALIAS.
* @return array an array $roleid => $rolename.
*/
-function get_viewable_roles(context $context, $userid = null) {
+function get_viewable_roles(context $context, $userid = null, $rolenamedisplay = ROLENAME_ALIAS) {
global $USER, $DB;
if ($userid == null) {
ORDER BY r.sortorder";
$roles = $DB->get_records_sql($query, $params);
- return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
+ return role_fix_names($roles, $context, $rolenamedisplay, true);
}
/**
return $result;
}
+ /**
+ * Determine if the current context is a parent of the possible child.
+ *
+ * @param context $possiblechild
+ * @param bool $includeself Whether to check the current context
+ * @return bool
+ */
+ public function is_parent_of(context $possiblechild, bool $includeself): bool {
+ // A simple substring check is used on the context path.
+ // The possible child's path is used as a haystack, with the current context as the needle.
+ // The path is prefixed with '+' to ensure that the parent always starts at the top.
+ // It is suffixed with '+' to ensure that parents are not included.
+ // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
+ // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
+ // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
+ $haystacksuffix = $includeself ? '/+' : '+';
+
+ $strpos = strpos(
+ "+{$possiblechild->path}{$haystacksuffix}",
+ "+{$this->path}/"
+ );
+ return $strpos === 0;
+ }
+
/**
* Returns parent contexts of this context in reversed order, i.e. parent first,
* then grand parent, etc.
return $result;
}
+ /**
+ * Determine if the current context is a child of the possible parent.
+ *
+ * @param context $possibleparent
+ * @param bool $includeself Whether to check the current context
+ * @return bool
+ */
+ public function is_child_of(context $possibleparent, bool $includeself): bool {
+ // A simple substring check is used on the context path.
+ // The current context is used as a haystack, with the possible parent as the needle.
+ // The path is prefixed with '+' to ensure that the parent always starts at the top.
+ // It is suffixed with '+' to ensure that children are not included.
+ // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
+ // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
+ // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
+ $haystacksuffix = $includeself ? '/+' : '+';
+
+ $strpos = strpos(
+ "+{$this->path}{$haystacksuffix}",
+ "+{$possibleparent->path}/"
+ );
+ return $strpos === 0;
+ }
+
/**
* Returns parent context ids of this context in reversed order, i.e. parent first,
* then grand parent, etc.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class admin_settings_coursecat_select extends admin_setting_configselect {
+class admin_settings_coursecat_select extends admin_setting_configselect_autocomplete {
/**
* Calls parent::__construct with specific arguments
*/
- public function __construct($name, $visiblename, $description, $defaultsetting) {
- parent::__construct($name, $visiblename, $description, $defaultsetting, NULL);
+ public function __construct($name, $visiblename, $description, $defaultsetting = 1) {
+ parent::__construct($name, $visiblename, $description, $defaultsetting, $choices = null);
}
/**
* @return bool
*/
public function load_choices() {
- global $CFG;
- require_once($CFG->dirroot.'/course/lib.php');
if (is_array($this->choices)) {
return true;
}
}
+/**
+ * Autocomplete as you type form element.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_setting_configselect_autocomplete extends admin_setting_configselect {
+ /** @var boolean $tags Should we allow typing new entries to the field? */
+ protected $tags = false;
+ /** @var string $ajax Name of an AMD module to send/process ajax requests. */
+ protected $ajax = '';
+ /** @var string $placeholder Placeholder text for an empty list. */
+ protected $placeholder = '';
+ /** @var bool $casesensitive Whether the search has to be case-sensitive. */
+ protected $casesensitive = false;
+ /** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
+ protected $showsuggestions = true;
+ /** @var string $noselectionstring String that is shown when there are no selections. */
+ protected $noselectionstring = '';
+
+ /**
+ * Returns XHTML select field and wrapping div(s)
+ *
+ * @see output_select_html()
+ *
+ * @param string $data the option to show as selected
+ * @param string $query
+ * @return string XHTML field and wrapping div
+ */
+ public function output_html($data, $query='') {
+ global $PAGE;
+
+ $html = parent::output_html($data, $query);
+
+ if ($html === '') {
+ return $html;
+ }
+
+ $this->placeholder = get_string('search');
+
+ $params = array('#' . $this->get_id(), $this->tags, $this->ajax,
+ $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring);
+
+ // Load autocomplete wrapper for select2 library.
+ $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params);
+
+ return $html;
+ }
+}
/**
* Dropdown menu with an advanced checkbox, that controls a additional $name.'_adv' setting.
toggleSlavesToState(root, toggleGroupName, targetState);
};
+ /**
+ * Toggles the master checkboxes and action elements in a given toggle group.
+ *
+ * @param {jQuery} root The root jQuery element.
+ * @param {String} toggleGroupName The name of the toggle group
+ */
+ var toggleMastersAndActionElements = function(root, toggleGroupName) {
+ var toggleGroupSlaves = getAllSlaveCheckboxes(root, toggleGroupName);
+ if (toggleGroupSlaves.length > 0) {
+ var toggleGroupCheckedSlaves = toggleGroupSlaves.filter(':checked');
+ var targetState = toggleGroupSlaves.length === toggleGroupCheckedSlaves.length;
+
+ // Make sure to toggle the exact master checkbox in the given toggle group.
+ setMasterStates(root, toggleGroupName, targetState, true);
+ // Enable the action elements if there's at least one checkbox checked in the given toggle group.
+ // Disable otherwise.
+ setActionElementStates(root, toggleGroupName, !toggleGroupCheckedSlaves.length);
+ }
+ };
+
+ /**
+ * Returns an array containing every toggle group level of a given toggle group.
+ *
+ * @param {String} toggleGroupName The name of the toggle group
+ * @return {Array} toggleGroupLevels Array that contains every toggle group level of a given toggle group
+ */
+ var getToggleGroupLevels = function(toggleGroupName) {
+ var toggleGroups = toggleGroupName.split(' ');
+ var toggleGroupLevels = [];
+ var toggleGroupLevel = '';
+
+ toggleGroups.forEach(function(toggleGroupName) {
+ toggleGroupLevel += ' ' + toggleGroupName;
+ toggleGroupLevels.push(toggleGroupLevel.trim());
+ });
+
+ return toggleGroupLevels;
+ };
+
/**
* Toggles the slave checkboxes to a specific state.
*
*/
var toggleSlavesToState = function(root, toggleGroupName, targetState) {
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
+ // Set the slave checkboxes from the masters and manually trigger the native 'change' event.
+ slaves.prop('checked', targetState).trigger('change');
+ // Get all checked slaves after the change of state.
var checkedSlaves = slaves.filter(':checked');
+ // Toggle the master checkbox in the given toggle group.
setMasterStates(root, toggleGroupName, targetState, false);
-
- // Set the slave checkboxes from the masters.
- slaves.prop('checked', targetState);
- // Trigger 'change' event to toggle other master checkboxes (e.g. parent master checkboxes) and action elements.
- slaves.trigger('change');
+ // Enable the action elements if there's at least one checkbox checked in the given toggle group. Disable otherwise.
+ setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
+
+ // Get all toggle group levels and toggle accordingly all parent master checkboxes and action elements from each
+ // level. Exclude the given toggle group (toggleGroupName) as the master checkboxes and action elements from this
+ // level have been already toggled.
+ var toggleGroupLevels = getToggleGroupLevels(toggleGroupName)
+ .filter(toggleGroupLevel => toggleGroupLevel !== toggleGroupName);
+
+ toggleGroupLevels.forEach(function(toggleGroupLevel) {
+ // Toggle the master checkboxes action elements in the given toggle group level.
+ toggleMastersAndActionElements(root, toggleGroupLevel);
+ });
PubSub.publish(events.checkboxToggled, {
root: root,
var toggleMastersFromSlaves = function(e) {
var root = e.data.root;
var target = $(e.target);
+ var toggleGroupName = target.data('togglegroup');
+ var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
+ var checkedSlaves = slaves.filter(':checked');
- var toggleGroups = target.data('togglegroup').split(' ');
- var toggleGroupLevels = [];
- var toggleGroupLevel = '';
- toggleGroups.forEach(function(toggleGroupName) {
- toggleGroupLevel += ' ' + toggleGroupName;
- toggleGroupLevels.push(toggleGroupLevel.trim());
+ // Get all toggle group levels for the given toggle group and toggle accordingly all master checkboxes
+ // and action elements from each level.
+ var toggleGroupLevels = getToggleGroupLevels(toggleGroupName);
+ toggleGroupLevels.forEach(function(toggleGroupLevel) {
+ // Toggle the master checkboxes action elements in the given toggle group level.
+ toggleMastersAndActionElements(root, toggleGroupLevel);
});
- toggleGroupLevels.forEach(function(toggleGroupName) {
- var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
- var checkedSlaves = slaves.filter(':checked');
- var targetState = (slaves.length === checkedSlaves.length);
-
- // Make sure to toggle the exact master checkbox.
- setMasterStates(root, toggleGroupName, targetState, true);
-
- // Enable action elements when there's at least one checkbox checked. Disable otherwise.
- setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
-
- PubSub.publish(events.checkboxToggled, {
- root: root,
- toggleGroupName: toggleGroupName,
- slaves: slaves,
- checkedSlaves: checkedSlaves,
- anyChecked: !!checkedSlaves.length,
- });
+ PubSub.publish(events.checkboxToggled, {
+ root: root,
+ toggleGroupName: toggleGroupName,
+ slaves: slaves,
+ checkedSlaves: checkedSlaves,
+ anyChecked: !!checkedSlaves.length,
});
};
var root = $(document.body);
root.on('click', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters);
- root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
+ root.on('click', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
}
};
/**
* The JS code to check that the page is ready.
+ *
+ * The document must be complete and either M.util.pending_js must be empty, or it must not be defined at all.
*/
- const PAGE_READY_JS = '(typeof M !== "undefined" && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
+ const PAGE_READY_JS = "document.readyState === 'complete' && " .
+ "(typeof M !== 'object' || typeof M.util !== 'object' || " .
+ "typeof M.util.pending_js === 'undefined' || M.util.pending_js.length === 0)";
/**
* Locates url, based on provided path.
try {
$jscode = trim(preg_replace('/\s+/', ' ', '
return (function() {
- if (typeof M === "undefined") {
- if (document.readyState === "complete") {
- return "";
- } else {
- return "incomplete";
- }
- } else if (' . self::PAGE_READY_JS . ') {
+ if (document.readyState !== "complete") {
+ return "incomplete";
+ }
+
+ if (typeof M !== "object" || typeof M.util !== "object" || typeof M.util.pending_js === "undefined") {
return "";
- } else if (typeof M.util !== "undefined") {
- return M.util.pending_js.join(":");
- } else {
- return "incomplete"
}
+
+ return M.util.pending_js.join(":");
})()'));
$pending = self::evaluate_script_in_session($session, $jscode);
} catch (NoSuchWindow $nsw) {
*/
public function ensure_content_created($region, $output) {
$this->ensure_instances_exist($region);
+
+ if (!has_capability('moodle/block:view', $this->page->context) ) {
+ $this->visibleblockcontent[$region] = [];
+ return;
+ }
+
if (!array_key_exists($region, $this->visibleblockcontent)) {
$contents = array();
if (array_key_exists($region, $this->extracontent)) {
use coding_exception;
use core_php_time_limit;
+use stored_file;
/**
* Dataformat utility class
return $filepath;
}
+
+ /**
+ * Writes a formatted data file to file storage
+ *
+ * @param array $filerecord File record for storage, 'filename' extension should be omitted as it's added by the dataformat
+ * @param string $dataformat
+ * @param array $columns
+ * @param Iterable $iterator Iterable set of records to write
+ * @param callable|null $callback Optional callback method to apply to each record prior to writing
+ * @return stored_file
+ */
+ public static function write_data_to_filearea(array $filerecord, string $dataformat, array $columns, Iterable $iterator,
+ callable $callback = null): stored_file {
+
+ $filepath = self::write_data($filerecord['filename'], $dataformat, $columns, $iterator, $callback);
+
+ // Update filename of returned file record.
+ $filerecord['filename'] = basename($filepath);
+
+ return get_file_storage()->create_file_from_pathname($filerecord, $filepath);
+ }
}
require_once("$CFG->libdir/externallib.php");
require_once("$CFG->libdir/gradelib.php");
+require_once("$CFG->dirroot/grade/edit/tree/lib.php");
require_once("$CFG->dirroot/grade/querylib.php");
/**
as defined in lib/grade/constants.php'
);
}
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.10
+ */
+ public static function create_gradecategory_parameters() {
+ return new external_function_parameters(
+ [
+ 'courseid' => new external_value(PARAM_INT, 'id of course', VALUE_REQUIRED),
+ 'fullname' => new external_value(PARAM_TEXT, 'fullname of category', VALUE_REQUIRED),
+ 'options' => new external_single_structure([
+ 'aggregation' => new external_value(PARAM_INT, 'aggregation method', VALUE_OPTIONAL),
+ 'aggregateonlygraded' => new external_value(PARAM_BOOL, 'exclude empty grades', VALUE_OPTIONAL),
+ 'aggregateoutcomes' => new external_value(PARAM_BOOL, 'aggregate outcomes', VALUE_OPTIONAL),
+ 'droplow' => new external_value(PARAM_INT, 'drop low grades', VALUE_OPTIONAL),
+ 'itemname' => new external_value(PARAM_TEXT, 'the category total name', VALUE_OPTIONAL),
+ 'iteminfo' => new external_value(PARAM_TEXT, 'the category iteminfo', VALUE_OPTIONAL),
+ 'idnumber' => new external_value(PARAM_TEXT, 'the category idnumber', VALUE_OPTIONAL),
+ 'gradetype' => new external_value(PARAM_INT, 'the grade type', VALUE_OPTIONAL),
+ 'grademax' => new external_value(PARAM_INT, 'the grade max', VALUE_OPTIONAL),
+ 'grademin' => new external_value(PARAM_INT, 'the grade min', VALUE_OPTIONAL),
+ 'gradepass' => new external_value(PARAM_INT, 'the grade to pass', VALUE_OPTIONAL),
+ 'display' => new external_value(PARAM_INT, 'the display type', VALUE_OPTIONAL),
+ 'decimals' => new external_value(PARAM_INT, 'the decimal count', VALUE_OPTIONAL),
+ 'hiddenuntil' => new external_value(PARAM_INT, 'grades hidden until', VALUE_OPTIONAL),
+ 'locktime' => new external_value(PARAM_INT, 'lock grades after', VALUE_OPTIONAL),
+ 'weightoverride' => new external_value(PARAM_BOOL, 'weight adjusted', VALUE_OPTIONAL),
+ 'aggregationcoef2' => new external_value(PARAM_RAW, 'weight coefficient', VALUE_OPTIONAL),
+ 'parentcategoryid' => new external_value(PARAM_INT, 'The parent category id', VALUE_OPTIONAL),
+ 'parentcategoryidnumber' => new external_value(PARAM_TEXT, 'the parent category idnumber', VALUE_OPTIONAL),
+ ], 'optional category data', VALUE_DEFAULT, [])
+ ]
+ );
+ }
+
+ /**
+ * Creates a gradecategory inside of the specified course.
+ *
+ * @param int $courseid the courseid to create the gradecategory in.
+ * @param string $fullname the fullname of the grade category to create.
+ * @param array $options array of options to set.
+ *
+ * @return array array of created categoryid and warnings.
+ */
+ public static function create_gradecategory(int $courseid, string $fullname, array $options) {
+ global $CFG, $DB;
+
+ $params = self::validate_parameters(self::create_gradecategory_parameters(),
+ ['courseid' => $courseid, 'fullname' => $fullname, 'options' => $options]);
+
+ // Now params are validated, update the references.
+ $courseid = $params['courseid'];
+ $fullname = $params['fullname'];
+ $options = $params['options'];
+
+ // Check that the context and permissions are OK.
+ $context = context_course::instance($courseid);
+ self::validate_context($context);
+ require_capability('moodle/grade:manage', $context);
+
+ $defaultparentcat = new grade_category(['courseid' => $courseid, 'depth' => 1], true);
+
+ // Setup default data so WS call needs to contain only data to set.
+ // This is not done in the Parameters, so that the array of options can be optional.
+ $data = [
+ 'fullname' => $fullname,
+ 'aggregation' => grade_get_setting($courseid, 'displaytype', $CFG->grade_displaytype),
+ 'aggregateonlygraded' => 1,
+ 'aggregateoutcomes' => 0,
+ 'droplow' => 0,
+ 'grade_item_itemname' => '',
+ 'grade_item_iteminfo' => '',
+ 'grade_item_idnumber' => '',
+ 'grade_item_gradetype' => GRADE_TYPE_VALUE,
+ 'grade_item_grademax' => 100,
+ 'grade_item_grademin' => 1,
+ 'grade_item_gradepass' => 1,
+ 'grade_item_display' => GRADE_DISPLAY_TYPE_DEFAULT,
+ // Hack. This must be -2 to use the default setting.
+ 'grade_item_decimals' => -2,
+ 'grade_item_hiddenuntil' => 0,
+ 'grade_item_locktime' => 0,
+ 'grade_item_weightoverride' => 0,
+ 'grade_item_aggregationcoef2' => 0,
+ 'parentcategory' => $defaultparentcat->id
+ ];
+
+ // Most of the data items need boilerplate prepended. These are the exceptions.
+ $ignorekeys = ['aggregation', 'aggregateonlygraded', 'aggregateoutcomes', 'droplow', 'parentcategoryid', 'parentcategoryidnumber'];
+ foreach ($options as $key => $value) {
+ if (!in_array($key, $ignorekeys)) {
+ $fullkey = 'grade_item_' . $key;
+ $data[$fullkey] = $value;
+ } else {
+ $data[$key] = $value;
+ }
+ }
+
+ // Handle parent category special case.
+ if (array_key_exists('parentcategoryid', $options) && $parentcat = $DB->get_record('grade_categories',
+ ['id' => $options['parentcategoryid'], 'courseid' => $courseid])) {
+ $data['parentcategory'] = $parentcat->id;
+ } else if (array_key_exists('parentcategoryidnumber', $options) && $parentcatgradeitem = $DB->get_record('grade_items',
+ ['itemtype' => 'category', 'idnumber' => $options['parentcategoryidnumber']], '*', IGNORE_MULTIPLE)) {
+ if ($parentcat = $DB->get_record('grade_categories', ['courseid' => $courseid, 'id' => $parentcatgradeitem->iteminstance])) {
+ $data['parentcategory'] = $parentcat->id;
+ }
+ }
+
+ // Create new gradecategory item.
+ $gradecategory = new grade_category(['courseid' => $courseid], false);
+ $gradecategory->apply_default_settings();
+ $gradecategory->apply_forced_settings();
+
+ // Data Validation.
+ if (array_key_exists('grade_item_gradetype', $data) and $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) {
+ if (empty($data['grade_item_scaleid'])) {
+ $warnings[] = ['item' => 'scaleid', 'warningcode' => 'invalidscale',
+ 'message' => get_string('missingscale', 'grades')];
+ }
+ }
+ if (array_key_exists('grade_item_grademin', $data) and array_key_exists('grade_item_grademax', $data)) {
+ if (($data['grade_item_grademax'] != 0 OR $data['grade_item_grademin'] != 0) AND
+ ($data['grade_item_grademax'] == $data['grade_item_grademin'] OR
+ $data['grade_item_grademax'] < $data['grade_item_grademin'])) {
+ $warnings[] = ['item' => 'grademax', 'warningcode' => 'invalidgrade',
+ 'message' => get_string('incorrectminmax', 'grades')];
+ }
+ }
+
+ if (!empty($warnings)) {
+ return ['categoryid' => null, 'warnings' => $warnings];
+ }
+
+ // Now call the update function with data. Transactioned so the gradebook isn't broken on bad data.
+ try {
+ $transaction = $DB->start_delegated_transaction();
+ grade_edit_tree::update_gradecategory($gradecategory, (object) $data);
+ $transaction->allow_commit();
+ } catch (Exception $e) {
+ // If the submitted data was broken for any reason.
+ $warnings['database'] = $e->getMessage();
+ $transaction->rollback();
+ return ['warnings' => $warnings];
+ }
+
+ return['categoryid' => $gradecategory->id, 'warnings' => []];
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 3.10
+ */
+ public static function create_gradecategory_returns() {
+ return new external_single_structure([
+ 'categoryid' => new external_value(PARAM_INT, 'The ID of the created category', VALUE_OPTIONAL),
+ 'warnings' => new external_warnings(),
+ ]);
+ }
}
$keepuserdata = (bool)$copyinfo->userdata;
$keptroles = $copyinfo->keptroles;
- $backupplan->get_setting('users')->set_value('1');
$bc->set_kept_roles($keptroles);
// If we are not keeping user data don't include users or data in the backup.
// In this case we'll add the user enrolments at the end.
// Also if we have no roles to keep don't backup users.
if (empty($keptroles) || !$keepuserdata) {
+ $backupplan->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
$backupplan->get_setting('users')->set_value('0');
+ } else {
+ $backupplan->get_setting('users')->set_value('1');
}
// Do some preflight checks on the backup.
* @return void
*/
protected static function fill_properties_cache() {
- global $CFG;
+ global $CFG, $SESSION;
if (self::$propertiescache !== null) {
return;
}
$fields['city'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->defaultcity);
$fields['country'] = array('type' => PARAM_ALPHA, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->country,
'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_countries(true, true)));
- $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->lang,
+ $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED,
+ 'default' => (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang,
'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_translations(false)));
$fields['calendartype'] = array('type' => PARAM_PLUGIN, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype,
'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types()));
'editingteacher' => CAP_ALLOW,
)
],
+
+ // Allow users to download content.
+ 'moodle/contentbank:downloadcontent' => [
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ 'manager' => CAP_ALLOW,
+ 'coursecreator' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ ]
+ ],
);
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
+ 'core_grades_create_gradecategory' => array (
+ 'classname' => 'core_grades_external',
+ 'methodname' => 'create_gradecategory',
+ 'description' => 'Create a grade category inside a course gradebook.',
+ 'type' => 'write',
+ 'capabilities' => 'moodle/grade:manage',
+ ),
'core_grading_get_definitions' => array(
'classname' => 'core_grading_external',
'methodname' => 'get_definitions',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
- 'core_message_block_contacts' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'block_contacts',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Block contacts',
- 'type' => 'write',
- 'ajax' => true,
- 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- ),
- 'core_message_create_contacts' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'create_contacts',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Add contacts to the contact list',
- 'type' => 'write',
- 'ajax' => true,
- 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- ),
'core_message_get_contact_requests' => array(
'classname' => 'core_message_external',
'methodname' => 'get_contact_requests',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
- 'core_message_delete_conversation' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'delete_conversation',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Deletes a conversation.',
- 'type' => 'write',
- 'capabilities' => 'moodle/site:deleteownmessage',
- 'ajax' => true,
- 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- ),
'core_message_delete_conversations_by_id' => array(
'classname' => 'core_message_external',
'methodname' => 'delete_conversations_by_id',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
- 'core_message_data_for_messagearea_search_users' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'data_for_messagearea_search_users',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Retrieve the template data for searching for people',
- 'type' => 'read',
- 'ajax' => true,
- ),
- 'core_message_data_for_messagearea_search_users_in_course' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'data_for_messagearea_search_users_in_course',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Retrieve the template data for searching for people in a course',
- 'type' => 'read',
- 'ajax' => true,
- ),
'core_message_message_search_users' => array(
'classname' => 'core_message_external',
'methodname' => 'message_search_users',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
- 'core_message_data_for_messagearea_conversations' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'data_for_messagearea_conversations',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Retrieve the template data for the conversation list',
- 'type' => 'read',
- 'ajax' => true,
- 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- ),
- 'core_message_data_for_messagearea_contacts' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'data_for_messagearea_contacts',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- Retrieve the template data for the contact list',
- 'type' => 'read',
- 'ajax' => true,
- 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
- ),
- 'core_message_data_for_messagearea_messages' => array(
- 'classname' => 'core_message_external',
- 'methodname' => 'data_for_messagearea_messages',
- 'classpath' => 'message/externallib.php',
- 'description' => '** DEPRECATED ** Please do not call this function any more.
- &nbs