public function display() {
if (count($this->capabilities) > self::NUM_CAPS_FOR_SEARCH) {
global $PAGE;
- $PAGE->requires->strings_for_js(array('filter', 'clear'), 'moodle');
- $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id));
+ $jsmodule = array(
+ 'name' => 'rolescapfilter',
+ 'fullpath' => '/admin/roles/module.js',
+ 'strings' => array(
+ array('filter', 'moodle'),
+ array('clear', 'moodle'), ),
+ 'requires' => array('node', 'cookie', 'escape')
+ );
+ $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id), false,
+ $jsmodule);
}
echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
echo '<tr><th class="name" align="left" scope="col">' . get_string('capability', 'core_role') . '</th>';
marginRight : 'auto'
});
// Create the capability search input.
- this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+filtervalue+'" />');
+ this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
// Create a label for the search input.
this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.str.moodle.filter+' </label>');
// Create a clear button to clear the input.
$string['acknowledgement'] = 'Acknowledgement';
$string['acknowledgementmust'] = 'You must acknowledge this';
-$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing add-ons. I accept and understand that add-ons (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
-$string['featuredisabled'] = 'The add-on installer is disabled on this site.';
-$string['installaddon'] = 'Install add-on!';
-$string['installaddons'] = 'Install add-ons';
-$string['installexception'] = 'Oops... An error occurred while trying to install the add-on. Turn debugging mode on to see details of the error.';
-$string['installfromrepo'] = 'Install add-ons from the Moodle plugins directory';
-$string['installfromrepo_help'] = 'You will be redirected to the Moodle plugins directory to search for and install an add-on. Note that your site full name, URL and Moodle version will be sent as well, to make the installation process easier for you.';
-$string['installfromzip'] = 'Install add-on from ZIP file';
-$string['installfromzip_help'] = 'An alternative to installing an add-on directly from the Moodle plugins directory is to upload a ZIP package of the add-on. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
+$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing additional plugins. I accept and understand that plugins (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
+$string['featuredisabled'] = 'The plugin installer is disabled on this site.';
+$string['installaddon'] = 'Install plugin!';
+$string['installaddons'] = 'Install plugins';
+$string['installexception'] = 'Oops... An error occurred while trying to install the plugin. Turn debugging mode on to see details of the error.';
+$string['installfromrepo'] = 'Install plugins from the Moodle plugins directory';
+$string['installfromrepo_help'] = 'You will be redirected to the Moodle plugins directory to search for and install a plugin. Note that your site full name, URL and Moodle version will be sent as well, to make the installation process easier for you.';
+$string['installfromzip'] = 'Install plugin from ZIP file';
+$string['installfromzip_help'] = 'An alternative to installing a plugin directly from the Moodle plugins directory is to upload a ZIP package of the plugin. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
$string['installfromzipfile'] = 'ZIP package';
$string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
$string['installfromziprootdir'] = 'Rename the root directory';
$string['installfromziprootdir_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. If so, the correct name may be entered here.';
-$string['installfromzipsubmit'] = 'Install add-on from the ZIP file';
+$string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
$string['installfromziptype'] = 'Plugin type';
$string['installfromziptype_help'] = 'Choose the correct type of plugin you are about to install. Warning: The installation procedure can fail badly if an incorrect plugin type is specified.';
$string['permcheck'] = 'Make sure the plugin type root location is writable by the web server process.';
$string['permcheckprogress'] = 'Checking for write permission ...';
$string['permcheckresultno'] = 'Plugin type location <em>{$a->path}</em> is not writable';
$string['permcheckresultyes'] = 'Plugin type location <em>{$a->path}</em> is writable';
-$string['pluginname'] = 'Add-on installer';
-$string['remoterequestalreadyinstalled'] = 'There is a request to install add-on {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, this plugin is <strong>already installed</strong> on the site.';
-$string['remoterequestconfirm'] = 'There is a request to install add-on <strong>{$a->name}</strong> ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. If you continue, the add-on ZIP package will be downloaded for validation. Nothing will be installed yet.';
-$string['remoterequestinvalid'] = 'There is a request to install an add-on from the Moodle plugins directory on this site. Unfortunately the request is not valid and so the add-on cannot be installed.';
-$string['remoterequestpermcheck'] = 'There is a request to install add-on {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the plugin type location <strong>{$a->typepath}</strong> is <strong>not writable</strong>. You need to give write access for the web server user to the plugin type location, then press the continue button to repeat the check.';
-$string['remoterequestpluginfoexception'] = 'Oops... An error occurred while trying to obtain information about the add-on {$a->name} ({$a->component}) version {$a->version}. The add-on cannot be installed. Turn debugging mode on to see details of the error.';
-$string['validation'] = 'Add-on package validation';
+$string['pluginname'] = 'Plugin installer';
+$string['remoterequestalreadyinstalled'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, this plugin is <strong>already installed</strong> on the site.';
+$string['remoterequestconfirm'] = 'There is a request to install plugin <strong>{$a->name}</strong> ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. If you continue, the plugin ZIP package will be downloaded for validation. Nothing will be installed yet.';
+$string['remoterequestinvalid'] = 'There is a request to install a plugin from the Moodle plugins directory on this site. Unfortunately the request is not valid and so the plugin cannot be installed.';
+$string['remoterequestpermcheck'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the location <strong>{$a->typepath}</strong> is <strong>not writable</strong>. You need to give write access for the web server user to the location, then press the continue button to repeat the check.';
+$string['remoterequestpluginfoexception'] = 'Oops... An error occurred while trying to obtain information about the plugin {$a->name} ({$a->component}) version {$a->version}. The plugin cannot be installed. Turn debugging mode on to see details of the error.';
+$string['validation'] = 'Plugin package validation';
$string['validationmsg_componentmatch'] = 'Full component name';
-$string['validationmsg_componentmismatchname'] = 'Add-on name mismatch';
-$string['validationmsg_componentmismatchname_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the declared add-on name.';
-$string['validationmsg_componentmismatchname_info'] = 'The add-on declares its name is \'{$a}\' but that does not match the name of the root directory.';
-$string['validationmsg_componentmismatchtype'] = 'Add-on type mismatch';
-$string['validationmsg_componentmismatchtype_info'] = 'You have selected the type \'{$a->expected}\' but the add-on declares its type is \'{$a->found}\'.';
+$string['validationmsg_componentmismatchname'] = 'Plugin name mismatch';
+$string['validationmsg_componentmismatchname_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the declared plugin name.';
+$string['validationmsg_componentmismatchname_info'] = 'The plugin declares its name is \'{$a}\' but that does not match the name of the root directory.';
+$string['validationmsg_componentmismatchtype'] = 'Plugin type mismatch';
+$string['validationmsg_componentmismatchtype_info'] = 'You have selected the type \'{$a->expected}\' but the plugin declares its type is \'{$a->found}\'.';
$string['validationmsg_filenotexists'] = 'Extracted file not found';
$string['validationmsg_filesnumber'] = 'Not enough files found in the package';
$string['validationmsg_filestatus'] = 'Unable to extract all files';
$string['validationmsg_filestatus_info'] = 'Attempting to extract file {$a->file} resulted in error \'{$a->status}\'.';
$string['validationmsg_maturity'] = 'Declared maturity level';
-$string['validationmsg_maturity_help'] = 'The add-on can declare its maturity level. If the maintainer considers the add-on stable, the declared maturity level will read MATURITY_STABLE. All other maturity levels (such as alpha or beta) should be considered unstable and a warning is raised.';
+$string['validationmsg_maturity_help'] = 'The plugin can declare its maturity level. If the maintainer considers the plugin stable, the declared maturity level will read MATURITY_STABLE. All other maturity levels (such as alpha or beta) should be considered unstable and a warning is raised.';
$string['validationmsg_missingexpectedlangenfile'] = 'English language file name mismatch';
-$string['validationmsg_missingexpectedlangenfile_info'] = 'The given add-on type is missing the expected English language file {$a}.';
+$string['validationmsg_missingexpectedlangenfile_info'] = 'The given plugin type is missing the expected English language file {$a}.';
$string['validationmsg_missinglangenfile'] = 'No English language file found';
$string['validationmsg_missinglangenfolder'] = 'Missing English language folder';
-$string['validationmsg_missingversion'] = 'Add-on does not declare its version';
+$string['validationmsg_missingversion'] = 'Plugin does not declare its version';
$string['validationmsg_missingversionphp'] = 'File version.php not found';
$string['validationmsg_multiplelangenfiles'] = 'Multiple English language files found';
$string['validationmsg_onedir'] = 'Invalid structure of the ZIP package.';
-$string['validationmsg_onedir_help'] = 'The ZIP package must contain just one root directory that holds the add-on code. The name of that root directory must match the name of the plugin.';
+$string['validationmsg_onedir_help'] = 'The ZIP package must contain just one root directory that holds the plugin code. The name of that root directory must match the name of the plugin.';
$string['validationmsg_pathwritable'] = 'Write access check';
-$string['validationmsg_pluginversion'] = 'Add-on version';
-$string['validationmsg_release'] = 'Add-on release';
+$string['validationmsg_pluginversion'] = 'Plugin version';
+$string['validationmsg_release'] = 'Plugin release';
$string['validationmsg_requiresmoodle'] = 'Required Moodle version';
-$string['validationmsg_rootdir'] = 'Name of the add-on to be installed';
-$string['validationmsg_rootdir_help'] = 'The name of the root directory in the ZIP package forms the name of the add-on to be installed. If the name is not correct, you may wish to rename the root directory in the ZIP prior to installing the add-on.';
-$string['validationmsg_rootdirinvalid'] = 'Invalid add-on name';
-$string['validationmsg_rootdirinvalid_help'] = 'The name of the root directory in the ZIP package violates formal syntax requirements. Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the add-on name.';
+$string['validationmsg_rootdir'] = 'Name of the plugin to be installed';
+$string['validationmsg_rootdir_help'] = 'The name of the root directory in the ZIP package forms the name of the plugin to be installed. If the name is not correct, you may wish to rename the root directory in the ZIP prior to installing the plugin.';
+$string['validationmsg_rootdirinvalid'] = 'Invalid plugin name';
+$string['validationmsg_rootdirinvalid_help'] = 'The name of the root directory in the ZIP package violates formal syntax requirements. Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the plugin name.';
$string['validationmsg_targetexists'] = 'Target location already exists';
-$string['validationmsg_targetexists_help'] = 'The directory that the add-on is to be installed to, must not exist yet.';
+$string['validationmsg_targetexists_help'] = 'The directory that the plugin is to be installed to must not yet exist.';
$string['validationmsg_unknowntype'] = 'Unknown plugin type';
$string['validationmsglevel_debug'] = 'Debug';
$string['validationmsglevel_error'] = 'Error';
$string['validationmsglevel_info'] = 'OK';
$string['validationmsglevel_warning'] = 'Warning';
$string['validationresult0'] = 'Validation failed!';
-$string['validationresult0_help'] = 'A serious problem was detected and so it is not safe to install the add-on. See the validation log messages for details.';
+$string['validationresult0_help'] = 'A serious problem was detected and so it is not safe to install the plugin. See the validation log messages for details.';
$string['validationresult1'] = 'Validation passed!';
-$string['validationresult1_help'] = 'No serious problems were detected. You can continue with the add-on installation. See the validation log messages for more details and eventual warnings.';
-$string['validationresult1_help'] = 'The add-on package has been validated and no serious problems were detected.';
+$string['validationresult2_help'] = 'No serious problems were detected. You can continue with the plugin installation. See the validation log messages for more details and eventual warnings.';
+$string['validationresult1_help'] = 'The plugin package has been validated and no serious problems were detected.';
$string['validationresultinfo'] = 'Info';
$string['validationresultmsg'] = 'Message';
$string['validationresultstatus'] = 'Status';
} else {
echo $OUTPUT->header();
- $error = optional_param('error', '', PARAM_RAW);
+ $error = optional_param('error', '', PARAM_NOTAGS);
if ($error) {
echo $OUTPUT->notification($error, 'notifyerror');
}
- $success = optional_param('success', '', PARAM_RAW);
+ $success = optional_param('success', '', PARAM_NOTAGS);
if ($success) {
echo $OUTPUT->notification($success, 'notifysuccess');
}
$ufiltering->display_add();
$ufiltering->display_active();
- if (has_capability('moodle/user:create', $sitecontext)) {
- echo $OUTPUT->heading('<a href="'.$securewwwroot.'/user/editadvanced.php?id=-1">'.get_string('addnewuser').'</a>');
- }
if (!empty($table)) {
echo html_writer::start_tag('div', array('class'=>'no-overflow'));
echo html_writer::table($table);
echo html_writer::end_tag('div');
echo $OUTPUT->paging_bar($usercount, $page, $perpage, $baseurl);
- if (has_capability('moodle/user:create', $sitecontext)) {
- echo $OUTPUT->heading('<a href="'.$securewwwroot.'/user/editadvanced.php?id=-1">'.get_string('addnewuser').'</a>');
- }
+ }
+ if (has_capability('moodle/user:create', $sitecontext)) {
+ $url = new moodle_url($securewwwroot . '/user/editadvanced.php', array('id' => -1));
+ echo $OUTPUT->single_button($url, get_string('addnewuser'), 'get');
}
echo $OUTPUT->footer();
-
-
-
return;
}
+ // If the multi-authentication setting is used, check for the param before connecting to CAS.
+ if ($this->config->multiauth) {
+ $authCAS = optional_param('authCAS', '', PARAM_RAW);
+ if ($authCAS == 'NOCAS') {
+ return;
+ }
+ // Show authentication form for multi-authentication.
+ // Test pgtIou parameter for proxy mode (https connection in background from CAS server to the php server).
+ if ($authCAS != 'CAS' && !isset($_GET['pgtIou'])) {
+ $PAGE->set_url('/login/index.php');
+ $PAGE->navbar->add($CASform);
+ $PAGE->set_title("$site->fullname: $CASform");
+ $PAGE->set_heading($site->fullname);
+ echo $OUTPUT->header();
+ include($CFG->dirroot.'/auth/cas/cas_form.html');
+ echo $OUTPUT->footer();
+ exit();
+ }
+ }
+
// Connection to CAS server
$this->connectCAS();
return;
}
- if ($this->config->multiauth) {
- $authCAS = optional_param('authCAS', '', PARAM_RAW);
- if ($authCAS == 'NOCAS') {
- return;
- }
-
- // Show authentication form for multi-authentication
- // test pgtIou parameter for proxy mode (https connection
- // in background from CAS server to the php server)
- if ($authCAS != 'CAS' && !isset($_GET['pgtIou'])) {
- $PAGE->set_url('/login/index.php');
- $PAGE->navbar->add($CASform);
- $PAGE->set_title("$site->fullname: $CASform");
- $PAGE->set_heading($site->fullname);
- echo $OUTPUT->header();
- include($CFG->dirroot.'/auth/cas/cas_form.html');
- echo $OUTPUT->footer();
- exit();
- }
- }
-
// Force CAS authentication (if needed).
if (!phpCAS::isAuthenticated()) {
phpCAS::setLang($this->config->language);
if (!empty($_SERVER[$pluginconfig->user_attribute])) { // Shibboleth auto-login
$frm = new stdClass();
$frm->username = strtolower($_SERVER[$pluginconfig->user_attribute]);
- $frm->password = substr(base64_encode($_SERVER[$pluginconfig->user_attribute]),0,8);
- // The random password consists of the first 8 letters of the base 64 encoded user ID
- // This password is never used unless the user account is converted to manual
+ // The password is never actually used, but needs to be passed to the functions 'user_login' and
+ // 'authenticate_user_login'. Shibboleth returns true for the function 'prevent_local_password', which is
+ // used when setting the password in 'update_internal_user_password'. When 'prevent_local_password'
+ // returns true, the password is set to 'not cached' (AUTH_PASSWORD_NOT_CACHED) in the Moodle DB. However,
+ // rather than setting the password to a hard-coded value, we will generate one each time, in case there are
+ // changes to the Shibboleth plugin and it is actually used.
+ $frm->password = generate_password(8);
/// Check if the user has actually submitted login data to us
$bname = $badge->name;
$imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1', false);
} else {
- $bname = $badge->assertion->badge->name;
+ $bname = s($badge->assertion->badge->name);
$imageurl = $badge->imageUrl;
}
$issuer = $assertion->badge->issuer;
$userinfo = $ibadge->recipient;
$table = new html_table();
- $today_date = date('Y-m-d');
- $today = strtotime($today_date);
- $expiration = isset($assertion->badge->expire) ? strtotime($assertion->badge->expire) : $today + 86400;
+ $today = strtotime(date('Y-m-d'));
$output = '';
$output .= html_writer::start_tag('div', array('id' => 'badge'));
$output .= html_writer::start_tag('div', array('id' => 'badge-image'));
- if ($expiration < $today) {
- $output .= $this->output->pix_icon('i/expired',
- get_string('expireddate', 'badges', $assertion->badge->expire),
- 'moodle',
- array('class' => 'expireimage'));
- } else {
- $output .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
+ $output .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
+ if (isset($assertion->expires)) {
+ $expiration = !strtotime($assertion->expires) ? s($assertion->expires) : strtotime($assertion->expires);
+ if ($expiration < $today) {
+ $output .= $this->output->pix_icon('i/expired',
+ get_string('expireddate', 'badges', userdate($expiration)),
+ 'moodle',
+ array('class' => 'expireimage'));
+ }
}
$output .= html_writer::end_tag('div');
$output .= $this->output->heading(get_string('issuerdetails', 'badges'), 3);
$dl = array();
- $dl[get_string('issuername', 'badges')] = $issuer->name;
+ $dl[get_string('issuername', 'badges')] = s($issuer->name);
$dl[get_string('issuerurl', 'badges')] = html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin));
if (isset($issuer->contact)) {
$output .= $this->output->heading(get_string('badgedetails', 'badges'), 3);
$dl = array();
- $dl[get_string('name')] = $assertion->badge->name;
- $dl[get_string('description', 'badges')] = $assertion->badge->description;
- $dl[get_string('bcriteria', 'badges')] = html_writer::tag('a', $assertion->badge->criteria, array('href' => $assertion->badge->criteria));
+ $dl[get_string('name')] = s($assertion->badge->name);
+ $dl[get_string('description', 'badges')] = s($assertion->badge->description);
+ $dl[get_string('bcriteria', 'badges')] = html_writer::tag('a', s($assertion->badge->criteria), array('href' => $assertion->badge->criteria));
$output .= $this->definition_list($dl);
$output .= $this->output->heading(get_string('issuancedetails', 'badges'), 3);
$dl = array();
if (isset($assertion->issued_on)) {
- $dl[get_string('dateawarded', 'badges')] = $assertion->issued_on;
+ $issuedate = !strtotime($assertion->issued_on) ? s($assertion->issued_on) : strtotime($assertion->issued_on);
+ $dl[get_string('dateawarded', 'badges')] = userdate($issuedate);
}
- if (isset($assertion->badge->expire)) {
+ if (isset($assertion->expires)) {
if ($expiration < $today) {
- $dl[get_string('expirydate', 'badges')] = $assertion->badge->expire . get_string('warnexpired', 'badges');
+ $dl[get_string('expirydate', 'badges')] = userdate($expiration) . get_string('warnexpired', 'badges');
} else {
- $dl[get_string('expirydate', 'badges')] = $assertion->badge->expire;
+ $dl[get_string('expirydate', 'badges')] = userdate($expiration);
}
}
if (isset($assertion->evidence)) {
- $dl[get_string('evidence', 'badges')] = html_writer::tag('a', $assertion->evidence, array('href' => $assertion->evidence));
+ $dl[get_string('evidence', 'badges')] = html_writer::tag('a', s($assertion->evidence), array('href' => $assertion->evidence));
}
$output .= $this->definition_list($dl);
$output .= html_writer::end_tag('div');
Scenario: The My Courses branch is collapsed when expand my courses is off
Given I log in as "admin"
And I set the following administration settings values:
- | Expand My Courses initially on My Moodle page | 0 |
+ | Show My courses expanded on My home | 0 |
And I log out
Given I log in as "student1"
And I follow "My home"
Scenario: My Courses can be expanded on the My Moodle page when expand my courses is off
Given I log in as "admin"
And I set the following administration settings values:
- | Expand My Courses initially on My Moodle page | 0 |
+ | Show My courses expanded on My home | 0 |
And I log out
Given I log in as "student1"
And I follow "My home"
And I expand "My courses" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
- And I should not see "c3" in the "Navigation" "block"
\ No newline at end of file
+ And I should not see "c3" in the "Navigation" "block"
$newexternal->description = (empty($data->description)) ? $rss->get_description() : $data->description;
$newexternal->userid = $USER->id;
$newexternal->url = $data->url;
- $newexternal->filtertags = $data->filtertags;
+ $newexternal->filtertags = (!empty($data->filtertags)) ? $data->filtertags : null;
$newexternal->timemodified = time();
$newexternal->id = $DB->insert_record('blog_external', $newexternal);
blog_sync_external_entries($newexternal);
- tag_set('blog_external', $newexternal->id, explode(',', $data->autotags), 'core',
- context_user::instance($newexternal->userid)->id);
+ if ($CFG->usetags) {
+ $autotags = (!empty($data->autotags)) ? $data->autotags : null;
+ tag_set('blog_external', $newexternal->id, explode(',', $autotags), 'core',
+ context_user::instance($newexternal->userid)->id);
+ }
break;
$external->description = (empty($data->description)) ? $rss->get_description() : $data->description;
$external->userid = $USER->id;
$external->url = $data->url;
- $external->filtertags = $data->filtertags;
+ $external->filtertags = (!empty($data->filtertags)) ? $data->filtertags : null;
$external->timemodified = time();
$DB->update_record('blog_external', $external);
- tag_set('blog_external', $external->id, explode(',', $data->autotags), 'core',
- context_user::instance($newexternal->userid)->id);
-
+ if ($CFG->usetags) {
+ $autotags = (!empty($data->autotags)) ? $data->autotags : null;
+ tag_set('blog_external', $external->id, explode(',', $autotags), 'core',
+ context_user::instance($external->userid)->id);
+ }
} else {
print_error('wrongexternalid', 'blog');
}
if ($id = $mform->getElementValue('id')) {
$mform->setDefault('autotags', implode(',', tag_get_tags_array('blog_external', $id)));
$mform->freeze('url');
- $mform->freeze('filtertags');
+ if ($mform->elementExists('filtertags')) {
+ $mform->freeze('filtertags');
+ }
// TODO change the filtertags element to a multiple select, using the tags of the external blog
// Use $rss->get_channel_tags()
}
* Initialises a test instance for unit tests.
*
* This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
- * By default however it calls initialise_test_instance to support backwards compatability.
+ * By default however it calls initialise_test_instance to support backwards compatibility.
*
* @since 2.8
* @param cache_definition $definition
* @return cache_store|false
*/
public static function initialise_unit_test_instance(cache_definition $definition) {
- return self::initialise_test_instance($definition);
+ return static::initialise_test_instance($definition);
}
}
throw new coding_exception('This mongodb instance has already been initialised.');
}
$this->database = $this->connection->selectDB($this->databasename);
- $this->definitionhash = $definition->generate_definition_hash();
+ $this->definitionhash = 'm'.$definition->generate_definition_hash();
$this->collection = $this->database->selectCollection($this->definitionhash);
$options = array('name' => 'idx_key');
$configuration = array();
$configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
+ $configuration['usesafe'] = 1;
$store = new cachestore_mongodb('Test mongodb', $configuration);
$store->initialise($definition);
protected function get_class_name() {
return 'cachestore_mongodb';
}
+
+ /**
+ * A small additional test to make sure definitions that hash a hash starting with a number work OK
+ */
+ public function test_collection_name() {
+ // This generates a definition that has a hash starting with a number. MDL-46208.
+ $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_mongodb', 'abc');
+ $instance = cachestore_mongodb::initialise_unit_test_instance($definition);
+
+ if (!$instance) {
+ $this->markTestSkipped();
+ }
+
+ $this->assertTrue($instance->set(1, 'alpha'));
+ $this->assertTrue($instance->set(2, 'beta'));
+ $this->assertEquals('alpha', $instance->get(1));
+ $this->assertEquals('beta', $instance->get(2));
+ $this->assertEquals(array(
+ 1 => 'alpha',
+ 2 => 'beta'
+ ), $instance->get_many(array(1, 2)));
+ }
}
\ No newline at end of file
$modes = $class::get_supported_modes();
if ($modes & cache_store::MODE_APPLICATION) {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, $class, 'phpunit_test');
- $instance = $class::initialise_test_instance($definition);
+ $instance = $class::initialise_unit_test_instance($definition);
if (!$instance) {
$this->markTestSkipped('Could not test '.$class.'. No test instance configured for application caches.');
} else {
}
if ($modes & cache_store::MODE_SESSION) {
$definition = cache_definition::load_adhoc(cache_store::MODE_SESSION, $class, 'phpunit_test');
- $instance = $class::initialise_test_instance($definition);
+ $instance = $class::initialise_unit_test_instance($definition);
if (!$instance) {
$this->markTestSkipped('Could not test '.$class.'. No test instance configured for session caches.');
} else {
}
if ($modes & cache_store::MODE_REQUEST) {
$definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $class, 'phpunit_test');
- $instance = $class::initialise_test_instance($definition);
+ $instance = $class::initialise_unit_test_instance($definition);
if (!$instance) {
$this->markTestSkipped('Could not test '.$class.'. No test instance configured for request caches.');
} else {
$groupevents = true;
$id = optional_param( 'id',0,PARAM_INT );
- $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out(false)), 'sesskey'=>sesskey()));
+ $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out_as_local_url(false)), 'sesskey'=>sesskey()));
$content = html_writer::start_tag('ul');
$seturl->param('var', 'showglobal');
mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}");
try {
$log = calendar_update_subscription_events($sub->id);
+ mtrace(trim(strip_tags($log)));
} catch (moodle_exception $ex) {
-
+ mtrace('Error updating calendar subscription: ' . $ex->getMessage());
}
- mtrace(trim(strip_tags($log)));
}
mtrace('Finished updating calendar subscriptions.');
if (calendar_user_can_add_event($calendar->course)) {
$output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
}
- //$output .= html_writer::tag('label', get_string('dayview', 'calendar'), array('for'=>'cal_course_flt_jump'));
- $output .= $this->course_filter_selector($returnurl, get_string('dayview', 'calendar'));
+ $output .= $this->course_filter_selector($returnurl, get_string('dayviewfor', 'calendar'));
$output .= html_writer::end_tag('div');
// Controls
$output .= html_writer::tag('div', calendar_top_controls('day', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class'=>'controls'));
if (calendar_user_can_add_event($calendar->course)) {
$output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
}
- $output .= get_string('detailedmonthview', 'calendar').': '.$this->course_filter_selector($returnurl);
+ $output .= $this->course_filter_selector($returnurl, get_string('detailedmonthviewfor', 'calendar'));
$output .= html_writer::end_tag('div', array('class'=>'header'));
// Controls
$output .= html_writer::tag('div', calendar_top_controls('month', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class' => 'controls'));
if (calendar_user_can_add_event($calendar->course)) {
$output .= $this->add_event_button($calendar->course->id);
}
- $output .= html_writer::tag('label', get_string('upcomingevents', 'calendar'), array('for'=>'cal_course_flt_jump'));
- $output .= $this->course_filter_selector($returnurl);
+ $output .= $this->course_filter_selector($returnurl, get_string('upcomingeventsfor', 'calendar'));
$output .= html_writer::end_tag('div');
if ($events) {
require_sesskey();
$var = required_param('var', PARAM_ALPHA);
-$return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_URL);
+$return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_LOCALURL);
$courseid = optional_param('id', -1, PARAM_INT);
if ($courseid != -1) {
$return = new moodle_url($return, array('course' => $courseid));
} else {
$return = new moodle_url($return);
}
-$url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
+$url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out_as_local_url(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
$PAGE->set_url($url);
$PAGE->set_context(context_system::instance());
*/
class core_calendar_lib_testcase extends advanced_testcase {
- public function test_calendar_get_course_cached() {
+ protected function setUp() {
$this->resetAfterTest(true);
+ }
+ public function test_calendar_get_course_cached() {
// Setup some test courses.
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$this->assertEquals($course3->shortname, $cachedcourse3->shortname);
$this->assertEquals($course3->fullname, $cachedcourse3->fullname);
}
+
+ /**
+ * Test calendar cron with a working subscription URL.
+ */
+ public function test_calendar_cron_working_url() {
+ global $CFG;
+ require_once($CFG->dirroot . '/lib/cronlib.php');
+
+ // Moodle ICal URL (moodle.org events).
+ $presetwhat = 'all';
+ $presettime = 'recentupcoming';
+ $userid = 1;
+ $authtoken = 'a8bcfee2fb868a05357f650bd65dc0699b026524';
+ $subscriptionurl = 'https://moodle.org/calendar/export_execute.php'
+ . '?preset_what='.$presetwhat.'&preset_time='.$presettime.'&userid='.$userid.'&authtoken='.$authtoken;
+
+ $subscription = new stdClass();
+ $subscription->eventtype = 'site';
+ $subscription->name = 'test';
+ $subscription->url = $subscriptionurl;
+ $subscription->pollinterval = 86400;
+ $subscription->lastupdated = 0;
+ calendar_add_subscription($subscription);
+
+ $this->expectOutputRegex('/Events imported: .* Events updated:/');
+ calendar_cron();
+ }
+
+ /**
+ * Test calendar cron with a broken subscription URL.
+ */
+ public function test_calendar_cron_broken_url() {
+ global $CFG;
+ require_once($CFG->dirroot . '/lib/cronlib.php');
+
+ $subscription = new stdClass();
+ $subscription->eventtype = 'site';
+ $subscription->name = 'test';
+ $subscription->url = 'brokenurl';
+ $subscription->pollinterval = 86400;
+ $subscription->lastupdated = 0;
+ calendar_add_subscription($subscription);
+
+ $this->expectOutputRegex('/Error updating calendar subscription: The given iCal URL is invalid/');
+ calendar_cron();
+ }
}
}
},
startShow : function() {
+ this.cancelHide();
if (this.get(SHOWTIMEOUT) !== null) {
this.cancelShow();
}
this.fire('showevent');
},
startHide : function() {
+ this.cancelShow();
if (this.get(HIDETIMEOUT) !== null) {
this.cancelHide();
}
+++ /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/>.
-
-
-/**
- * Delete category form.
- *
- * This file has been deprecated since 2.6.
- * The class delete_category_form has been renamed to core_course_deletecategory_form and is now autloaded.
- * Please update your code to use that new class.
- *
- * @deprecated since 2.6
- * @package core_course
- * @copyright 2002 onwards Martin Dougiamas (http://dougiamas.com)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die;
-
-/**
- * Class delete_category_form
- *
- * The class delete_category_form has been renamed to core_course_deletecategory_form and is now autloaded.
- * Please update your code to use that new class.
- *
- * @deprecated since 2.6
- * @todo remove in 2.7 MDL-41502
- * @package core_course
- * @copyright 2002 onwards Martin Dougiamas (http://dougiamas.com)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class delete_category_form extends core_course_deletecategory_form {
- // Nothing to do here.
-}
-
+++ /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/>.
-
-/**
- * Edit category form.
- *
- * This file and class have been deprecated, the form has been renamed to core_course_editcategory_form and is not autoloaded when
- * first used. Please update your code to use this new form.
- *
- * @deprecated since 2.6
- * @todo remove in 2.7 MDL-41502
- *
- * @package core_course
- * @copyright 2002 onwards Martin Dougiamas (http://dougiamas.com)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die;
-
-debugging('Please update your code to use core_course_editcategory_form (autloaded). This file will be removed in 2.7');
-
-/**
- * Class editcategory_form.
- *
- * This file and class have been deprecated, the form has been renamed to core_course_editcategory_form and is not autoloaded when
- * first used. Please update your code to use this new form.
- *
- * @deprecated since 2.6
- * @todo remove in 2.7 MDL-41502
- * @package core_course
- * @copyright 2002 onwards Martin Dougiamas (http://dougiamas.com)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class editcategory_form extends core_course_editcategory_form {
-
- /**
- * Constructs the form.
- * @param null $action
- * @param null $customdata
- * @param string $method
- * @param string $target
- * @param null $attributes
- * @param bool $editable
- */
- public function __construct($action = null, $customdata = null, $method = 'post', $target = '', $attributes = null,
- $editable = true) {
- $customdata['categoryid'] = $customdata['category']->id;
- $customdata['parent'] = $customdata['category']->parent;
- unset($customdata['category']);
- parent::moodleform($action, $customdata, $method, $target, $attributes, $editable);
- }
-
-};
\ No newline at end of file
+++ /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/>.
-
-/**
- * Allows the admin to create, delete and rename course categories rearrange courses
- *
- * This script has been deprecated since Moodle 2.6.
- * Please update your links as
- *
- * @deprecated
- * @todo remove in 2.7 MDL-41502
- * @package core_course
- * @copyright 2013 Marina Glancy
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once("../config.php");
-require_once($CFG->dirroot.'/course/lib.php');
-require_once($CFG->libdir.'/coursecatlib.php');
-
-$id = optional_param('categoryid', 0, PARAM_INT); // Category id.
-$page = optional_param('page', 0, PARAM_INT); // Which page to show.
-$perpage = optional_param('perpage', $CFG->coursesperpage, PARAM_INT); // How many per page.
-$search = optional_param('search', '', PARAM_RAW); // Search words.
-$blocklist = optional_param('blocklist', 0, PARAM_INT);
-$modulelist = optional_param('modulelist', '', PARAM_PLUGIN);
-
-debugging('This script has been deprecated and will be removed in the future. Please update any bookmarks you have.',
- DEBUG_DEVELOPER);
-
-// Look for legacy actions.
-// If there are any we're going to make and equivalent request to management.php.
-$sesskey = optional_param('sesskey', null, PARAM_RAW);
-if ($sesskey !== null && confirm_sesskey($sesskey)) {
- // Actions to manage categories.
- $deletecat = optional_param('deletecat', 0, PARAM_INT);
- $hidecat = optional_param('hidecat', 0, PARAM_INT);
- $showcat = optional_param('showcat', 0, PARAM_INT);
- $movecat = optional_param('movecat', 0, PARAM_INT);
- $movetocat = optional_param('movetocat', -1, PARAM_INT);
- $moveupcat = optional_param('moveupcat', 0, PARAM_INT);
- $movedowncat = optional_param('movedowncat', 0, PARAM_INT);
-
- // Actions to manage courses.
- $hide = optional_param('hide', 0, PARAM_INT);
- $show = optional_param('show', 0, PARAM_INT);
- $moveup = optional_param('moveup', 0, PARAM_INT);
- $movedown = optional_param('movedown', 0, PARAM_INT);
- $moveto = optional_param('moveto', 0, PARAM_INT);
- $resort = optional_param('resort', 0, PARAM_BOOL);
-
- $murl = new moodle_url('/course/management.php', array('sesskey' => sesskey()));
-
- // Process any category actions.
- if (!empty($deletecat)) {
- // Redirect to the new management script.
- redirect(new moodle_url($murl, array('categoryid' => $deletecat, 'action' => 'deletecategory')));
- }
-
- if (!empty($movecat) and $movetocat >= 0) {
- // Redirect to the new management script.
- redirect(new moodle_url($murl, array(
- 'action' => 'bulkaction',
- 'bulkmovecategories' => true,
- 'movecategoriesto' => $movetocat,
- 'bcat[]' => $movecat
- )));
- }
-
- // Hide or show a category.
- if ($hidecat) {
- // Redirect to the new management script.
- redirect(new moodle_url($murl, array('categoryid' => $hidecat, 'action' => 'hidecategory')));
- } else if ($showcat) {
- // Redirect to the new management script.
- redirect(new moodle_url($murl, array('categoryid' => $showcat, 'action' => 'showcategory')));
- }
-
- if (!empty($moveupcat) or !empty($movedowncat)) {
- // Redirect to the new management script.
- if (!empty($moveupcat)) {
- redirect(new moodle_url($murl, array('categoryid' => $moveupcat, 'action' => 'movecategoryup')));
- } else {
- redirect(new moodle_url($murl, array('categoryid' => $movedowncat, 'action' => 'movecategorydown')));
- }
- }
-
- if ($resort && $id) {
- // Redirect to the new management script.
- redirect(new moodle_url($murl, array('categoryid' => $id, 'action' => 'resortcategories', 'resort' => 'name')));
- }
-
- if (!empty($moveto) && ($data = data_submitted())) {
- // Redirect to the new management script.
- $courses = array();
- foreach ($data as $key => $value) {
- if (preg_match('/^c\d+$/', $key)) {
- $courses[] = substr($key, 1);
- }
- }
- redirect(new moodle_url($murl, array(
- 'action' => 'bulkaction',
- 'bulkmovecourses' => true,
- 'movecoursesto' => $moveto,
- 'bc' => $courses
- )));
- }
-
- if (!empty($hide) or !empty($show)) {
- // Redirect to the new management script.
- if (!empty($hide)) {
- redirect(new moodle_url($murl, array('courseid' => $hide, 'action' => 'hidecourse')));
- } else {
- redirect(new moodle_url($murl, array('courseid' => $show, 'action' => 'showcourse')));
- }
- }
-
- if (!empty($moveup) or !empty($movedown)) {
- // Redirect to the new management script.
- if (!empty($moveup)) {
- redirect(new moodle_url($murl, array('courseid' => $moveup, 'action' => 'movecourseup')));
- } else {
- redirect(new moodle_url($murl, array('courseid' => $movedown, 'action' => 'movecoursedown')));
- }
- }
-}
-
-// Now check if its a search or not. If its not we'll head to the new management page.
-$url = new moodle_url('/course/management.php');
-if ($id !== 0) {
- // We've got an ID it can't be a search.
- $url->param('categoryid', $id);
-} else {
- // No $id, perhaps its a search.
- if ($search !== '') {
- $url->param('search', $search);
- }
- if ($blocklist !== 0) {
- $url->param('blocklist', $blocklist);
- }
- if ($modulelist !== '') {
- $url->param('modulelist', $modulelist);
- }
-}
-redirect($url);
} else {
$course = null;
$courseid = null;
- $category = null;
- $categoryid = null;
- if ($viewmode === 'default') {
- $viewmode = 'categories';
- }
- $context = $systemcontext;
+ $category = coursecat::get_default();
+ $categoryid = $category->id;
+ $context = context_coursecat::instance($category->id);
+ $url->param('categoryid', $category->id);
}
// Check if there is a selected category param, and if there is apply it.
if (!$category->can_delete()) {
throw new moodle_exception('permissiondenied', 'error', '', null, 'coursecat::can_resort');
}
- require_once($CFG->dirroot.'/course/delete_category_form.php');
$mform = new core_course_deletecategory_form(null, $category);
if ($mform->is_cancelled()) {
redirect($PAGE->url);
break;
case "Course categories":
$return[] = new Given('"#category-listing" "css_element" should exist');
- $return[] = new Given('"#course-listing" "css_element" should not exist');
+ $return[] = new Given('"#course-listing" "css_element" should exist');
break;
case "Courses categories and courses":
default:
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And category in management listing should be visible "CAT1"
And I toggle visibility of category "CAT1" in management listing
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And category in management listing should be visible "CAT1"
And I toggle visibility of category "CAT1" in management listing
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
And category in management listing should be visible "CAT1"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
And category in management listing should be visible "CAT1"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "edit" action for "Cat 1" in management category listing
# Redirect
And I should see "Edit category settings"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
And I should see "Deleted course category Cat 2"
And I press "Continue"
# Redirect
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "assignroles" action for "Cat 1" in management category listing
# Redirect
And I should see "Assign roles in Category: Cat 1"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "permissions" action for "Cat 1" in management category listing
# Redirect
And I should see "Permissions in Category: Cat 1"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "cohorts" action for "Cat 1" in management category listing
# Redirect
And I should see "Category: Cat 1: available cohorts"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "filters" action for "Cat 1" in management category listing
# Redirect
And I should see "Filter settings in Category: Cat 1"
| Cat 1 | 0 | CAT1 |
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Create new category" "link" in the ".category-listing-actions" "css_element"
# Redirect.
And I should see "Add new category"
And I set the following fields to these values:
+ | Parent category | Top |
| Category name | Test category 2 |
| Category ID number | TC2 |
And I press "Create category"
# Redirect
And I should see "Add new category"
And I set the following fields to these values:
+ | Parent category | Top |
| Category name | Test category 3 |
| Category ID number | TC3 |
And I press "Create category"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I set the field "menuselectsortby" to "All categories"
And I set the field "menuresortcategoriesby" to <sortby>
And I press "Sort"
# Redirect.
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
And I should see category listing <cat2> before <cat3>
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Test category" "link"
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Master cat" "link"
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Master cat" category in the management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect. We should a 1, 1a, 1b, 1c, 2.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I should see "Course and category management" in the "h2" "css_element"
And I should see "Course categories" in the ".view-mode-selector" "css_element"
And I should see "Course categories" in the "h3" "css_element"
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
@javascript
Scenario: Test view mode functionality
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Course categories" in the "#category-listing h3" "css_element"
And I should see "Cat 1" in the "#category-listing" "css_element"
And I should see "Course categories" in the ".view-mode-selector" "css_element"
# Redirect.
And I should see the "Course categories and courses" management page
And I should see "Course categories" in the "#category-listing h3" "css_element"
- And I should see "Courses" in the "#course-listing h3" "css_element"
+ And I should see "Miscellaneous" in the "#course-listing h3" "css_element"
And I should see "Cat 1" in the "#category-listing" "css_element"
- And I should see "Please select a category" in the "#course-listing" "css_element"
+ And I should see "No courses in this category" in the "#course-listing" "css_element"
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I set the field "menuselectsortby" to "All categories"
And I set the field "menuresortcategoriesby" to <sortby>
And I press "Sort"
# Redirect.
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
And I should see category listing <cat2> before <cat3>
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Master cat" category in the management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Cat 1" "link"
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Cat 1" "link"
# Redirect.
And I should see the "Course categories and courses" management page
| CAT1 | Course 5 | Course 5 | C5 |
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Cat 1" "link"
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on "Cat 1" "link"
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
And I click on "edit" action for "Course 1" in management course listing
# Redirect
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I should see "Cat A (1)" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat C (1-1)" in the "#course-category-listings ul.ml" "css_element"
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect.
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect
And I should see the "Course categories and courses" management page
And I press "Save changes"
# Redirect
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect
And I should see the "Course categories and courses" management page
And I log in as "admin"
And I go to the courses management page
- And I should see the "Course categories" management page
+ And I should see the "Course categories and courses" management page
And I click on category "Cat 1" in the management interface
# Redirect
And I should see the "Course categories and courses" management page
/**
* Test user_enrolment_created event.
*/
- public function test_user_enrolment_created_observer() {
+ public function test_user_enrolment_created_event() {
global $DB;
$this->resetAfterTest();
}
/**
- * Test user_enrolment_deleted observer.
+ * Test user_enrolment_deleted event.
*/
- public function test_user_enrolment_deleted_observer() {
+ public function test_user_enrolment_deleted_event() {
global $DB;
$this->resetAfterTest(true);
/**
* Test user_enrolment_updated event.
*/
- public function test_user_enrolment_updated_observer() {
+ public function test_user_enrolment_updated_event() {
global $DB;
$this->resetAfterTest(true);
'name' => $cm->name,
'url' => $cm->url,
'id' => $cm->id,
- 'namelen' => strlen($cm->name),
+ 'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
);
}
}
- core_collator::asort_objects_by_property($sortedactivities, 'namelen', SORT_NUMERIC);
+ // 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)));
$scalearray = array_map('trim', $scalearray);
$scaleoptioncount = count($scalearray);
- if (count($scalearray) < 2) {
+ if (count($scalearray) < 1) {
$errors['scale'] = get_string('badlyformattedscale', 'grades');
} else {
$thescale = implode(',',$scalearray);
$mform->setAdvanced('aggregatesubcats');
}
- $options = array(0 => get_string('none'));
-
- for ($i=1; $i<=20; $i++) {
- $options[$i] = $i;
- }
-
- $mform->addElement('select', 'keephigh', get_string('keephigh', 'grades'), $options);
+ $mform->addElement('text', 'keephigh', get_string('keephigh', 'grades'), 'size="3"');
+ $mform->setType('keephigh', PARAM_INT);
$mform->addHelpButton('keephigh', 'keephigh', 'grades');
if ((int)$CFG->grade_keephigh_flag & 2) {
$mform->setAdvanced('keephigh');
}
- $mform->addElement('select', 'droplow', get_string('droplow', 'grades'), $options);
+ $mform->addElement('text', 'droplow', get_string('droplow', 'grades'), 'size="3"');
+ $mform->setType('droplow', PARAM_INT);
$mform->addHelpButton('droplow', 'droplow', 'grades');
$mform->disabledIf('droplow', 'keephigh', 'noteq', 0);
if ((int)$CFG->grade_droplow_flag & 2) {
$mode = gradingform_guide_controller::DISPLAY_EDIT_FULL;
$module = array('name'=>'gradingform_guideeditor',
'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js',
+ 'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
'strings' => array(
array('confirmdeletecriterion', 'gradingform_guide'),
array('clicktoedit', 'gradingform_guide'),
currentfocus = e.currentTarget;
});
Y.all('.markingguidecomment').on('click', function(e) {
- currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('innerHTML'));
+ currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('text'));
currentfocus.focus();
});
value = M.str.gradingform_guide.clicktoedit
taplain.addClass('empty')
}
- taplain.one('.textvalue').set('innerHTML', value)
+ taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value))
if (tb) {
- tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
+ tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
}
// hide/display textarea, textbox and plaintexts
taplain.removeClass('hiddenelement')
if (!empty($this->validationerrors)) {
foreach ($this->validationerrors as $id => $err) {
$a = new stdClass();
- $a->criterianame = $criteria[$id]['shortname'];
+ $a->criterianame = s($criteria[$id]['shortname']);
$a->maxscore = $criteria[$id]['maxscore'];
$html .= html_writer::tag('div', get_string('err_scoreinvalid', 'gradingform_guide', $a),
array('class' => 'gradingform_guide-error'));
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
$shortname = html_writer::empty_tag('input', array('type'=> 'text',
- 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => htmlspecialchars($criterion['shortname']),
+ 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => $criterion['shortname'],
'id ' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
$shortname = html_writer::tag('div', $shortname, array('class'=>'criterionname'));
- $description = html_writer::tag('textarea', htmlspecialchars($criterion['description']),
+ $description = html_writer::tag('textarea', s($criterion['description']),
array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
- $descriptionmarkers = html_writer::tag('textarea', htmlspecialchars($criterion['descriptionmarkers']),
+ $descriptionmarkers = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
$descriptionmarkers = html_writer::tag('div', $descriptionmarkers, array('class'=>'criteriondescmarkers'));
$maxscore = html_writer::empty_tag('input', array('type'=> 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
- 'value' => htmlspecialchars($criterion['maxscore']),
+ 'value' => $criterion['maxscore'],
'id' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
$maxscore = html_writer::tag('div', $maxscore, array('class'=>'criterionmaxscore'));
} else {
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
$descriptionclass = 'descriptionreadonly';
}
- $shortname = html_writer::tag('div', $criterion['shortname'],
+ $shortname = html_writer::tag('div', s($criterion['shortname']),
array('class'=>'criterionshortname', 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
$descmarkerclass = '';
$descstudentclass = '';
$descstudentclass = ' hide';
}
}
- $description = html_writer::tag('div', $criterion['description'],
+ $description = html_writer::tag('div', s($criterion['description']),
array('class'=>'criteriondescription'.$descstudentclass,
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
- $descriptionmarkers = html_writer::tag('div', $criterion['descriptionmarkers'],
+ $descriptionmarkers = html_writer::tag('div', s($criterion['descriptionmarkers']),
array('class'=>'criteriondescriptionmarkers'.$descmarkerclass,
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
- $maxscore = html_writer::tag('div', $criterion['maxscore'],
+ $maxscore = html_writer::tag('div', s($criterion['maxscore']),
array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
}
$scoreclass = 'error';
$currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
}
- $input = html_writer::tag('textarea', htmlspecialchars($currentremark),
+ $input = html_writer::tag('textarea', s($currentremark),
array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '65', 'rows' => '5',
'class' => 'markingguideremark'));
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
$score .= html_writer::empty_tag('input', array('type'=> 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass,
'id' => '{NAME}[criteria][{CRITERION-id}][score]',
- 'size' => '3', 'value' => htmlspecialchars($currentscore)));
+ 'size' => '3', 'value' => $currentscore));
$score .= '/'.$maxscore;
$criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
} else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
- $criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
+ $criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
if (!empty($options['showmarkspercriterionstudents'])) {
- $criteriontemplate .= html_writer::tag('td', htmlspecialchars($currentscore). ' / '.$maxscore,
+ $criteriontemplate .= html_writer::tag('td', s($currentscore). ' / '.$maxscore,
array('class' => 'score'));
}
}
$criteriontemplate .= html_writer::end_tag('td'); // Controls.
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
- $description = html_writer::tag('textarea', htmlspecialchars($comment['description']),
+ $description = html_writer::tag('textarea', s($comment['description']),
array('name' => '{NAME}[comments][{COMMENT-id}][description]', 'cols' => '65', 'rows' => '5'));
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
} else {
'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
}
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
- $description = html_writer::tag('span', htmlspecialchars($comment['description']),
+ $description = html_writer::tag('span', s($comment['description']),
array('name' => '{NAME}[comments][{COMMENT-id}][description]',
'title' => get_string('clicktocopy', 'gradingform_guide'),
'id' => '{NAME}[comments][{COMMENT-id}]', 'class'=>'markingguidecomment'));
} else {
- $description = $comment['description'];
+ $description = s($comment['description']);
}
}
$descriptionclass = 'description';
value = (el.hasClass('level')) ? M.str.gradingform_rubric.levelempty : M.str.gradingform_rubric.criterionempty
taplain.addClass('empty')
}
- taplain.one('.textvalue').set('innerHTML', value)
- if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
+ taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value));
+ if (tb) tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')));
// hide/display textarea, textbox and plaintexts
taplain.removeClass('hiddenelement')
ta.addClass('hiddenelement')
}
$criteriontemplate .= html_writer::end_tag('td'); // .controls
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
- $description = html_writer::tag('textarea', htmlspecialchars($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
+ $description = html_writer::tag('textarea', s($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
} else {
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
}
- $description = $criterion['description'];
+ $description = s($criterion['description']);
}
$descriptionclass = 'description';
if (isset($criterion['error_description'])) {
$currentremark = $value['remark'];
}
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
- $input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
+ $input = html_writer::tag('textarea', s($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
} else if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
}else if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW) {
- $criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
+ $criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
}
}
$criteriontemplate .= html_writer::end_tag('tr'); // .criterion
$leveltemplate = html_writer::start_tag('td', $tdattributes);
$leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
- $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
+ $definition = html_writer::tag('textarea', s($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
$score = html_writer::label(get_string('criterionempty', 'gradingform_rubric'), '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', false, array('class' => 'accesshide'));
$score .= html_writer::empty_tag('input', array('type' => 'text','id' => '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
} else {
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
}
- $definition = $level['definition'];
+ $definition = s($level['definition']);
$score = $level['score'];
}
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
if (!$this->_flagFrozen) {
$mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
$module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
+ 'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
));
$string['invalidmd5'] = 'A variável de verificação está errada - tente novamente.';
$string['missingrequiredfield'] = 'Um dos campos obrigatórios está em falta';
$string['remotedownloaderror'] = 'O download do componente para o servidor falhou. Verifique as configurações do proxy. A instalação da extensão cURL do PHP é muito recomendada.<br /><br />Terá de fazer manualmente o download do ficheiro <a href="{$a->url}">{$a->url}</a>, copiá-lo para a pasta "{$a->dest}" no seu servidor e descompactá-lo';
-$string['wrongdestpath'] = 'Caminho de destino errado.';
+$string['wrongdestpath'] = 'Caminho de destino errado';
$string['wrongsourcebase'] = 'Base do URL de origem errada';
-$string['wrongzipfilename'] = 'Nome de ficheiro ZIP errado.';
+$string['wrongzipfilename'] = 'Nome de ficheiro ZIP errado';
$string['doctonewwindow'] = 'Open in new window';
$string['download'] = 'Download';
$string['edithelpdocs'] = 'Edit help documents';
-$string['editingnoncorelangfile'] = 'You are trying to modify translation of an add-on module/plugin. You can save translation of 3rd party modules in your _local folder only. You may want to move the file with translation into the module\'s lang directory and/or send it to the maintainer of the add-on module.';
$string['editlang'] = '<b>Edit</b>';
$string['editorbackgroundcolor'] = 'Background colour';
$string['editordictionary'] = 'Editor dictionary';
$string['navadduserpostslinks_help'] = 'If enabled two links will be added to each user in the navigation to view discussions the user has started and posts the user has made in forums throughout the site or in specific courses.';
$string['navigationupgrade'] = 'This upgrade introduces two new navigation blocks that will replace these blocks: Administration, Courses, Activities and Participants. If you had set any special permissions on those blocks you should check to make sure everything is behaving as you want it.';
$string['navcourselimit'] = 'Course limit';
-$string['navexpandmycourses'] = 'Expand My Courses initially on My Moodle page';
-$string['navexpandmycourses_desc'] = 'When enabled the My Courses branch will be expanded initially on the My Moodle page and any pages within the My Moodle area.';
+$string['navexpandmycourses'] = 'Show My courses expanded on My home';
+$string['navexpandmycourses_desc'] = 'If enabled, My courses is initially shown expanded in the navigation block on My home.';
$string['navshowfullcoursenames'] = 'Show course full names';
-$string['navshowfullcoursenames_help'] = 'If enabled courses in the navigation will be shown with using their full name rather than their short name.';
+$string['navshowfullcoursenames_help'] = 'If enabled, course full names will be used in the navigation rather than short names.';
$string['navshowfrontpagemods'] = 'Show front page activities in the navigation';
$string['navshowfrontpagemods_help'] = 'If enabled, front page activities will be shown on the navigation under site pages.';
$string['navshowallcourses'] = 'Show all courses';
$string['sitesectionhelp'] = 'If selected, a topic section will be displayed on the site\'s front page.';
$string['slasharguments'] = 'Use slash arguments';
$string['smartpix'] = 'Smart pix search';
-$string['soaprecommended'] = 'Installing the optional soap extension is useful for web services and some add-ons.';
+$string['soaprecommended'] = 'Installing the optional SOAP extension is useful for web services and some plugins.';
$string['sort_fullname'] = 'Course full name';
$string['sort_idnumber'] = 'Course ID number';
$string['sort_shortname'] = 'Course short name';
$string['cachedef_databasemeta'] = 'Database meta information';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
$string['cachedef_externalbadges'] = 'External badges for particular user';
+$string['cachedef_suspended_userids'] = 'List of suspended users per course';
$string['cachedef_gradecondition'] = 'User grades cached for evaluating conditional availability';
$string['cachedef_groupdata'] = 'Course group information';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['courses'] = 'Courses';
$string['customexport'] = 'Custom range ({$a->timestart} - {$a->timeend})';
$string['daily'] = 'Daily';
-$string['dayview'] = 'Day view';
+$string['dayviewfor'] = 'Day view for:';
$string['dayviewtitle'] = 'Day view: {$a}';
$string['daywithnoevents'] = 'There are no events this day.';
$string['default'] = 'Default';
$string['deleteevent'] = 'Delete event';
$string['deleteevents'] = 'Delete events';
-$string['detailedmonthview'] = 'Detailed month view';
+$string['detailedmonthviewfor'] = 'Detailed month view for:';
$string['detailedmonthviewtitle'] = 'Detailed month view: {$a}';
$string['durationminutes'] = 'Duration in minutes';
$string['durationnone'] = 'Without duration';
$string['typesite'] = 'Site event';
$string['typeuser'] = 'User event';
$string['upcomingevents'] = 'Upcoming events';
+$string['upcomingeventsfor'] = 'Upcoming events for:';
$string['urlforical'] = 'URL for iCalendar export, for subscribing to calendar';
$string['user'] = 'User';
$string['userevent'] = 'User event';
$string['unreadmessages'] = 'Unread messages ({$a})';
$string['unreadnewmessages'] = 'New messages ({$a})';
$string['unreadnewmessage'] = 'New message from {$a}';
-$string['unreadnewnotification'] = 'New notification';
-$string['unreadnewnotifications'] = 'New notifications ({$a})';
$string['userisblockingyou'] = 'This user has blocked you from sending messages to them';
$string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
$string['userssearchresults'] = 'Users found: {$a}';
$string['trysearching'] = 'Try searching instead.';
$string['turneditingoff'] = 'Turn editing off';
$string['turneditingon'] = 'Turn editing on';
+$string['unauthorisedlogin'] = 'The user account "{$a}" is not available on this site';
$string['undecided'] = 'Undecided';
$string['unfinished'] = 'Unfinished';
$string['unknowncategory'] = 'Unknown category';
$string['configenableplagiarism'] = 'This will allow administrators to configure plagiarism plugins (if installed)';
$string['manageplagiarism'] = 'Manage plagiarism plugins';
$string['nopluginsinstalled'] = 'No plagiarism plugins are installed.';
-$string['plagiarism'] = 'Plagiarism prevention';
+$string['plagiarism'] = 'Plagiarism';
$string['err_response_format_version'] = 'Unexpected version of the response format. Please try to re-check for available updates.';
$string['err_response_http_code'] = 'Unable to fetch available updates data - unexpected HTTP response code.';
$string['filterall'] = 'Show all';
-$string['filtercontribonly'] = 'Show add-ons only';
-$string['filtercontribonlyactive'] = 'Showing add-ons only';
+$string['filtercontribonly'] = 'Show additional plugins only';
+$string['filtercontribonlyactive'] = 'Showing additional plugins only';
$string['filterupdatesonly'] = 'Show updateable only';
$string['filterupdatesonlyactive'] = 'Showing updateable only';
$string['moodleversion'] = 'Moodle {$a}';
$string['notwritable_link'] = 'admin/mdeploy/notwritable';
$string['numtotal'] = 'Installed: {$a}';
$string['numdisabled'] = 'Disabled: {$a}';
-$string['numextension'] = 'Add-ons: {$a}';
+$string['numextension'] = 'Additional: {$a}';
$string['numupdatable'] = 'Updates available: {$a}';
$string['otherplugin'] = '{$a->component}';
$string['otherpluginversion'] = '{$a->component} ({$a->version})';
$string['showall'] = 'Reload and show all plugins';
-$string['pluginchecknotice'] = 'This page displays plugins that may require your attention during the upgrade. Highlighted items include new plugins that are about to be installed, updated plugins that are about to be upgraded and any missing plugins. Add-ons are highlighted if there is an available update for them. It is recommended that you check whether there are more recent versions of add-ons available and update their source code before continuing with this Moodle upgrade.';
+$string['pluginchecknotice'] = 'This page displays plugins that may require your attention during the upgrade. Highlighted items include new plugins that are about to be installed, updated plugins that are about to be upgraded and any missing plugins. Additional plugins are highlighted if there is an available update for them. It is recommended that you check whether there are more recent versions of plugins available and update their source code before continuing with this Moodle upgrade.';
$string['plugindisable'] = 'Disable';
$string['plugindisabled'] = 'Disabled';
$string['pluginenable'] = 'Enable';
$string['somehighlightedinfo'] = 'Display the full list of installed plugins';
$string['somehighlightedonly'] = 'Display only plugins requiring your attention';
$string['source'] = 'Source';
-$string['sourceext'] = 'Add-on';
+$string['sourceext'] = 'Additional';
$string['sourcestd'] = 'Standard';
$string['status'] = 'Status';
$string['status_delete'] = 'To be deleted';
$string['type_mnetservice_plural'] = 'MNet services';
$string['type_mod'] = 'Activity module';
$string['type_mod_plural'] = 'Activity modules';
-$string['type_plagiarism'] = 'Plagiarism prevention plugin';
-$string['type_plagiarism_plural'] = 'Plagiarism prevention plugins';
+$string['type_plagiarism'] = 'Plagiarism plugin';
+$string['type_plagiarism_plural'] = 'Plagiarism plugins';
$string['type_portfolio'] = 'Portfolio';
$string['type_portfolio_plural'] = 'Portfolios';
$string['type_profilefield'] = 'Profile field type';
$string['areauserpersonal'] = 'Private files';
$string['areauserprofile'] = 'Profile';
$string['attachedfiles'] = 'Attached files';
-$string['attachment'] = 'Attachment:';
-$string['author'] = 'Author:';
+$string['attachment'] = 'Attachment';
+$string['author'] = 'Author';
$string['back'] = '« Back';
$string['backtodraftfiles'] = '« Back to draft files manager';
$string['cachecleared'] = 'Cached files are removed';
$string['createrepository'] = 'Create a repository instance';
$string['createxxinstance'] = 'Create "{$a}" instance';
$string['date'] = 'Date';
-$string['datecreated'] = 'Created:';
+$string['datecreated'] = 'Created';
$string['deleted'] = 'Repository deleted';
$string['deleterepository'] = 'Delete this repository';
$string['detailview'] = 'View details';
-$string['dimensions'] = 'Dimensions:';
+$string['dimensions'] = 'Dimensions';
$string['disabled'] = 'Disabled';
$string['displaydetails'] = 'Display folder with file details';
$string['displayicons'] = 'Display folder with file icons';
$string['hidden'] = 'Hidden';
$string['help'] = 'Help';
$string['choosealink'] = 'Choose a link...';
-$string['chooselicense'] = 'Choose license:';
+$string['chooselicense'] = 'Choose license';
$string['iconview'] = 'View as icons';
$string['imagesize'] = '{$a->width} x {$a->height} px';
$string['instance'] = 'instance';
$string['invalidparams'] = 'Invalid parameters';
$string['isactive'] = 'Active?';
$string['keyword'] = 'Keyword';
-$string['lastmodified'] = 'Last modified:';
-$string['license'] = 'License:';
+$string['lastmodified'] = 'Last modified';
+$string['license'] = 'License';
$string['linkexternal'] = 'Link external';
$string['listview'] = 'View as list';
$string['loading'] = 'Loading...';
$string['manageurl'] = 'Manage';
$string['manageuserrepository'] = 'Manage individual repository';
$string['moving'] = 'Moving';
-$string['name'] = 'Name:';
+$string['name'] = 'Name';
$string['newfolder'] = 'New folder';
-$string['newfoldername'] = 'New folder name:';
+$string['newfoldername'] = 'New folder name';
$string['noenter'] = 'Nothing entered';
$string['nofilesattached'] = 'No files attached';
$string['nofilesavailable'] = 'No files available';
$string['on'] = 'Enabled and visible';
$string['overwrite'] = 'Overwrite';
$string['overwriteall'] = 'Overwrite all';
-$string['path'] = 'Path:';
+$string['path'] = 'Path';
$string['personalrepositories'] = 'Available repository instances';
$string['plugin'] = 'Repository plug-ins';
$string['pluginerror'] = 'Errors in repository plugin.';
$string['repositoryicon'] = 'Repository icon';
$string['repositoryerror'] = 'Remote repository returned error: {$a}';
$string['save'] = 'Save';
-$string['saveas'] = 'Save as:';
+$string['saveas'] = 'Save as';
$string['saved'] = 'Saved';
$string['saving'] = 'Saving';
$string['automatedbackup'] = 'Automated backups';
$string['setmainfile'] = 'Set main file';
$string['setmainfile_help'] = 'If there are multiple files in the folder, the main file is the one that appears on the view page. Other files such as images or videos may be embedded in it. In filemanager the main file is indicated with a title in bold.';
$string['siteinstances'] = 'Repositories instances of the site';
-$string['size'] = 'Size:';
+$string['size'] = 'Size';
$string['submit'] = 'Submit';
$string['sync'] = 'Sync';
$string['syncfiletimeout'] = 'Sync file timeout';
* or enrolment has expired or not started.
*
* @param context $context context in which user enrolment is checked.
+ * @param bool $usecache Enable or disable (default) the request cache
* @return array list of suspended user id's.
*/
-function get_suspended_userids($context){
+function get_suspended_userids(context $context, $usecache = false) {
global $DB;
+ // Check the cache first for performance reasons if enabled.
+ if ($usecache) {
+ $cache = cache::make('core', 'suspended_userids');
+ $susers = $cache->get($context->id);
+ if ($susers !== false) {
+ return $susers;
+ }
+ }
+
// Get all enrolled users.
list($sql, $params) = get_enrolled_sql($context);
$users = $DB->get_records_sql($sql, $params);
}
}
}
+
+ // Cache results for the remainder of this request.
+ if ($usecache) {
+ $cache->set($context->id, $susers);
+ }
+
+ // Return.
return $susers;
}
/** Can not login because user is locked out. */
define('AUTH_LOGIN_LOCKOUT', 4);
+/** Can not login becauser user is not authorised. */
+define('AUTH_LOGIN_UNAUTHORISED', 5);
/**
* Abstract authentication plugin.
* @return string
*/
public function get_description() {
- return "Login failed for the username '{$this->other['username']}' for the reason with id '{$this->other['reason']}'.";
+ // Note that username could be any random user input.
+ $username = s($this->other['username']);
+ return "Login failed for the username '{$username}' for the reason with id '{$this->other['reason']}'.";
}
/**
$this->customdata = json_encode($customdata);
}
+ /**
+ * Alternate setter for $customdata. Expects the data as a json_encoded string.
+ * @param string $customdata json_encoded string
+ */
+ public function set_custom_data_as_string($customdata) {
+ $this->customdata = $customdata;
+ }
+
/**
* Getter for $customdata.
* @return mixed (anything that can be handled by json_decode).
return json_decode($this->customdata);
}
+ /**
+ * Alternate getter for $customdata.
+ * @return string this is the raw json encoded version.
+ */
+ public function get_custom_data_as_string() {
+ return $this->customdata;
+ }
+
+
}
$record->blocking = $task->is_blocking();
$record->nextruntime = $task->get_next_run_time();
$record->faildelay = $task->get_fail_delay();
- $record->customdata = $task->get_custom_data();
+ $record->customdata = $task->get_custom_data_as_string();
return $record;
}
$task->set_fail_delay($record->faildelay);
}
if (isset($record->customdata)) {
- $task->set_custom_data($record->customdata);
+ $task->set_custom_data_as_string($record->customdata);
}
return $task;
}
}
+ /**
+ * Reverse UTF-8 multibytes character sets (used for RTL languages)
+ * (We only do this because there is no mb_strrev or iconv_strrev)
+ *
+ * @param string $str the multibyte string to reverse
+ * @return string the reversed multi byte string
+ */
+ public static function strrev($str) {
+ preg_match_all('/./us', $str, $ar);
+ return join('', array_reverse($ar[0]));
+ }
+
/**
* Try to convert upper unicode characters to plain ascii,
* the returned string may contain unconverted unicode characters.
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simpledata' => true
- )
+ ),
+
+ // Caches suspended userids by course.
+ // The key is the courseid, the value is an array of user ids.
+ 'suspended_userids' => array(
+ 'mode' => cache_store::MODE_REQUEST,
+ 'simplekeys' => true,
+ 'simpledata' => true,
+ ),
);
}
if ($oldversion < 2013021100.01) {
+ // Make sure there are no bogus nulls in old MySQL tables.
+ $DB->set_field_select('user', 'password', '', "password IS NULL");
// Changing precision of field password on table user to (255).
$table = new xmldb_table('user');
if (empty($xmldb_length)) {
$xmldb_length='255';
}
- $dbtype .= '(' . $xmldb_length . ')';
+ $dbtype .= '(' . $xmldb_length . ') COLLATE database_default';
break;
case XMLDB_TYPE_TEXT:
- $dbtype = 'NVARCHAR(MAX)';
+ $dbtype = 'NVARCHAR(MAX) COLLATE database_default';
break;
case XMLDB_TYPE_BINARY:
$dbtype = 'VARBINARY(MAX)';
$dbman->drop_temp_table($table1);
$this->assertFalse($dbman->table_exists('test_table1'));
$this->assertDebuggingCalled();
+
+ // Try join with normal tables - MS SQL may use incompatible collation.
+ $table1 = new xmldb_table('test_table');
+ $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table1->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
+ $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $dbman->create_table($table1);
+
+ $table2 = new xmldb_table('test_temp');
+ $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table2->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
+ $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $dbman->create_temp_table($table2);
+
+ $record = array('name' => 'a');
+ $DB->insert_record('test_table', $record);
+ $DB->insert_record('test_temp', $record);
+
+ $record = array('name' => 'b');
+ $DB->insert_record('test_table', $record);
+
+ $record = array('name' => 'c');
+ $DB->insert_record('test_temp', $record);
+
+ $sql = "SELECT *
+ FROM {test_table} n
+ JOIN {test_temp} t ON t.name = n.name";
+ $records = $DB->get_records_sql($sql);
+ $this->assertCount(1, $records);
}
public function test_concurrent_temp_tables() {
}
var button = this.addButton({
- icon: M.util.image_url('icon', PLUGINNAME),
+ icon: 'icon',
+ iconComponent: PLUGINNAME,
callback: this._toggle
});
$DB->update_record('user_enrolments', $ue);
context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
+ // Invalidate core_access cache for get_suspended_userids.
+ cache_helper::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid));
+
// Trigger event.
$event = \core\event\user_enrolment_updated::create(
array(
editors_head_setup();
}
+ /**
+ * Called by HTML_QuickForm whenever form event is made on this element
+ *
+ * @param string $event Name of event
+ * @param mixed $arg event arguments
+ * @param object $caller calling object
+ * @return bool
+ */
+ function onQuickFormEvent($event, $arg, &$caller)
+ {
+ switch ($event) {
+ case 'createElement':
+ $caller->setType($arg[0] . '[format]', PARAM_ALPHANUM);
+ $caller->setType($arg[0] . '[itemid]', PARAM_INT);
+ break;
+ }
+ return parent::onQuickFormEvent($event, $arg, $caller);
+ }
+
/**
* Sets name of editor
*
if (!during_initial_install() && empty($CFG->adminsetuppending)) {
// 0 means no files, -1 unlimited
if ($maxfiles != 0 ) {
- $str .= '<input type="hidden" name="'.$elname.'[itemid]" value="'.$draftitemid.'" />';
+ $str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
+ 'value' => $draftitemid));
// used by non js editor only
$editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
parent::HTML_QuickForm_element($elementName, $elementLabel, $attributes);
}
+ /**
+ * Called by HTML_QuickForm whenever form event is made on this element
+ *
+ * @param string $event Name of event
+ * @param mixed $arg event arguments
+ * @param object $caller calling object
+ * @return bool
+ */
+ function onQuickFormEvent($event, $arg, &$caller)
+ {
+ switch ($event) {
+ case 'createElement':
+ $caller->setType($arg[0], PARAM_INT);
+ break;
+ }
+ return parent::onQuickFormEvent($event, $arg, $caller);
+ }
+
/**
* Sets name of filemanager
*
$output = $PAGE->get_renderer('core', 'files');
$html .= $output->render($fm);
- $html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
+ $html .= html_writer::empty_tag('input', array('value' => $draftitemid, 'name' => $elname, 'type' => 'hidden'));
// label element needs 'for' attribute work
- $html .= '<input value="" id="id_'.$elname.'" type="hidden" />';
+ $html .= html_writer::empty_tag('input', array('value' => '', 'id' => 'id_'.$elname, 'type' => 'hidden'));
return $html;
}
$this->sub_test_grade_scale_fetch();
$this->sub_test_scale_load_items();
$this->sub_test_scale_compact_items();
+ $this->sub_test_scale_one_item();
}
protected function sub_test_scale_construct() {
// The original string and the new string may have differences in whitespace around the delimiter, and that's OK.
$this->assertEquals(preg_replace('/\s*,\s*/', ',', $this->scale[0]->scale), $scale->scale);
}
+
+ protected function sub_test_scale_one_item() {
+ $params = new stdClass();
+ $params->name = 'unittestscale1i';
+ $params->courseid = $this->course->id;
+ $params->userid = $this->userid;
+ $params->scale = 'Like';
+ $params->description = 'This scale is used to like something.';
+ $params->timemodified = time();
+
+ $scale = new grade_scale($params, false);
+ $scale->load_items();
+
+ $this->assertCount(1, $scale->scale_items);
+ $this->assertSame(array('Like'), $scale->scale_items);
+ $this->assertSame('Like', $scale->compact_items());
+
+ $scale->insert();
+
+ // Manual grade item with 1 item scale.
+ $grade_item = new stdClass();
+ $grade_item->courseid = $this->course->id;
+ $grade_item->categoryid = $this->grade_categories[0]->id;
+ $grade_item->itemname = 'manual grade_item scale_1';
+ $grade_item->itemtype = 'manual';
+ $grade_item->itemnumber = 0;
+ $grade_item->needsupdate = false;
+ $grade_item->gradetype = GRADE_TYPE_SCALE;
+ $grade_item->scaleid = $scale->id;
+ $grade_item->iteminfo = 'Manual grade item used for unit testing';
+ $grade_item->timecreated = time();
+ $grade_item->timemodified = time();
+
+ $grade_item = new grade_item($grade_item);
+ $grade_item->insert();
+
+ $this->assertNotEmpty($grade_item->id);
+ $this->assertEquals(1, $grade_item->grademin);
+ $this->assertEquals(1, $grade_item->grademax);
+ }
}
//ImageColorTransparent($this->image, $this->colour['white']); // colour for transparency
}
+ /**
+ * Prepare label's text for GD output.
+ *
+ * @param string $label string to be prepared.
+ * @return string Reversed input string, if we are in RTL mode and has no numbers.
+ * Otherwise, returns the string as is.
+ */
+ private function prepare_label_text($label) {
+ if (right_to_left() and !preg_match('/[0-9]/i', $label)) {
+ return core_text::strrev($label);
+ } else {
+ return $label;
+ }
+ }
+
function print_TTF($message) {
$points = $message['points'];
$angle = $message['angle'];
- $text = $message['text'];
+ // We have to manually reverse the label, since php GD cannot handle RTL characters properly in UTF8 strings.
+ $text = $this->prepare_label_text($message['text']);
$colour = $this->colour[$message['colour']];
$font = $this->parameter['path_to_fonts'].$message['font'];
return false;
}
- // Do not try to authenticate non-existent accounts when user creation is disabled.
- if (!empty($CFG->authpreventaccountcreation)) {
- $failurereason = AUTH_LOGIN_NOUSER;
-
- // Trigger login failed event.
- $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
- 'reason' => $failurereason)));
- $event->trigger();
-
- error_log('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ".
- $_SERVER['HTTP_USER_AGENT']);
- return false;
- }
-
// User does not exist.
$auths = $authsenabled;
$user = new stdClass();
$user = update_user_record_by_id($user->id);
}
} else {
- // Create account, we verified above that user creation is allowed.
- $user = create_user_record($username, $password, $auth);
+ // The user is authenticated but user creation may be disabled.
+ if (!empty($CFG->authpreventaccountcreation)) {
+ $failurereason = AUTH_LOGIN_UNAUTHORISED;
+
+ // Trigger login failed event.
+ $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
+ 'reason' => $failurereason)));
+ $event->trigger();
+
+ error_log('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ".
+ $_SERVER['HTTP_USER_AGENT']);
+ return false;
+ } else {
+ $user = create_user_record($username, $password, $auth);
+ }
}
$authplugin->sync_roles($user);
// If we have new messages to notify the user about.
if (!empty($messageusers)) {
- $strmessages = '';
- if (count($messageusers)>1) {
- $strmessages = get_string('unreadnewmessages', 'message', count($messageusers));
- } else {
- $messageusers = reset($messageusers);
-
- // Show who the message is from if its not a notification.
- if (!$messageusers->notification) {
- $strmessages = get_string('unreadnewmessage', 'message', fullname($messageusers) );
- }
-
- // Try to display the small version of the message.
- $smallmessage = null;
- if (!empty($messageusers->smallmessage)) {
- // Display the first 200 chars of the message in the popup.
- $smallmessage = null;
- if (core_text::strlen($messageusers->smallmessage) > 200) {
- $smallmessage = core_text::substr($messageusers->smallmessage, 0, 200).'...';
- } else {
- $smallmessage = $messageusers->smallmessage;
- }
-
- // Prevent html symbols being displayed.
- if ($messageusers->fullmessageformat == FORMAT_HTML) {
- $smallmessage = html_to_text($smallmessage);
- } else {
- $smallmessage = s($smallmessage);
- }
- } else if ($messageusers->notification) {
- // Its a notification with no smallmessage so just say they have a notification.
- $smallmessage = get_string('unreadnewnotification', 'message');
- }
- if (!empty($smallmessage)) {
- $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
- }
- }
-
+ $strmessages = get_string('unreadnewmessages', 'message', count($messageusers));
$strgomessage = get_string('gotomessages', 'message');
$strstaymessage = get_string('ignore', 'admin');
$a->attempts = $count;
$loggedinas .= get_string('failedloginattempts', '', $a);
if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
- $loggedinas .= html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
- 'id' => 0 , 'modid' => 'site_errors')), '(' . get_string('logs') . ')');
+ $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
+ 'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
}
$loggedinas .= '</div>';
}
$suites .= $suite;
}
}
+ // Start a sequence between 100000 and 199000 to ensure each call to init produces
+ // different ids in the database. This reduces the risk that hard coded values will
+ // end up being placed in phpunit or behat test code.
+ $sequencestart = 100000 + mt_rand(0, 99) * 1000;
$data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
+ $data = preg_replace('|<!--@PHPUNIT_SEQUENCE_START@-->|s', $sequencestart, $data, 1);
$result = false;
if (is_writable($CFG->dirroot)) {
</testsuite>
</testsuites>';
+ // Start a sequence between 100000 and 199000 to ensure each call to init produces
+ // different ids in the database. This reduces the risk that hard coded values will
+ // end up being placed in phpunit or behat test code.
+ $sequencestart = 100000 + mt_rand(0, 99) * 1000;
+
// Use the upstream file as source for the distributed configurations
$ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
$ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
// Apply it to the file template
$fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
+ $fcontents = preg_replace('|<!--@PHPUNIT_SEQUENCE_START@-->|s', $sequencestart, $fcontents, 1);
// fix link to schema
$level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
// To reduce the chance of the coding error, we start sequences at different values where possible.
// In a attempt to avoid tables with existing id's we start at a high number.
// Reset the value each time all database sequences are reset.
- self::$sequencenextstartingid = 100000;
+ if (defined('PHPUNIT_SEQUENCE_START')) {
+ self::$sequencenextstartingid = PHPUNIT_SEQUENCE_START;
+ } else {
+ self::$sequencenextstartingid = 100000;
+ }
$dbfamily = $DB->get_dbfamily();
if ($dbfamily === 'postgres') {
$this->assertSame($str, core_text::strtoupper($str, 'GB18030'));
}
+ /**
+ * Test the strrev method.
+ */
+ public function test_strrev() {
+ $strings = array(
+ "Žluťoučký koníček" => "kečínok ýkčuoťulŽ",
+ 'ŽLUŤOUČKÝ KONÍČEK' => "KEČÍNOK ÝKČUOŤULŽ",
+ '言語設定' => '定設語言',
+ '简体中文' => '文中体简',
+ "Der eine stößt den Speer zum Mann" => "nnaM muz reepS ned tßöts enie reD"
+ );
+ foreach ($strings as $before => $after) {
+ // Make sure we can reverse it both ways and that it comes out the same.
+ $this->assertSame($after, core_text::strrev($before));
+ $this->assertSame($before, core_text::strrev($after));
+ // Reverse it twice to be doubly sure.
+ $this->assertSame($after, core_text::strrev(core_text::strrev($after)));
+ }
+ }
+
/**
* Tests the static strpos method.
*/
delay = this.get('hideTimeoutDelay');
this.get(BASE).addClass('moodle-dialogue-exception');
this.setStdModContent(Y.WidgetStdMod.HEADER,
- '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + config.name + '</h1>', Y.WidgetStdMod.REPLACE);
+ '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + Y.Escape.html(config.name) + '</h1>',
+ Y.WidgetStdMod.REPLACE);
content = Y.Node.create('<div class="moodle-ajaxexception"></div>')
- .append(Y.Node.create('<div class="moodle-exception-message">'+this.get('error')+'</div>'))
+ .append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('error'))+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>URL:</label> ' +
this.get('reproductionlink')+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> ' +
- this.get('debuginfo')+'</div>'))
+ Y.Escape.html(this.get('debuginfo'))+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>' +
- this.get('stacktrace')+'</pre></div>'));
+ Y.Escape.html(this.get('stacktrace'))+'</pre></div>'));
if (M.cfg.developerdebug) {
content.all('.moodle-exception-param').removeClass('hidden');
}
reproductionlink : {
setter : function(link) {
if (link !== null) {
+ link = Y.Escape.html(link);
link = '<a href="'+link+'">'+link.replace(M.cfg.wwwroot, '')+'</a>';
}
return link;
delay = this.get('hideTimeoutDelay');
this.get(BASE).addClass('moodle-dialogue-exception');
this.setStdModContent(Y.WidgetStdMod.HEADER,
- '<h1 id="moodle-dialogue-'+config.COUNT+'-header-text">' + config.name + '</h1>', Y.WidgetStdMod.REPLACE);
+ '<h1 id="moodle-dialogue-'+config.COUNT+'-header-text">' + Y.Escape.html(config.name) + '</h1>',
+ Y.WidgetStdMod.REPLACE);
content = Y.Node.create('<div class="moodle-exception"></div>')
- .append(Y.Node.create('<div class="moodle-exception-message">'+this.get('message')+'</div>'))
+ .append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('message'))+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>File:</label> ' +
- this.get('fileName')+'</div>'))
+ Y.Escape.html(this.get('fileName'))+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> ' +
- this.get('lineNumber')+'</div>'))
+ Y.Escape.html(this.get('lineNumber'))+'</div>'))
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>' +
this.get('stack')+'</pre></div>'));
if (M.cfg.developerdebug) {
*/
stack : {
setter : function(str) {
- var lines = str.split("\n"),
+ var lines = Y.Escape.html(str).split("\n"),
pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'),
i;
for (i in lines) {
"base",
"node",
"panel",
+ "escape",
"event-key",
"dd-plugin",
"moodle-core-widget-focusafterclose",
$frm = false;
} else {
if (empty($errormsg)) {
- $user = authenticate_user_login($frm->username, $frm->password);
+ $user = authenticate_user_login($frm->username, $frm->password, false, $errorcode);
}
}
} else {
if (empty($errormsg)) {
- $errormsg = get_string("invalidlogin");
- $errorcode = 3;
+ if ($errorcode == AUTH_LOGIN_UNAUTHORISED) {
+ $errormsg = get_string("unauthorisedlogin", "", $frm->username);
+ } else {
+ $errormsg = get_string("invalidlogin");
+ $errorcode = 3;
+ }
}
}
}
/** @var bool whether to exclude users with inactive enrolment */
private $showonlyactiveenrol = null;
- /** @var array list of suspended user IDs in form of ([id1] => id1) */
- public $susers = null;
-
/** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
private $participants = array();
* @return bool true is user is active in course.
*/
public function is_active_user($userid) {
- if (is_null($this->susers) && !is_null($this->context)) {
- $this->susers = get_suspended_userids($this->context);
- }
- return !in_array($userid, $this->susers);
+ return !in_array($userid, get_suspended_userids($this->context, true));
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
+ return new \moodle_url('/mod/forum/subscribers.php', array('id' => $this->other['forumid']));
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
+ return new \moodle_url('/mod/forum/subscribers.php', array('id' => $this->other['forumid']));
}
/**
$mform->setConstants(array('timestart'=> 0, 'timeend'=>0));
}
- if (groups_get_activity_groupmode($cm, $course)) { // hack alert
+ if ($groupmode = groups_get_activity_groupmode($cm, $course)) { // hack alert
$groupdata = groups_get_activity_allowed_groups($cm);
$groupcount = count($groupdata);
+ $groupinfo = array();
$modulecontext = context_module::instance($cm->id);
- $contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount;
- if ($contextcheck) {
+
+ // Check whether the user has access to all groups in this forum from the accessallgroups cap.
+ if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $modulecontext)) {
+ // Only allow posting to all groups if the user has access to all groups.
$groupinfo = array('0' => get_string('allparticipants'));
+ $groupcount++;
+ }
+
+ $contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount > 1;
+ if ($contextcheck) {
foreach ($groupdata as $grouptemp) {
$groupinfo[$grouptemp->id] = $grouptemp->name;
}
}
}
+/**
+ * Filters the forum discussions according to groups membership and config.
+ *
+ * @since Moodle 2.8, 2.7.1, 2.6.4
+ * @param array $discussions Discussions with new posts array
+ * @return array Forums with the number of new posts
+ */
+function forum_filter_user_groups_discussions($discussions) {
+
+ // Group the remaining discussions posts by their forumid.
+ $filteredforums = array();
+
+ // Discard not visible groups.
+ foreach ($discussions as $discussion) {
+
+ // Course data is already cached.
+ $instances = get_fast_modinfo($discussion->course)->get_instances();
+ $forum = $instances['forum'][$discussion->forum];
+
+ // Continue if the user should not see this discussion.
+ if (!forum_is_user_group_discussion($forum, $discussion->groupid)) {
+ continue;
+ }
+
+ // Grouping results by forum.
+ if (empty($filteredforums[$forum->instance])) {
+ $filteredforums[$forum->instance] = new stdClass();
+ $filteredforums[$forum->instance]->id = $forum->id;
+ $filteredforums[$forum->instance]->count = 0;
+ }
+ $filteredforums[$forum->instance]->count += $discussion->count;
+ }
+
+ return $filteredforums;
+}
+
+/**
+ * Returns whether the discussion group is visible by the current user or not.
+ *
+ * @since Moodle 2.8, 2.7.1, 2.6.4
+ * @param cm_info $cm The discussion course module
+ * @param int $discussiongroupid The discussion groupid
+ * @return bool
+ */
+function forum_is_user_group_discussion(cm_info $cm, $discussiongroupid) {
+
+ if ($discussiongroupid == -1 || $cm->effectivegroupmode != SEPARATEGROUPS) {
+ return true;
+ }
+ if (isguestuser()) {
+ return false;
+ }
+ if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id)) ||
+ in_array($discussiongroupid, $cm->get_modinfo()->get_groups($cm->groupingid))) {
+ return true;
+ }
+ return false;
+}
/**
* @global object
// If the user has never entered into the course all posts are pending
if ($course->lastaccess == 0) {
- $coursessqls[] = '(f.course = ?)';
+ $coursessqls[] = '(d.course = ?)';
$params[] = $course->id;
// Only posts created after the course last access
} else {
- $coursessqls[] = '(f.course = ? AND p.created > ?)';
+ $coursessqls[] = '(d.course = ? AND p.created > ?)';
$params[] = $course->id;
$params[] = $course->lastaccess;
}
$params[] = $USER->id;
$coursessql = implode(' OR ', $coursessqls);
- $sql = "SELECT f.id, COUNT(*) as count "
- .'FROM {forum} f '
- .'JOIN {forum_discussions} d ON d.forum = f.id '
+ $sql = "SELECT d.id, d.forum, d.course, d.groupid, COUNT(*) as count "
+ .'FROM {forum_discussions} d '
.'JOIN {forum_posts} p ON p.discussion = d.id '
."WHERE ($coursessql) "
.'AND p.userid != ? '
- .'GROUP BY f.id';
+ .'GROUP BY d.id, d.forum, d.course, d.groupid';
- if (!$new = $DB->get_records_sql($sql, $params)) {
- $new = array(); // avoid warnings
+ // Avoid warnings.
+ if (!$discussions = $DB->get_records_sql($sql, $params)) {
+ $discussions = array();
}
+ $forumsnewposts = forum_filter_user_groups_discussions($discussions);
+
// also get all forum tracking stuff ONCE.
$trackingforums = array();
foreach ($forums as $forum) {
$unread = array();
}
- if (empty($unread) and empty($new)) {
+ if (empty($unread) and empty($forumsnewposts)) {
return;
}
$thisunread = 0;
$showunread = false;
// either we have something from logs, or trackposts, or nothing.
- if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
- $count = $new[$forum->id]->count;
+ if (array_key_exists($forum->id, $forumsnewposts) && !empty($forumsnewposts[$forum->id])) {
+ $count = $forumsnewposts[$forum->id]->count;
}
if (array_key_exists($forum->id,$unread)) {
$thisunread = $unread[$forum->id]->count;
}
}
- $groupmode = groups_get_activity_groupmode($cm, $course);
-
- if ($groupmode) {
- if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
- // oki (Open discussions have groupid -1)
- } else {
- // separate mode
- if (isguestuser()) {
- // shortcut
- continue;
- }
-
- if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
- continue;
- }
- }
+ // Check that the user can see the discussion.
+ if (forum_is_user_group_discussion($cm, $post->groupid)) {
+ $printposts[] = $post;
}
- $printposts[] = $post;
}
unset($posts);
--- /dev/null
+@mod @mod_forum
+Feature: Posting to all groups in a separate group discussion is restricted to users with access to all groups
+ In order to post to all groups in a forum with separate groups
+ As a teacher
+ I need to have the accessallgroups capability or be a member of all of the groups
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@asd.com |
+ | noneditor1 | Non-editing teacher | 1 | noneditor1@asd.com |
+ | noneditor2 | Non-editing teacher | 2 | noneditor2@asd.com |
+ | student1 | Student | 1 | student1@asd.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | noneditor1 | C1 | teacher |
+ | noneditor2 | C1 | teacher |
+ | student1 | C1 | student |
+ And the following "groups" exist:
+ | name | course | idnumber |
+ | Group A | C1 | G1 |
+ | Group B | C1 | G2 |
+ | Group C | C1 | G3 |
+ And the following "group members" exist:
+ | user | group |
+ | teacher1 | G1 |
+ | teacher1 | G2 |
+ | noneditor1 | G1 |
+ | noneditor1 | G2 |
+ | noneditor1 | G3 |
+ | noneditor2 | G1 |
+ | noneditor2 | G2 |
+ | student1 | G1 |
+ | student1 | G2 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Forum" to section "1" and I fill the form with:
+ | Forum name | Standard forum name |
+ | Forum type | Standard forum for general use |
+ | Description | Standard forum description |
+ | Group mode | Separate groups |
+ And I log out
+
+ Scenario: Teacher with accessallgroups can post in all groups
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Standard forum name"
+ When I click on "Add a new discussion topic" "button"
+ Then the "Group" select box should contain "All participants"
+ And the "Group" select box should contain "Group A"
+ And the "Group" select box should contain "Group B"
+
+ @javascript
+ Scenario: Teacher in all groups but without accessallgroups can only post in their groups
+ And I log in as "admin"
+ And I set the following system permissions of "Non-editing teacher" role:
+ | moodle/site:accessallgroups | Prohibit |
+ And I log out
+ Given I log in as "noneditor1"
+ And I follow "Course 1"
+ And I follow "Standard forum name"
+ When I click on "Add a new discussion topic" "button"
+ Then the "Group" select box should not contain "All participants"
+ And the "Group" select box should contain "Group A"
+ And the "Group" select box should contain "Group B"
+
+ @javascript
+ Scenario: Teacher in some groups and without accessallgroups can only post in their groups
+ And I log in as "admin"
+ And I set the following system permissions of "Non-editing teacher" role:
+ | moodle/site:accessallgroups | Prohibit |
+ And I log out
+ Given I log in as "noneditor1"
+ And I follow "Course 1"
+ And I follow "Standard forum name"
+ When I click on "Add a new discussion topic" "button"
+ Then the "Group" select box should not contain "All participants"
+ And the "Group" select box should contain "Group A"
+ And the "Group" select box should contain "Group B"
--- /dev/null
+@mod @mod_forum
+Feature: Posting to groups in a separate group discussion when restricted to groupings
+ In order to post to groups in a forum with separate groups and groupings
+ As a teacher
+ I need to have groups configured to post to a group
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | teacher1 | teacher1 | teacher1@asd.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | teacher |
+ And the following "groups" exist:
+ | name | course | idnumber |
+ | G1G1 | C1 | G1G1 |
+ | G1G2 | C1 | G1G2 |
+ | G2G1 | C1 | G2G1 |
+ And the following "groupings" exist:
+ | name | course | idnumber |
+ | G1 | C1 | G1 |
+ | G2 | C1 | G2 |
+ And the following "group members" exist:
+ | user | group |
+ | teacher1 | G1G1 |
+ | teacher1 | G1G2 |
+ | teacher1 | G2G1 |
+ And the following "grouping groups" exist:
+ | grouping | group |
+ | G1 | G1G1 |
+ | G1 | G1G2 |
+ | G2 | G2G1 |
+ And I log in as "admin"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Forum" to section "1" and I fill the form with:
+ | Forum name | Multiple groups forum |
+ | Forum type | Standard forum for general use |
+ | Description | Standard forum description |
+ | Group mode | Separate groups |
+ | Grouping | G1 |
+ And I add a "Forum" to section "1" and I fill the form with:
+ | Forum name | Single groups forum |
+ | Forum type | Standard forum for general use |
+ | Description | Standard forum description |
+ | Group mode | Separate groups |
+ | Grouping | G2 |
+ And I log out
+
+ Scenario: Teacher with accessallgroups can post in all groups
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Multiple groups forum"
+ When I click on "Add a new discussion topic" "button"
+ Then the "Group" select box should contain "All participants"
+ And the "Group" select box should contain "G1G1"
+ And the "Group" select box should contain "G1G2"
+ And I follow "Course 1"
+ And I follow "Single groups forum"
+ And I click on "Add a new discussion topic" "button"
+ And the "Group" select box should contain "All participants"
+ And the "Group" select box should contain "G2G1"
+
+ @javascript
+ Scenario: Teacher in all groups but without accessallgroups can post in either group but not to All Participants
+ And I log in as "admin"
+ And I set the following system permissions of "Non-editing teacher" role:
+ | moodle/site:accessallgroups | Prohibit |
+ And I log out
+ Given I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Multiple groups forum"
+ When I click on "Add a new discussion topic" "button"
+ Then the "Group" select box should not contain "All participants"
+ And the "Group" select box should contain "G1G1"
+ And the "Group" select box should contain "G1G2"
+ And I follow "Course 1"
+ And I follow "Single groups forum"
+ And I click on "Add a new discussion topic" "button"
+ And I should see "G2G1"
+ And "Group" "select" should not exist
$this->assertEquals($context, $event->get_context());
$expected = array($course->id, 'forum', 'subscribe', "view.php?f={$forum->id}", $forum->id, $forum->cmid);
$this->assertEventLegacyLogData($expected, $event);
- $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id));
+ $url = new \moodle_url('/mod/forum/subscribers.php', array('id' => $forum->id));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertEquals($context, $event->get_context());
$expected = array($course->id, 'forum', 'unsubscribe', "view.php?f={$forum->id}", $forum->id, $forum->cmid);
$this->assertEventLegacyLogData($expected, $event);
- $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id));
+ $url = new \moodle_url('/mod/forum/subscribers.php', array('id' => $forum->id));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertEquals($context, $event->get_context());
}
+
+ /**
+ * Test mod_forum_observer methods.
+ */
+ public function test_observers() {
+ global $DB, $CFG;
+
+ require_once($CFG->dirroot . '/mod/forum/lib.php');
+
+ $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
+
+ $course = $this->getDataGenerator()->create_course();
+ $trackedrecord = array('course' => $course->id, 'type' => 'general', 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
+ $untrackedrecord = array('course' => $course->id, 'type' => 'general');
+ $trackedforum = $this->getDataGenerator()->create_module('forum', $trackedrecord);
+ $untrackedforum = $this->getDataGenerator()->create_module('forum', $untrackedrecord);
+
+ // Used functions don't require these settings; adding
+ // them just in case there are APIs changes in future.
+ $user = $this->getDataGenerator()->create_user(array(
+ 'maildigest' => 1,
+ 'trackforums' => 1
+ ));
+
+ $manplugin = enrol_get_plugin('manual');
+ $manualenrol = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
+ $student = $DB->get_record('role', array('shortname' => 'student'));
+
+ // The role_assign observer does it's job adding the forum_subscriptions record.
+ $manplugin->enrol_user($manualenrol, $user->id, $student->id);
+
+ // They are not required, but in a real environment they are supposed to be required;
+ // adding them just in case there are APIs changes in future.
+ set_config('forum_trackingtype', 1);
+ set_config('forum_trackreadposts', 1);
+
+ $record = array();
+ $record['course'] = $course->id;
+ $record['forum'] = $trackedforum->id;
+ $record['userid'] = $user->id;
+ $discussion = $forumgen->create_discussion($record);
+
+ $record = array();
+ $record['discussion'] = $discussion->id;
+ $record['userid'] = $user->id;
+ $post = $forumgen->create_post($record);
+
+ forum_tp_add_read_record($user->id, $post->id);
+ forum_set_user_maildigest($trackedforum, 2, $user);
+ forum_tp_stop_tracking($untrackedforum->id, $user->id);
+
+ $this->assertEquals(1, $DB->count_records('forum_subscriptions'));
+ $this->assertEquals(1, $DB->count_records('forum_digests'));
+ $this->assertEquals(1, $DB->count_records('forum_track_prefs'));
+ $this->assertEquals(1, $DB->count_records('forum_read'));
+
+ // The course_module_created observer does it's job adding a subscription.
+ $forumrecord = array('course' => $course->id, 'type' => 'general', 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
+ $extraforum = $this->getDataGenerator()->create_module('forum', $forumrecord);
+ $this->assertEquals(2, $DB->count_records('forum_subscriptions'));
+
+ $manplugin->unenrol_user($manualenrol, $user->id);
+
+ $this->assertEquals(0, $DB->count_records('forum_digests'));
+ $this->assertEquals(0, $DB->count_records('forum_subscriptions'));
+ $this->assertEquals(0, $DB->count_records('forum_track_prefs'));
+ $this->assertEquals(0, $DB->count_records('forum_read'));
+ }
+
}
*/
function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) {
$doc = new DOMDocument();
+ $oldentities = libxml_disable_entity_loader(true);
if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) {
return null;
}
+ libxml_disable_entity_loader($oldentities);
// we put this fake URL as base in order to detect path changes caused by xml:base attributes
$doc->documentURI = 'http://grrr/';
if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) {
return null;
}
+
$doc = new DOMDocument();
+ $oldentities = libxml_disable_entity_loader(true);
if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) {
return null;
}
+ libxml_disable_entity_loader($oldentities);
+
$xmlresources = $doc->getElementsByTagName('resource');
foreach ($xmlresources as $res) {
if (!$href = $res->attributes->getNamedItem('href')) {
<FIELD NAME="instructorchoiceallowsetting" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Allow a tool to store a setting"/>
<FIELD NAME="instructorcustomparameters" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Additional custom parameters provided by the instructor"/>
<FIELD NAME="instructorchoiceacceptgrades" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Accept grades from tool"/>
- <FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="100" SEQUENCE="false" DECIMALS="5" COMMENT="Grade scale"/>
+ <FIELD NAME="grade" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="100" SEQUENCE="false" COMMENT="Grade scale"/>
<FIELD NAME="launchcontainer" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Launch external tool in a pop-up"/>
<FIELD NAME="resourcekey" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
// Moodle v2.7.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2014060201) {
+
+ // Changing type of field grade on table lti to int.
+ $table = new xmldb_table('lti');
+ $field = new xmldb_field('grade', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '100',
+ 'instructorchoiceacceptgrades');
+
+ // Launch change of type for field grade.
+ $dbman->change_field_type($table, $field);
+
+ // Lti savepoint reached.
+ upgrade_mod_savepoint(true, 2014060201, 'lti');
+ }
+
return true;
}
$instanceid = optional_param('instanceid', 0, PARAM_INT);
$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
+$msg = optional_param('lti_msg', '', PARAM_RAW);
$unsigned = optional_param('unsigned', '0', PARAM_INT);
$launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
require_login($course);
-if (!empty($errormsg)) {
+if (!empty($errormsg) || !empty($msg)) {
$url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
$PAGE->set_url($url);
if (!empty($lti) and !empty($context)) {
echo $OUTPUT->heading(format_string($lti->name, true, array('context' => $context)));
}
+}
+if (!empty($errormsg)) {
echo get_string('lti_launch_error', 'lti');
echo htmlspecialchars($errormsg);
}
echo $OUTPUT->footer();
+} else if (!empty($msg)) {
+
+ echo htmlspecialchars($msg);
+
+ echo $OUTPUT->footer();
+
} else {
$courseurl = new moodle_url('/course/view.php', array('id' => $courseid));
$url = $courseurl->out();
throw new Exception('Message signature not valid');
}
-$xml = new SimpleXMLElement($rawbody);
+// TODO MDL-46023 Replace this code with a call to the new library.
+$origentity = libxml_disable_entity_loader(true);
+$xml = simplexml_load_string($rawbody);
+if (!$xml) {
+ libxml_disable_entity_loader($origentity);
+ throw new Exception('Invalid XML content');
+}
+libxml_disable_entity_loader($origentity);
$body = $xml->imsx_POXBody;
foreach ($body->children() as $child) {
This files describes API changes in the lti code.
+=== 2.8 ===
+
+* The field 'grade' in the table {lti} is now an integer rather than a numeric to bring it
+ in line with the 'grade' field in other activities.
+
=== 2.7 ===
* mod_lti\event\unknown_service_api_called now has less data stored in 'other'
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2014060200; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2014060201; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2014050800; // Requires this Moodle version
$plugin->component = 'mod_lti'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
$string['reportresponses'] = 'Detailed responses';
$string['reports'] = 'Reports';
$string['reportshowonly'] = 'Show only attempts';
+$string['reportshowonlyfinished'] = 'Show at most one finished attempt per user ({$a})';
$string['reportsimplestat'] = 'Simple statistics';
$string['reportusersall'] = 'all users who have attempted the quiz';
$string['reportuserswith'] = 'enrolled users who have attempted the quiz';
// Highlight the highest grade if appropriate.
if ($viewobj->overallstats && !$attemptobj->is_preview()
&& $viewobj->numattempts > 1 && !is_null($viewobj->mygrade)
+ && $attemptobj->get_state() == quiz_attempt::FINISHED
&& $attemptgrade == $viewobj->mygrade
&& $quiz->grademethod == QUIZ_GRADEHIGHEST) {
$table->rowclasses[$attemptobj->get_attempt_number()] = 'bestrow';
$gm = html_writer::tag('span',
quiz_get_grading_option_name($this->_customdata['quiz']->grademethod),
array('class' => 'highlight'));
- $mform->addElement('advcheckbox', 'onlygraded', get_string('reportshowonly', 'quiz'),
- get_string('optonlygradedattempts', 'quiz_overview', $gm));
+ $mform->addElement('advcheckbox', 'onlygraded', '',
+ get_string('reportshowonlyfinished', 'quiz', $gm));
$mform->disabledIf('onlygraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
$mform->disabledIf('onlygraded', 'statefinished', 'notchecked');
}
$this->onlygraded = false;
}
- if ($this->onlygraded) {
- $this->states = array(quiz_attempt::FINISHED);
+ if (!$this->is_showing_finished_attempts()) {
+ $this->onlygraded = false;
}
if ($this->pagesize < 1) {
$this->pagesize = quiz_attempts_report::DEFAULT_PAGE_SIZE;
}
}
+
+ /**
+ * Whether the options are such that finished attempts are being shown.
+ * @return boolean
+ */
+ protected function is_showing_finished_attempts() {
+ return $this->states === null || in_array(quiz_attempt::FINISHED, $this->states);
+ }
}
$params = array('quizid' => $this->quiz->id);
if ($this->qmsubselect && $this->options->onlygraded) {
- $from .= " AND $this->qmsubselect";
+ $from .= " AND (quiza.state <> :finishedstate OR $this->qmsubselect)";
+ $params['finishedstate'] = quiz_attempt::FINISHED;
}
switch ($this->options->attempts) {
$string['optallstudents'] = 'all {$a} who have or have not attempted the quiz';
$string['optattemptsonly'] = '{$a} who have attempted the quiz';
$string['optnoattemptsonly'] = '{$a} who have not attempted the quiz';
-$string['optonlygradedattempts'] = 'that are graded for each user ({$a})';
$string['optonlyregradedattempts'] = 'that have been regraded / are marked as needing regrading';
$string['overview'] = 'Grades';
$string['overviewdownload'] = 'Overview download';
protected function other_attempt_fields(MoodleQuickForm $mform) {
if (has_capability('mod/quiz:regrade', $this->_customdata['context'])) {
- $mform->addElement('advcheckbox', 'onlyregraded', '',
+ $mform->addElement('advcheckbox', 'onlyregraded', get_string('reportshowonly', 'quiz'),
get_string('optonlyregradedattempts', 'quiz_overview'));
$mform->disabledIf('onlyregraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
}
public function resolve_dependencies() {
parent::resolve_dependencies();
+ if ($this->attempts == quiz_attempts_report::ENROLLED_WITHOUT) {
+ $this->onlyregraded = false;
+ }
+
if (!$this->usercanseegrades) {
$this->slotmarks = false;
}
// Construct the SQL.
$fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') .
' AS uniqueid, ';
- if ($this->qmsubselect) {
- $fields .=
- "(CASE " .
- " WHEN {$this->qmsubselect} THEN 1" .
- " ELSE 0 " .
- "END) AS gradedattempt, ";
- }
list($fields, $from, $where, $params) = $table->base_sql($allowed);
--- /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 the quiz overview report.
+ *
+ * @package quiz_overview
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
+require_once($CFG->dirroot . '/mod/quiz/report/default.php');
+require_once($CFG->dirroot . '/mod/quiz/report/overview/report.php');
+
+
+/**
+ * Tests for the quiz overview report.
+ *
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class quiz_overview_report_testcase extends advanced_testcase {
+
+ public function test_report_sql() {
+ global $DB, $SITE;
+ $this->resetAfterTest(true);
+
+ $generator = $this->getDataGenerator();
+ $quizgenerator = $generator->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id,
+ 'grademethod' => QUIZ_GRADEHIGHEST, 'grade' => 100.0, 'sumgrades' => 10.0,
+ 'attempts' => 10));
+
+ $student1 = $generator->create_user();
+ $student2 = $generator->create_user();
+ $student3 = $generator->create_user();
+
+ $quizid = 123;
+ $timestamp = 1234567890;
+
+ // The test data.
+ $fields = array('quiz', 'userid', 'attempt', 'sumgrades', 'state');
+ $attempts = array(
+ array($quiz->id, $student1->id, 1, 0.0, quiz_attempt::FINISHED),
+ array($quiz->id, $student1->id, 2, 5.0, quiz_attempt::FINISHED),
+ array($quiz->id, $student1->id, 3, 8.0, quiz_attempt::FINISHED),
+ array($quiz->id, $student1->id, 4, null, quiz_attempt::ABANDONED),
+ array($quiz->id, $student1->id, 5, null, quiz_attempt::IN_PROGRESS),
+ array($quiz->id, $student2->id, 1, null, quiz_attempt::ABANDONED),
+ array($quiz->id, $student2->id, 2, null, quiz_attempt::ABANDONED),
+ array($quiz->id, $student2->id, 3, 7.0, quiz_attempt::FINISHED),
+ array($quiz->id, $student2->id, 4, null, quiz_attempt::ABANDONED),
+ array($quiz->id, $student2->id, 5, null, quiz_attempt::ABANDONED),
+ );
+
+ // Load it in to quiz attempts table.
+ $uniqueid = 1;
+ foreach ($attempts as $attempt) {
+ $data = array_combine($fields, $attempt);
+ $data['timestart'] = $timestamp + 3600 * $data['attempt'];
+ $data['timemodifed'] = $data['timestart'];
+ if ($data['state'] == quiz_attempt::FINISHED) {
+ $data['timefinish'] = $data['timestart'] + 600;
+ $data['timemodifed'] = $data['timefinish'];
+ }
+ $data['layout'] = ''; // Not used, but cannot be null.
+ $data['uniqueid'] = $uniqueid++;
+ $data['preview'] = 0;
+ $DB->insert_record('quiz_attempts', $data);
+ }
+
+ // Actually getting the SQL to run is quit hard. Do a minimal set up of
+ // some objects.
+ $context = context_module::instance($quiz->cmid);
+ $cm = get_coursemodule_from_id('quiz', $quiz->cmid);
+ $qmsubselect = quiz_report_qm_filter_select($quiz);
+ $reportstudents = array($student1->id, $student2->id, $student3->id);
+
+ // Set the options.
+ $reportoptions = new quiz_overview_options('overview', $quiz, $cm, null);
+ $reportoptions->attempts = quiz_attempts_report::ENROLLED_ALL;
+ $reportoptions->onlygraded = true;
+ $reportoptions->states = array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE, quiz_attempt::FINISHED);
+
+ // Now do a minimal set-up of the table class.
+ $table = new quiz_overview_table($quiz, $context, $qmsubselect, $reportoptions,
+ array(), $reportstudents, array(1), null);
+ $table->define_columns(array('attempt'));
+ $table->sortable(true, 'uniqueid');
+ $table->define_baseurl(new moodle_url('/mod/quiz/report.php'));
+ $table->setup();
+
+ // Run the query.
+ list($fields, $from, $where, $params) = $table->base_sql($reportstudents);
+ $table->set_sql($fields, $from, $where, $params);
+ $table->query_db(30, false);
+
+ // Verify what was returned: Student 1's best and in progress attempts.
+ // Stuent 2's finshed attempt, and Student 3 with no attempt.
+ // The array key is {student id}#{attempt number}.
+ $this->assertEquals(4, count($table->rawdata));
+ $this->assertArrayHasKey($student1->id . '#3', $table->rawdata);
+ $this->assertEquals(1, $table->rawdata[$student1->id . '#3']->gradedattempt);
+ $this->assertArrayHasKey($student1->id . '#3', $table->rawdata);
+ $this->assertEquals(0, $table->rawdata[$student1->id . '#5']->gradedattempt);
+ $this->assertArrayHasKey($student2->id . '#3', $table->rawdata);
+ $this->assertEquals(1, $table->rawdata[$student2->id . '#3']->gradedattempt);
+ $this->assertArrayHasKey($student3->id . '#0', $table->rawdata);
+ $this->assertEquals(0, $table->rawdata[$student3->id . '#0']->gradedattempt);
+ }
+}
function quiz_report_grade_method_sql($grademethod, $quizattemptsalias = 'quiza') {
switch ($grademethod) {
case QUIZ_GRADEHIGHEST :
- return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+ return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+ SELECT 1 FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
- qa2.userid = $quizattemptsalias.userid AND (
+ qa2.userid = $quizattemptsalias.userid AND
+ qa2.state = 'finished' AND (
COALESCE(qa2.sumgrades, 0) > COALESCE($quizattemptsalias.sumgrades, 0) OR
(COALESCE(qa2.sumgrades, 0) = COALESCE($quizattemptsalias.sumgrades, 0) AND qa2.attempt < $quizattemptsalias.attempt)
- ))";
+ )))";
case QUIZ_GRADEAVERAGE :
return '';
case QUIZ_ATTEMPTFIRST :
- return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+ return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+ SELECT 1 FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid AND
- qa2.attempt < $quizattemptsalias.attempt)";
+ qa2.state = 'finished' AND
+ qa2.attempt < $quizattemptsalias.attempt))";
case QUIZ_ATTEMPTLAST :
- return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+ return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+ SELECT 1 FROM {quiz_attempts} qa2
WHERE qa2.quiz = $quizattemptsalias.quiz AND
qa2.userid = $quizattemptsalias.userid AND
- qa2.attempt > $quizattemptsalias.attempt)";
+ qa2.state = 'finished' AND
+ qa2.attempt > $quizattemptsalias.attempt))";
}
}
#page-mod-quiz-view #page .quizattemptsummary td p {
margin-top: 0;
}
-table.quizattemptsummary .bestrow td {
- background-color: #e8e8e8;
+#page-mod-quiz-view table.quizattemptsummary tr.bestrow td {
+ border-color: #bce8f1;
+ background-color: #d9edf7;
}
table.quizattemptsummary .noreviewmessage {
color: gray;
display: inline;
}
-.mod-quiz .gradedattempt,
-.mod-quiz tr.gradedattempt td {
- background-color: #e8e8e8;
+body.path-mod-quiz .gradedattempt,
+body.path-mod-quiz table tbody tr.gradedattempt > td {
+ border-color: #bce8f1;
+ background-color: #d9edf7;
}
.quizattemptcounts {
background-color: #fcc;
}
#page-mod-quiz-report .highlight {
- border : medium solid yellow;
- background-color: lightYellow;
+ border: 1px solid #bce8f1;
+ background-color: #d9edf7;
}
#page-mod-quiz-report .negcovar {
border : medium solid pink;
#page-mod-quiz-report .gradetheselink {
font-size: 0.8em;
}
-#page-mod-quiz-report .mform fieldset {
- margin: 0;
-}
-#page-mod-quiz-report fieldset.felement.fgroup {
- margin: 0;
+#page-mod-quiz-report .mform fieldset.fgroup span label {
+ margin-right: 14px;
}
#page-mod-quiz-report table th {
white-space: normal;
$fakeattempt->userid = 123;
$fakeattempt->quiz = 456;
$fakeattempt->layout = '1,2,0,3,4,0,5';
+ $fakeattempt->state = quiz_attempt::FINISHED;
// We intentionally insert these in a funny order, to test the SQL better.
// The test data is:
- // id | quizid | user | attempt | sumgrades
- // ----------------------------------------
- // 4 | 456 | 123 | 1 | 30
- // 2 | 456 | 123 | 2 | 50
- // 1 | 456 | 123 | 3 | 50
- // 3 | 456 | 123 | 4 | null
- // 5 | 456 | 1 | 1 | 100
+ // id | quizid | user | attempt | sumgrades | state
+ // ---------------------------------------------------
+ // 4 | 456 | 123 | 1 | 30 | finished
+ // 2 | 456 | 123 | 2 | 50 | finished
+ // 1 | 456 | 123 | 3 | 50 | finished
+ // 3 | 456 | 123 | 4 | null | inprogress
+ // 5 | 456 | 1 | 1 | 100 | finished
// layout is only given because it has a not-null constraint.
// uniqueid values are meaningless, but that column has a unique constraint.
$fakeattempt->attempt = 4;
$fakeattempt->sumgrades = null;
$fakeattempt->uniqueid = 39;
+ $fakeattempt->state = quiz_attempt::IN_PROGRESS;
$DB->insert_record('quiz_attempts', $fakeattempt);
$fakeattempt->attempt = 1;
$fakeattempt->sumgrades = 30;
$fakeattempt->uniqueid = 52;
+ $fakeattempt->state = quiz_attempt::FINISHED;
$DB->insert_record('quiz_attempts', $fakeattempt);
$fakeattempt->attempt = 1;
. quiz_report_qm_filter_select($quiz), array(123, 456));
$this->assertEquals(1, count($lastattempt));
$lastattempt = reset($lastattempt);
- $this->assertEquals(4, $lastattempt->attempt);
+ $this->assertEquals(3, $lastattempt->attempt);
$quiz->attempts = 0;
$quiz->grademethod = QUIZ_GRADEHIGHEST;
$filtertype = optional_param('filtertype', '', PARAM_ALPHA);
$filterselect = optional_param('filterselect', 0, PARAM_INT);
+if (empty($CFG->enablenotes)) {
+ print_error('notesdisabled', 'notes');
+}
+
$url = new moodle_url('/notes/index.php');
if ($courseid != SITEID) {
$url->param('course', $courseid);
} else {
$coursecontext = context_course::instance($course->id); // Course context
}
+require_capability('moodle/notes:view', $coursecontext);
$systemcontext = context_system::instance(); // SYSTEM context
// Trigger event.
));
$event->trigger();
-if (empty($CFG->enablenotes)) {
- print_error('notesdisabled', 'notes');
-}
-
$strnotes = get_string('notes', 'notes');
if ($userid) {
$PAGE->set_context(context_user::instance($user->id));
<php>
<!--<const name="PHPUNIT_LONGTEST" value="1"/> uncomment to execute also slow or otherwise expensive tests-->
+ <const name="PHPUNIT_SEQUENCE_START" value="<!--@PHPUNIT_SEQUENCE_START@-->"/>
<!--Following constants instruct tests to fetch external test files from alternative location or skip tests if empty, clone https://github.com/moodlehq/moodle-exttests to local web server-->
<!--<const name="TEST_EXTERNAL_FILES_HTTP_URL" value="http://download.moodle.org/unittest"/> uncomment and alter to fetch external test files from alternative location-->
/// Interface for editing existing categories
if ($category = $DB->get_record("question_categories", array("id" => $categoryid))) {
- $category->parent = "$category->parent,$category->contextid";
+ $category->parent = "{$category->parent},{$category->contextid}";
$category->submitbutton = get_string('savechanges');
$category->categoryheader = $this->str->edit;
$this->catform->set_data($category);
array('qperpage' => DEFAULT_QUESTIONS_PER_PAGE)));
$showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>';
}
- echo "<div class='paging'>$showall</div>";
+ echo "<div class='paging'>{$showall}</div>";
}
echo '</div>';
if ($canmoveall && count($addcontexts)) {
echo '<input type="submit" name="move" value="'.get_string('moveto', 'question')."\" />\n";
- question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid");
+ question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
}
if (function_exists('module_specific_controls') && $canuseall) {
$modulespecific = module_specific_controls($totalnumber, $recurse, $category, $this->cm->id, $cmoptions);
if (!empty($modulespecific)) {
- echo "<hr />$modulespecific";
+ echo "<hr />{$modulespecific}";
}
}
}
SELECT q.*, c.contextid
FROM {question} q
JOIN {question_categories} c ON c.id = q.category
- WHERE q.id $usql", $params);
+ WHERE q.id {$usql}", $params);
foreach ($questions as $question) {
question_require_capability_on($question, 'move');
}
question_move_questions_to_category($questionids, $tocategory->id);
redirect($this->baseurl->out(false,
- array('category' => "$tocategoryid,$contextid")));
+ array('category' => "{$tocategoryid},{$contextid}")));
}
}
// Get the list of questions for the category
list($usql, $params) = $DB->get_in_or_equal($categorylist);
- $questions = $DB->get_records_select('question', "category $usql $npsql", $params, 'qtype, name');
+ $questions = $DB->get_records_select('question', "category {$usql} {$npsql}", $params, 'qtype, name');
// Iterate through questions, getting stuff we need
$qresults = array();
$contextlistarr = array();
foreach ($contexts->having_one_edit_tab_cap($edittab) as $context){
- $contextlistarr[] = "'$context->id'";
+ $contextlistarr[] = "'{$context->id}'";
}
$contextlist = join($contextlistarr, ' ,');
if (!empty($pagevars['cat'])){
}
} else {
$category = $defaultcategory;
- $pagevars['cat'] = "$category->id,$category->contextid";
+ $pagevars['cat'] = "{$category->id},{$category->contextid}";
}
// Display options.
include_once($file);
$class = 'qtype_' . $qtypename;
if (!class_exists($class)) {
- throw new coding_exception("Class $class must be defined in $file");
+ throw new coding_exception("Class {$class} must be defined in {$file}.");
}
self::$questiontypes[$qtypename] = new $class();
return self::$questiontypes[$qtypename];
// The the positive grades in descending order.
foreach ($rawfractions as $fraction) {
$percentage = format_float(100 * $fraction, 5, true, true) . '%';
- self::$fractionoptions["$fraction"] = $percentage;
- self::$fractionoptionsfull["$fraction"] = $percentage;
+ self::$fractionoptions["{$fraction}"] = $percentage;
+ self::$fractionoptionsfull["{$fraction}"] = $percentage;
}
// The the negative grades in descending order.
}
return $DB->get_records_select_menu('question',
- "category $qcsql
+ "category {$qcsql}
AND parent = 0
AND hidden = 0
- $extraconditions", $qcparams + $extraparams, '', 'id,id AS id2');
+ {$extraconditions}", $qcparams + $extraparams, '', 'id,id AS id2');
}
/* See cache_data_source::load_for_cache. */
if (!empty($slots)) {
list($slottest, $slotsparams) = $this->db->get_in_or_equal(
$slots, SQL_PARAMS_NAMED, 'slot');
- $slotwhere = " AND qa.slot $slottest";
+ $slotwhere = " AND qa.slot {$slottest}";
} else {
$slotwhere = '';
$slotsparams = array();
$this->delete_response_files($context->id, $test, $params);
$this->db->delete_records_select('question_attempt_step_data',
- "attemptstepid $test", $params);
+ "attemptstepid {$test}", $params);
$this->db->delete_records_select('question_attempt_steps',
- "id $test", $params);
+ "id {$test}", $params);
}
/**
protected function full_states_to_summary_state_sql() {
$sql = '';
foreach (question_state::get_all() as $state) {
- $sql .= "WHEN '$state' THEN '{$state->get_summary_state()}'\n";
+ $sql .= "WHEN '{$state}' THEN '{$state->get_summary_state()}'\n";
}
return $sql;
}
JOIN {question_attempt_steps} {$alias}qas ON {$alias}qas.questionattemptid = {$alias}qa.id
AND {$alias}qas.sequencenumber = {$this->latest_step_for_qa_subquery($alias . 'qa.id')}
WHERE {$qubaids->where()}
- ) $alias", $qubaids->from_where_params());
+ ) {$alias}", $qubaids->from_where_params());
}
protected function latest_step_for_qa_subquery($questionattemptid = 'qa.id') {
}
public function from_question_attempts($alias) {
- return "$this->from
+ return "{$this->from}
JOIN {question_attempts} {$alias} ON " .
"{$alias}.questionusageid = $this->usageidcolumn";
}
}
public function usage_id_in() {
- return "IN (SELECT $this->usageidcolumn FROM $this->from WHERE $this->where)";
+ return "IN (SELECT {$this->usageidcolumn} FROM {$this->from} WHERE {$this->where})";
}
public function usage_id_in_params() {
$qid = $qa->get_question()->id;
$slot = $qa->get_slot();
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
- return "qaid=$qaid&qubaid=$qubaid&qid=$qid&slot=$slot&checksum=$checksum&sesskey=" .
+ return "qaid={$qaid}&qubaid={$qubaid}&qid={$qid}&slot={$slot}&checksum={$checksum}&sesskey=" .
sesskey() . '&newstate=';
}
$postdata = data_submitted();
}
parent::__construct('submissionoutofsequence', 'question', '', null,
- "QUBAid: $qubaid, slot: $slot, post data: " . print_r($postdata, true));
+ "QUBAid: {$qubaid}, slot: {$slot}, post data: " . print_r($postdata, true));
}
}
while ($record->questionattemptid != $questionattemptid) {
$record = $records->next();
if (!$records->valid()) {
- throw new coding_exception("Question attempt $questionattemptid not found in the database.");
+ throw new coding_exception("Question attempt {$questionattemptid} not found in the database.");
}
$record = $records->current();
}
while ($record->qubaid != $qubaid) {
$records->next();
if (!$records->valid()) {
- throw new coding_exception("Question usage $qubaid not found in the database.");
+ throw new coding_exception("Question usage {$qubaid} not found in the database.");
}
$record = $records->current();
}
$compare = (array)$compare;
foreach ($expect as $k=>$v) {
if (!array_key_exists($k, $compare)) {
- $this->fail("Property $k does not exist");
+ $this->fail("Property {$k} does not exist");
}
if ($v != $compare[$k]) {
- $this->fail("Property $k is different");
+ $this->fail("Property {$k} is different");
}
}
$this->assertTrue(true);
if ($from_form = $export_form->get_data()) {
$thiscontext = $contexts->lowest();
- if (!is_readable("format/$from_form->format/format.php")) {
+ if (!is_readable("format/{$from_form->format}/format.php")) {
print_error('unknowformat', '', '', $from_form->format);
}
$withcategories = 'nocategories';
foreach ($fileformatnames as $shortname => $fileformatname) {
$currentgrp1 = array();
$currentgrp1[] = $mform->createElement('radio', 'format', '', $fileformatname, $shortname);
- $mform->addGroup($currentgrp1, "formathelp[$i]", '', array('<br />'), false);
+ $mform->addGroup($currentgrp1, "formathelp[{$i}]", '', array('<br />'), false);
if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) {
- $mform->addHelpButton("formathelp[$i]", 'pluginname', 'qformat_' . $shortname);
+ $mform->addHelpButton("formathelp[{$i}]", 'pluginname', 'qformat_' . $shortname);
}
$i++ ;
$importerrorquestion = get_string('importerrorquestion', 'question');
echo "<div class=\"importerror\">\n";
- echo "<strong>$importerrorquestion $questionname</strong>";
+ echo "<strong>{$importerrorquestion} {$questionname}</strong>";
if (!empty($text)) {
$text = s($text);
- echo "<blockquote>$text</blockquote>\n";
+ echo "<blockquote>{$text}</blockquote>\n";
}
- echo "<strong>$message</strong>\n";
+ echo "<strong>{$message}</strong>\n";
echo "</div>";
$this->importerrors++;
// work out what format we are using
$formatname = substr(get_class($this), strlen('qformat_'));
- $methodname = "import_from_$formatname";
+ $methodname = "import_from_{$formatname}";
//first try importing using a hint from format
if (!empty($qtypehint)) {
$count = 0;
foreach ($questions as $question) { // Process and store each question
+ $transaction = $DB->start_delegated_transaction();
// reset the php timeout
core_php_time_limit::raise();
$this->category = $newcategory;
}
}
+ $transaction->allow_commit();
continue;
}
$question->context = $this->importcontext;
$count++;
- echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
+ echo "<hr /><p><b>{$count}</b>. ".$this->format_question_text($question)."</p>";
$question->category = $this->category->id;
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
if (!empty($result->error)) {
echo $OUTPUT->notification($result->error);
+ // Can't use $transaction->rollback(); since it requires an exception,
+ // and I don't want to rewrite this code to change the error handling now.
+ $DB->force_transaction_rollback();
return false;
}
+ $transaction->allow_commit();
+
if (!empty($result->notice)) {
echo $OUTPUT->notification($result->notice);
return true;
protected function readquestion($lines) {
$formatnotimplemented = get_string('formatnotimplemented', 'question');
- echo "<p>$formatnotimplemented</p>";
+ echo "<p>{$formatnotimplemented}</p>";
return null;
}
protected function try_exporting_using_qtypes($name, $question, $extra=null) {
// work out the name of format in use
$formatname = substr(get_class($this), strlen('qformat_'));
- $methodname = "export_to_$formatname";
+ $methodname = "export_to_{$formatname}";
$qtype = question_bank::get_qtype($name, false);
if (method_exists($qtype, $methodname)) {
// continue path for following error checks
$course = $this->course;
- $continuepath = "$CFG->wwwroot/question/export.php?courseid=$course->id";
+ $continuepath = "{$CFG->wwwroot}/question/export.php?courseid={$course->id}";
// did we actually process anything
if ($count==0) {
protected function writequestion($question) {
// if not overidden, then this is an error.
$formatnotimplemented = get_string('formatnotimplemented', 'question');
- echo "<p>$formatnotimplemented</p>";
+ echo "<p>{$formatnotimplemented}</p>";
return null;
}
$dirpath = dirname($path);
$filename = basename($path);
$newfilename = $this->store_file_for_text_field($data, $this->filebase, $dirpath, $filename);
- $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+ $text = preg_replace("|{$path}|", "@@PLUGINFILE@@/" . $newfilename, $text);
$filepaths[] = $path;
}
list($answer, $wrongfeedback, $rightfeedback) =
$this->split_truefalse_comment($answertext, $question->questiontextformat);
- if ($answer['text'] == "T" OR $answer['text'] == "TRUE") {
+ if ($answer['text'] == "T" || $answer['text'] == "TRUE") {
$question->correctanswer = 1;
$question->feedbacktrue = $rightfeedback;
$question->feedbackfalse = $wrongfeedback;
global $OUTPUT;
// Start with a comment.
- $expout = "// question: $question->id name: $question->name\n";
+ $expout = "// question: {$question->id} name: {$question->name}\n";
// Output depends on question type.
switch($question->qtype) {
}
// Replace it!
- $formula = "$splits[0]pow($base,$exp)$splits[1]";
+ $formula = "{$splits[0]}pow({$base},{$exp}){$splits[1]}";
}
// Nothing more is known to need to be converted.
$dirpath = dirname($path);
$filename = basename($path);
$newfilename = $this->store_file_for_text_field($data, $this->tempdir, $dirpath, $filename);
- $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+ $text = preg_replace("|{$path}|", "@@PLUGINFILE@@/" . $newfilename, $text);
$filepaths[] = $path;
}
case 'shortanswer':
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
- $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+ $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
.' '.get_string('fractionsnomax', 'question', $maxfraction);
$questionok = false;
}
if ($question->single) {
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
- $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+ $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
.' '.get_string('fractionsnomax', 'question', $maxfraction);
$questionok = false;
}
$totalfraction = round($totalfraction, 2);
if ($totalfraction != 1) {
$totalfraction = $totalfraction * 100;
- $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+ $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
.' '.get_string('fractionsaddwrong', 'question', $totalfraction);
$questionok = false;
}
case 'calculated':
foreach ($question->answers as $answer) {
if ($formulaerror = qtype_calculated_find_formula_errors($answer)) {
- $warnings[] = "'$question->name': ". $formulaerror;
+ $warnings[] = "'{$question->name}': ". $formulaerror;
$questionok = false;
}
}
// Calculated Question.
$question = $this->defaultquestion();
$question->qtype = 'calculated';
- $question->answers = array(); // No problem as they go as :FORMULA: from webct.
+ $question->answer = array(); // No problem as they go as :FORMULA: from webct.
$question->units = array();
$question->dataset = array();
-
- // To make us pass the end-of-question sanity checks.
- $question->answer = array('dummy');
$question->fraction = array('1.0');
$question->feedback = array();
continue;
}
if (isset($question->qtype ) && 'calculated' == $question->qtype && preg_match(
- "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?($webctnumberregex)~", $line, $webctoptions)) {
+ "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?({$webctnumberregex})~", $line, $webctoptions)) {
$datasetname = preg_replace('/^::/', '', $webctoptions[1]);
$datasetvalue = qformat_webct_convert_formula($webctoptions[4]);
switch ($webctoptions[2])&nb