Merge branch 'MOODLE_26_STABLE' into install_26_STABLE

This commit is contained in:
AMOS bot
2014-06-13 00:42:09 +00:00
50 changed files with 772 additions and 162 deletions
+1 -1
View File
@@ -1369,7 +1369,7 @@ class core_admin_renderer extends plugin_renderer_base {
get_string('report'),
get_string('status'),
);
$servertable->colclasses = array('centeralign name', 'centeralign status', 'leftalign report', 'centeralign info');
$servertable->colclasses = array('centeralign name', 'centeralign info', 'leftalign report', 'centeralign status');
$servertable->attributes['class'] = 'admintable environmenttable generaltable';
$servertable->id = 'serverstatus';
+6 -8
View File
@@ -28,8 +28,7 @@
defined('MOODLE_INTERNAL') || die();
/**
* create the temp dir where backup/restore will happen,
* delete old directories and create temp ids table
* Create the temp dir where backup/restore will happen and create temp ids table.
*/
class create_and_clean_temp_stuff extends backup_execution_step {
@@ -38,7 +37,6 @@ class create_and_clean_temp_stuff extends backup_execution_step {
$progress->start_progress('Deleting backup directories');
backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
backup_helper::clear_backup_dir($this->get_backupid(), $progress); // Empty temp dir, just in case
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress); // Delete > 4 hours temp dirs
backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
$progress->end_progress();
@@ -46,11 +44,11 @@ class create_and_clean_temp_stuff extends backup_execution_step {
}
/**
* delete the temp dir used by backup/restore (conditionally),
* delete old directories and drop tem ids table. Note we delete
* Delete the temp dir used by backup/restore (conditionally),
* delete old directories and drop temp ids table. Note we delete
* the directory but not the corresponding log file that will be
* there for, at least, 4 hours - only delete_old_backup_dirs()
* deletes log files (for easier access to them)
* there for, at least, 1 week - only delete_old_backup_dirs() or cron
* deletes log files (for easier access to them).
*/
class drop_and_clean_temp_stuff extends backup_execution_step {
@@ -60,7 +58,7 @@ class drop_and_clean_temp_stuff extends backup_execution_step {
global $CFG;
backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60)); // Delete > 4 hours temp dirs
backup_helper::delete_old_backup_dirs(strtotime('-1 week')); // Delete > 1 week old temp dirs.
// Delete temp dir conditionally:
// 1) If $CFG->keeptempdirectoriesonbackup is not enabled
// 2) If backup temp dir deletion has been marked to be avoided
+83 -47
View File
@@ -67,7 +67,7 @@ class restore_drop_and_clean_temp_stuff extends restore_execution_step {
restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table
$progress = $this->task->get_progress();
$progress->start_progress('Deleting backup dir');
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress); // Delete > 4 hours temp dirs
backup_helper::delete_old_backup_dirs(strtotime('-1 week'), $progress); // Delete > 1 week old temp dirs.
if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally
backup_helper::delete_backup_dir($this->task->get_tempdir(), $progress); // Empty restore dir
}
@@ -3558,13 +3558,16 @@ class restore_move_module_questions_categories extends restore_execution_step {
* Execution step that will create all the question/answers/qtype-specific files for the restored
* questions. It must be executed after {@link restore_move_module_questions_categories}
* because only then each question is in its final category and only then the
* context can be determined
*
* TODO: Improve this. Instead of looping over each question, it can be reduced to
* be done by contexts (this will save a huge ammount of queries)
* contexts can be determined.
*/
class restore_create_question_files extends restore_execution_step {
/** @var array Question-type specific component items cache. */
private $qtypecomponentscache = array();
/**
* Preform the restore_create_question_files step.
*/
protected function define_execution() {
global $DB;
@@ -3572,60 +3575,93 @@ class restore_create_question_files extends restore_execution_step {
$progress = $this->task->get_progress();
$progress->start_progress($this->get_name(), core_backup_progress::INDETERMINATE);
// Let's process only created questions
$questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
// Parentitemids of question_createds in backup_ids_temp are the category it is in.
// MUST use a recordset, as there is no unique key in the first (or any) column.
$catqtypes = $DB->get_recordset_sql("SELECT DISTINCT bi.parentitemid AS categoryid, q.qtype as qtype
FROM {backup_ids_temp} bi
JOIN {question} q ON q.id = bi.newitemid
WHERE bi.backupid = ?
AND bi.itemname = 'question_created'", array($this->get_restoreid()));
foreach ($questionsrs as $question) {
// Report progress for each question.
$progress->progress();
AND bi.itemname = 'question_created'
ORDER BY categoryid ASC", array($this->get_restoreid()));
// Get question_category mapping, it contains the target context for the question
if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'question_category', $question->parentitemid)) {
// Something went really wrong, cannot find the question_category for the question
debugging('Error fetching target context for question', DEBUG_DEVELOPER);
continue;
}
// Calculate source and target contexts
$oldctxid = $qcatmapping->info->contextid;
$newctxid = $qcatmapping->parentitemid;
$currentcatid = -1;
foreach ($catqtypes as $categoryid => $row) {
$qtype = $row->qtype;
// Add common question files (question and question_answer ones)
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
$oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
// Check if we are in a new category.
if ($currentcatid !== $categoryid) {
// Report progress for each category.
$progress->progress();
// Add qtype dependent files
$components = backup_qtype_plugin::get_components_and_fileareas($question->qtype);
foreach ($components as $component => $fileareas) {
foreach ($fileareas as $filearea => $mapping) {
// Use itemid only if mapping is question_created
$itemid = ($mapping == 'question_created') ? $question->itemid : null;
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
$oldctxid, $this->task->get_userid(), $mapping, $itemid, $newctxid, true, $progress);
if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(),
'question_category', $categoryid)) {
// Something went really wrong, cannot find the question_category for the question_created records.
debugging('Error fetching target context for question', DEBUG_DEVELOPER);
continue;
}
// Calculate source and target contexts.
$oldctxid = $qcatmapping->info->contextid;
$newctxid = $qcatmapping->parentitemid;
$this->send_common_files($oldctxid, $newctxid, $progress);
$currentcatid = $categoryid;
}
$this->send_qtype_files($qtype, $oldctxid, $newctxid, $progress);
}
$questionsrs->close();
$catqtypes->close();
$progress->end_progress();
}
}
/**
* Send the common question files to a new context.
*
* @param int $oldctxid Old context id.
* @param int $newctxid New context id.
* @param \core\progress $progress Progress object to use.
*/
private function send_common_files($oldctxid, $newctxid, $progress) {
// Add common question files (question and question_answer ones).
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
$oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
}
/**
* Send the question type specific files to a new context.
*
* @param text $qtype The qtype name to send.
* @param int $oldctxid Old context id.
* @param int $newctxid New context id.
* @param \core\progress $progress Progress object to use.
*/
private function send_qtype_files($qtype, $oldctxid, $newctxid, $progress) {
if (!isset($this->qtypecomponentscache[$qtype])) {
$this->qtypecomponentscache[$qtype] = backup_qtype_plugin::get_components_and_fileareas($qtype);
}
$components = $this->qtypecomponentscache[$qtype];
foreach ($components as $component => $fileareas) {
foreach ($fileareas as $filearea => $mapping) {
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
$oldctxid, $this->task->get_userid(), $mapping, null, $newctxid, true, $progress);
}
}
}
}
/**
* Try to restore aliases and references to external files.
@@ -103,7 +103,18 @@ abstract class backup_structure_dbops extends backup_dbops {
}
}
public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid) {
/**
* Adds backup id database record for all files in the given file area.
*
* @param string $backupid Backup ID
* @param int $contextid Context id
* @param string $component Component
* @param string $filearea File area
* @param int $itemid Item id
* @param core_backup_progress $progress
*/
public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid,
core_backup_progress $progress = null) {
global $DB;
$sql = 'SELECT id
FROM {files}
@@ -120,10 +131,19 @@ abstract class backup_structure_dbops extends backup_dbops {
$sql .= ' AND itemid = ?';
$params[] = $itemid;
}
if ($progress) {
$progress->start_progress('');
}
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $record) {
if ($progress) {
$progress->progress();
}
self::insert_backup_ids_record($backupid, 'file', $record->id);
}
if ($progress) {
$progress->end_progress();
}
$rs->close();
}
@@ -86,7 +86,7 @@ class backup_structure_processor extends base_processor {
foreach ($area as $filearea => $info) {
$contextid = !is_null($info->contextid) ? $info->contextid : $this->get_var(backup::VAR_CONTEXTID);
$itemid = !is_null($info->element) ? $info->element->get_value() : null;
backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid);
backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid, $this->progress);
}
}
}
@@ -0,0 +1,190 @@
@block @block_navigation
Feature: Expand the courses nodes within the navigation block
In order to navigate the site
As an anonymous user, a guest, a student, and an admin
I need to expand the courses node in the navigation block and check the display of courses and categories.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@local.host |
| student1 | Student | 1 | student1@local.host |
And the following "categories" exist:
| name | category | idnumber | visible |
| cat1 | 0 | cat1 | 1 |
| cat2 | 0 | cat2 | 1 |
| cat21 | cat2 | cat21 | 1 |
| cat211 | cat21 | cat211 | 1 |
| cat3 | 0 | cat3 | 0 |
And the following "courses" exist:
| fullname | shortname | category | visible |
| Course 1 | c1 | cat1 | 1 |
| Course 2 | c2 | cat2 | 1 |
| Course 3 | c3 | cat21 | 1 |
| Course 4 | c4 | cat211 | 1 |
| Course 5 | c5 | cat211 | 0 |
| Course 6 | c6 | cat211 | 0 |
| Course 7 | c7 | cat3 | 1 |
| Course 8 | c8 | cat3 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | c1 | teacher |
| teacher1 | c3 | teacher |
| teacher1 | c5 | teacher |
| student1 | c1 | student |
| student1 | c2 | student |
| student1 | c4 | student |
And I log in as "admin"
And I follow "Course 2"
And I turn editing mode on
And I click on "Edit settings" "link" in the "Administration" "block"
And I set the following fields to these values:
| Allow guest access | Yes |
And I press "Save changes"
And I set the following administration settings values:
| Show all courses | 1 |
And I log out
@javascript
Scenario: As an anonymous user I expand the courses node to see courses.
When I should see "You are not logged in." in the ".logininfo" "css_element"
And I should see "Home" in the "Navigation" "block"
And I should see "Courses" in the "Navigation" "block"
And I expand "Courses" node
And I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should not see "cat3" in the "Navigation" "block"
And I expand "cat1" node
And I expand "cat2" node
And I should see "cat21" in the "Navigation" "block"
And I expand "cat21" node
And I should see "cat211" in the "Navigation" "block"
And I expand "cat211" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
And I should see "c3" in the "Navigation" "block"
And I should see "c4" in the "Navigation" "block"
And I should not see "c5" in the "Navigation" "block"
And I should not see "c6" in the "Navigation" "block"
And navigation node "c1" should not be expandable
And navigation node "c2" should not be expandable
And navigation node "c3" should not be expandable
And navigation node "c4" should not be expandable
@javascript
Scenario: As the admin user I expand the courses and category nodes to see courses.
When I log in as "admin"
And I should see "Home" in the "Navigation" "block"
And I should see "Courses" in the "Navigation" "block"
And I expand "Courses" node
And I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should see "cat3" in the "Navigation" "block"
And I expand "cat1" node
And I expand "cat2" node
And I expand "cat3" node
And I should see "cat21" in the "Navigation" "block"
And I expand "cat21" node
And I should see "cat211" in the "Navigation" "block"
And I expand "cat211" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
And I should see "c3" in the "Navigation" "block"
And I should see "c4" in the "Navigation" "block"
And I should see "c5" in the "Navigation" "block"
And I should see "c6" in the "Navigation" "block"
And I should see "c7" in the "Navigation" "block"
And I should see "c8" in the "Navigation" "block"
And navigation node "c1" should be expandable
And navigation node "c2" should be expandable
And navigation node "c3" should be expandable
And navigation node "c4" should be expandable
And navigation node "c5" should be expandable
And navigation node "c6" should be expandable
And navigation node "c7" should be expandable
And navigation node "c8" should be expandable
@javascript
Scenario: As teacher1 I expand the courses and category nodes to see courses.
When I log in as "teacher1"
And I should see "Home" in the "Navigation" "block"
And I should see "Courses" in the "Navigation" "block"
And I expand "Courses" node
And I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should not see "cat3" in the "Navigation" "block"
And I expand "cat1" node
And I expand "cat2" node
And I should see "cat21" in the "Navigation" "block"
And I expand "cat21" node
And I should see "cat211" in the "Navigation" "block"
And I expand "cat211" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
And I should see "c3" in the "Navigation" "block"
And I should see "c4" in the "Navigation" "block"
And I should see "c5" in the "Navigation" "block"
And I should not see "c6" in the "Navigation" "block"
And I should not see "c7" in the "Navigation" "block"
And I should not see "c8" in the "Navigation" "block"
And navigation node "c1" should be expandable
And navigation node "c2" should be expandable
And navigation node "c3" should be expandable
And navigation node "c4" should not be expandable
And navigation node "c5" should be expandable
@javascript
Scenario: As student1 I expand the courses and category nodes to see courses.
When I log in as "student1"
And I should see "Home" in the "Navigation" "block"
And I should see "Courses" in the "Navigation" "block"
And I expand "Courses" node
And I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should not see "cat3" in the "Navigation" "block"
And I expand "cat1" node
And I expand "cat2" node
And I should see "cat21" in the "Navigation" "block"
And I expand "cat21" node
And I should see "cat211" in the "Navigation" "block"
And I expand "cat211" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
And I should see "c3" in the "Navigation" "block"
And I should see "c4" in the "Navigation" "block"
And I should not see "c5" in the "Navigation" "block"
And I should not see "c6" in the "Navigation" "block"
And I should not see "c7" in the "Navigation" "block"
And I should not see "c8" in the "Navigation" "block"
And navigation node "c1" should be expandable
And navigation node "c2" should be expandable
And navigation node "c3" should not be expandable
And navigation node "c4" should be expandable
@javascript
Scenario: As guest I expand the courses and category nodes to see courses.
When I log in as "guest"
And I should see "Home" in the "Navigation" "block"
And I should see "Courses" in the "Navigation" "block"
And I expand "Courses" node
And I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should not see "cat3" in the "Navigation" "block"
And I expand "cat1" node
And I expand "cat2" node
And I should see "cat21" in the "Navigation" "block"
And I expand "cat21" node
And I should see "cat211" in the "Navigation" "block"
And I expand "cat211" node
Then I should see "c1" in the "Navigation" "block"
And I should see "c2" in the "Navigation" "block"
And I should see "c3" in the "Navigation" "block"
And I should see "c4" in the "Navigation" "block"
And I should not see "c5" in the "Navigation" "block"
And I should not see "c6" in the "Navigation" "block"
And I should not see "c7" in the "Navigation" "block"
And I should not see "c8" in the "Navigation" "block"
And navigation node "c1" should not be expandable
And navigation node "c2" should be expandable
And navigation node "c3" should not be expandable
And navigation node "c4" should not be expandable
@@ -87,8 +87,6 @@ Feature: View my courses in navigation block
When I expand "cat3" node
And I expand "cat31" node
And I expand "cat1" node
And I should see "c1" in the "Navigation" "block"
And I expand "c1" node
Then I should see "cat1" in the "Navigation" "block"
And I should see "cat2" in the "Navigation" "block"
And I should see "cat3" in the "Navigation" "block"
@@ -64,14 +64,14 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
And I press "Create group"
And I fill the moodle form with:
| Group name | Group 1 |
| Enrolment key | testgroupenrolkey |
| Enrolment key | Test-groupenrolkey1 |
And I press "Save changes"
And I log out
And I log in as "student1"
And I follow "Course 1"
And I fill the moodle form with:
| Enrolment key | testgroupenrolkey |
| Enrolment key | Test-groupenrolkey1 |
And I press "Enrol me"
Then I should see "Topic 1"
And I should not see "Enrolment options"
And I should not see "Enrol me in this course"
And I should not see "Enrol me in this course"
+20 -3
View File
@@ -111,11 +111,19 @@ class group_form extends moodleform {
}
}
if (!empty($CFG->groupenrolmentkeypolicy) and $data['enrolmentkey'] != '' and $group->enrolmentkey !== $data['enrolmentkey']) {
// enforce password policy only if changing password
if ($data['enrolmentkey'] != '') {
$errmsg = '';
if (!check_password_policy($data['enrolmentkey'], $errmsg)) {
if (!empty($CFG->groupenrolmentkeypolicy) && $group->enrolmentkey !== $data['enrolmentkey']
&& !check_password_policy($data['enrolmentkey'], $errmsg)) {
// Enforce password policy when the password is changed.
$errors['enrolmentkey'] = $errmsg;
} else {
// Prevent twice the same enrolment key in course groups.
$sql = "SELECT id FROM {groups} WHERE id <> :groupid AND courseid = :courseid AND enrolmentkey = :key";
$params = array('groupid' => $data['id'], 'courseid' => $COURSE->id, 'key' => $data['enrolmentkey']);
if ($DB->record_exists_sql($sql, $params)) {
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
}
}
}
@@ -123,6 +131,15 @@ class group_form extends moodleform {
$errors['name'] = get_string('groupnameexists', 'group', $name);
} else if (!empty($idnumber) && groups_get_group_by_idnumber($COURSE->id, $idnumber)) {
$errors['idnumber']= get_string('idnumbertaken');
} else if ($data['enrolmentkey'] != '') {
$errmsg = '';
if (!empty($CFG->groupenrolmentkeypolicy) && !check_password_policy($data['enrolmentkey'], $errmsg)) {
// Enforce password policy.
$errors['enrolmentkey'] = $errmsg;
} else if ($DB->record_exists('groups', array('courseid' => $COURSE->id, 'enrolmentkey' => $data['enrolmentkey']))) {
// Prevent the same enrolment key from being used multiple times in course groups.
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
}
}
return $errors;
+39
View File
@@ -89,3 +89,42 @@ Feature: Organize students into groups
| Grouping name | Not the greatest grouping, but it's ok! |
And I press "Save changes"
And I should see "Not the greatest grouping, but it's ok!"
Scenario: Create groups with enrolment key
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 1 |
And I log in as "admin"
And I follow "Course 1"
And I expand "Users" node
And I follow "Groups"
When I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | badpasswd |
And I press "Save changes"
And I should see "Passwords must have at least 1 digit(s)"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group B |
| Enrolment key | Abcdef-1 |
And I press "Save changes"
Then I should see "This enrolment key is already used for another group."
And I set the following fields to these values:
| Enrolment key | Abcdef-2 |
And I press "Save changes"
And the "groups" select box should contain "Group B (0)"
And I am on homepage
And I follow "Course 2"
And I expand "Users" node
And I follow "Groups"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | Abcdef-1 |
And I should not see "This enrolment key is already used for another group."
+51
View File
@@ -100,3 +100,54 @@ Feature: Automatic updating of groups and groupings
And the "idnumber" "field" should be readonly
And the "idnumber" field should match "An ID" value
@javascript
Scenario: Update groups with enrolment key
Given the following "courses" exist:
| fullname | shortname |
| Course 2 | C2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C2 | editingteacher |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I expand "Users" node
And I follow "Groups"
And I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | badpasswd |
When I press "Save changes"
Then I should see "Passwords must have at least 1 digit(s)"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I set the field "groups" to "Group (without ID)"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I should see "This enrolment key is already used for another group."
And I set the following fields to these values:
| Enrolment key | Abcdef-2 |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I am on homepage
And I follow "Course 2"
And I expand "Users" node
And I follow "Groups"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I set the field "groups" to "Group A"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
+1
View File
@@ -51,6 +51,7 @@ $string['cachedef_groupdata'] = 'Course group information';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['cachedef_langmenu'] = 'List of available languages';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_navigation_expandcourse'] = 'Navigation expandable courses';
$string['cachedef_observers'] = 'Event observers';
$string['cachedef_plugin_manager'] = 'Plugin info manager';
$string['cachedef_questiondata'] = 'Question definitions';
+1 -1
View File
@@ -489,7 +489,7 @@ $string['statsnodata'] = 'There is no available data for that combination of cou
$string['storedfilecannotcreatefile'] = 'Can not create local file pool file, please verify permissions in dataroot and available disk space.';
$string['storedfilecannotcreatefiledirs'] = 'Can not create local file pool directories, please verify permissions in dataroot.';
$string['storedfilecannotread'] = 'Can not read file, either file does not exist or there are permission problems';
$string['storedfilenotcreated'] = 'Can not create file "{$a->contextid}/{$a->component}/{$a->filearea}/{$a->itemid}/{$a->filepath}/{$a->filename}"';
$string['storedfilenotcreated'] = 'Can not create file "{$a->contextid}/{$a->component}/{$a->filearea}/{$a->itemid}{$a->filepath}{$a->filename}"';
$string['storedfileproblem'] = 'Unknown exception related to local files ({$a})';
$string['tagdisabled'] = 'Tags are disabled!';
$string['tagnotfound'] = 'The specified tag was not found in the database';
+1
View File
@@ -61,6 +61,7 @@ $string['enrolmentkey'] = 'Enrolment key';
$string['enrolmentkey_help'] = 'An enrolment key enables access to the course to be restricted to only those who know the key. If a group enrolment key is specified, then not only will entering that key let the user into the course, but it will also automatically make them a member of this group.
Note: Group enrolment keys must be enabled in the self enrolment settings and an enrolment key for the course must also be specified.';
$string['enrolmentkeyalreadyinuse'] = 'This enrolment key is already used for another group.';
$string['erroraddremoveuser'] = 'Error adding/removing user {$a} to group';
$string['erroreditgroup'] = 'Error creating/updating group {$a}';
$string['erroreditgrouping'] = 'Error creating/updating grouping {$a}';
+6 -1
View File
@@ -120,7 +120,12 @@ class core_useragent {
protected function __construct($forceuseragent = null) {
global $CFG;
if (!empty($CFG->devicedetectregex)) {
$this->devicetypecustoms = json_decode($CFG->devicedetectregex);
$this->devicetypecustoms = json_decode($CFG->devicedetectregex, true);
}
if ($this->devicetypecustoms === null) {
// This shouldn't happen unless you're hardcoding the config value.
debugging('Config devicedetectregex is not valid JSON object');
$this->devicetypecustoms = array();
}
if ($forceuseragent !== null) {
$this->useragent = $forceuseragent;
+10
View File
@@ -212,4 +212,14 @@ $definitions = array(
'staticaccelerationsize' => 2, // Should not be required for more than one user at a time.
'ttl' => 3600,
),
// A simple cache that stores whether a user can expand a course in the navigation.
// The key is the course ID and the value will either be 1 or 0 (cast to bool).
// The cache isn't always up to date, it should only ever be used to save a costly call to
// can_access_course on the first page request a user makes.
'navigation_expandcourse' => array(
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simpledata' => true
)
);
+7
View File
@@ -2984,5 +2984,12 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2013111803.01);
}
if ($oldversion < 2013111803.05) {
// Fixing possible wrong MIME type for Publisher files.
$filetypes = array('%.pub'=>'application/x-mspublisher');
upgrade_mimetypes($filetypes);
upgrade_main_savepoint(true, 2013111803.05);
}
return true;
}
+21 -1
View File
@@ -347,4 +347,24 @@ function upgrade_course_modules_sequences() {
unset($sections);
// Note that we don't need to reset course cache here because it is reset automatically after upgrade.
}
}
/**
* Updates the mime-types for files that exist in the database, based on their
* file extension.
*
* @param array $filetypes Array with file extension as the key, and mimetype as the value
*/
function upgrade_mimetypes($filetypes) {
global $DB;
$select = $DB->sql_like('filename', '?', false);
foreach ($filetypes as $extension=>$mimetype) {
$DB->set_field_select(
'files',
'mimetype',
$mimetype,
$select,
array($extension)
);
}
}
+2 -2
View File
@@ -1507,7 +1507,6 @@ function &get_mimetypes_array() {
'pic' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
'pict' => array ('type'=>'image/pict', 'icon'=>'image', 'groups'=>array('image'), 'string'=>'image'),
'png' => array ('type'=>'image/png', 'icon'=>'png', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
'pps' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
'ppt' => array ('type'=>'application/vnd.ms-powerpoint', 'icon'=>'powerpoint', 'groups'=>array('presentation')),
'pptx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'icon'=>'powerpoint'),
@@ -1517,8 +1516,9 @@ function &get_mimetypes_array() {
'ppam' => array ('type'=>'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'icon'=>'powerpoint'),
'ppsx' => array ('type'=>'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'icon'=>'powerpoint'),
'ppsm' => array ('type'=>'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'icon'=>'powerpoint'),
'ps' => array ('type'=>'application/postscript', 'icon'=>'pdf'),
'pub' => array ('type'=>'application/x-mspublisher', 'icon'=>'publisher', 'groups'=>array('presentation')),
'qt' => array ('type'=>'video/quicktime', 'icon'=>'quicktime', 'groups'=>array('video','web_video'), 'string'=>'video'),
'ra' => array ('type'=>'audio/x-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio','web_audio'), 'string'=>'audio'),
'ram' => array ('type'=>'audio/x-pn-realaudio-plugin', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
+8 -1
View File
@@ -205,7 +205,14 @@ class stored_file {
*/
public function rename($filepath, $filename) {
if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
throw new file_exception('storedfilenotcreated', '', 'file exists, cannot rename');
$a = new stdClass();
$a->contextid = $this->get_contextid();
$a->component = $this->get_component();
$a->filearea = $this->get_filearea();
$a->itemid = $this->get_itemid();
$a->filepath = $filepath;
$a->filename = $filename;
throw new file_exception('storedfilenotcreated', $a, 'file exists, cannot rename');
}
$filerecord = new stdClass;
$filerecord->filepath = $filepath;
+4 -3
View File
@@ -300,7 +300,8 @@ class core_files_file_storage_testcase extends advanced_testcase {
$this->assertEquals($contenthash, $file->get_contenthash());
// Try break it.
$this->setExpectedException('file_exception');
$this->setExpectedException('file_exception',
'Can not create file "1/core/unittest/0/test/newtest.txt" (file exists, cannot rename)');
// This shall throw exception.
$originalfile->rename($newpath, $newname);
}
@@ -1146,7 +1147,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
$this->assertInstanceOf('stored_file', $file1);
// Creating a file validating unique constraint.
$this->setExpectedException('stored_file_creation_exception');
$this->setExpectedException('stored_file_creation_exception', 'Can not create file "1/core/phpunit/0/testfile.txt"');
$fs->create_file_from_storedfile($filerecord, $file1->get_id());
}
@@ -1426,7 +1427,7 @@ class core_files_file_storage_testcase extends advanced_testcase {
$this->assertInstanceOf('stored_file', $file1);
// Creating a file validating unique constraint.
$this->setExpectedException('stored_file_creation_exception');
$this->setExpectedException('stored_file_creation_exception', 'Can not create file "1/core/phpunit/0/testfile.txt"');
$file2 = $fs->create_file_from_pathname($filerecord, $path);
}
+70 -1
View File
@@ -1001,6 +1001,8 @@ class global_navigation extends navigation_node {
protected $expansionlimit = 0;
/** @var int userid to allow parent to see child's profile page navigation */
protected $useridtouseforparentchecks = 0;
/** @var cache_session A cache that stores information on expanded courses */
protected $cacheexpandcourse = null;
/** Used when loading categories to load all top level categories [parent = 0] **/
const LOAD_ROOT_CATEGORIES = 0;
@@ -1166,6 +1168,14 @@ class global_navigation extends navigation_node {
// Not enrolled, can't view, and hasn't switched roles
if (!can_access_course($course)) {
if ($coursenode->isexpandable === true) {
// Obviously the situation has changed, update the cache and adjust the node.
// This occurs if the user access to a course has been revoked (one way or another) after
// initially logging in for this session.
$this->get_expand_course_cache()->set($course->id, 1);
$coursenode->isexpandable = true;
$coursenode->nodetype = self::NODETYPE_BRANCH;
}
// Very ugly hack - do not force "parents" to enrol into course their child is enrolled in,
// this hack has been propagated from user/view.php to display the navigation node. (MDL-25805)
if (!$this->current_user_is_parent_role()) {
@@ -1175,6 +1185,15 @@ class global_navigation extends navigation_node {
}
}
if ($coursenode->isexpandable === false) {
// Obviously the situation has changed, update the cache and adjust the node.
// This occurs if the user has been granted access to a course (one way or another) after initially
// logging in for this session.
$this->get_expand_course_cache()->set($course->id, 1);
$coursenode->isexpandable = true;
$coursenode->nodetype = self::NODETYPE_BRANCH;
}
// Add the essentials such as reports etc...
$this->add_course_essentials($coursenode, $course);
// Extend course navigation with it's sections/activities
@@ -2373,6 +2392,8 @@ class global_navigation extends navigation_node {
// This is the name that will be shown for the course.
$coursename = empty($CFG->navshowfullcoursenames) ? $shortname : $fullname;
// Can the user expand the course to see its content.
$canexpandcourse = true;
if ($issite) {
$parent = $this;
$url = null;
@@ -2392,6 +2413,8 @@ class global_navigation extends navigation_node {
} else {
$parent = $this->rootnodes['courses'];
$url = new moodle_url('/course/view.php', array('id'=>$course->id));
// They can only expand the course if they can access it.
$canexpandcourse = $this->can_expand_course($course);
if (!empty($course->category) && $this->show_categories($coursetype == self::COURSE_MY)) {
if (!$this->is_category_fully_loaded($course->category)) {
// We need to load the category structure for this course
@@ -2408,11 +2431,18 @@ class global_navigation extends navigation_node {
}
$coursenode = $parent->add($coursename, $url, self::TYPE_COURSE, $shortname, $course->id);
$coursenode->nodetype = self::NODETYPE_BRANCH;
$coursenode->hidden = (!$course->visible);
// We need to decode &amp;'s here as they will have been added by format_string above and attributes will be encoded again
// later.
$coursenode->title(str_replace('&amp;', '&', $fullname));
if ($canexpandcourse) {
// This course can be expanded by the user, make it a branch to make the system aware that its expandable by ajax.
$coursenode->nodetype = self::NODETYPE_BRANCH;
$coursenode->isexpandable = true;
} else {
$coursenode->nodetype = self::NODETYPE_LEAF;
$coursenode->isexpandable = false;
}
if (!$forcegeneric) {
$this->addedcourses[$course->id] = $coursenode;
}
@@ -2420,6 +2450,45 @@ class global_navigation extends navigation_node {
return $coursenode;
}
/**
* Returns a cache instance to use for the expand course cache.
* @return cache_session
*/
protected function get_expand_course_cache() {
if ($this->cacheexpandcourse === null) {
$this->cacheexpandcourse = cache::make('core', 'navigation_expandcourse');
}
return $this->cacheexpandcourse;
}
/**
* Checks if a user can expand a course in the navigation.
*
* We use a cache here because in order to be accurate we need to call can_access_course which is a costly function.
* Because this functionality is basic + non-essential and because we lack good event triggering this cache
* permits stale data.
* In the situation the user is granted access to a course after we've initialised this session cache the cache
* will be stale.
* It is brought up to date in only one of two ways.
* 1. The user logs out and in again.
* 2. The user browses to the course they've just being given access to.
*
* Really all this controls is whether the node is shown as expandable or not. It is uber un-important.
*
* @param stdClass $course
* @return bool
*/
protected function can_expand_course($course) {
$cache = $this->get_expand_course_cache();
$canexpand = $cache->get($course->id);
if ($canexpand === false) {
$canexpand = isloggedin() && can_access_course($course);
$canexpand = (int)$canexpand;
$cache->set($course->id, $canexpand);
}
return ($canexpand === 1);
}
/**
* Returns true if the category has already been loaded as have any child categories
*
-3
View File
@@ -321,9 +321,6 @@ function question_delete_question($questionid) {
return;
}
// Check permissions.
question_require_capability_on($question, 'edit');
$dm = new question_engine_data_mapper();
$dm->delete_previews($questionid);
+27
View File
@@ -126,4 +126,31 @@ class core_theme_config_testcase extends advanced_testcase {
}
}
}
/**
* This function will test custom device detection regular expression setting.
*/
public function test_devicedetectregex() {
global $CFG;
$this->resetAfterTest();
// Check config currently empty.
$this->assertEmpty(json_decode($CFG->devicedetectregex));
$this->assertTrue(core_useragent::set_user_device_type('tablet'));
$exceptionoccured = false;
try {
core_useragent::set_user_device_type('featurephone');
} catch (moodle_exception $e) {
$exceptionoccured = true;
}
$this->assertTrue($exceptionoccured);
// Set config and recheck.
$config = array('featurephone' => '(Symbian|MIDP-1.0|Maemo|Windows CE)');
$CFG->devicedetectregex = json_encode($config);
core_useragent::instance(true); // Clears singleton cache.
$this->assertTrue(core_useragent::set_user_device_type('tablet'));
$this->assertTrue(core_useragent::set_user_device_type('featurephone'));
}
}
+3 -1
View File
@@ -348,7 +348,9 @@ class data_portfolio_caller extends portfolio_module_caller_base {
$includedfiles = array();
foreach ($fields as $singlefield) {
if (is_callable(array($singlefield, 'get_file'))) {
$includedfiles[] = $singlefield->get_file($record->id);
if ($file = $singlefield->get_file($record->id)) {
$includedfiles[] = $file;
}
}
}
if (count($includedfiles) == 1 && count($fields) == 1) {
+17 -9
View File
@@ -29,7 +29,7 @@ require_once($CFG->dirroot.'/mod/lti/lib.php');
require_once($CFG->dirroot.'/mod/lti/locallib.php');
$courseid = required_param('course', PARAM_INT);
$instanceid = required_param('instanceid', PARAM_INT);
$instanceid = optional_param('instanceid', 0, PARAM_INT);
$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
$unsigned = optional_param('unsigned', '0', PARAM_INT);
@@ -37,9 +37,14 @@ $unsigned = optional_param('unsigned', '0', PARAM_INT);
$launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$lti = $DB->get_record('lti', array('id' => $instanceid), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('lti', $lti->id, $lti->course, false, MUST_EXIST);
$context = context_module::instance($cm->id);
$lti = null;
$context = null;
if (!empty($instanceid)) {
$lti = $DB->get_record('lti', array('id' => $instanceid), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('lti', $lti->id, $lti->course, false, MUST_EXIST);
$context = context_module::instance($cm->id);
}
require_login($course);
@@ -59,7 +64,9 @@ if (!empty($errormsg)) {
}
echo $OUTPUT->header();
echo $OUTPUT->heading(format_string($lti->name, true, array('context' => $context)));
if (!empty($lti) and !empty($context)) {
echo $OUTPUT->heading(format_string($lti->name, true, array('context' => $context)));
}
echo get_string('lti_launch_error', 'lti');
@@ -74,12 +81,13 @@ if (!empty($errormsg)) {
$coursetooleditor = new moodle_url('/mod/lti/instructor_edit_tool_type.php', array('course' => $courseid, 'action' => 'add'));
$links->course_tool_editor = $coursetooleditor->out(false);
$adminrequesturl = new moodle_url('/mod/lti/request_tool.php', array('instanceid' => $instanceid));
$links->admin_request_url = $adminrequesturl->out(false);
echo get_string('lti_launch_error_unsigned_help', 'lti', $links);
echo get_string('lti_launch_error_tool_request', 'lti', $links);
if (!empty($lti)) {
$adminrequesturl = new moodle_url('/mod/lti/request_tool.php', array('instanceid' => $lti->id));
$links->admin_request_url = $adminrequesturl->out(false);
echo get_string('lti_launch_error_tool_request', 'lti', $links);
}
}
echo $OUTPUT->footer();
+8 -2
View File
@@ -76,6 +76,12 @@ function quiz_remove_question($quiz, $questionid) {
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
$DB->delete_records('quiz_question_instances',
array('quiz' => $quiz->instance, 'question' => $questionid));
$qtype = $DB->get_field('question', 'qtype', array('id' => $questionid));
if ($qtype === 'random') {
// This function automatically checks if the question is in use, and won't delete if it is.
question_delete_question($questionid);
}
}
/**
@@ -197,7 +203,7 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
SELECT *
FROM {quiz_question_instances}
WHERE question = q.id)
ORDER BY id", array($category->id, $includesubcategories))) {
ORDER BY id", array($category->id, ($includesubcategories ? '1' : '0')))) {
// Take as many of these as needed.
while (($existingquestion = array_shift($existingquestions)) && $number > 0) {
quiz_add_quiz_question($existingquestion->id, $quiz, $addonpage);
@@ -212,7 +218,7 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
// More random questions are needed, create them.
for ($i = 0; $i < $number; $i += 1) {
$form = new stdClass();
$form->questiontext = array('text' => $includesubcategories, 'format' => 0);
$form->questiontext = array('text' => ($includesubcategories ? '1' : '0'), 'format' => 0);
$form->category = $category->id . ',' . $category->contextid;
$form->defaultmark = 1;
$form->hidden = 1;
+13
View File
@@ -170,7 +170,20 @@ function quiz_delete_instance($id) {
quiz_delete_all_attempts($quiz);
quiz_delete_all_overrides($quiz);
// Look for random questions that may no longer be used when this quiz is gone.
$sql = "SELECT q.id
FROM {quiz_question_instances} instance
JOIN {question} q ON q.id = instance.question
WHERE instance.quiz = ? AND q.qtype = ?";
$questionids = $DB->get_fieldset_sql($sql, array($quiz->id, 'random'));
// We need to do this before we try and delete randoms, otherwise they would still be 'in use'.
$DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id));
foreach ($questionids as $questionid) {
question_delete_question($questionid);
}
$DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
quiz_access_manager::delete_settings($quiz);
+2 -1
View File
@@ -1381,7 +1381,8 @@ function scorm_format_duration($duration) {
function scorm_get_toc_object($user, $scorm, $currentorg='', $scoid='', $mode='normal', $attempt='', $play=false, $organizationsco=null) {
global $CFG, $DB, $PAGE, $OUTPUT;
$modestr = '';
// Always pass the mode even if empty as that is what is done elsewhere and the urls have to match.
$modestr = '&mode=';
if ($mode != 'normal') {
$modestr = '&mode='.$mode;
}
+10 -17
View File
@@ -60,25 +60,16 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
};
Y.TreeView.prototype.openAll = function () {
var tree = this;
Y.all('.yui3-treeview-can-have-children').each(function() {
var node = tree.getNodeById(this.get('id'));
node.open();
});
this.get('container').all('.yui3-treeview-can-have-children').each(function(target) {
this.getNodeById(target.get('id')).open();
}, this);
};
// TODO: Remove next(), previous() prototype functions after YUI has been updated to 3.11.0 - MDL-41208.
Y.Tree.Node.prototype.next = function () {
if (this.parent) {
return this.parent.children[this.index() + 1];
}
};
Y.Tree.Node.prototype.previous = function () {
if (this.parent) {
return this.parent.children[this.index() - 1];
}
};
Y.TreeView.prototype.closeAll = function () {
this.get('container').all('.yui3-treeview-can-have-children').each(function(target) {
this.getNodeById(target.get('id')).close();
}, this);
}
var scorm_parse_toc_tree = function(srcNode) {
var SELECTORS = {
@@ -141,6 +132,7 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
scorm_current_node.select();
}
scorm_tree_node.closeAll();
// remove any reference to the old API
if (window.API) {
window.API = null;
@@ -202,6 +194,7 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
}
scorm_fixnav();
}
scorm_tree_node.openAll();
};
mod_scorm_activate_item = scorm_activate_item;
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+4 -1
View File
@@ -339,7 +339,10 @@ class question_type {
$question->length = $this->actual_number_of_questions($question);
$question->penalty = isset($form->penalty) ? $form->penalty : 0;
if (empty($form->questiontext['text'])) {
// The trim call below has the effect of casting any strange values received,
// like null or false, to an appropriate string, so we only need to test for
// missing values. Be careful not to break the value '0' here.
if (!isset($form->questiontext['text'])) {
$question->questiontext = '';
} else {
$question->questiontext = trim($form->questiontext['text']);
@@ -33,6 +33,33 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_qtype_random_plugin extends restore_qtype_plugin {
/**
* Define the plugin structure.
*
* @return array Array of {@link restore_path_elements}.
*/
protected function define_question_plugin_structure() {
$paths = array();
// We have to specify a path here if we want after_execute_question to be called.
$elename = 'donothing';
$elepath = $this->get_pathfor('/');
$paths[] = new restore_path_element($elename, $elepath);
return $paths; // And we return the interesting paths.
}
/**
* Required function to process path. Should never be called.
*
* @param object $data Data elements.
*/
public function process_donothing($data) {
// Intentionally blank.
}
/**
* Given one question_states record, return the answer
* recoded pointing to all the restored stuff for random questions
@@ -69,4 +96,23 @@ class restore_qtype_random_plugin extends restore_qtype_plugin {
}
return $result;
}
/**
* After restoring, make sure questiontext is set properly.
*/
public function after_execute_question() {
global $DB;
// Update any blank random questiontexts to 0.
$sql = "UPDATE {question}
SET questiontext = '0'
WHERE qtype = 'random'
AND " . $DB->sql_compare_text('questiontext') . " = ?
AND id IN (SELECT bi.newitemid
FROM {backup_ids_temp} bi
WHERE bi.backupid = ?
AND bi.itemname = 'question_created')";
$DB->execute($sql, array('', $this->get_restoreid()));
}
}
+65
View File
@@ -0,0 +1,65 @@
<?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/>.
/**
* Random question type upgrade code.
*
* @package qtype_random
* @copyright 2014 Eric Merrill
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Upgrade code for the random question type.
* @param int $oldversion the version we are upgrading from.
*/
function xmldb_qtype_random_upgrade($oldversion) {
global $CFG, $DB;
$dbman = $DB->get_manager();
// Moodle v2.2.0 release upgrade line
// Put any upgrade step following this.
// Moodle v2.3.0 release upgrade line
// Put any upgrade step following this.
// Moodle v2.4.0 release upgrade line
// Put any upgrade step following this.
// Moodle v2.5.0 release upgrade line.
// Put any upgrade step following this.
// Moodle v2.6.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2013110501) {
$sql = "UPDATE {question}
SET questiontext = '0'
WHERE qtype = 'random'
AND " . $DB->sql_compare_text('questiontext') . " = ?";
$DB->execute($sql, array(''));
// Record that qtype_random savepoint was reached.
upgrade_plugin_savepoint(true, 2013110501, 'qtype', 'random');
}
return true;
}
+7 -1
View File
@@ -151,7 +151,13 @@ class qtype_random extends question_type {
public function save_question($question, $form) {
$form->name = '';
$form->questiontextformat = FORMAT_MOODLE;
// In case someone set the question text to true/false in the old style, set it properly.
if ($form->questiontext['text']) {
$form->questiontext['text'] = '1';
} else {
$form->questiontext['text'] = '0';
}
$form->tags = array();
// Name is not a required field for random questions, but
+1 -1
View File
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qtype_random';
$plugin->version = 2013110500;
$plugin->version = 2013110501;
$plugin->requires = 2013110500;
+3 -1
View File
@@ -135,7 +135,6 @@ $adminediting = optional_param('adminedit', -1, PARAM_BOOL);
if ($PAGE->user_allowed_editing() && $adminediting != -1) {
$USER->editing = $adminediting;
}
\core\session\manager::write_close();
if (!empty($chooselog)) {
$userinfo = get_string('allparticipants');
@@ -175,6 +174,7 @@ if (!empty($chooselog)) {
}
break;
case 'downloadascsv':
\core\session\manager::write_close();
if (!print_log_csv($course, $user, $date, 'l.time DESC', $modname,
$modid, $modaction, $group)) {
echo $OUTPUT->notification("No logs found!");
@@ -182,6 +182,7 @@ if (!empty($chooselog)) {
}
exit;
case 'downloadasods':
\core\session\manager::write_close();
if (!print_log_ods($course, $user, $date, 'l.time DESC', $modname,
$modid, $modaction, $group)) {
echo $OUTPUT->notification("No logs found!");
@@ -189,6 +190,7 @@ if (!empty($chooselog)) {
}
exit;
case 'downloadasexcel':
\core\session\manager::write_close();
if (!print_log_xls($course, $user, $date, 'l.time DESC', $modname,
$modid, $modaction, $group)) {
echo $OUTPUT->notification("No logs found!");
+3 -2
View File
@@ -815,8 +815,9 @@ body.tag .managelink {padding: 5px;}
.mod-indent-12 {width:360px;}
.mod-indent-13 {width:390px;}
.mod-indent-14 {width:420px;}
.mod-indent-15,
.mod-indent-huge {width:420px;}
.mod-indent-15 {width:450px;}
.mod-indent-16,
.mod-indent-huge {width:480px;}
.dir-rtl .mform .fitem .felement {margin-right: 16%;margin-left:auto;text-align: right;}
.dir-rtl .mform .fitem .felement input[name=email],
+14 -44
View File
@@ -1246,52 +1246,22 @@ body.tag .managelink {
float:left;
padding-top:20px
}
.mod-indent-1 {
width: 30px;
/* Creates a series of .mod-indent-# rule declarations based on indent size and number of indent levels. */
@mod-indent-size: 30px;
@mod-indent-levels: 16;
.mod-indent-generate(@n, @i: 1) when (@i =< @n) {
.mod-indent-@{i} {
width: (@i * @mod-indent-size);
}
.mod-indent-generate(@n, (@i + 1));
}
.mod-indent-2 {
width: 60px;
}
.mod-indent-3 {
width: 90px;
}
.mod-indent-4 {
width: 120px;
}
.mod-indent-5 {
width: 150px;
}
.mod-indent-6 {
width: 180px;
}
.mod-indent-7 {
width: 210px;
}
.mod-indent-8 {
width: 240px;
}
.mod-indent-9 {
width: 270px;
}
.mod-indent-10 {
width: 300px;
}
.mod-indent-11 {
width: 330px;
}
.mod-indent-12 {
width: 360px;
}
.mod-indent-13 {
width: 390px;
}
.mod-indent-14 {
width: 420px;
}
.mod-indent-15,
.mod-indent-huge {
width: 420px;
.mod-indent-generate(@n, @i: 1) when (@i = @n) {
.mod-indent-huge {
width: (@i * @mod-indent-size);
}
}
.mod-indent-generate(@mod-indent-levels);
/* Audio player size in 'block' mode (can only change width, height is hardcoded in JS) */
.resourcecontent .mediaplugin_mp3 object {
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -29,11 +29,11 @@
defined('MOODLE_INTERNAL') || die();
$version = 2013111803.04; // 20131118 = branching date YYYYMMDD - do not modify!
$version = 2013111803.06; // 20131118 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '2.6.3+ (Build: 20140605)'; // Human-friendly version name
$release = '2.6.3+ (Build: 20140613)'; // Human-friendly version name
$branch = '26'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.