Merge branch 'MOODLE_26_STABLE' into install_26_STABLE
@@ -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';
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 &'s here as they will have been added by format_string above and attributes will be encoded again
|
||||
// later.
|
||||
$coursenode->title(str_replace('&', '&', $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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qtype_random';
|
||||
$plugin->version = 2013110500;
|
||||
$plugin->version = 2013110501;
|
||||
|
||||
$plugin->requires = 2013110500;
|
||||
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||