Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'MOODLE_26_STABLE' into install_26_STABLE

  • Loading branch information...
commit eb4b4c7ba094668f43b2e8fc86900e873465d53a 2 parents 0d893ad + 89c7782
AMOS bot authored
Showing with 775 additions and 165 deletions.
  1. +1 −1  admin/renderer.php
  2. +6 −8 backup/moodle2/backup_stepslib.php
  3. +86 −50 backup/moodle2/restore_stepslib.php
  4. +21 −1 backup/util/dbops/backup_structure_dbops.class.php
  5. +1 −1  backup/util/structure/backup_structure_processor.class.php
  6. +190 −0 blocks/navigation/tests/behat/expand_courses_node.feature
  7. +0 −2  blocks/navigation/tests/behat/view_my_courses.feature
  8. +3 −3 enrol/self/tests/behat/self_enrolment.feature
  9. +20 −3 group/group_form.php
  10. +39 −0 group/tests/behat/create_groups.feature
  11. +51 −0 group/tests/behat/update_groups.feature
  12. +1 −0  lang/en/cache.php
  13. +1 −1  lang/en/error.php
  14. +1 −0  lang/en/group.php
  15. +6 −1 lib/classes/useragent.php
  16. +10 −0 lib/db/caches.php
  17. +7 −0 lib/db/upgrade.php
  18. +21 −1 lib/db/upgradelib.php
  19. +2 −2 lib/filelib.php
  20. +8 −1 lib/filestorage/stored_file.php
  21. +4 −3 lib/filestorage/tests/file_storage_test.php
  22. +70 −1 lib/navigationlib.php
  23. +0 −3  lib/questionlib.php
  24. +27 −0 lib/tests/theme_config_test.php
  25. +3 −1 mod/data/locallib.php
  26. +17 −9 mod/lti/return.php
  27. +8 −2 mod/quiz/editlib.php
  28. +13 −0 mod/quiz/lib.php
  29. +2 −1  mod/scorm/locallib.php
  30. +10 −17 mod/scorm/module.js
  31. BIN  pix/f/publisher-128.png
  32. BIN  pix/f/publisher-24.png
  33. BIN  pix/f/publisher-256.png
  34. BIN  pix/f/publisher-32.png
  35. BIN  pix/f/publisher-48.png
  36. BIN  pix/f/publisher-64.png
  37. BIN  pix/f/publisher-72.png
  38. BIN  pix/f/publisher-80.png
  39. BIN  pix/f/publisher-96.png
  40. BIN  pix/f/publisher.png
  41. +4 −1 question/type/questiontypebase.php
  42. +46 −0 question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
  43. +65 −0 question/type/random/db/upgrade.php
  44. +7 −1 question/type/random/questiontype.php
  45. +1 −1  question/type/random/version.php
  46. +3 −1 report/log/index.php
  47. +3 −2 theme/base/style/core.css
  48. +14 −44 theme/bootstrapbase/less/moodle/core.less
  49. +1 −1  theme/bootstrapbase/style/moodle.css
  50. +2 −2 version.php
View
2  admin/renderer.php
@@ -1369,7 +1369,7 @@ public function environment_check_table($result, $environment_results) {
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';
View
14 backup/moodle2/backup_stepslib.php
@@ -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 @@ protected function define_execution() {
$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 @@ protected function define_execution() {
}
/**
- * 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 @@ protected function define_execution() {
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
View
136 backup/moodle2/restore_stepslib.php
@@ -67,7 +67,7 @@ protected function define_execution() {
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 @@ protected function define_execution() {
* 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 @@ protected function define_execution() {
$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();
-
- // 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;
-
- // 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);
-
- // 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);
+ AND bi.itemname = 'question_created'
+ ORDER BY categoryid ASC", array($this->get_restoreid()));
+
+ $currentcatid = -1;
+ foreach ($catqtypes as $categoryid => $row) {
+ $qtype = $row->qtype;
+
+ // Check if we are in a new category.
+ if ($currentcatid !== $categoryid) {
+ // Report progress for each category.
+ $progress->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.
View
22 backup/util/dbops/backup_structure_dbops.class.php
@@ -103,7 +103,18 @@ public static function insert_backup_ids_record($backupid, $itemname, $itemid) {
}
}
- 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 @@ public static function annotate_files($backupid, $contextid, $component, $filear
$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();
}
View
2  backup/util/structure/backup_structure_processor.class.php
@@ -86,7 +86,7 @@ public function process_nested_element(base_nested_element $nested) {
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);
}
}
}
View
190 blocks/navigation/tests/behat/expand_courses_node.feature
@@ -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
View
2  blocks/navigation/tests/behat/view_my_courses.feature
@@ -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"
View
6 enrol/self/tests/behat/self_enrolment.feature
@@ -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"
View
23 group/group_form.php
@@ -111,11 +111,19 @@ function validation($data, $files) {
}
}
- 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 @@ function validation($data, $files) {
$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;
View
39 group/tests/behat/create_groups.feature
@@ -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."
View
51 group/tests/behat/update_groups.feature
@@ -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."
View
1  lang/en/cache.php
@@ -51,6 +51,7 @@
$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';
View
2  lang/en/error.php
@@ -489,7 +489,7 @@
$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';
View
1  lang/en/group.php
@@ -61,6 +61,7 @@
$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}';
View
7 lib/classes/useragent.php
@@ -120,7 +120,12 @@ public static function instance($reload = false, $forceuseragent = null) {
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;
View
10 lib/db/caches.php
@@ -212,4 +212,14 @@
'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
+ )
);
View
7 lib/db/upgrade.php
@@ -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;
}
View
22 lib/db/upgradelib.php
@@ -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)
+ );
+ }
+}
View
4 lib/filelib.php
@@ -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'),
View
9 lib/filestorage/stored_file.php
@@ -205,7 +205,14 @@ protected function update($dataobject) {
*/
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;
View
7 lib/filestorage/tests/file_storage_test.php
@@ -300,7 +300,8 @@ public function test_file_renaming() {
$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 @@ public function test_create_file_from_storedfile_duplicate() {
$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 @@ public function test_create_file_from_pathname_duplicate_file() {
$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);
}
View
71 lib/navigationlib.php
@@ -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 @@ public function initialise() {
// 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 @@ public function initialise() {
}
}
+ 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 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype
// 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 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype
} 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 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype
}
$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;
}
@@ -2421,6 +2451,45 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype
}
/**
+ * 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
*
* @param int $categoryid
View
3  lib/questionlib.php
@@ -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);
View
27 lib/tests/theme_config_test.php
@@ -126,4 +126,31 @@ public function test_svg_image_use() {
}
}
}
+
+ /**
+ * 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'));
+ }
}
View
4 mod/data/locallib.php
@@ -348,7 +348,9 @@ public static function formats($fields, $record) {
$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) {
View
26 mod/lti/return.php
@@ -29,7 +29,7 @@
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 @@
$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 @@
}
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 @@
$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();
View
10 mod/quiz/editlib.php
@@ -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;
View
13 mod/quiz/lib.php
@@ -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);
View
3  mod/scorm/locallib.php
@@ -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;
}
View
27 mod/scorm/module.js
@@ -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();
- });
- };
-
- // 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];
- }
+ this.get('container').all('.yui3-treeview-can-have-children').each(function(target) {
+ this.getNodeById(target.get('id')).open();
+ }, this);
};
- 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;
View
BIN  pix/f/publisher-128.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-24.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-256.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-32.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-48.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-64.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-72.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-80.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher-96.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  pix/f/publisher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
5 question/type/questiontypebase.php
@@ -339,7 +339,10 @@ public function save_question($question, $form) {
$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']);
View
46 question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
@@ -33,6 +33,33 @@
* @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 @@ public function recode_legacy_state_answer($state) {
}
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()));
+ }
}
View
65 question/type/random/db/upgrade.php
@@ -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;
+}
View
8 question/type/random/questiontype.php
@@ -151,7 +151,13 @@ protected function set_selected_question_name($question, $randomname) {
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
View
2  question/type/random/version.php
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qtype_random';
-$plugin->version = 2013110500;
+$plugin->version = 2013110501;
$plugin->requires = 2013110500;
View
4 report/log/index.php
@@ -135,7 +135,6 @@
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 @@
}
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 @@
}
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 @@
}
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!");
View
5 theme/base/style/core.css
@@ -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],
View
58 theme/bootstrapbase/less/moodle/core.less
@@ -1246,52 +1246,22 @@ body.tag .managelink {
float:left;
padding-top:20px
}
-.mod-indent-1 {
- width: 30px;
-}
-.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;
+
+/* 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-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 {
View
2  theme/bootstrapbase/style/moodle.css
1 addition, 1 deletion not shown
View
4 version.php
@@ -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.
Please sign in to comment.
Something went wrong with that request. Please try again.