diff --git a/admin/environment.xml b/admin/environment.xml
index 19997a449b3..66e4337c0de 100644
--- a/admin/environment.xml
+++ b/admin/environment.xml
@@ -957,6 +957,8 @@
+
+
diff --git a/admin/tests/behat/behat_admin.php b/admin/tests/behat/behat_admin.php
index 0cd23064ad4..6b4f88f69ae 100644
--- a/admin/tests/behat/behat_admin.php
+++ b/admin/tests/behat/behat_admin.php
@@ -62,7 +62,7 @@ class behat_admin extends behat_base {
}
// Search by label.
- $searchbox = $this->find_field('Search in settings');
+ $searchbox = $this->find_field(get_string('searchinsettings', 'admin'));
$searchbox->setValue($label);
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
$submitsearch->press();
@@ -72,8 +72,12 @@ class behat_admin extends behat_base {
// Admin settings does not use the same DOM structure than other moodle forms
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
$exception = new ElementNotFoundException($this->getSession(), '"' . $label . '" administration setting ');
+
+ // The argument should be converted to an xpath literal.
+ $label = $this->getSession()->getSelectorsHandler()->xpathLiteral($label);
+
$fieldxpath = "//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
- "[@id=//label[contains(normalize-space(string(.)), '" . $label . "')]/@for]";
+ "[@id=//label[contains(normalize-space(.), $label)]/@for]";
$fieldnode = $this->find('xpath', $fieldxpath, $exception);
$formfieldtypenode = $this->find('xpath', $fieldxpath . "/ancestor::div[@class='form-setting']" .
"/child::div[contains(concat(' ', @class, ' '), ' form-')]/child::*/parent::div");
@@ -90,7 +94,7 @@ class behat_admin extends behat_base {
$field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
$field->set_value($value);
- $this->find_button('Save changes')->press();
+ $this->find_button(get_string('savechanges'))->press();
}
}
diff --git a/admin/tests/behat/display_short_names.feature b/admin/tests/behat/display_short_names.feature
index b6c825812e8..f17f092191f 100644
--- a/admin/tests/behat/display_short_names.feature
+++ b/admin/tests/behat/display_short_names.feature
@@ -15,7 +15,7 @@ Feature: Display extended course names
And I should not see "C_shortname Course fullname"
Scenario: Courses list with extended course names
- Given I click on "Courses" "link" in the "//div[@id='settingsnav']//descendant::li[contains(concat(' ', @class, ' '), ' type_setting ')][not(contains(., 'Site administration'))][contains(., 'Appearance')]" "xpath_element"
+ Given I click on "Courses" "link" in the "//div[@id='settingsnav']/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' type_setting ')][not(contains(., 'Site administration'))][contains(., 'Appearance')]" "xpath_element"
And I check "Display extended course names"
When I press "Save changes"
And I am on homepage
diff --git a/admin/tool/behat/cli/util.php b/admin/tool/behat/cli/util.php
index b28f4fcb6b0..5c24cf8c05d 100644
--- a/admin/tool/behat/cli/util.php
+++ b/admin/tool/behat/cli/util.php
@@ -142,6 +142,9 @@ foreach ($vars as $var) {
$CFG->{$var} = $CFG->{'behat_' . $var};
}
+// Clean $CFG extra values before performing any action.
+behat_clean_init_config();
+
$CFG->noemailever = true;
$CFG->passwordsaltmain = 'moodle';
diff --git a/admin/tool/behat/lang/en/tool_behat.php b/admin/tool/behat/lang/en/tool_behat.php
index 086e78c54f3..9f426cc6a01 100644
--- a/admin/tool/behat/lang/en/tool_behat.php
+++ b/admin/tool/behat/lang/en/tool_behat.php
@@ -41,6 +41,5 @@ $string['theninfo'] = 'Then. Checkings to ensure the outcomes are the expected o
$string['viewsteps'] = 'Filter';
$string['wheninfo'] = 'When. Actions that provokes an event';
$string['wrongbehatsetup'] = 'Something is wrong with behat setup, ensure:
-- You ran "curl http://getcomposer.org/installer | php"
-- You ran "php composer.phar install --dev"
+- You ran "php admin/tool/behat/cli/init.php" from your moodle root directory
- vendor/bin/behat file has execution permissions
';
diff --git a/admin/tool/behat/tests/behat/basic_actions.feature b/admin/tool/behat/tests/behat/basic_actions.feature
index c2f829bd659..637086f20a8 100644
--- a/admin/tool/behat/tests/behat/basic_actions.feature
+++ b/admin/tool/behat/tests/behat/basic_actions.feature
@@ -37,7 +37,7 @@ Feature: Page contents assertions
And I follow "Course 1"
When I click on "Move this to the dock" "button" in the ".block_settings" "css_element"
Then I should not see "Question bank"
- And I click on "//div[@id='dock']/descendant::*[contains(., 'Administration')]/h2" "xpath_element"
+ And I click on "//div[@id='dock']/descendant::h2[normalize-space(.)='Administration']" "xpath_element"
@javascript
Scenario: Locators inside specific DOM nodes using XPath
@@ -45,5 +45,5 @@ Feature: Page contents assertions
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And I log in as "admin"
- When I click on "Move this to the dock" "button" in the "//*[contains(concat(' ', normalize-space(@class), ' '), ' block_settings ')]" "xpath_element"
+ When I click on "Move this to the dock" "button" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' block_settings ')]" "xpath_element"
Then I should not see "Turn editing on"
diff --git a/admin/tool/behat/tests/behat/data_generators.feature b/admin/tool/behat/tests/behat/data_generators.feature
index a98adcf9f1a..b19db483ed7 100644
--- a/admin/tool/behat/tests/behat/data_generators.feature
+++ b/admin/tool/behat/tests/behat/data_generators.feature
@@ -30,17 +30,17 @@ Feature: Set up contextual data for tests
Then I should see "Course 1"
And I should see "Course 2"
And I should see "Course 3"
- When I go to the courses management page
+ And I go to the courses management page
And I follow "Cat 1"
- Then I should see "Cat 2"
+ And I should see "Cat 2"
And I should see "Cat 3"
- When I follow "Cat 3"
- Then I should see "Course 1"
+ And I follow "Cat 3"
+ And I should see "Course 1"
And I should see "Course 2"
- When I select "Cat 2" from "Course categories:"
- Then I should see "No courses in this category"
- When I select "Miscellaneous" from "Course categories:"
- Then I should see "Course 3"
+ And I select "Cat 1 / Cat 2" from "Course categories:"
+ And I should see "No courses in this category"
+ And I select "Miscellaneous" from "Course categories:"
+ And I should see "Course 3"
@javascript
Scenario: Add a bunch of groups and groupings
diff --git a/admin/tool/profiling/index.php b/admin/tool/profiling/index.php
index a5e21d3badf..e602e44196b 100644
--- a/admin/tool/profiling/index.php
+++ b/admin/tool/profiling/index.php
@@ -74,12 +74,11 @@ if (isset($script)) {
$prevreferences = $DB->get_records_select('profiling',
'url = ? AND runreference = 1 AND timecreated < ?',
array($run->url, $run->timecreated),
- 'timecreated DESC', 'runid', 0, 1);
- $prevrunid = $prevreferences ? reset($prevreferences)->runid : false;
+ 'timecreated DESC', 'runid, runcomment, timecreated', 0, 10);
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
$header = get_string('lastrunof', 'tool_profiling', $script);
echo $OUTPUT->heading($header);
- $table = profiling_print_run($run, $prevrunid);
+ $table = profiling_print_run($run, $prevreferences);
echo $table;
echo $OUTPUT->box_end();
@@ -126,12 +125,11 @@ if (isset($script)) {
$prevreferences = $DB->get_records_select('profiling',
'url = ? AND runreference = 1 AND timecreated < ?',
array($run->url, $run->timecreated),
- 'timecreated DESC', 'runid', 0, 1);
- $prevrunid = $prevreferences ? reset($prevreferences)->runid : false;
+ 'timecreated DESC', 'runid, runcomment, timecreated', 0, 10);
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
$header = get_string('summaryof', 'tool_profiling', $run->url);
echo $OUTPUT->heading($header);
- $table = profiling_print_run($run, $prevrunid);
+ $table = profiling_print_run($run, $prevreferences);
echo $table;
echo $OUTPUT->box_end();
diff --git a/admin/tool/profiling/lang/en/tool_profiling.php b/admin/tool/profiling/lang/en/tool_profiling.php
index 4a687f8f279..7c80c848c3a 100644
--- a/admin/tool/profiling/lang/en/tool_profiling.php
+++ b/admin/tool/profiling/lang/en/tool_profiling.php
@@ -51,5 +51,5 @@ $string['referencerun'] = 'Reference run/comment';
$string['runid'] = 'Run ID';
$string['summaryof'] = 'Summary of {$a}';
$string['viewdetails'] = 'View profiling details';
-$string['viewdiff'] = 'View profiling differences with last reference run';
+$string['viewdiff'] = 'View profiling differences with:';
$string['viewdiffdetails'] = 'View profiling diff details';
diff --git a/auth/tests/behat/behat_auth.php b/auth/tests/behat/behat_auth.php
index c6d47aa4958..4ced1542330 100644
--- a/auth/tests/behat/behat_auth.php
+++ b/auth/tests/behat/behat_auth.php
@@ -49,10 +49,10 @@ class behat_auth extends behat_base {
public function i_log_in_as($username) {
return array(new Given('I am on homepage'),
- new Given('I follow "Login"'),
- new Given('I fill in "Username" with "'.$username.'"'),
- new Given('I fill in "Password" with "'.$username.'"'),
- new Given('I press "Login"')
+ new Given('I follow "' . get_string('login') . '"'),
+ new Given('I fill in "' . get_string('username') . '" with "' . $this->escape($username) . '"'),
+ new Given('I fill in "' . get_string('password') . '" with "'. $this->escape($username) . '"'),
+ new Given('I press "' . get_string('login') . '"')
);
}
@@ -62,7 +62,7 @@ class behat_auth extends behat_base {
* @Given /^I log out$/
*/
public function i_log_out() {
- return new When('I follow "Logout"');
+ return new When('I follow "' . get_string('logout') . '"');
}
}
diff --git a/backup/backupfilesedit_form.php b/backup/backupfilesedit_form.php
index 36a4030da07..dce09fe9fa0 100644
--- a/backup/backupfilesedit_form.php
+++ b/backup/backupfilesedit_form.php
@@ -1,5 +1,4 @@
libdir.'/formslib.php');
class backup_files_edit_form extends moodleform {
- function definition() {
+
+ /**
+ * Form definition.
+ */
+ public function definition() {
$mform =& $this->_form;
- $contextid = $this->_customdata['contextid'];
- $options = array('subdirs'=>0, 'maxfiles'=>-1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL | FILE_REFERENCE);
+
+ $options = array('subdirs' => 0, 'maxfiles' => -1, 'accepted_types' => '*', 'return_types' => FILE_INTERNAL | FILE_REFERENCE);
+
$mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options);
+
$mform->addElement('hidden', 'contextid', $this->_customdata['contextid']);
+ $mform->setType('contextid', PARAM_INT);
+
$mform->addElement('hidden', 'currentcontext', $this->_customdata['currentcontext']);
+ $mform->setType('currentcontext', PARAM_INT);
+
$mform->addElement('hidden', 'filearea', $this->_customdata['filearea']);
+ $mform->setType('filearea', PARAM_AREA);
+
$mform->addElement('hidden', 'component', $this->_customdata['component']);
+ $mform->setType('component', PARAM_COMPONENT);
+
$mform->addElement('hidden', 'returnurl', $this->_customdata['returnurl']);
+ $mform->setType('returnurl', PARAM_URL);
+
$this->add_action_buttons(true, get_string('savechanges'));
$this->set_data($this->_customdata['data']);
}
diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php
index 518d7bac1d6..a3dfe1e6161 100644
--- a/backup/util/dbops/restore_dbops.class.php
+++ b/backup/util/dbops/restore_dbops.class.php
@@ -614,13 +614,20 @@ abstract class restore_dbops {
} else {
self::set_backup_ids_record($restoreid, 'question_category', $category->id, $matchcat->id, $targetcontext->id);
$questions = self::restore_get_questions($restoreid, $category->id);
+
+ // Collect all the questions for this category into memory so we only talk to the DB once.
+ $questioncache = $DB->get_records_sql_menu("SELECT ".$DB->sql_concat('stamp', "' '", 'version').", id
+ FROM {question}
+ WHERE category = ?", array($matchcat->id));
+
foreach ($questions as $question) {
- $matchq = $DB->get_record('question', array(
- 'category' => $matchcat->id,
- 'stamp' => $question->stamp,
- 'version' => $question->version));
+ if (isset($questioncache[$question->stamp." ".$question->version])) {
+ $matchqid = $questioncache[$question->stamp." ".$question->version];
+ } else {
+ $matchqid = false;
+ }
// 5a) No match, check if user can add q
- if (!$matchq) {
+ if (!$matchqid) {
// 6a) User can, mark the q to be created
if ($canadd) {
// Nothing to mark, newitemid means create
@@ -645,7 +652,7 @@ abstract class restore_dbops {
// 5b) Match, mark q to be mapped
} else {
- self::set_backup_ids_record($restoreid, 'question', $question->id, $matchq->id);
+ self::set_backup_ids_record($restoreid, 'question', $question->id, $matchqid);
}
}
}
diff --git a/backup/util/ui/tests/behat/backup_courses.feature b/backup/util/ui/tests/behat/backup_courses.feature
index 488ab6013cb..f8b85d75f0b 100644
--- a/backup/util/ui/tests/behat/backup_courses.feature
+++ b/backup/util/ui/tests/behat/backup_courses.feature
@@ -31,7 +31,7 @@ Feature: Backup Moodle courses
And I should not see "Section 3"
And I press "Continue"
And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
- And I click on "//div[contains(concat(' ', @class, ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element"
- And I click on "setting_root_logs" "checkbox" in the "//div[contains(@class, 'fitem')][contains(., 'Include course logs')]" "xpath_element"
+ And "//div[contains(concat(' ', normalize-space(@class), ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element" should exists
+ And I check "Include course logs"
And I press "Cancel"
And I click on "Cancel" "button" in the ".confirmation-dialogue" "css_element"
diff --git a/backup/util/ui/tests/behat/behat_backup.php b/backup/util/ui/tests/behat/behat_backup.php
index b0d99986caf..d06185ed304 100644
--- a/backup/util/ui/tests/behat/behat_backup.php
+++ b/backup/util/ui/tests/behat/behat_backup.php
@@ -61,25 +61,25 @@ class behat_backup extends behat_base {
$this->find_link($backupcourse)->click();
// Click the backup link.
- $this->find_link('Backup')->click();
+ $this->find_link(get_string('backup'))->click();
// Initial settings.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('backupstage1action', 'backup'))->press();
// Schema settings.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('backupstage2action', 'backup'))->press();
// Confirmation and review, backup filename can also be specified.
$this->fill_backup_restore_form($options);
- $this->find_button('Perform backup')->press();
+ $this->find_button(get_string('backupstage4action', 'backup'))->press();
// Waiting for it to finish.
$this->wait(10);
// Last backup continue button.
- $this->find_button('Continue')->press();
+ $this->find_button(get_string('backupstage16action', 'backup'))->press();
}
/**
@@ -105,36 +105,37 @@ class behat_backup extends behat_base {
// Click the course link.
$this->find_link($tocourse)->click();
- // Click the backup link.
- $this->find_link('Import')->click();
+ // Click the import link.
+ $this->find_link(get_string('import'))->click();
// Select the course.
$exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from', $this->getSession());
- $fromcourse = str_replace("'", "\'", $fromcourse);
- $xpath = "//div[contains(concat(' ', @class, ' '), ' ics-results ')]" .
- "/descendant::tr[contains(., '" . $fromcourse . "')]" .
+ // The argument should be converted to an xpath literal.
+ $fromcourse = $this->getSession()->getSelectorsHandler()->xpathLiteral($fromcourse);
+ $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ics-results ')]" .
+ "/descendant::tr[contains(., $fromcourse)]" .
"/descendant::input[@type='radio']";
$radionode = $this->find('xpath', $xpath, $exception);
$radionode->check();
$radionode->click();
- $this->find_button('Continue')->press();
+ $this->find_button(get_string('continue'))->press();
// Initial settings.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('importbackupstage1action', 'backup'))->press();
// Schema settings.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('importbackupstage2action', 'backup'))->press();
// Run it.
- $this->find_button('Perform import')->press();
+ $this->find_button(get_string('importbackupstage4action', 'backup'))->press();
$this->wait();
// Continue and redirect to 'to' course.
- $this->find_button('Continue')->press();
+ $this->find_button(get_string('continue'))->press();
}
/**
@@ -150,17 +151,20 @@ class behat_backup extends behat_base {
// Confirm restore.
$this->select_backup($backupfilename);
+ // The argument should be converted to an xpath literal.
+ $existingcourse = $this->getSession()->getSelectorsHandler()->xpathLiteral($existingcourse);
+
// Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
- $existingcourse = str_replace("'", "\'", $existingcourse);
- $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-existing-course')]" .
+ $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
"/descendant::div[@class='restore-course-search']" .
- "/descendant::tr[contains(., '" . $existingcourse . "')]" .
+ "/descendant::tr[contains(., $existingcourse)]" .
"/descendant::input[@type='radio']");
$radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
- $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-existing-course')]/descendant::input[@type='submit'][@value='Continue']");
+ $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
+ "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
$continuenode->click();
$this->wait();
@@ -181,14 +185,15 @@ class behat_backup extends behat_base {
$this->select_backup($backupfilename);
// The first category in the list.
- $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-new-course')]" .
+ $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::div[@class='restore-course-search']" .
"/descendant::input[@type='radio']");
$radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
- $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-new-course')]/descendant::input[@type='submit'][@value='Continue']");
+ $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
+ "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
$continuenode->click();
$this->wait();
@@ -209,14 +214,14 @@ class behat_backup extends behat_base {
$this->select_backup($backupfilename);
// Merge without deleting radio option.
- $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]" .
+ $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='1']");
$radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
- $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]" .
- "/descendant::input[@type='submit'][@value='Continue']");
+ $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
+ "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
$continuenode->click();
$this->wait();
@@ -237,14 +242,14 @@ class behat_backup extends behat_base {
$this->select_backup($backupfilename);
// Delete contents radio option.
- $radionode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]" .
+ $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='0']");
$radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
- $continuenode = $this->find('xpath', "//div[contains(@class, 'bcs-current-course')]" .
- "/descendant::input[@type='submit'][@value='Continue']");
+ $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
+ "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
$continuenode->click();
$this->wait();
@@ -263,12 +268,16 @@ class behat_backup extends behat_base {
// Using xpath as there are other restore links before this one.
$exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page', $this->getSession());
- $xpath = "//tr[contains(., '" . $backupfilename . "')]/descendant::a[contains(., 'Restore')]";
+
+ // The argument should be converted to an xpath literal.
+ $backupfilename = $this->getSession()->getSelectorsHandler()->xpathLiteral($backupfilename);
+
+ $xpath = "//tr[contains(., $backupfilename)]/descendant::a[contains(., '" . get_string('restore') . "')]";
$restorelink = $this->find('xpath', $xpath, $exception);
$restorelink->click();
// Confirm the backup contents.
- $restore = $this->find_button('Continue')->press();
+ $restore = $this->find_button(get_string('continue'))->press();
}
/**
@@ -284,18 +293,18 @@ class behat_backup extends behat_base {
// Settings.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('restorestage4action', 'backup'))->press();
// Schema.
$this->fill_backup_restore_form($options);
- $this->find_button('Next')->press();
+ $this->find_button(get_string('restorestage8action', 'backup'))->press();
// Review, no options here.
- $this->find_button('Perform restore')->press();
+ $this->find_button(get_string('restorestage16action', 'backup'))->press();
$this->wait(10);
// Last restore continue button, redirected to restore course after this.
- $this->find_button('Continue')->press();
+ $this->find_button(get_string('restorestage32action', 'backup'))->press();
}
/**
@@ -339,6 +348,10 @@ class behat_backup extends behat_base {
*/
protected function wait($timeout = false) {
+ if (!$this->running_javascript()) {
+ return;
+ }
+
if (!$timeout) {
$timeout = self::TIMEOUT;
}
diff --git a/backup/util/xml/parser/processors/grouped_parser_processor.class.php b/backup/util/xml/parser/processors/grouped_parser_processor.class.php
index 48782b7540b..748daace21b 100644
--- a/backup/util/xml/parser/processors/grouped_parser_processor.class.php
+++ b/backup/util/xml/parser/processors/grouped_parser_processor.class.php
@@ -43,6 +43,18 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
protected $groupedpaths; // Paths we are requesting grouped
protected $currentdata; // Where we'll be acummulating data
+ /**
+ * Keep cache of parent directory paths for XML parsing.
+ * @var array
+ */
+ protected $parentcache = array();
+
+ /**
+ * Remaining space for parent directory paths.
+ * @var integer
+ */
+ protected $parentcacheavailablesize = 2048;
+
public function __construct(array $paths = array()) {
$this->groupedpaths = array();
$this->currentdata = null;
@@ -65,7 +77,7 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
$a->child = $found;
throw new progressive_parser_exception('xml_grouped_child_found', $a);
}
- $this->groupedpaths[] = $path;
+ $this->groupedpaths[$path] = true;
}
parent::add_path($path);
}
@@ -141,7 +153,7 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
}
protected function path_is_grouped($path) {
- return in_array($path, $this->groupedpaths);
+ return isset($this->groupedpaths[$path]);
}
/**
@@ -150,16 +162,41 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
* false if not
*/
protected function grouped_parent_exists($path) {
- $parentpath = progressive_parser::dirname($path);
+ $parentpath = $this->get_parent_path($path);
+
while ($parentpath != '/') {
if ($this->path_is_grouped($parentpath)) {
return $parentpath;
}
- $parentpath = progressive_parser::dirname($parentpath);
+ $parentpath = $this->get_parent_path($parentpath);
}
return false;
}
+ /**
+ * Get the parent path using a local cache for performance.
+ *
+ * @param $path string The pathname you wish to obtain the parent name for.
+ * @return string The parent pathname.
+ */
+ protected function get_parent_path($path) {
+ if (!isset($this->parentcache[$path])) {
+ $this->parentcache[$path] = progressive_parser::dirname($path);
+ $this->parentcacheavailablesize--;
+ if ($this->parentcacheavailablesize < 0) {
+ // Older first is cheaper than LRU. We use 10% as items are grouped together and the large quiz
+ // restore from MDL-40585 used only 600 parent paths. This is an XML heirarchy, so common paths
+ // are grouped near each other. eg; /question_bank/question_category/question/element. After keeping
+ // question_bank paths in the cache when we move to another area and the question_bank cache is not
+ // useful any longer.
+ $this->parentcache = array_slice($this->parentcache, 200, null, true);
+ $this->parentcacheavailablesize += 200;
+ }
+ }
+ return $this->parentcache[$path];
+ }
+
+
/**
* Function that will look for any grouped
* child for the given path, returning it if found,
@@ -167,7 +204,7 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
*/
protected function grouped_child_exists($path) {
$childpath = $path . '/';
- foreach ($this->groupedpaths as $groupedpath) {
+ foreach ($this->groupedpaths as $groupedpath => $set) {
if (strpos($groupedpath, $childpath) === 0) {
return $groupedpath;
}
diff --git a/badges/backpack.js b/badges/backpack.js
index 68e736bee08..98678e8f29e 100644
--- a/badges/backpack.js
+++ b/badges/backpack.js
@@ -2,7 +2,20 @@
* Push badges to backpack.
*/
function addtobackpack(event, args) {
- OpenBadges.issue([args.assertion], function(errors, successes) { });
+ var badgetable = Y.one('#issued-badge-table');
+ var errordiv = Y.one('#addtobackpack-error');
+ var errortext = M.util.get_string('error:backpackproblem', 'badges');
+ var errorhtml = '' + errortext + '
';
+
+ if (typeof OpenBadges !== 'undefined') {
+ OpenBadges.issue([args.assertion], function(errors, successes) { });
+ } else {
+ // Add error div if it doesn't exist yet.
+ if (!errordiv) {
+ var badgerror = Y.Node.create(errorhtml);
+ badgetable.insert(badgerror, 'before');
+ }
+ }
}
/**
diff --git a/badges/badge.php b/badges/badge.php
index cd90239502c..339b147313e 100644
--- a/badges/badge.php
+++ b/badges/badge.php
@@ -56,10 +56,8 @@ if (isloggedin()) {
navigation_node::override_active_url($url);
}
-// TODO: Better way of pushing badges to Mozilla backpack?
-if (!empty($CFG->badges_allowexternalbackpack)) {
- $PAGE->requires->js(new moodle_url('http://backpack.openbadges.org/issuer.js'), true);
-}
+// Include JS files for backpack support.
+badges_setup_backpack_js();
echo $OUTPUT->header();
diff --git a/badges/mybadges.php b/badges/mybadges.php
index 46f4c1dce2d..d944e736beb 100644
--- a/badges/mybadges.php
+++ b/badges/mybadges.php
@@ -90,11 +90,8 @@ $PAGE->set_title($title);
$PAGE->set_heading($title);
$PAGE->set_pagelayout('mydashboard');
-// TODO: Better way of pushing badges to Mozilla backpack?
-if (!empty($CFG->badges_allowexternalbackpack)) {
- $PAGE->requires->js(new moodle_url('http://backpack.openbadges.org/issuer.js'), true);
- $PAGE->requires->js('/badges/backpack.js', true);
-}
+// Include JS files for backpack support.
+badges_setup_backpack_js();
$output = $PAGE->get_renderer('core', 'badges');
$badges = badges_get_user_badges($USER->id);
diff --git a/badges/renderer.php b/badges/renderer.php
index 171bdb3bb9b..0100410eb0a 100644
--- a/badges/renderer.php
+++ b/badges/renderer.php
@@ -282,6 +282,7 @@ class core_badges_renderer extends plugin_renderer_base {
$today = strtotime($today_date);
$table = new html_table();
+ $table->id = 'issued-badge-table';
$imagetable = new html_table();
$imagetable->attributes = array('class' => 'clearfix badgeissuedimage');
@@ -294,11 +295,13 @@ class core_badges_renderer extends plugin_renderer_base {
$expiration = isset($issued['expires']) ? strtotime($issued['expires']) : $today + 1;
if (!empty($CFG->badges_allowexternalbackpack) && ($expiration > $today) && badges_user_has_backpack($USER->id)) {
$assertion = new moodle_url('/badges/assertion.php', array('b' => $ibadge->hash));
+ $action = new component_action('click', 'addtobackpack', array('assertion' => $assertion->out(false)));
$attributes = array(
- 'type' => 'button',
- 'value' => get_string('addtobackpack', 'badges'),
- 'onclick' => 'OpenBadges.issue(["' . $assertion->out(false) . '"], function(errors, successes) { })');
+ 'type' => 'button',
+ 'id' => 'addbutton',
+ 'value' => get_string('addtobackpack', 'badges'));
$tobackpack = html_writer::tag('input', '', $attributes);
+ $this->output->add_action_handler($action, 'addbutton');
$imagetable->data[] = array($tobackpack);
}
}
@@ -459,7 +462,7 @@ class core_badges_renderer extends plugin_renderer_base {
get_string('downloadall'), 'POST', array('class' => 'activatebadge'));
// Local badges.
- $localhtml = html_writer::start_tag('fieldset', array('class' => 'generalbox'));
+ $localhtml = html_writer::start_tag('fieldset', array('id' => 'issued-badge-table', 'class' => 'generalbox'));
$localhtml .= html_writer::tag('legend',
$this->output->heading_with_help(get_string('localbadges', 'badges', $SITE->fullname), 'localbadgesh', 'badges'));
if ($badges->badges) {
diff --git a/blocks/comments/tests/behat/behat_block_comments.php b/blocks/comments/tests/behat/behat_block_comments.php
index 548246062a3..3cac07a0d8f 100644
--- a/blocks/comments/tests/behat/behat_block_comments.php
+++ b/blocks/comments/tests/behat/behat_block_comments.php
@@ -63,7 +63,7 @@ class behat_block_comments extends behat_base {
$commentstextarea = $this->find('css', '.comment-area textarea', $exception);
$commentstextarea->setValue($comment);
- $this->find_link('Save comment')->click();
+ $this->find_link(get_string('savecomment'))->click();
// Wait for the AJAX request.
$this->getSession()->wait(4 * 1000, false);
@@ -91,8 +91,11 @@ class behat_block_comments extends behat_base {
$exception = new ElementNotFoundException($this->getSession(), '"' . $comment . '" comment ');
- $commentxpath = "//div[contains(concat(' ', @class, ' '), ' block_comments ')]" .
- "/descendant::div[@class='comment-message'][contains(., '" . $comment . "')]";
+ // Using xpath liternal to avoid possible problems with comments containing quotes.
+ $commentliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($comment);
+
+ $commentxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' block_comments ')]" .
+ "/descendant::div[@class='comment-message'][contains(., $commentliteral)]";
$commentnode = $this->find('xpath', $commentxpath, $exception);
// Click on delete icon.
@@ -101,7 +104,7 @@ class behat_block_comments extends behat_base {
$deleteicon->click();
// Yes confirm.
- $confirmnode = $this->find('xpath', "//div[@class='comment-delete-confirm']/descendant::a[contains(., 'Yes')]");
+ $confirmnode = $this->find('xpath', "//div[@class='comment-delete-confirm']/descendant::a[contains(., '" . get_string('yes') . "')]");
$confirmnode->click();
// Wait for the AJAX request.
diff --git a/blocks/course_overview/renderer.php b/blocks/course_overview/renderer.php
index bb2956908ad..6f33899ac12 100644
--- a/blocks/course_overview/renderer.php
+++ b/blocks/course_overview/renderer.php
@@ -103,10 +103,10 @@ class block_course_overview_renderer extends plugin_renderer_base {
// No need to pass title through s() here as it will be done automatically by html_writer.
$attributes = array('title' => $course->fullname);
- if (empty($course->visible)) {
- $attributes['class'] = 'dimmed';
- }
if ($course->id > 0) {
+ if (empty($course->visible)) {
+ $attributes['class'] = 'dimmed';
+ }
$courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
$coursefullname = format_string($course->fullname, true, $course->id);
$link = html_writer::link($courseurl, $coursefullname, $attributes);
diff --git a/blocks/tests/behat/behat_blocks.php b/blocks/tests/behat/behat_blocks.php
index 1c8f70d9aa1..42fe9a117c7 100644
--- a/blocks/tests/behat/behat_blocks.php
+++ b/blocks/tests/behat/behat_blocks.php
@@ -46,13 +46,13 @@ class behat_blocks extends behat_base {
* @param string $blockname
*/
public function i_add_the_block($blockname) {
- $steps = new Given('I select "' . $blockname . '" from "bui_addblock"');
+ $steps = new Given('I select "' . $this->escape($blockname) . '" from "bui_addblock"');
// If we are running without javascript we need to submit the form.
if (!$this->running_javascript()) {
$steps = array(
$steps,
- new Given('I click on "Go" "button" in the "#add_block" "css_element"')
+ new Given('I click on "' . get_string('go') . '" "button" in the "#add_block" "css_element"')
);
}
return $steps;
diff --git a/blocks/tests/behat/configure_block_throughout_site.feature b/blocks/tests/behat/configure_block_throughout_site.feature
index fb97f0483d1..9f74446e7ca 100644
--- a/blocks/tests/behat/configure_block_throughout_site.feature
+++ b/blocks/tests/behat/configure_block_throughout_site.feature
@@ -32,4 +32,4 @@ Feature: Add and configure blocks throughout the site
And I press "Save changes"
And I follow "Course 1"
# The first block matching the pattern should be top-left block
- And I should see "Comments" in the "//*[@id='region-pre']/descendant::div[contains(concat(' ', @class, ' '), ' block ')]" "xpath_element"
+ And I should see "Comments" in the "//*[@id='region-pre']/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]" "xpath_element"
diff --git a/cache/stores/memcache/addinstanceform.php b/cache/stores/memcache/addinstanceform.php
index 984966a8b56..eb7c999a1be 100644
--- a/cache/stores/memcache/addinstanceform.php
+++ b/cache/stores/memcache/addinstanceform.php
@@ -49,7 +49,8 @@ class cachestore_memcache_addinstance_form extends cachestore_addinstance_form {
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_memcache'),
array('maxlength' => 5, 'size' => 5));
$form->addHelpButton('prefix', 'prefix', 'cachestore_memcache');
- $form->setType('prefix', PARAM_ALPHAEXT);
+ $form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
$form->setDefault('prefix', 'mdl_');
+ $form->addRule('prefix', get_string('prefixinvalid', 'cachestore_memcache'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
}
}
\ No newline at end of file
diff --git a/cache/stores/memcache/lang/en/cachestore_memcache.php b/cache/stores/memcache/lang/en/cachestore_memcache.php
index 5cba887de78..fb87d289836 100644
--- a/cache/stores/memcache/lang/en/cachestore_memcache.php
+++ b/cache/stores/memcache/lang/en/cachestore_memcache.php
@@ -31,6 +31,7 @@ $string['prefix'] = 'Key prefix';
$string['prefix_help'] = 'This prefix is used for all key names on the memcache server.
* If you only have one Moodle instance using this server, you can leave this value default.
* Due to key length restrictions, a maximum of 5 characters is permitted.';
+$string['prefixinvalid'] = 'Invalid prefix. You can only use a-z A-Z 0-9-_.';
$string['servers'] = 'Servers';
$string['servers_help'] = 'This sets the servers that should be utilised by this memcache adapter.
Servers should be defined one per line and consist of a server address and optionally a port and weight.
diff --git a/cache/stores/memcache/lib.php b/cache/stores/memcache/lib.php
index 882ab3775cb..2e104bd6385 100644
--- a/cache/stores/memcache/lib.php
+++ b/cache/stores/memcache/lib.php
@@ -404,7 +404,7 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
* Generates an instance of the cache store that can be used for testing.
*
* @param cache_definition $definition
- * @return false
+ * @return cachestore_memcache|false
*/
public static function initialise_test_instance(cache_definition $definition) {
if (!self::are_requirements_met()) {
diff --git a/cache/stores/memcache/tests/memcache_test.php b/cache/stores/memcache/tests/memcache_test.php
index 023e6cc699b..4e3c2aa9e43 100644
--- a/cache/stores/memcache/tests/memcache_test.php
+++ b/cache/stores/memcache/tests/memcache_test.php
@@ -59,4 +59,35 @@ class cachestore_memcache_test extends cachestore_tests {
protected function get_class_name() {
return 'cachestore_memcache';
}
-}
\ No newline at end of file
+
+ /**
+ * Tests the valid keys to ensure they work.
+ */
+ public function test_valid_keys() {
+ $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcache', 'phpunit_test');
+ $instance = cachestore_memcache::initialise_test_instance($definition);
+
+ if (!$instance) { // Something prevented memcache store to be inited (extension, TEST_CACHESTORE_MEMCACHE_TESTSERVERS...).
+ $this->markTestSkipped();
+ }
+
+ $keys = array(
+ // Alphanumeric.
+ 'abc', 'ABC', '123', 'aB1', '1aB',
+ // Hyphens.
+ 'a-1', '1-a', '-a1', 'a1-',
+ // Underscores.
+ 'a_1', '1_a', '_a1', 'a1_'
+ );
+ foreach ($keys as $key) {
+ $this->assertTrue($instance->set($key, $key), "Failed to set key `$key`");
+ }
+ foreach ($keys as $key) {
+ $this->assertEquals($key, $instance->get($key), "Failed to get key `$key`");
+ }
+ $values = $instance->get_many($keys);
+ foreach ($values as $key => $value) {
+ $this->assertEquals($key, $value);
+ }
+ }
+}
diff --git a/cache/stores/memcached/addinstanceform.php b/cache/stores/memcached/addinstanceform.php
index a8e98bf0f48..c833a18bbae 100644
--- a/cache/stores/memcached/addinstanceform.php
+++ b/cache/stores/memcached/addinstanceform.php
@@ -57,11 +57,12 @@ class cachestore_memcached_addinstance_form extends cachestore_addinstance_form
$form->addElement('select', 'serialiser', get_string('useserialiser', 'cachestore_memcached'), $serialiseroptions);
$form->addHelpButton('serialiser', 'useserialiser', 'cachestore_memcached');
$form->setDefault('serialiser', Memcached::SERIALIZER_PHP);
- $form->setType('serialiser', PARAM_NUMBER);
+ $form->setType('serialiser', PARAM_INT);
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_memcached'), array('size' => 16));
- $form->setType('prefix', PARAM_ALPHANUM);
+ $form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
$form->addHelpButton('prefix', 'prefix', 'cachestore_memcached');
+ $form->addRule('prefix', get_string('prefixinvalid', 'cachestore_memcached'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
$hashoptions = cachestore_memcached::config_get_hash_options();
$form->addElement('select', 'hash', get_string('hash', 'cachestore_memcached'), $hashoptions);
@@ -74,4 +75,4 @@ class cachestore_memcached_addinstance_form extends cachestore_addinstance_form
$form->setDefault('bufferwrites', 0);
$form->setType('bufferwrites', PARAM_BOOL);
}
-}
\ No newline at end of file
+}
diff --git a/cache/stores/memcached/lang/en/cachestore_memcached.php b/cache/stores/memcached/lang/en/cachestore_memcached.php
index 67c3cd22ec2..87713f6755d 100644
--- a/cache/stores/memcached/lang/en/cachestore_memcached.php
+++ b/cache/stores/memcached/lang/en/cachestore_memcached.php
@@ -42,6 +42,7 @@ $string['hash_murmur'] = 'Murmur';
$string['pluginname'] = 'Memcached';
$string['prefix'] = 'Prefix key';
$string['prefix_help'] = 'This can be used to create a "domain" for your item keys allowing you to create multiple memcached stores on a single memcached installation. It cannot be longer than 16 characters in order to ensure key length issues are not encountered.';
+$string['prefixinvalid'] = 'Invalid prefix. You can only use a-z A-Z 0-9-_.';
$string['serialiser_igbinary'] = 'The igbinary serializer.';
$string['serialiser_json'] = 'The JSON serializer.';
$string['serialiser_php'] = 'The default PHP serializer.';
diff --git a/cache/stores/memcached/lib.php b/cache/stores/memcached/lib.php
index 6e3834cce0f..a74dc83e053 100644
--- a/cache/stores/memcached/lib.php
+++ b/cache/stores/memcached/lib.php
@@ -339,7 +339,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
$options[Memcached::SERIALIZER_JSON] = get_string('serialiser_json', 'cachestore_memcached');
}
if (Memcached::HAVE_IGBINARY) {
- $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_php', 'cachestore_memcached');
+ $options[Memcached::SERIALIZER_IGBINARY] = get_string('serialiser_igbinary', 'cachestore_memcached');
}
return $options;
}
@@ -444,7 +444,7 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
* Generates an instance of the cache store that can be used for testing.
*
* @param cache_definition $definition
- * @return false
+ * @return cachestore_memcached|false
*/
public static function initialise_test_instance(cache_definition $definition) {
diff --git a/cache/stores/memcached/tests/memcached_test.php b/cache/stores/memcached/tests/memcached_test.php
index f0896ca4954..80a918a38c7 100644
--- a/cache/stores/memcached/tests/memcached_test.php
+++ b/cache/stores/memcached/tests/memcached_test.php
@@ -59,4 +59,35 @@ class cachestore_memcached_test extends cachestore_tests {
protected function get_class_name() {
return 'cachestore_memcached';
}
-}
\ No newline at end of file
+
+ /**
+ * Tests the valid keys to ensure they work.
+ */
+ public function test_valid_keys() {
+ $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
+ $instance = cachestore_memcached::initialise_test_instance($definition);
+
+ if (!$instance) { // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
+ $this->markTestSkipped();
+ }
+
+ $keys = array(
+ // Alphanumeric.
+ 'abc', 'ABC', '123', 'aB1', '1aB',
+ // Hyphens.
+ 'a-1', '1-a', '-a1', 'a1-',
+ // Underscores.
+ 'a_1', '1_a', '_a1', 'a1_'
+ );
+ foreach ($keys as $key) {
+ $this->assertTrue($instance->set($key, $key), "Failed to set key `$key`");
+ }
+ foreach ($keys as $key) {
+ $this->assertEquals($key, $instance->get($key), "Failed to get key `$key`");
+ }
+ $values = $instance->get_many($keys);
+ foreach ($values as $key => $value) {
+ $this->assertEquals($key, $value);
+ }
+ }
+}
diff --git a/cache/tests/fixtures/stores.php b/cache/tests/fixtures/stores.php
index 2927b4b8f07..a350f6d37fb 100644
--- a/cache/tests/fixtures/stores.php
+++ b/cache/tests/fixtures/stores.php
@@ -62,7 +62,7 @@ abstract class cachestore_tests extends advanced_testcase {
}
}
if ($modes & cache_store::MODE_SESSION) {
- $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, $class, 'phpunit_test');
+ $definition = cache_definition::load_adhoc(cache_store::MODE_SESSION, $class, 'phpunit_test');
$instance = $class::initialise_test_instance($definition);
if (!$instance) {
$this->markTestSkipped('Could not test '.$class.'. No test instance configured for session caches.');
@@ -71,7 +71,7 @@ abstract class cachestore_tests extends advanced_testcase {
}
}
if ($modes & cache_store::MODE_REQUEST) {
- $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, $class, 'phpunit_test');
+ $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $class, 'phpunit_test');
$instance = $class::initialise_test_instance($definition);
if (!$instance) {
$this->markTestSkipped('Could not test '.$class.'. No test instance configured for request caches.');
diff --git a/cohort/tests/behat/behat_cohort.php b/cohort/tests/behat/behat_cohort.php
index 68ff6073fcd..5349c53e751 100644
--- a/cohort/tests/behat/behat_cohort.php
+++ b/cohort/tests/behat/behat_cohort.php
@@ -54,10 +54,10 @@ class behat_cohort extends behat_base {
$userid = $DB->get_field('user', 'id', array('username' => $username));
$steps = array(
- new Given('I click on "Assign" "link" in the "//table[@id=\'cohorts\']//tr[contains(., \'' . $cohortidnumber . '\')]" "xpath_element"'),
- new Given('I select "' . $userid . '" from "Potential users"'),
- new Given('I press "Add"'),
- new Given('I press "Back to cohorts"')
+ new Given('I click on "' . get_string('assign', 'cohort') . '" "link" in the "' . $this->escape($cohortidnumber) . '" table row'),
+ new Given('I select "' . $userid . '" from "' . get_string('potusers', 'cohort') . '"'),
+ new Given('I press "' . get_string('add') . '"'),
+ new Given('I press "' . get_string('backtocohorts', 'cohort') . '"')
);
// If we are not in the cohorts management we should move there before anything else.
@@ -65,11 +65,11 @@ class behat_cohort extends behat_base {
$steps = array_merge(
array(
new Given('I am on homepage'),
- new Given('I collapse "Front page settings" node'),
- new Given('I expand "Site administration" node'),
- new Given('I expand "Users" node'),
- new Given('I expand "Accounts" node'),
- new Given('I follow "Cohorts"')
+ new Given('I collapse "' . get_string('frontpagesettings', 'admin') . '" node'),
+ new Given('I expand "' . get_string('administrationsite') . '" node'),
+ new Given('I expand "' . get_string('users', 'admin') . '" node'),
+ new Given('I expand "' . get_string('accounts', 'admin') . '" node'),
+ new Given('I follow "' . get_string('cohorts', 'cohort') . '"')
),
$steps
);
diff --git a/cohort/tests/behat/upload_cohort_users.feature b/cohort/tests/behat/upload_cohort_users.feature
index c40095f16db..47b169a29d7 100644
--- a/cohort/tests/behat/upload_cohort_users.feature
+++ b/cohort/tests/behat/upload_cohort_users.feature
@@ -32,11 +32,11 @@ Feature: Upload users to a cohort
And I press "Upload users"
And I press "Continue"
And I follow "Cohorts"
- And I click on "Assign" "link" in the "//table[@id='cohorts']//tr[contains(., 'Cohort 1')]" "xpath_element"
+ And I click on "Assign" "link" in the "Cohort 1" table row
Then the "Current users" select box should contain "Tom Jones (tomjones@example.com)"
And the "Current users" select box should contain "Bob Jones (bobjones@example.com)"
And I press "Back to cohorts"
- And I click on "Assign" "link" in the "//table[@id='cohorts']//tr[contains(., 'Cohort 2')]" "xpath_element"
+ And I click on "Assign" "link" in the "Cohort 2" table row
And the "Current users" select box should contain "Mary Smith (marysmith@example.com)"
And the "Current users" select box should contain "Alice Smith (alicesmith@example.com)"
And I am on homepage
diff --git a/completion/tests/behat/behat_completion.php b/completion/tests/behat/behat_completion.php
index ef05aeb328d..89b6344b85f 100644
--- a/completion/tests/behat/behat_completion.php
+++ b/completion/tests/behat/behat_completion.php
@@ -50,12 +50,13 @@ class behat_completion extends behat_base {
public function user_has_completed_activity($userfullname, $activityname) {
// Will throw an exception if the element can not be hovered.
+ $titleliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($userfullname . ", " . $activityname . ": Completed");
$xpath = "//table[@id='completion-progress']" .
- "/descendant::img[contains(@title, '" . $userfullname . ", " . $activityname . ": Completed')]";
+ "/descendant::img[contains(@title, $titleliteral)]";
return array(
new Given('I go to the current course activity completion report'),
- new Given('I hover "' . $xpath . '" "xpath_element"')
+ new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
);
}
@@ -68,11 +69,13 @@ class behat_completion extends behat_base {
*/
public function user_has_not_completed_activity($userfullname, $activityname) {
+ // Will throw an exception if the element can not be hovered.
+ $titleliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($userfullname . ", " . $activityname . ": Not completed");
$xpath = "//table[@id='completion-progress']" .
- "/descendant::img[contains(@title, '" . $userfullname . ", " . $activityname . ": Not completed')]";
+ "/descendant::img[contains(@title, $titleliteral)]";
return array(
new Given('I go to the current course activity completion report'),
- new Given('I hover "' . $xpath . '" "xpath_element"')
+ new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
);
return $steps;
@@ -89,15 +92,15 @@ class behat_completion extends behat_base {
// Expand reports node if we can't see the link.
try {
- $this->find('xpath', "//*[@id='settingsnav']" .
+ $this->find('xpath', "//div[@id='settingsnav']" .
"/descendant::li" .
- "/descendant::li[not(contains(@class,'collapsed'))]" .
- "/descendant::p[contains(., 'Activity completion')]");
+ "/descendant::li[not(contains(concat(' ', normalize-space(@class), ' '), ' collapsed '))]" .
+ "/descendant::p[contains(., '" . get_string('pluginname', 'report_progress') . "')]");
} catch (ElementNotFoundException $e) {
- $steps[] = new Given('I expand "Reports" node');
+ $steps[] = new Given('I expand "' . get_string('reports') . '" node');
}
- $steps[] = new Given('I follow "Activity completion"');
+ $steps[] = new Given('I follow "' . get_string('pluginname', 'report_progress') . '"');
return $steps;
}
diff --git a/course/renderer.php b/course/renderer.php
index 9bc279d7a21..d8ea0e58e68 100644
--- a/course/renderer.php
+++ b/course/renderer.php
@@ -670,7 +670,6 @@ class core_course_renderer extends plugin_renderer_base {
//Accessibility: for files get description via icon, this is very ugly hack!
$instancename = $mod->get_formatted_name();
- $altname = '';
$altname = $mod->modfullname;
// Avoid unnecessary duplication: if e.g. a forum name already
// includes the word forum (or Forum, etc) then it is unhelpful
@@ -706,7 +705,7 @@ class core_course_renderer extends plugin_renderer_base {
}
if ($mod->uservisible) {
// show accessibility note only if user can access the module himself
- $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
+ $accesstext = get_accesshide(get_string('hiddenfromstudents').':'. $mod->modfullname);
}
}
@@ -723,7 +722,7 @@ class core_course_renderer extends plugin_renderer_base {
// Display link itself.
$activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
- 'class' => 'iconlarge activityicon', 'alt' => $mod->modfullname)) . $accesstext .
+ 'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) . $accesstext .
html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
if ($mod->uservisible) {
$output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick)) .
diff --git a/course/tests/behat/activities_group_icons.feature b/course/tests/behat/activities_group_icons.feature
index 78c342aa7cb..65abea5d721 100644
--- a/course/tests/behat/activities_group_icons.feature
+++ b/course/tests/behat/activities_group_icons.feature
@@ -27,21 +27,21 @@ Feature: Toggle activities groups mode from the course page
| Force group mode | No |
When I press "Save changes"
Then "No groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groupn')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groupn')]" "xpath_element" should exists
And I click on "No groups (Click to change)" "link" in the "Test forum name" activity
And I wait "3" seconds
And "Separate groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groups')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groups')]" "xpath_element" should exists
And I reload the page
And "Separate groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groups')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groups')]" "xpath_element" should exists
And I click on "Separate groups (Click to change)" "link" in the "Test forum name" activity
And I wait "3" seconds
And "Visible groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groupv')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groupv')]" "xpath_element" should exists
And I reload the page
And "Visible groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groupv')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groupv')]" "xpath_element" should exists
And I click on "Visible groups (Click to change)" "link" in the "Test forum name" activity
And "No groups (Click to change)" "link" should exists
- And ".//a//img[contains(@src, 'groupn')]" "xpath_element" should exists
+ And "//a/child::img[contains(@src, 'groupn')]" "xpath_element" should exists
diff --git a/course/tests/behat/activities_indentation.feature b/course/tests/behat/activities_indentation.feature
index 5621e49aae9..7cd00794680 100644
--- a/course/tests/behat/activities_indentation.feature
+++ b/course/tests/behat/activities_indentation.feature
@@ -26,7 +26,7 @@ Feature: Indent items on the course page
When I indent right "Test glossary name" activity
Then "#section-1 li.glossary div.mod-indent-1" "css_element" should exists
And I indent right "Test glossary name" activity
- And "//*[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should exists
+ And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should exists
And "#section-1 li.glossary div.mod-indent-2" "css_element" should exists
And I reload the page
And "#section-1 li.glossary div.mod-indent-2" "css_element" should exists
@@ -34,4 +34,4 @@ Feature: Indent items on the course page
And I indent left "Test glossary name" activity
And "#section-1 li.glossary div.mod-indent-2" "css_element" should not exists
And "#section-1 li.glossary div.mod-indent-1" "css_element" should not exists
- And "//*[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should not exists
+ And "//li[@id='section-1']/descendant::li[contains(concat(' ', @class, ' '), ' glossary ')]/descendant::a[@title='Move left']" "xpath_element" should not exists
diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php
index c12a9381941..49145f29859 100644
--- a/course/tests/behat/behat_course.php
+++ b/course/tests/behat/behat_course.php
@@ -48,7 +48,7 @@ class behat_course extends behat_base {
* @Given /^I turn editing mode on$/
*/
public function i_turn_editing_mode_on() {
- return new Given('I press "Turn editing on"');
+ return new Given('I press "' . get_string('turneditingon') . '"');
}
/**
@@ -56,7 +56,7 @@ class behat_course extends behat_base {
* @Given /^I turn editing mode off$/
*/
public function i_turn_editing_mode_off() {
- return new Given('I press "Turn editing off"');
+ return new Given('I press "' . get_string('turneditingoff') . '"');
}
/**
@@ -68,9 +68,9 @@ class behat_course extends behat_base {
public function i_create_a_course_with(TableNode $table) {
return array(
new Given('I go to the courses management page'),
- new Given('I press "Add a new course"'),
+ new Given('I press "' . get_string('addnewcourse') . '"'),
new Given('I fill the moodle form with:', $table),
- new Given('I press "Save changes"')
+ new Given('I press "' . get_string('savechanges') . '"')
);
}
@@ -83,9 +83,9 @@ class behat_course extends behat_base {
return array(
new Given('I am on homepage'),
- new Given('I expand "Site administration" node'),
- new Given('I expand "Courses" node'),
- new Given('I follow "Add/edit courses"'),
+ new Given('I expand "' . get_string('administrationsite') . '" node'),
+ new Given('I expand "' . get_string('courses', 'admin') . '" node'),
+ new Given('I follow "' . get_string('coursemgmt', 'admin') . '"'),
);
}
@@ -100,9 +100,9 @@ class behat_course extends behat_base {
public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) {
return array(
- new Given('I add a "'.$activity.'" to section "'.$section.'"'),
+ new Given('I add a "' . $this->escape($activity) . '" to section "' . $this->escape($section) . '"'),
new Given('I fill the moodle form with:', $data),
- new Given('I press "Save and return to course"')
+ new Given('I press "' . get_string('savechangesandreturntocourse') . '"')
);
}
@@ -116,7 +116,9 @@ class behat_course extends behat_base {
*/
public function i_add_to_section($activity, $section) {
- $sectionxpath = "//*[@id='section-" . $section . "']";
+ $sectionxpath = "//li[@id='section-" . $section . "']";
+
+ $activityliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral(ucfirst($activity));
if ($this->running_javascript()) {
@@ -126,9 +128,9 @@ class behat_course extends behat_base {
$sectionnode->click();
// Clicks the selected activity if it exists.
- $activity = ucfirst($activity);
$activityxpath = "//div[@id='chooseform']/descendant::label" .
- "/descendant::span[contains(concat(' ', @class, ' '), ' typename ')][contains(.,'" . $activity . "')]" .
+ "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
+ "[contains(., $activityliteral)]" .
"/parent::label/child::input";
$activitynode = $this->find('xpath', $activityxpath);
$activitynode->doubleClick();
@@ -137,8 +139,8 @@ class behat_course extends behat_base {
// Without Javascript.
// Selecting the option from the select box which contains the option.
- $selectxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' section_add_menus ')]" .
- "/descendant::select[contains(., '" . $activity . "')]";
+ $selectxpath = $sectionxpath . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
+ "/descendant::select[contains(., $activityliteral)]";
$selectnode = $this->find('xpath', $selectxpath);
$selectnode->selectOption($activity);
@@ -162,7 +164,7 @@ class behat_course extends behat_base {
$xpath = $this->section_exists($sectionnumber);
return array(
- new Given('I click on "' . get_string('markthistopic') . '" "link" in the "' . $xpath . '" "xpath_element"'),
+ new Given('I click on "' . get_string('markthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"'),
new Given('I wait "2" seconds')
);
}
@@ -179,7 +181,7 @@ class behat_course extends behat_base {
$xpath = $this->section_exists($sectionnumber);
return array(
- new Given('I click on "' . get_string('markedthistopic') . '" "link" in the "' . $xpath . '" "xpath_element"'),
+ new Given('I click on "' . get_string('markedthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"'),
new Given('I wait "2" seconds')
);
}
@@ -268,7 +270,7 @@ class behat_course extends behat_base {
// Section should be hidden.
$exception = new ExpectationException('The section is not hidden', $this->getSession());
- $this->find('xpath', $sectionxpath . "[contains(concat(' ', @class, ' '), ' hidden ')]", $exception);
+ $this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception);
// The checking are different depending on user permissions.
if ($this->is_course_editor()) {
@@ -284,8 +286,8 @@ class behat_course extends behat_base {
foreach ($activities as $activity) {
// Dimmed.
- $this->find('xpath', "//div[contains(concat(' ', @class, ' '), ' activityinstance ')]" .
- "/a[contains(concat(' ', @class, ' '), ' dimmed ')]", $dimmedexception, $activity);
+ $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
+ "/a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
// Non-JS browsers can not click on img elements.
if ($this->running_javascript()) {
@@ -319,7 +321,8 @@ class behat_course extends behat_base {
$sectionxpath = $this->section_exists($sectionnumber);
// Section should not be hidden.
- if (!$this->getSession()->getPage()->find('xpath', $sectionxpath . "[not(contains(concat(' ', @class, ' '), ' hidden '))]")) {
+ $xpath = $sectionxpath . "[not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]";
+ if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
throw new ExpectationException('The section is hidden', $this->getSession());
}
@@ -449,10 +452,11 @@ class behat_course extends behat_base {
// JS enabled.
if ($this->running_javascript()) {
- $destinationxpath = $sectionxpath . "/descendant::ul[contains(@class, 'yui3-dd-drop')]";
+ $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
return array(
- new Given('I drag "' . $activitynode->getXpath() . '" "xpath_element" and I drop it in "' . $destinationxpath . '" "xpath_element"'),
+ new Given('I drag "' . $this->escape($activitynode->getXpath()) . '" "xpath_element" ' .
+ 'and I drop it in "' . $this->escape($destinationxpath) . '" "xpath_element"'),
);
} else {
@@ -460,8 +464,8 @@ class behat_course extends behat_base {
// Moving to the fist spot of the section (before all other section's activities).
return array(
- new Given('I click on "a.editing_move" "css_element" in the "' . $activityname . '" activity'),
- new Given('I click on "li.movehere a" "css_element" in the "' . $sectionxpath . '" "xpath_element"'),
+ new Given('I click on "a.editing_move" "css_element" in the "' . $this->escape($activityname) . '" activity'),
+ new Given('I click on "li.movehere a" "css_element" in the "' . $this->escape($sectionxpath) . '" "xpath_element"'),
);
}
}
@@ -482,8 +486,8 @@ class behat_course extends behat_base {
// Adding chr(10) to save changes.
return array(
- new Given('I click on "' . get_string('edittitle') . '" "link" in the "' . $activityname .'" activity'),
- new Given('I fill in "title" with "' . $newactivityname . chr(10) . '"'),
+ new Given('I click on "' . get_string('edittitle') . '" "link" in the "' . $this->escape($activityname) .'" activity'),
+ new Given('I fill in "title" with "' . $this->escape($newactivityname) . chr(10) . '"'),
new Given('I wait "2" seconds')
);
}
@@ -497,7 +501,7 @@ class behat_course extends behat_base {
public function i_indent_right_activity($activityname) {
$steps = array(
- new Given('I click on "' . get_string('moveright') . '" "link" in the "' . $activityname . '" activity')
+ new Given('I click on "' . get_string('moveright') . '" "link" in the "' . $this->escape($activityname) . '" activity')
);
if ($this->running_javascript()) {
@@ -516,7 +520,7 @@ class behat_course extends behat_base {
public function i_indent_left_activity($activityname) {
$steps = array(
- new Given('I click on "' . get_string('moveleft') . '" "link" in the "' . $activityname . '" activity')
+ new Given('I click on "' . get_string('moveleft') . '" "link" in the "' . $this->escape($activityname) . '" activity')
);
if ($this->running_javascript()) {
@@ -553,7 +557,7 @@ class behat_course extends behat_base {
// With JS disabled.
$steps = array(
- new Given('I click on "' . $deletestring . '" "link" in the "' . $activityname . '" activity'),
+ new Given('I click on "' . $this->escape($deletestring) . '" "link" in the "' . $this->escape($activityname) . '" activity'),
new Given('I press "' . get_string('yes') . '"')
);
@@ -569,7 +573,7 @@ class behat_course extends behat_base {
*/
public function i_duplicate_activity($activityname) {
return array(
- new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $activityname . '" activity'),
+ new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $this->escape($activityname) . '" activity'),
new Given('I press "' . get_string('continue') .'"'),
new Given('I press "' . get_string('duplicatecontcourse') .'"')
);
@@ -584,7 +588,7 @@ class behat_course extends behat_base {
*/
public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) {
return array(
- new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $activityname . '" activity'),
+ new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $this->escape($activityname) . '" activity'),
new Given('I press "' . get_string('continue') .'"'),
new Given('I press "' . get_string('duplicatecontedit') . '"'),
new Given('I fill the moodle form with:', $data),
@@ -657,9 +661,9 @@ class behat_course extends behat_base {
$courseformat = $this->get_course_format();
// Checking the show button alt text and show icon.
- $showtext = get_string('showfromothers', $courseformat);
- $linkxpath = $xpath . "/descendant::a[@title='". $showtext ."']";
- $imgxpath = $linkxpath . "/descendant::img[@alt='". $showtext ."'][contains(@src, 'show')]";
+ $showtext = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('showfromothers', $courseformat));
+ $linkxpath = $xpath . "/descendant::a[@title=$showtext]";
+ $imgxpath = $linkxpath . "/descendant::img[@alt=$showtext][contains(@src, 'show')]";
$exception = new ElementNotFoundException($this->getSession(), 'Show section icon ');
$this->find('xpath', $imgxpath, $exception);
@@ -684,9 +688,9 @@ class behat_course extends behat_base {
$courseformat = $this->get_course_format();
// Checking the hide button alt text and hide icon.
- $hidetext = get_string('hidefromothers', $courseformat);
- $linkxpath = $xpath . "/descendant::a[@title='" . $hidetext . "']";
- $imgxpath = $linkxpath . "/descendant::img[@alt='" . $hidetext ."'][contains(@src, 'hide')]";
+ $hidetext = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('hidefromothers', $courseformat));
+ $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
+ $imgxpath = $linkxpath . "/descendant::img[@alt=$hidetext][contains(@src, 'hide')]";
$exception = new ElementNotFoundException($this->getSession(), 'Hide section icon ');
$this->find('xpath', $imgxpath, $exception);
@@ -730,7 +734,7 @@ class behat_course extends behat_base {
*/
protected function get_section_activities($sectionxpath) {
- $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', @class, ' '), ' activity ')]";
+ $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]";
// We spin here, as activities usually require a lot of time to load.
try {
@@ -751,8 +755,8 @@ class behat_course extends behat_base {
*/
protected function get_activity_node($activityname) {
- $activityname = str_replace("'", "\'", $activityname);
- $xpath = "//li[contains(concat(' ', @class, ' '), ' activity ')][contains(., '" .$activityname. "')]";
+ $activityname = $this->getSession()->getSelectorsHandler()->xpathLiteral($activityname);
+ $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]";
return $this->find('xpath', $xpath);
}
@@ -765,8 +769,8 @@ class behat_course extends behat_base {
protected function is_course_editor() {
// We don't need to behat_base::spin() here as all is already loaded.
- if (!$this->getSession()->getPage()->findButton('Turn editing off') &&
- !$this->getSession()->getPage()->findButton('Turn editing on')) {
+ if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
+ !$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
return false;
}
diff --git a/course/tests/behat/force_group_mode.feature b/course/tests/behat/force_group_mode.feature
index 1d88c8d4a54..59cc3219878 100644
--- a/course/tests/behat/force_group_mode.feature
+++ b/course/tests/behat/force_group_mode.feature
@@ -28,11 +28,11 @@ Feature: Force group mode in a course
| Group mode | Separate groups |
| Force group mode | Yes |
When I press "Save changes"
- Then ".//a//img[contains(./@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'Separate groups (forced mode)')]" "xpath_element" should exists
- And I click on "//img[contains(./@alt, 'Separate groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
- And ".//a//img[contains(./@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'Separate groups (forced mode)')]" "xpath_element" should exists
+ Then "//a/child::img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should exists
+ And I click on "//img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
+ And "//a/child::img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should exists
@javascript
Scenario: Forced group mode using visible groups
@@ -40,11 +40,11 @@ Feature: Force group mode in a course
| Group mode | Visible groups |
| Force group mode | Yes |
And I press "Save changes"
- Then ".//a//img[contains(./@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'Visible groups (forced mode)')]" "xpath_element" should exists
- And I click on "//img[contains(./@alt, 'Visible groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
- And ".//a//img[contains(./@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'Visible groups (forced mode)')]" "xpath_element" should exists
+ Then "//a/child::img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should exists
+ And I click on "//img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
+ And "//a/child::img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should exists
@javascript
Scenario: Forced group mode without groups
@@ -52,9 +52,9 @@ Feature: Force group mode in a course
| Group mode | No groups |
| Force group mode | Yes |
And I press "Save changes"
- Then ".//a//img[contains(./@alt, 'No groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'No groups (forced mode)')]" "xpath_element" should exists
- And I click on "//img[contains(./@alt, 'No groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
- And ".//a//img[contains(./@alt, 'No groups (forced mode)')]" "xpath_element" should not exists
- And ".//img[contains(./@alt, 'No groups (forced mode)')]" "xpath_element" should exists
+ Then "//a/child::img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should exists
+ And I click on "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" in the "li.activity.chat" "css_element"
+ And "//a/child::img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exists
+ And "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should exists
diff --git a/enrol/tests/behat/behat_enrol.php b/enrol/tests/behat/behat_enrol.php
index 0b18d78b202..7e751d517aa 100644
--- a/enrol/tests/behat/behat_enrol.php
+++ b/enrol/tests/behat/behat_enrol.php
@@ -50,11 +50,11 @@ class behat_enrol extends behat_base {
public function i_add_enrolment_method_with($enrolmethod, TableNode $table) {
return array(
- new Given('I expand "Users" node'),
- new Given('I follow "Enrolment methods"'),
- new Given('I select "' . $enrolmethod . '" from "Add method"'),
+ new Given('I expand "' . get_string('users', 'admin') . '" node'),
+ new Given('I follow "' . get_string('type_enrol_plural', 'plugin') . '"'),
+ new Given('I select "' . $this->escape($enrolmethod) . '" from "' . get_string('addinstance', 'enrol') . '"'),
new Given('I fill the moodle form with:', $table),
- new Given('I press "Add method"')
+ new Given('I press "' . get_string('addinstance', 'enrol') . '"')
);
}
diff --git a/grade/grading/form/guide/lib.php b/grade/grading/form/guide/lib.php
index c5292ba8eb0..942b19b37fb 100644
--- a/grade/grading/form/guide/lib.php
+++ b/grade/grading/form/guide/lib.php
@@ -820,10 +820,9 @@ class gradingform_guide_instance extends gradingform_instance {
/**
* Calculates the grade to be pushed to the gradebook
*
- * @return int the valid grade from $this->get_controller()->get_grade_range()
+ * @return float|int the valid grade from $this->get_controller()->get_grade_range()
*/
public function get_grade() {
- global $DB, $USER;
$grade = $this->get_guide_filling();
if (!($scores = $this->get_controller()->get_min_max_score()) || $scores['maxscore'] <= $scores['minscore']) {
@@ -842,8 +841,12 @@ class gradingform_guide_instance extends gradingform_instance {
foreach ($grade['criteria'] as $record) {
$curscore += $record['score'];
}
- return round(($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*
- ($maxgrade-$mingrade), 0) + $mingrade;
+ $gradeoffset = ($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*
+ ($maxgrade-$mingrade);
+ if ($this->get_controller()->get_allow_grade_decimals()) {
+ return $gradeoffset + $mingrade;
+ }
+ return round($gradeoffset, 0) + $mingrade;
}
/**
diff --git a/grade/grading/form/guide/version.php b/grade/grading/form/guide/version.php
index 6d92a4fa0cf..48ba85c0ba4 100644
--- a/grade/grading/form/guide/version.php
+++ b/grade/grading/form/guide/version.php
@@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'gradingform_guide';
-$plugin->version = 2013050100;
-$plugin->requires = 2013050100;
+$plugin->version = 2013051401.05;
+$plugin->requires = 2013051401.05;
$plugin->maturity = MATURITY_STABLE;
\ No newline at end of file
diff --git a/grade/grading/form/lib.php b/grade/grading/form/lib.php
index 6c8c8acc5da..574d087d71c 100644
--- a/grade/grading/form/lib.php
+++ b/grade/grading/form/lib.php
@@ -73,6 +73,9 @@ abstract class gradingform_controller {
/** @var array graderange array of valid grades for this area. Use set_grade_range and get_grade_range to access this */
private $graderange = null;
+ /** @var bool if decimal values are allowed as grades. */
+ private $allowgradedecimals = false;
+
/** @var boolean|null cached result of function has_active_instances() */
protected $hasactiveinstances = null;
@@ -622,13 +625,23 @@ abstract class gradingform_controller {
/**
* Sets the range of grades used in this area. This is usually either range like 0-100
- * or the scale where keys start from 1. Typical use:
- * $controller->set_grade_range(make_grades_menu($gradingtype));
+ * or the scale where keys start from 1.
*
- * @param array $graderange
+ * Typically modules will call it:
+ * $controller->set_grade_range(make_grades_menu($gradingtype), $gradingtype > 0);
+ * Negative $gradingtype means that scale is used and the grade must be rounded
+ * to the nearest int. Positive $gradingtype means that range 0..$gradingtype
+ * is used for the grades and in this case grade does not have to be rounded.
+ *
+ * Sometimes modules always expect grade to be rounded (like mod_assignment does).
+ *
+ * @param array $graderange array where first _key_ is the minimum grade and the
+ * last key is the maximum grade.
+ * @param bool $allowgradedecimals if decimal values are allowed as grades.
*/
- public final function set_grade_range(array $graderange) {
+ public final function set_grade_range(array $graderange, $allowgradedecimals = false) {
$this->graderange = $graderange;
+ $this->allowgradedecimals = $allowgradedecimals;
}
/**
@@ -643,6 +656,15 @@ abstract class gradingform_controller {
return $this->graderange;
}
+ /**
+ * Returns if decimal values are allowed as grades
+ *
+ * @return bool
+ */
+ public final function get_allow_grade_decimals() {
+ return $this->allowgradedecimals;
+ }
+
/**
* Overridden by sub classes that wish to make definition details available to web services.
* When not overridden, only definition data common to all grading methods is made available.
@@ -866,7 +888,11 @@ abstract class gradingform_instance {
/**
* Calculates the grade to be pushed to the gradebook
*
- * @return int the valid grade from $this->get_controller()->get_grade_range()
+ * Returned grade must be in range $this->get_controller()->get_grade_range()
+ * Plugins must returned grade converted to int unless
+ * $this->get_controller()->get_allow_grade_decimals() is true.
+ *
+ * @return float|int
*/
abstract public function get_grade();
diff --git a/grade/grading/form/rubric/lib.php b/grade/grading/form/rubric/lib.php
index ae4f5d507b1..39e6adcbe15 100644
--- a/grade/grading/form/rubric/lib.php
+++ b/grade/grading/form/rubric/lib.php
@@ -814,10 +814,9 @@ class gradingform_rubric_instance extends gradingform_instance {
/**
* Calculates the grade to be pushed to the gradebook
*
- * @return int the valid grade from $this->get_controller()->get_grade_range()
+ * @return float|int the valid grade from $this->get_controller()->get_grade_range()
*/
public function get_grade() {
- global $DB, $USER;
$grade = $this->get_rubric_filling();
if (!($scores = $this->get_controller()->get_min_max_score()) || $scores['maxscore'] <= $scores['minscore']) {
@@ -836,7 +835,11 @@ class gradingform_rubric_instance extends gradingform_instance {
foreach ($grade['criteria'] as $id => $record) {
$curscore += $this->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$record['levelid']]['score'];
}
- return round(($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*($maxgrade-$mingrade), 0) + $mingrade;
+ $gradeoffset = ($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*($maxgrade-$mingrade);
+ if ($this->get_controller()->get_allow_grade_decimals()) {
+ return $gradeoffset + $mingrade;
+ }
+ return round($gradeoffset, 0) + $mingrade;
}
/**
diff --git a/grade/grading/form/rubric/styles.css b/grade/grading/form/rubric/styles.css
index 50a6b37df51..35b22bf83d0 100644
--- a/grade/grading/form/rubric/styles.css
+++ b/grade/grading/form/rubric/styles.css
@@ -80,7 +80,7 @@
.gradingform_rubric .plainvalue.empty {font-style: italic; color: #AAA;}
-.gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;bottom:0;}
+.gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;}
.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;white-space:nowrap;}
.gradingform_rubric .criterion .levels .level .score .scorevalue {padding-right:5px;}
@@ -106,7 +106,7 @@
.gradingform_rubric.editor .addcriterion input,
.gradingform_rubric.editor .addlevel input {background: transparent url([[pix:t/add]]) no-repeat top left ;display:block;color:#555555;font-weight:bold;text-decoration:none;}
.gradingform_rubric.editor .addcriterion input {background-position: 5px 8px;height:30px;line-height:29px;margin-bottom:14px;padding-left:20px;padding-right:10px;}
-.gradingform_rubric.editor .addlevel input {background-position: 5px 5px;height:25px;line-height:24px;margin-bottom:10px;padding-left:18px;padding-right:8px;}
+.gradingform_rubric.editor .addlevel input {background-position: 5px 5px;height:25px;line-height:24px;margin-bottom:45px;padding-left:18px;padding-right:8px;}
.gradingform_rubric .options .optionsheading {font-weight:bold;font-size:1.1em;padding-bottom:5px;}
.gradingform_rubric .options .option {padding-bottom:2px;}
diff --git a/grade/grading/form/rubric/version.php b/grade/grading/form/rubric/version.php
index d69563909bc..020b4ae0d65 100644
--- a/grade/grading/form/rubric/version.php
+++ b/grade/grading/form/rubric/version.php
@@ -25,8 +25,8 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'gradingform_rubric';
-$plugin->version = 2013050100;
+$plugin->version = 2013051401.05;
-$plugin->requires = 2013050100;
+$plugin->requires = 2013051401.05;
$plugin->maturity = MATURITY_STABLE;
diff --git a/grade/grading/form/upgrade.txt b/grade/grading/form/upgrade.txt
new file mode 100644
index 00000000000..21cc6316240
--- /dev/null
+++ b/grade/grading/form/upgrade.txt
@@ -0,0 +1,9 @@
+This files describes API changes in /grade/grading/form/* - Advanced grading methods
+information provided here is intended especially for developers.
+
+=== 2.5.2 ===
+
+* Grading methods now can return grade with decimals. See API functions
+ gradingform_controller::set_grade_range() and
+ gradingform_controller::get_allow_grade_decimals(), and also examples
+ in gradingform_rubric_instance::get_grade().
diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php
index c99f6727d5d..51eddc26a2a 100644
--- a/grade/report/grader/lib.php
+++ b/grade/report/grader/lib.php
@@ -226,6 +226,10 @@ class grade_report_grader extends grade_report {
$changedgrades = true;
} else if ($datatype === 'feedback') {
+ // If quick grading is on, feedback needs to be compared without line breaks.
+ if ($this->get_pref('quickgrading')) {
+ $oldvalue->feedback = preg_replace("/\r\n|\r|\n/", "", $oldvalue->feedback);
+ }
if (($oldvalue->feedback === $postedvalue) or ($oldvalue->feedback === NULL and empty($postedvalue))) {
continue;
}
diff --git a/group/tests/behat/behat_groups.php b/group/tests/behat/behat_groups.php
index 847727d3b78..2afb6f383b8 100644
--- a/group/tests/behat/behat_groups.php
+++ b/group/tests/behat/behat_groups.php
@@ -51,36 +51,39 @@ class behat_groups extends behat_base {
global $DB;
$user = $DB->get_record('user', array('username' => $username));
- $userfullname = fullname($user);
+ $userfullname = $this->getSession()->getSelectorsHandler()->xpathLiteral(fullname($user));
+
+ // Using a xpath liternal to avoid problems with quotes and double quotes.
+ $groupname = $this->getSession()->getSelectorsHandler()->xpathLiteral($groupname);
// We don't know the option text as it contains the number of users in the group.
$select = $this->find_field('groups');
- $xpath = "//select[@id='groups']/descendant::option[contains(., '" . $groupname . "')]";
+ $xpath = "//select[@id='groups']/descendant::option[contains(., $groupname)]";
$groupoption = $this->find('xpath', $xpath);
$fulloption = $groupoption->getText();
$select->selectOption($fulloption);
// Here we don't need to wait for the AJAX response.
- $this->find_button('Add/remove users')->click();
+ $this->find_button(get_string('adduserstogroup', 'group'))->click();
// Wait for add/remove members page to be loaded.
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
// Getting the option and selecting it.
$select = $this->find_field('addselect');
- $xpath = "//select[@id='addselect']/descendant::option[contains(., '" . $userfullname . "')]";
+ $xpath = "//select[@id='addselect']/descendant::option[contains(., $userfullname)]";
$memberoption = $this->find('xpath', $xpath);
$fulloption = $memberoption->getText();
$select->selectOption($fulloption);
// Click add button.
- $this->find_button('Add')->click();
+ $this->find_button(get_string('add'))->click();
// Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
// Returning to the main groups page.
- $this->find_button('Back to groups')->click();
+ $this->find_button(get_string('backtogroups', 'group'))->click();
}
}
diff --git a/lang/en/badges.php b/lang/en/badges.php
index 437c5dd1ecf..9e5b147dabe 100644
--- a/lang/en/badges.php
+++ b/lang/en/badges.php
@@ -198,6 +198,7 @@ $string['donotaward'] = 'Currently, this badge is not active, so it cannot be aw
$string['editsettings'] = 'Edit settings';
$string['enablebadges'] = 'Enable badges';
$string['error:backpacknotavailable'] = 'Your site is not accessible from the Internet, so any badges issued from this site cannot be verified by external backpack services.';
+$string['error:backpackproblem'] = 'There was a problem connecting to your backpack service provider. Please try again later.';
$string['error:cannotact'] = 'Cannot activate the badge. ';
$string['error:cannotawardbadge'] = 'Cannot award badge to a user.';
$string['error:clone'] = 'Cannot clone the badge.';
diff --git a/lib/badgeslib.php b/lib/badgeslib.php
index daf2d44f0ce..f4d38cbb467 100644
--- a/lib/badgeslib.php
+++ b/lib/badgeslib.php
@@ -1348,3 +1348,19 @@ function badges_handle_course_deletion($courseid) {
$DB->update_record('badge', $toupdate);
}
}
+
+/**
+ * Loads JS files required for backpack support.
+ *
+ * @uses $CFG, $PAGE
+ * @return void
+ */
+function badges_setup_backpack_js() {
+ global $CFG, $PAGE;
+ if (!empty($CFG->badges_allowexternalbackpack)) {
+ $PAGE->requires->string_for_js('error:backpackproblem', 'badges');
+ $protocol = (strpos($CFG->wwwroot, 'https://') === 0) ? 'https://' : 'http://';
+ $PAGE->requires->js(new moodle_url($protocol . 'backpack.openbadges.org/issuer.js'), true);
+ $PAGE->requires->js('/badges/backpack.js', true);
+ }
+}
diff --git a/lib/behat/behat_files.php b/lib/behat/behat_files.php
index 4e8b7d171cd..9d12a3a783d 100644
--- a/lib/behat/behat_files.php
+++ b/lib/behat/behat_files.php
@@ -64,10 +64,12 @@ class behat_files extends behat_base {
$exception = new ExpectationException('"' . $filepickerelement . '" filepicker can not be found', $this->getSession());
// Gets the ffilemanager node specified by the locator which contains the filepicker container.
+ $filepickerelement = $this->getSession()->getSelectorsHandler()->xpathLiteral($filepickerelement);
$filepickercontainer = $this->find(
'xpath',
- "//input[./@id = //label[contains(normalize-space(string(.)), '" . $filepickerelement . "')]/@for]
-//ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' ffilemanager ') or contains(concat(' ', normalize-space(@class), ' '), ' ffilepicker ')]",
+ "//input[./@id = //label[normalize-space(.)=$filepickerelement]/@for]" .
+ "//ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' ffilemanager ') or " .
+ "contains(concat(' ', normalize-space(@class), ' '), ' ffilepicker ')]",
$exception
);
@@ -117,17 +119,20 @@ class behat_files extends behat_base {
$exception = new ExpectationException($exceptionmsg, $this->getSession());
+ // Avoid quote-related problems.
+ $name = $this->getSession()->getSelectorsHandler()->xpathLiteral($name);
+
// Get a filepicker element (folder or file).
try {
// First we look at the folder as we need to click on the contextual menu otherwise it would be opened.
$node = $this->find(
'xpath',
- "//div[@class='fp-content']
-//descendant::*[self::div | self::a][contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]
-[contains(concat(' ', normalize-space(@class), ' '), ' fp-folder ')][contains(normalize-space(string(.)), '" . $name . "')]
-//descendant::a[contains(concat(' ', normalize-space(@class), ' '), ' fp-contextmenu ')]
-",
+ "//div[@class='fp-content']" .
+ "//descendant::*[self::div | self::a][contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]" .
+ "[contains(concat(' ', normalize-space(@class), ' '), ' fp-folder ')]" .
+ "[normalize-space(.)=$name]" .
+ "//descendant::a[contains(concat(' ', normalize-space(@class), ' '), ' fp-contextmenu ')]",
$exception,
$containernode
);
@@ -137,10 +142,10 @@ class behat_files extends behat_base {
// Here the contextual menu is hidden, we click on the thumbnail.
$node = $this->find(
'xpath',
- "//div[@class='fp-content']
-//descendant::*[self::div | self::a][contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')][contains(normalize-space(string(.)), '" . $name . "')]
-//descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-thumbnail ')]
-",
+ "//div[@class='fp-content']" .
+ "//descendant::*[self::div | self::a][contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]" .
+ "[normalize-space(.)=$name]" .
+ "//descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-thumbnail ')]",
false,
$containernode
);
@@ -176,12 +181,15 @@ class behat_files extends behat_base {
// Getting the repository link and opening it.
$repoexception = new ExpectationException('The "' . $repositoryname . '" repository has not been found', $this->getSession());
+ // Avoid problems with both double and single quotes in the same string.
+ $repositoryname = $this->getSession()->getSelectorsHandler()->xpathLiteral($repositoryname);
+
// Here we don't need to look inside the selected filepicker because there can only be one modal window.
$repositorylink = $this->find(
'xpath',
- "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-area ')]
-//descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-name ')]
-[contains(normalize-space(string(.)), '" . $repositoryname . "')]",
+ "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-area ')]" .
+ "//descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-name ')]" .
+ "[normalize-space(.)=$repositoryname]",
$repoexception
);
@@ -226,10 +234,10 @@ class behat_files extends behat_base {
// only used when accessing the filepicker, there is no filemanager-loading after selecting the file.
$this->find(
'xpath',
- "//div[contains(concat(' ', @class, ' '), ' filemanager ')]" .
- "[not(contains(concat(' ', @class, ' '), ' fm-updating '))]" .
+ "//div[contains(concat(' ', normalize-space(@class), ' '), ' filemanager ')]" .
+ "[not(contains(concat(' ', normalize-space(@class), ' '), ' fm-updating '))]" .
"|" .
- "//div[contains(concat(' ', @class, ' '), ' filemanager-loading ')]" .
+ "//div[contains(concat(' ', normalize-space(@class), ' '), ' filemanager-loading ')]" .
"[contains(@style, 'display: none;')]",
$exception,
$filepickernode
diff --git a/lib/behat/form_field/behat_form_select.php b/lib/behat/form_field/behat_form_select.php
index fbde5719266..5be8135633e 100644
--- a/lib/behat/form_field/behat_form_select.php
+++ b/lib/behat/form_field/behat_form_select.php
@@ -58,10 +58,13 @@ class behat_form_select extends behat_form_field {
// Single select needs an extra click in the option.
if (!$this->field->hasAttribute('multiple')) {
+
+ $value = $this->session->getSelectorsHandler()->xpathLiteral($value);
+
// Using the driver direcly because Element methods are messy when dealing
// with elements inside containers.
$optionxpath = $this->field->getXpath() .
- "/descendant::option[(./@value = '" . $value . "' or contains(normalize-space(string(.)), '" . $value . "'))]";
+ "/descendant::option[(./@value=$value or normalize-space(.)=$value)]";
$optionnodes = $this->session->getDriver()->find($optionxpath);
if ($optionnodes) {
current($optionnodes)->click();
diff --git a/lib/behat/lib.php b/lib/behat/lib.php
index e35b0e6192c..42ed4418ffd 100644
--- a/lib/behat/lib.php
+++ b/lib/behat/lib.php
@@ -141,3 +141,29 @@ function behat_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
// Also use the internal error handler so we keep the usual behaviour.
return false;
}
+
+/**
+ * Restrict the config.php settings allowed.
+ *
+ * When running the behat features the config.php
+ * settings should not affect the results.
+ *
+ * @return void
+ */
+function behat_clean_init_config() {
+ global $CFG;
+
+ $allowed = array_flip(array(
+ 'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
+ 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
+ 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass'
+ ));
+
+ // Also allowing behat_ prefixed attributes.
+ foreach ($CFG as $key => $value) {
+ if (!isset($allowed[$key]) && strpos($key, 'behat_') !== 0) {
+ unset($CFG->{$key});
+ }
+ }
+
+}
diff --git a/lib/editor/tinymce/plugins/spellchecker/config.php b/lib/editor/tinymce/plugins/spellchecker/config.php
index 4f1ac23e6e4..98adf18e4cd 100644
--- a/lib/editor/tinymce/plugins/spellchecker/config.php
+++ b/lib/editor/tinymce/plugins/spellchecker/config.php
@@ -28,25 +28,18 @@ require('../../../../../config.php');
// General settings
$engine = get_config('tinymce_spellchecker', 'spellengine');
-if (!$engine) {
- $engine = 'GoogleSpell';
+if (!$engine or $engine === 'GoogleSpell') {
+ $engine = 'PSpell';
}
$config['general.engine'] = $engine;
-// GoogleSpell settings
-$config['GoogleSpell.proxyhost'] = isset($CFG->proxyhost) ? $CFG->proxyhost : '';
-$config['GoogleSpell.proxyport'] = isset($CFG->proxyport) ? $CFG->proxyport : '';
-$config['GoogleSpell.proxytype'] = isset($CFG->proxytype) ? $CFG->proxytype : 'HTML';
-$config['GoogleSpell.proxyuser'] = isset($CFG->proxyuser) ? $CFG->proxyuser : '';
-$config['GoogleSpell.proxypassword'] = isset($CFG->proxypassword) ? $CFG->proxypassword : '';
-
-if ($config['general.engine'] === 'PSpell' || $config['general.engine'] === 'PSpellShell') {
+if ($config['general.engine'] === 'PSpell') {
// PSpell settings
$config['PSpell.mode'] = PSPELL_FAST;
$config['PSpell.spelling'] = "";
$config['PSpell.jargon'] = "";
$config['PSpell.encoding'] = "";
-
+} else if ($config['general.engine'] === 'PSpellShell') {
// PSpellShell settings
$config['PSpellShell.mode'] = PSPELL_FAST;
$config['PSpellShell.aspell'] = $CFG->aspellpath;
diff --git a/lib/editor/tinymce/plugins/spellchecker/lib.php b/lib/editor/tinymce/plugins/spellchecker/lib.php
index 3f856656693..8c14e08546d 100644
--- a/lib/editor/tinymce/plugins/spellchecker/lib.php
+++ b/lib/editor/tinymce/plugins/spellchecker/lib.php
@@ -36,6 +36,12 @@ class tinymce_spellchecker extends editor_tinymce_plugin {
return;
}
+ // Check some speller is configured.
+ $engine = $this->get_config('spellengine', '');
+ if (!$engine or $engine === 'GoogleSpell') {
+ return;
+ }
+
// Check at least one language is supported.
$spelllanguagelist = $this->get_config('spelllanguagelist', '');
if ($spelllanguagelist !== '') {
diff --git a/lib/editor/tinymce/plugins/spellchecker/settings.php b/lib/editor/tinymce/plugins/spellchecker/settings.php
index c4081e3a2ea..2be8fde4dd4 100644
--- a/lib/editor/tinymce/plugins/spellchecker/settings.php
+++ b/lib/editor/tinymce/plugins/spellchecker/settings.php
@@ -26,11 +26,11 @@ defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
$options = array(
+ ''=>get_string('none'),
'PSpell'=>'PSpell',
- 'GoogleSpell'=>'Google Spell',
'PSpellShell'=>'PSpellShell');
$settings->add(new admin_setting_configselect('tinymce_spellchecker/spellengine',
- get_string('spellengine', 'admin'), '', 'GoogleSpell', $options));
+ get_string('spellengine', 'admin'), '', '', $options));
$settings->add(new admin_setting_configtextarea('tinymce_spellchecker/spelllanguagelist',
get_string('spelllanguagelist', 'admin'), '',
'+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,' .
diff --git a/lib/navigationlib.php b/lib/navigationlib.php
index c691a83ef64..0243a88d0b2 100644
--- a/lib/navigationlib.php
+++ b/lib/navigationlib.php
@@ -1266,7 +1266,8 @@ class global_navigation extends navigation_node {
// Remove any empty root nodes
foreach ($this->rootnodes as $node) {
// Dont remove the home node
- if ($node->key !== 'home' && !$node->has_children()) {
+ /** @var navigation_node $node */
+ if ($node->key !== 'home' && !$node->has_children() && !$node->isactive) {
$node->remove();
}
}
diff --git a/lib/setup.php b/lib/setup.php
index 0b9cbd2f40a..c776a891623 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -131,6 +131,10 @@ if (!defined('BEHAT_SITE_RUNNING') && !empty($CFG->behat_dataroot) &&
// actions like reset the site or use data generators.
define('BEHAT_SITE_RUNNING', true);
+ // Clean extra config.php settings.
+ require_once(__DIR__ . '/../lib/behat/lib.php');
+ behat_clean_init_config();
+
$CFG->wwwroot = $CFG->behat_wwwroot;
$CFG->passwordsaltmain = 'moodle';
$CFG->prefix = $CFG->behat_prefix;
diff --git a/lib/tablelib.php b/lib/tablelib.php
index ebd4886b0d9..b14231007d0 100644
--- a/lib/tablelib.php
+++ b/lib/tablelib.php
@@ -1686,6 +1686,7 @@ class table_xhtml_export_format extends table_default_export_format_parent {
+