Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa0b5768cc | |||
| 7b33c5e9ec | |||
| d394a22ee6 | |||
| 83fe3c44c0 | |||
| 282c4224da | |||
| 1312197a82 | |||
| 5dc6fd3b23 | |||
| b49b76f397 | |||
| a4f4656110 | |||
| 83bdb872dc | |||
| 39a83aa250 | |||
| e70853386d | |||
| 451d514d84 | |||
| 72f5a6e119 | |||
| 7f624852e4 | |||
| 8abe8980d6 | |||
| 431c7ba150 | |||
| 9b632e17d8 | |||
| b731ea9798 | |||
| 472b1b095e | |||
| 2cddf32f7e | |||
| 9b898bf8b4 | |||
| a87d3a64bb | |||
| 55cd43d397 | |||
| 9c1462ecfe | |||
| e22aba78ca | |||
| d952f227d0 | |||
| d80cec9c2b | |||
| 77fe56108a | |||
| 03550b7b8f | |||
| 274ccc948b | |||
| bc8d5da483 | |||
| 94bf3e9c78 | |||
| e92bd648db | |||
| e4bb1cc603 | |||
| c2f12aa742 | |||
| 454af7cf52 | |||
| f73e64f166 | |||
| 18a738f88f | |||
| 493a30cdc2 | |||
| 65edc59a51 | |||
| 28b9209af5 | |||
| 8b2c91e627 | |||
| 8256d18453 | |||
| ab11164395 | |||
| 29933a945f | |||
| c7b384ce66 | |||
| 665e256961 | |||
| 879532b41f | |||
| 4e83a79cd5 | |||
| 3ecba892e3 | |||
| b92f4ba9f1 | |||
| 6a3a83af93 | |||
| 6e3a070030 | |||
| 5d701a706c | |||
| 612e4d9d02 | |||
| df6f010a86 | |||
| bf4904c32b | |||
| 2821f78e6c | |||
| f904215fa7 | |||
| 635b605eab | |||
| 08dd41b304 | |||
| 04cd0cb2ca | |||
| 5cacda33bb | |||
| 864ef22534 | |||
| f008a03c60 | |||
| 8831cc14c7 | |||
| ebb87a1e63 | |||
| 93d9b19607 | |||
| 92d04a7913 | |||
| a5cdde3c79 | |||
| d073f5daf0 | |||
| 3972ef0a32 | |||
| 01b63287ef | |||
| b3259b51bb | |||
| b683a48f69 | |||
| 944888a509 | |||
| 21ac04e12e | |||
| ab9b65b6a7 | |||
| 4a852e8467 | |||
| 9c211250ed | |||
| a4eb84f12f | |||
| 1e3da12a5f | |||
| d5ac2c69cf | |||
| 3cf2cce110 | |||
| 1df418416b | |||
| fdee2f256c | |||
| 15974729df | |||
| 99f8dea24b | |||
| 27829f24e1 | |||
| 6fd3764d99 | |||
| 8563f23446 | |||
| 033afe1028 | |||
| c21f3b540b | |||
| 9fa3c6756e | |||
| d543802d90 | |||
| 374d0b4702 | |||
| 043da28910 | |||
| b35de2fdf0 | |||
| 2b1b27dfbd | |||
| bd370cda69 | |||
| 86cbf2a643 | |||
| d33ae6c0cc | |||
| 61b51f93ef | |||
| eb2f9593a7 | |||
| e8b6cb418f | |||
| f8798a6b07 | |||
| 75259ad00f | |||
| e149838c2a | |||
| 7e0c5d1c38 | |||
| b78a289579 | |||
| de168198e6 | |||
| d4cd76a5c8 | |||
| 7430be37c5 | |||
| 327c830b2a | |||
| f2c38390c9 | |||
| 55bbdac9d9 | |||
| 7ed867fa20 | |||
| fc6f41655b | |||
| 15ddbf3907 | |||
| 27b547db5a | |||
| 0d13442b29 | |||
| d1bc79e02a | |||
| 3abca9b368 | |||
| 3a000a8def | |||
| 1c3c38fb00 | |||
| 86cbd0840b | |||
| 2ea211e78a | |||
| b60ec99fec | |||
| 953b1b85da | |||
| 45e2df1ce4 | |||
| 9f1149ac13 | |||
| a157baa65a | |||
| 15d2dcf037 | |||
| 9eab90a827 | |||
| 2bee1f66b9 | |||
| c3731344e5 | |||
| c46b2eaf18 | |||
| d6af305bb9 | |||
| fa319a92b8 | |||
| e11d0e35de | |||
| 313fd64469 | |||
| 9f51263a37 | |||
| b375088f23 | |||
| 6cdfbdc119 | |||
| c3e7a570f3 | |||
| b12ee51f8b | |||
| 9192613a6a | |||
| 74fc0a9620 | |||
| 264ea8c7fc | |||
| 06079db957 | |||
| 59c9dd780b | |||
| 5ec6a4e462 | |||
| 4ce08a765d | |||
| 0498684ae2 | |||
| 9a0078f100 | |||
| d9126f03dc | |||
| 382fc36eba | |||
| defe05cbcf | |||
| 71548acf7e | |||
| de75e7471d | |||
| 8635cc3871 | |||
| a8d72a836c | |||
| 140b417065 | |||
| 0094a77ccc | |||
| c7f46956d4 | |||
| 648e72316d | |||
| 6b8c4ddacc | |||
| 3bf1e69a3e | |||
| df4041b745 | |||
| ac9e0f9ad5 | |||
| c0cfb0ee09 | |||
| 91998db5f5 | |||
| e76b0e1014 |
@@ -203,7 +203,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/amd/src/*.js"],
|
||||
files: ["**/amd/src/*.js", "Gruntfile.js"],
|
||||
// Check AMD with some slightly stricter rules.
|
||||
rules: {
|
||||
'no-unused-vars': 'error',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
**/amd/**
|
||||
/*.js
|
||||
@@ -24,6 +24,7 @@
|
||||
* Grunt configuration
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
module.exports = function(grunt) {
|
||||
var path = require('path'),
|
||||
tasks = {},
|
||||
|
||||
@@ -2451,6 +2451,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -2632,6 +2633,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.1.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
|
||||
@@ -48,7 +48,6 @@ class behat_admin extends behat_base {
|
||||
* @param TableNode $table
|
||||
*/
|
||||
public function i_set_the_following_administration_settings_values(TableNode $table) {
|
||||
|
||||
if (!$data = $table->getRowsHash()) {
|
||||
return;
|
||||
}
|
||||
@@ -57,17 +56,12 @@ class behat_admin extends behat_base {
|
||||
|
||||
// We expect admin block to be visible, otherwise go to homepage.
|
||||
if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
|
||||
$this->getSession()->visit($this->locate_path('/'));
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
$this->execute('behat_forms::i_am_on_homepage');
|
||||
}
|
||||
|
||||
// Search by label.
|
||||
$searchbox = $this->find_field(get_string('searchinsettings', 'admin'));
|
||||
$searchbox->setValue($label);
|
||||
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
|
||||
$submitsearch->press();
|
||||
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
$this->execute('behat_forms::i_set_the_field_to', [get_string('searchinsettings', 'admin'), $label]);
|
||||
$this->execute("behat_general::i_click_on_in_the", [get_string('search', 'admin'), 'button', '.block_settings', 'css_element']);
|
||||
|
||||
// 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.
|
||||
@@ -83,35 +77,17 @@ class behat_admin extends behat_base {
|
||||
"@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@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");
|
||||
|
||||
} catch (ElementNotFoundException $e) {
|
||||
|
||||
// Multi element settings, interacting only the first one.
|
||||
$fieldxpath = "//*[label[normalize-space(.)= $label]|span[normalize-space(.)= $label]]/" .
|
||||
"ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
|
||||
"/descendant::div[@class='form-group']/descendant::*[self::input | self::textarea | self::select]" .
|
||||
"[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
|
||||
$fieldnode = $this->find('xpath', $fieldxpath);
|
||||
|
||||
// It is the same one that contains the type.
|
||||
$formfieldtypenode = $fieldnode;
|
||||
}
|
||||
|
||||
// Getting the class which contains the field type.
|
||||
$classes = explode(' ', $formfieldtypenode->getAttribute('class'));
|
||||
foreach ($classes as $class) {
|
||||
if (substr($class, 0, 5) == 'form-') {
|
||||
$type = substr($class, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiating the appropiate field type.
|
||||
$field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
|
||||
$field->set_value($value);
|
||||
|
||||
$this->find_button(get_string('savechanges'))->press();
|
||||
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
|
||||
$this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ Feature: Manage tokens
|
||||
| username | password | firstname | lastname |
|
||||
| testuser | testuser | Joe | Bloggs |
|
||||
| testuser2 | testuser2 | TestFirstname | TestLastname |
|
||||
And I change window size to "small"
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@tool_behat
|
||||
Feature: Behat steps for interacting with form work
|
||||
In order to test my Moodle code
|
||||
As a developer
|
||||
I need the Behat steps for form elements to work reliably
|
||||
|
||||
@javascript
|
||||
Scenario: Test fields in containers
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
When I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
# Just get to any form.
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Course full name" in the "General" "fieldset" to "Frog"
|
||||
And I set the following fields in the "Appearance" "fieldset" to these values:
|
||||
| Show activity reports | Yes |
|
||||
| Number of announcements | 1 |
|
||||
Then the field "Show activity reports" in the "Appearance" "fieldset" matches value "Yes"
|
||||
And the field "Show activity reports" in the "Appearance" "fieldset" does not match value "No"
|
||||
And the following fields in the "region-main" "region" match these values:
|
||||
| Course full name | Frog |
|
||||
| Number of announcements | 1 |
|
||||
And the following fields in the "region-main" "region" do not match these values:
|
||||
| Course full name | Course 1 |
|
||||
| Number of announcements | 5 |
|
||||
@@ -222,6 +222,48 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the legacy role assignments which are stale.
|
||||
$paramsclean['usercontext'] = CONTEXT_USER;
|
||||
$paramsclean['component'] = 'tool_cohortroles';
|
||||
$sql = 'SELECT DISTINCT(ra.id), ra.roleid, ra.userid, ra.contextid, ctx.instanceid
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
|
||||
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
|
||||
LEFT JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid
|
||||
AND tc.userid = ra.userid
|
||||
AND tc.roleid = ra.roleid
|
||||
WHERE ra.component = :component
|
||||
AND tc.id is null';
|
||||
if ($candidatelegacyassignments = $DB->get_records_sql($sql, $paramsclean)) {
|
||||
$sql = 'SELECT DISTINCT(ra.id)
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
|
||||
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
|
||||
JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid AND tc.userid = ra.userid
|
||||
WHERE ra.component = :component';
|
||||
if ($currentvalidroleassignments = $DB->get_records_sql($sql, $paramsclean)) {
|
||||
foreach ($candidatelegacyassignments as $candidate) {
|
||||
if (!array_key_exists($candidate->id, $currentvalidroleassignments)) {
|
||||
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
|
||||
$rolesremoved[] = array(
|
||||
'useridassignedto' => $candidate->userid,
|
||||
'useridassignedover' => $candidate->instanceid,
|
||||
'roleid' => $candidate->roleid
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($candidatelegacyassignments as $candidate) {
|
||||
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
|
||||
$rolesremoved[] = array(
|
||||
'useridassignedto' => $candidate->userid,
|
||||
'useridassignedover' => $candidate->instanceid,
|
||||
'roleid' => $candidate->roleid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array('rolesadded' => $rolesadded, 'rolesremoved' => $rolesremoved);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,5 +103,4 @@ class cohort_role_assignment extends persistent {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
|
||||
* Setup function- we will create a course and add an assign instance to it.
|
||||
*/
|
||||
protected function setUp() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create some users.
|
||||
@@ -133,14 +131,91 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
|
||||
|
||||
public function test_delete_cohort_role_assignment() {
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
// Create a cohort role assigment.
|
||||
$params = (object) [
|
||||
'userid' => $this->userassignto->id,
|
||||
'roleid' => $this->roleid,
|
||||
'cohortid' => $this->cohort->id
|
||||
);
|
||||
$result = api::create_cohort_role_assignment($params);
|
||||
$worked = api::delete_cohort_role_assignment($result->get('id'));
|
||||
$this->assertTrue($worked);
|
||||
];
|
||||
$cohortroleassignment = api::create_cohort_role_assignment($params);
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
$rolesadded = [
|
||||
[
|
||||
'useridassignedto' => $this->userassignto->id,
|
||||
'useridassignedover' => $this->userassignover->id,
|
||||
'roleid' => $this->roleid
|
||||
]
|
||||
];
|
||||
$expected = [
|
||||
'rolesadded' => $rolesadded,
|
||||
'rolesremoved' => []
|
||||
];
|
||||
$this->assertEquals($sync, $expected);
|
||||
|
||||
// Delete the cohort role assigment and confirm the roles are removed.
|
||||
$result = api::delete_cohort_role_assignment($cohortroleassignment->get('id'));
|
||||
$this->assertTrue($result);
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
$expected = [
|
||||
'rolesadded' => [],
|
||||
'rolesremoved' => $rolesadded
|
||||
];
|
||||
$this->assertEquals($expected, $sync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case verifying that syncing won't remove role assignments if they are valid for another cohort role assignment.
|
||||
*/
|
||||
public function test_delete_cohort_role_assignment_cohorts_having_same_members() {
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create 2 cohorts, with a 1 user (user1) present in both,
|
||||
// and user2 and user3 members of 1 cohort each.
|
||||
$cohort1 = $this->getDataGenerator()->create_cohort();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
cohort_add_member($cohort1->id, $user1->id);
|
||||
cohort_add_member($cohort1->id, $user2->id);
|
||||
cohort_add_member($cohort2->id, $user1->id);
|
||||
cohort_add_member($cohort2->id, $user3->id);
|
||||
|
||||
// And a role and a user to assign that role to.
|
||||
$user4 = $this->getDataGenerator()->create_user(); // A cohort manager, for example.
|
||||
$roleid = create_role('Role 1', 'myrole', 'test');
|
||||
|
||||
// Assign the role for user4 in both cohorts.
|
||||
$params = (object) [
|
||||
'userid' => $user4->id,
|
||||
'roleid' => $roleid,
|
||||
'cohortid' => $cohort1->id
|
||||
];
|
||||
$cohort1roleassignment = api::create_cohort_role_assignment($params);
|
||||
$params->cohortid = $cohort2->id;
|
||||
$cohort2roleassignment = api::create_cohort_role_assignment($params);
|
||||
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
|
||||
// There is no guarantee about the order of roles assigned.
|
||||
// so confirm we have 3 role assignments, and they are for the users 1, 2 and 3.
|
||||
$this->assertCount(3, $sync['rolesadded']);
|
||||
$addedusers = array_column($sync['rolesadded'], 'useridassignedover');
|
||||
$this->assertContains($user1->id, $addedusers);
|
||||
$this->assertContains($user2->id, $addedusers);
|
||||
$this->assertContains($user3->id, $addedusers);
|
||||
|
||||
// Remove the role assignment for user4/cohort1.
|
||||
// Verify only 1 role is unassigned as the others are still valid for the other cohort role assignment.
|
||||
$result = api::delete_cohort_role_assignment($cohort1roleassignment->get('id'));
|
||||
$this->assertTrue($result);
|
||||
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
|
||||
$this->assertCount(0, $sync['rolesadded']);
|
||||
$this->assertCount(1, $sync['rolesremoved']);
|
||||
$removedusers = array_column($sync['rolesremoved'], 'useridassignedover');
|
||||
$this->assertContains($user2->id, $removedusers);
|
||||
}
|
||||
|
||||
public function test_list_cohort_role_assignments() {
|
||||
|
||||
@@ -440,6 +440,9 @@ class purpose extends persistent {
|
||||
}
|
||||
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
|
||||
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
|
||||
} else {
|
||||
// Nothing selected. Set default value of null.
|
||||
$data->sensitivedatareasons = null;
|
||||
}
|
||||
|
||||
// A single value.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","tool_lp/dialogue","core/str","core/ajax","core/templates"],function(a,b,c,d,e,f){var g=function(b){a(b).on("click",this.configureSettings.bind(this))};return g.prototype._dialogue=null,g.prototype.configureSettings=function(e){var g=a(e.target).closest("a").data("courseid"),h=a(e.target).closest("a").data("pushratingstouserplans"),i={courseid:g,settings:{pushratingstouserplans:h}};e.preventDefault(),f.render("tool_lp/course_competency_settings",i).done(function(a){d.get_string("configurecoursecompetencysettings","tool_lp").done(function(b){this._dialogue=new c(b,a,this.addListeners.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g.prototype.addListeners=function(){var a=this._find('[data-action="save"]');a.on("click",this.saveSettings.bind(this));var b=this._find('[data-action="cancel"]');b.on("click",this.cancelChanges.bind(this))},g.prototype.cancelChanges=function(a){a.preventDefault(),this._dialogue.close()},g.prototype._find=function(b){return a('[data-region="coursecompetencysettings"]').find(b)},g.prototype.saveSettings=function(a){a.preventDefault();var c=this._find('input[name="pushratingstouserplans"]:checked').val(),d=this._find('input[name="courseid"]').val(),f={pushratingstouserplans:c};e.call([{methodname:"core_competency_update_course_competency_settings",args:{courseid:d,settings:f}}])[0].done(function(){this.refreshCourseCompetenciesPage()}.bind(this)).fail(b.exception)},g.prototype.refreshCourseCompetenciesPage=function(){var c=this._find('input[name="courseid"]').val();e.call([{methodname:"tool_lp_data_for_course_competencies_page",args:{courseid:c}}])[0].done(function(c){f.render("tool_lp/course_competencies_page",c).done(function(b,c){a('[data-region="coursecompetenciespage"]').replaceWith(b),f.runTemplateJS(c),this._dialogue.close()}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g});
|
||||
define(["jquery","core/notification","tool_lp/dialogue","core/str","core/ajax","core/templates","core/pending"],function(a,b,c,d,e,f,g){var h=function(b){a(b).on("click",this.configureSettings.bind(this))};return h.prototype._dialogue=null,h.prototype.configureSettings=function(e){var h=new g,i=a(e.target).closest("a").data("courseid"),j=a(e.target).closest("a").data("pushratingstouserplans"),k={courseid:i,settings:{pushratingstouserplans:j}};e.preventDefault(),a.when(d.get_string("configurecoursecompetencysettings","tool_lp"),f.render("tool_lp/course_competency_settings",k)).then(function(a,b){return this._dialogue=new c(a,b[0],this.addListeners.bind(this)),this._dialogue}.bind(this)).then(h.resolve)["catch"](b.exception)},h.prototype.addListeners=function(){var a=this._find('[data-action="save"]');a.on("click",this.saveSettings.bind(this));var b=this._find('[data-action="cancel"]');b.on("click",this.cancelChanges.bind(this))},h.prototype.cancelChanges=function(a){a.preventDefault(),this._dialogue.close()},h.prototype._find=function(b){return a('[data-region="coursecompetencysettings"]').find(b)},h.prototype.saveSettings=function(a){var c=new g;a.preventDefault();var d=this._find('input[name="pushratingstouserplans"]:checked').val(),f=this._find('input[name="courseid"]').val(),h={pushratingstouserplans:d};e.call([{methodname:"core_competency_update_course_competency_settings",args:{courseid:f,settings:h}}])[0].then(function(){return this.refreshCourseCompetenciesPage()}.bind(this)).then(c.resolve)["catch"](b.exception)},h.prototype.refreshCourseCompetenciesPage=function(){var c=this._find('input[name="courseid"]').val(),d=new g;e.call([{methodname:"tool_lp_data_for_course_competencies_page",args:{courseid:c}}])[0].then(function(a){return f.render("tool_lp/course_competencies_page",a)}).then(function(b,c){f.replaceNode(a('[data-region="coursecompetenciespage"]'),b,c),this._dialogue.close()}.bind(this)).then(d.resolve)["catch"](b.exception)},h});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return this._render().then(function(a){return f.get_string("rate","tool_lp").then(function(b){this._popup=new d(b,a,this._afterRender.bind(this))}.bind(this))}.bind(this)).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return a.when(f.get_string("rate","tool_lp"),this._render()).then(function(a,b){return this._popup=new d(a,b[0],this._afterRender.bind(this)),this._popup}.bind(this))["catch"](b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.done(function(f){a.push({value:"",name:d._chooseStr});for(var g=0;g<f.length;g++){var h=f[g];a.push({value:h.id,name:h.name})}d._dialogue=new e(a),d._dialogue.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])})}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.then(function(b){a.push({value:"",name:d._chooseStr});for(var c=0;c<b.length;c++){var e=b[c];a.push({value:e.id,name:e.name})}return a}).then(function(a){return new e(a)}).then(function(a){return a.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])}),a}).then(function(a){d._dialogue=a}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
@@ -27,8 +27,9 @@ define(['jquery',
|
||||
'core/templates',
|
||||
'core/str',
|
||||
'tool_lp/competencypicker',
|
||||
'tool_lp/dragdrop-reorder'],
|
||||
function($, notification, ajax, templates, str, Picker, dragdrop) {
|
||||
'tool_lp/dragdrop-reorder',
|
||||
'core/pending'],
|
||||
function($, notification, ajax, templates, str, Picker, dragdrop, Pending) {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -117,6 +118,7 @@ define(['jquery',
|
||||
* Pick a competency
|
||||
*
|
||||
* @method pickCompetency
|
||||
* @return {Promise}
|
||||
*/
|
||||
competencies.prototype.pickCompetency = function() {
|
||||
var self = this;
|
||||
@@ -132,6 +134,7 @@ define(['jquery',
|
||||
self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
|
||||
self.pickerInstance.on('save', function(e, data) {
|
||||
var compIds = data.competencyIds;
|
||||
var pendingPromise = new Pending();
|
||||
|
||||
if (self.itemtype === "course") {
|
||||
requests = [];
|
||||
@@ -181,17 +184,20 @@ define(['jquery',
|
||||
pagerender = 'tool_lp/plan_page';
|
||||
pageregion = 'plan-page';
|
||||
}
|
||||
ajax.call(requests)[requests.length - 1].then(function(context) {
|
||||
ajax.call(requests)[requests.length - 1]
|
||||
.then(function(context) {
|
||||
return templates.render(pagerender, context);
|
||||
}).then(function(html, js) {
|
||||
$('[data-region="' + pageregion + '"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
|
||||
return;
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
self.pickerInstance.display();
|
||||
return self.pickerInstance.display();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -302,6 +308,7 @@ define(['jquery',
|
||||
if (localthis.itemtype == 'course') {
|
||||
// Course completion rule handling.
|
||||
$('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
var requests = [];
|
||||
var pagerender = 'tool_lp/course_competencies_page';
|
||||
var pageregion = 'coursecompetenciespage';
|
||||
@@ -314,18 +321,24 @@ define(['jquery',
|
||||
args: {courseid: localthis.itemid}}
|
||||
]);
|
||||
|
||||
requests[1].done(function(context) {
|
||||
templates.render(pagerender, context).done(function(html, js) {
|
||||
$('[data-region="' + pageregion + '"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
}).fail(notification.exception);
|
||||
}).fail(notification.exception);
|
||||
requests[1].then(function(context) {
|
||||
return templates.render(pagerender, context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
return templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-region="actions"] button').click(function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
e.preventDefault();
|
||||
localthis.pickCompetency();
|
||||
|
||||
localthis.pickCompetency()
|
||||
.then(pendingPromise.resolve)
|
||||
.catch();
|
||||
});
|
||||
$('[data-action="delete-competency-link"]').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -33,8 +33,12 @@ define(['jquery',
|
||||
'tool_lp/menubar',
|
||||
'tool_lp/competencypicker',
|
||||
'tool_lp/competency_outcomes',
|
||||
'tool_lp/competencyruleconfig'],
|
||||
function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
|
||||
'tool_lp/competencyruleconfig',
|
||||
'core/pending',
|
||||
],
|
||||
function(
|
||||
$, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending
|
||||
) {
|
||||
|
||||
// Private variables and functions.
|
||||
/** @var {Object} treeModel - This is an object representing the nodes in the tree. */
|
||||
@@ -412,6 +416,7 @@ define(['jquery',
|
||||
if (!pickerInstance) {
|
||||
pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
|
||||
pickerInstance.on('save', function(e, data) {
|
||||
var pendingPromise = new Pending();
|
||||
var compIds = data.competencyIds;
|
||||
|
||||
var calls = [];
|
||||
@@ -436,7 +441,9 @@ define(['jquery',
|
||||
templates.runTemplateJS(js);
|
||||
updatedRelatedCompetencies();
|
||||
return;
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ define(['jquery',
|
||||
'core/templates',
|
||||
'tool_lp/dialogue',
|
||||
'core/str',
|
||||
'tool_lp/tree'],
|
||||
function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
|
||||
'tool_lp/tree',
|
||||
'core/pending'
|
||||
],
|
||||
function($, Notification, Ajax, Templates, Dialogue, Str, Tree, Pending) {
|
||||
|
||||
/**
|
||||
* Competency picker class.
|
||||
@@ -157,6 +159,7 @@ define(['jquery',
|
||||
// Add listener for add.
|
||||
self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
|
||||
e.preventDefault();
|
||||
var pendingPromise = new Pending();
|
||||
if (!self._selectedCompetencies.length) {
|
||||
return;
|
||||
}
|
||||
@@ -168,7 +171,10 @@ define(['jquery',
|
||||
self._trigger('save', {competencyId: self._selectedCompetencies[0]});
|
||||
}
|
||||
|
||||
// The dialogue here is a YUI dialogue and doesn't support Promises at all.
|
||||
// However, it is typically synchronous so this shoudl suffice.
|
||||
self.close();
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
|
||||
// The list of selected competencies will be modified while looping (because of the listeners above).
|
||||
|
||||
@@ -26,8 +26,10 @@ define(['jquery',
|
||||
'tool_lp/dialogue',
|
||||
'core/str',
|
||||
'core/ajax',
|
||||
'core/templates'],
|
||||
function($, notification, Dialogue, str, ajax, templates) {
|
||||
'core/templates',
|
||||
'core/pending'
|
||||
],
|
||||
function($, notification, Dialogue, str, ajax, templates, Pending) {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -48,6 +50,7 @@ define(['jquery',
|
||||
* @method configureSettings
|
||||
*/
|
||||
settingsMod.prototype.configureSettings = function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
var courseid = $(e.target).closest('a').data('courseid');
|
||||
var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
|
||||
var context = {
|
||||
@@ -56,16 +59,21 @@ define(['jquery',
|
||||
};
|
||||
e.preventDefault();
|
||||
|
||||
templates.render('tool_lp/course_competency_settings', context).done(function(html) {
|
||||
str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
|
||||
this._dialogue = new Dialogue(
|
||||
title,
|
||||
html,
|
||||
this.addListeners.bind(this)
|
||||
);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
$.when(
|
||||
str.get_string('configurecoursecompetencysettings', 'tool_lp'),
|
||||
templates.render('tool_lp/course_competency_settings', context)
|
||||
)
|
||||
.then(function(title, templateResult) {
|
||||
this._dialogue = new Dialogue(
|
||||
title,
|
||||
templateResult[0],
|
||||
this.addListeners.bind(this)
|
||||
);
|
||||
|
||||
return this._dialogue;
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,6 +116,7 @@ define(['jquery',
|
||||
* @method saveSettings
|
||||
*/
|
||||
settingsMod.prototype.saveSettings = function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
e.preventDefault();
|
||||
|
||||
var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
|
||||
@@ -117,9 +126,12 @@ define(['jquery',
|
||||
ajax.call([
|
||||
{methodname: 'core_competency_update_course_competency_settings',
|
||||
args: {courseid: courseId, settings: settings}}
|
||||
])[0].done(function() {
|
||||
this.refreshCourseCompetenciesPage();
|
||||
}.bind(this)).fail(notification.exception);
|
||||
])[0]
|
||||
.then(function() {
|
||||
return this.refreshCourseCompetenciesPage();
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
|
||||
};
|
||||
|
||||
@@ -131,18 +143,23 @@ define(['jquery',
|
||||
*/
|
||||
settingsMod.prototype.refreshCourseCompetenciesPage = function() {
|
||||
var courseId = this._find('input[name="courseid"]').val();
|
||||
var pendingPromise = new Pending();
|
||||
|
||||
ajax.call([
|
||||
{methodname: 'tool_lp_data_for_course_competencies_page',
|
||||
args: {courseid: courseId}}
|
||||
])[0].done(function(context) {
|
||||
templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
|
||||
$('[data-region="coursecompetenciespage"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
this._dialogue.close();
|
||||
}.bind(this)).fail(notification.exception);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
])[0]
|
||||
.then(function(context) {
|
||||
return templates.render('tool_lp/course_competencies_page', context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode($('[data-region="coursecompetenciespage"]'), html, js);
|
||||
this._dialogue.close();
|
||||
|
||||
return;
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
|
||||
|
||||
@@ -102,15 +102,20 @@ define(['jquery',
|
||||
* @return {Promise}
|
||||
*/
|
||||
Grade.prototype.display = function() {
|
||||
return this._render().then(function(html) {
|
||||
return Str.get_string('rate', 'tool_lp').then(function(title) {
|
||||
this._popup = new Dialogue(
|
||||
title,
|
||||
html,
|
||||
this._afterRender.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
}.bind(this)).fail(Notification.exception);
|
||||
return $.when(
|
||||
Str.get_string('rate', 'tool_lp'),
|
||||
this._render()
|
||||
)
|
||||
.then(function(title, templateResult) {
|
||||
this._popup = new Dialogue(
|
||||
title,
|
||||
templateResult[0],
|
||||
this._afterRender.bind(this)
|
||||
);
|
||||
|
||||
return this._popup;
|
||||
}.bind(this))
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ define(['jquery',
|
||||
self = this;
|
||||
|
||||
var promise = ScaleValues.get_values(self._scaleId);
|
||||
promise.done(function(scalevalues) {
|
||||
promise.then(function(scalevalues) {
|
||||
options.push({
|
||||
value: '',
|
||||
name: self._chooseStr
|
||||
@@ -109,8 +109,13 @@ define(['jquery',
|
||||
});
|
||||
}
|
||||
|
||||
self._dialogue = new GradeDialogue(options);
|
||||
self._dialogue.on('rated', function(e, data) {
|
||||
return options;
|
||||
})
|
||||
.then(function(options) {
|
||||
return new GradeDialogue(options);
|
||||
})
|
||||
.then(function(dialogue) {
|
||||
dialogue.on('rated', function(e, data) {
|
||||
var args = self._args;
|
||||
args.grade = data.rating;
|
||||
args.note = data.note;
|
||||
@@ -123,7 +128,15 @@ define(['jquery',
|
||||
fail: notification.exception
|
||||
}]);
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
|
||||
return dialogue;
|
||||
})
|
||||
.then(function(dialogue) {
|
||||
self._dialogue = dialogue;
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(notification.exception);
|
||||
};
|
||||
|
||||
/** @type {Number} The scale id for this competency. */
|
||||
|
||||
@@ -6,6 +6,7 @@ Feature: Manage competency frameworks
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And I change window size to "small"
|
||||
And I am on site homepage
|
||||
|
||||
Scenario: Create a new framework
|
||||
|
||||
@@ -6,6 +6,7 @@ Feature: Manage plearning plan templates
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And I change window size to "small"
|
||||
And I am on site homepage
|
||||
|
||||
Scenario: Create a new learning plan template
|
||||
|
||||
@@ -30,7 +30,7 @@ require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
$serviceshortname = required_param('service', PARAM_ALPHANUMEXT);
|
||||
$passport = required_param('passport', PARAM_RAW); // Passport send from the app to validate the response URL.
|
||||
$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports.
|
||||
$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_ALPHANUM); // The URL scheme the app supports.
|
||||
$confirmed = optional_param('confirmed', false, PARAM_BOOL); // If we are being redirected after user confirmation.
|
||||
$oauthsso = optional_param('oauthsso', 0, PARAM_INT); // Id of the OpenID issuer (for OAuth direct SSO).
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($hassiteconfig) {
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
|
||||
new lang_string('forcedurlscheme_key', 'tool_mobile'),
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_ALPHANUM));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
use \tool_monitor\privacy\provider;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\approved_userlist;
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
|
||||
/**
|
||||
* Privacy test for the event monitor
|
||||
@@ -37,7 +38,7 @@ use \core_privacy\local\request\approved_userlist;
|
||||
* @copyright 2018 Adrian Greeve <adriangreeve.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_monitor_privacy_testcase extends advanced_testcase {
|
||||
class tool_monitor_privacy_testcase extends provider_testcase {
|
||||
|
||||
/**
|
||||
* Set up method.
|
||||
|
||||
@@ -123,9 +123,12 @@ class issuer extends persistent {
|
||||
$mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
|
||||
$mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
|
||||
|
||||
// Require confirmation email for new accounts.
|
||||
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
|
||||
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
|
||||
$showrequireconfirm = $this->_customdata['showrequireconfirm'] ?? false;
|
||||
if ($showrequireconfirm) {
|
||||
// Require confirmation email for new accounts.
|
||||
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
|
||||
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
|
||||
}
|
||||
|
||||
$mform->addElement('hidden', 'sortorder');
|
||||
$mform->setType('sortorder', PARAM_INT);
|
||||
|
||||
@@ -58,7 +58,25 @@ if ($action == 'edit') {
|
||||
$PAGE->navbar->add(get_string('createnewissuer', 'tool_oauth2'));
|
||||
}
|
||||
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer]);
|
||||
if (!empty($issuerid)) {
|
||||
// Show the "Require confirmation email" checkbox for trusted issuers like Google, Facebook and Microsoft.
|
||||
$likefacebook = $DB->sql_like('url', ':facebook');
|
||||
$likegoogle = $DB->sql_like('url', ':google');
|
||||
$likemicrosoft = $DB->sql_like('url', ':microsoft');
|
||||
$params = [
|
||||
'issuerid' => $issuerid,
|
||||
'facebook' => '%facebook%',
|
||||
'google' => '%google%',
|
||||
'microsoft' => '%microsoft%',
|
||||
];
|
||||
$select = "issuerid = :issuerid AND ($likefacebook OR $likegoogle OR $likemicrosoft)";
|
||||
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
|
||||
$showrequireconfirm = $DB->record_exists_select('oauth2_endpoint', $select, $params);
|
||||
} else {
|
||||
$showrequireconfirm = optional_param('showrequireconfirm', false, PARAM_BOOL);
|
||||
}
|
||||
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'showrequireconfirm' => $showrequireconfirm]);
|
||||
}
|
||||
|
||||
if ($mform && $mform->is_cancelled()) {
|
||||
@@ -157,21 +175,31 @@ if ($mform && $mform->is_cancelled()) {
|
||||
$issuers = core\oauth2\api::get_all_issuers();
|
||||
echo $renderer->issuers_table($issuers);
|
||||
|
||||
// Google template.
|
||||
$docs = 'admin/tool/oauth2/issuers/google';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewgoogleissuer', 'tool_oauth2'));
|
||||
|
||||
// Microsoft template.
|
||||
$docs = 'admin/tool/oauth2/issuers/microsoft';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewmicrosoftissuer', 'tool_oauth2'));
|
||||
|
||||
// Facebook template.
|
||||
$docs = 'admin/tool/oauth2/issuers/facebook';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewfacebookissuer', 'tool_oauth2'));
|
||||
|
||||
// Generic issuer.
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
|
||||
echo $renderer->single_button($addurl, get_string('createnewissuer', 'tool_oauth2'));
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e){var f={VIEW_POLICY:'[data-action="view"]'},g=function(){this.registerEvents()};return g.prototype.registerEvents=function(){a(f.VIEW_POLICY).click(function(f){f.preventDefault();var g=a(this).data("versionid"),h=a(this).data("behalfid"),i={versionid:g,behalfid:h},j={methodname:"tool_policy_get_policy_version",args:i},k=a.Deferred(),l=a.Deferred(),m=d.create({title:k,body:l,large:!0}).then(function(a){return a.getRoot().on(e.hidden,function(){a.destroy()}),a}).then(function(a){return a.show(),a})["catch"](c.exception),n=b.call([j]);a.when(n[0]).then(function(a){if(a.result.policy)return k.resolve(a.result.policy.name),l.resolve(a.result.policy.content),a;throw new Error(a.warnings[0].message)})["catch"](function(a){return m.then(function(a){return a.hide(),a.destroy(),a})["catch"](c.exception),c.addNotification({message:a,type:"error"})})})},{init:function(){return new g}}});
|
||||
define(["jquery","core/ajax","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e){var f=function(a){this.registerEvents(a)};return f.prototype.registerEvents=function(f){f.on("click",function(f){f.preventDefault();var g=a(this).data("versionid"),h=a(this).data("behalfid"),i={versionid:g,behalfid:h},j={methodname:"tool_policy_get_policy_version",args:i},k=a.Deferred(),l=a.Deferred(),m=d.create({title:k,body:l,large:!0}).then(function(a){return a.getRoot().on(e.hidden,function(){a.destroy()}),a}).then(function(a){return a.show(),a})["catch"](c.exception),n=b.call([j]);a.when(n[0]).then(function(a){if(a.result.policy)return k.resolve(a.result.policy.name),l.resolve(a.result.policy.content),a;throw new Error(a.warnings[0].message)})["catch"](function(a){return m.then(function(a){return a.hide(),a.destroy(),a})["catch"](c.exception),c.addNotification({message:a,type:"error"})})})},{init:function(b){return b=a(b),new f(b)}}});
|
||||
@@ -29,27 +29,18 @@ define([
|
||||
'core/modal_events'],
|
||||
function($, Ajax, Notification, ModalFactory, ModalEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{VIEW_POLICY: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
VIEW_POLICY: '[data-action="view"]'
|
||||
};
|
||||
|
||||
/**
|
||||
* PolicyActions class.
|
||||
*/
|
||||
var PolicyActions = function() {
|
||||
this.registerEvents();
|
||||
var PolicyActions = function(root) {
|
||||
this.registerEvents(root);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
PolicyActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.VIEW_POLICY).click(function(e) {
|
||||
PolicyActions.prototype.registerEvents = function(root) {
|
||||
root.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var versionid = $(this).data('versionid');
|
||||
@@ -127,8 +118,9 @@ function($, Ajax, Notification, ModalFactory, ModalEvents) {
|
||||
* @method init
|
||||
* @return {PolicyActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new PolicyActions();
|
||||
'init': function(root) {
|
||||
root = $(root);
|
||||
return new PolicyActions(root);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ class accept_policy extends \moodleform {
|
||||
}
|
||||
}
|
||||
|
||||
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init');
|
||||
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init', ['[data-action="view"]']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,4 +178,4 @@ class accept_policy extends \moodleform {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ require(['jquery', 'tool_policy/jquery-eu-cookie-law-popup', 'tool_policy/policy
|
||||
"<ul>{{#policies}}" +
|
||||
"<li>" +
|
||||
"<a href=\"{{pluginbaseurl}}/view.php?versionid={{id}}{{#returnurl}}&returnurl={{.}}{{/returnurl}}\" " +
|
||||
" data-action=\"view\" data-versionid=\"{{id}}\" data-behalfid=\"1\" >" +
|
||||
" data-action=\"view-guest\" data-versionid=\"{{id}}\" data-behalfid=\"1\" >" +
|
||||
"{{{name}}}" +
|
||||
"</a>" +
|
||||
"</li>" +
|
||||
@@ -81,7 +81,7 @@ require(['jquery', 'tool_policy/jquery-eu-cookie-law-popup', 'tool_policy/policy
|
||||
{{/haspolicies}}
|
||||
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view-guest"]');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -123,6 +123,6 @@
|
||||
{{#js}}
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
require(['tool_policy/policyactions'], function(ActionsMod) {
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view"]');
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -73,6 +73,6 @@
|
||||
{{#js}}
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
require(['tool_policy/policyactions'], function(ActionsMod) {
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view"]');
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -47,5 +47,39 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.5.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2018051401) {
|
||||
// Fetch Facebook, Google, and Microsoft issuers. We use the URL field to determine the issuer type as it's the only
|
||||
// field that contains the keyword that can somewhat let us reliably determine the issuer type.
|
||||
$likefacebook = $DB->sql_like('oe.url', ':facebook');
|
||||
$likegoogle = $DB->sql_like('oe.url', ':google');
|
||||
$likemicrosoft = $DB->sql_like('oe.url', ':microsoft');
|
||||
|
||||
$params = [
|
||||
'facebook' => '%facebook%',
|
||||
'google' => '%google%',
|
||||
'microsoft' => '%microsoft%',
|
||||
];
|
||||
|
||||
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
|
||||
$subsql = "
|
||||
SELECT DISTINCT oe.issuerid
|
||||
FROM {oauth2_endpoint} oe
|
||||
WHERE $likefacebook
|
||||
OR $likegoogle
|
||||
OR $likemicrosoft";
|
||||
|
||||
// Update non-Facebook/Google/Microsoft issuers and set requireconfirmation to 1.
|
||||
$updatesql = "
|
||||
UPDATE {oauth2_issuer}
|
||||
SET requireconfirmation = 1
|
||||
WHERE id NOT IN ({$subsql})";
|
||||
$DB->execute($updatesql, $params);
|
||||
|
||||
// Delete linked logins for non-Facebook/Google/Microsoft issuers. They can easily re-link their logins anyway.
|
||||
$DB->delete_records_select('auth_oauth2_linked_login', "issuerid NOT IN ($subsql)", $params);
|
||||
|
||||
upgrade_plugin_savepoint(true, 2018051401, 'auth', 'oauth2');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
Please contact the site administrator immediately.';
|
||||
$string['confirmaccountemailsubject'] = '{$a}: account confirmation';
|
||||
$string['confirmationinvalid'] = 'The confirmation link is either invalid, or has expired. Please start the login process again to generate a new confirmation email.';
|
||||
$string['confirmationpending'] = 'This account is pending email confirmation.';
|
||||
@@ -60,7 +63,10 @@ then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
Please contact the site administrator immediately.';
|
||||
$string['confirmlinkedloginemailsubject'] = '{$a}: linked login confirmation';
|
||||
$string['createaccountswarning'] = 'This authentication plugin allows users to create accounts on your site. You may want to enable the setting "authpreventaccountcreation" if you use this plugin.';
|
||||
$string['createnewlinkedlogin'] = 'Link a new account ({$a})';
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2018051401; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'auth_oauth2'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -42,10 +42,18 @@ class behat_auth extends behat_base {
|
||||
* Logs in the user. There should exist a user with the same value as username and password.
|
||||
*
|
||||
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
|
||||
* @param string $username the user to log in as.
|
||||
* @param moodle_url|null $wantsurl optional, URL to go to after logging in.
|
||||
*/
|
||||
public function i_log_in_as($username) {
|
||||
public function i_log_in_as(string $username, moodle_url $wantsurl = null) {
|
||||
|
||||
$loginurl = new moodle_url('/login/index.php');
|
||||
if ($wantsurl !== null) {
|
||||
$loginurl->param('wantsurl', $wantsurl->out_as_local_url());
|
||||
}
|
||||
|
||||
// Visit login page.
|
||||
$this->getSession()->visit($this->locate_path('login/index.php'));
|
||||
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
|
||||
|
||||
// Enter username and password.
|
||||
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
|
||||
|
||||
@@ -92,3 +92,25 @@ Feature: availability_grouping
|
||||
# P1 should show but not B2.
|
||||
Then I should see "P1" in the "region-main" "region"
|
||||
And I should not see "P2" in the "region-main" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Check grouping access restriction message on course homepage
|
||||
Given the following "groupings" exist:
|
||||
| name | course | idnumber |
|
||||
| Grouping A | C1 | GA |
|
||||
And the following "grouping groups" exist:
|
||||
| grouping | group |
|
||||
| GA | GI1 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | groupmode | grouping |
|
||||
| assign | Test assign | Assign description | C1 | assign1 | 1 | GA |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I turn editing mode on
|
||||
And I open "Test assign" actions menu
|
||||
And I choose "Edit settings" in the open action menu
|
||||
And I expand all fieldsets
|
||||
And the field "groupingid" matches value "Grouping A"
|
||||
And I press "Add group/grouping access restriction"
|
||||
When I press "Save and return to course"
|
||||
Then I should see "Not available unless: You belong to a group in Grouping A"
|
||||
|
||||
@@ -114,12 +114,8 @@ class behat_block_site_main_menu extends behat_base {
|
||||
protected function get_site_menu_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_site_menu_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,12 +114,8 @@ class behat_block_social_activities extends behat_base {
|
||||
protected function get_social_block_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_social_block_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+2
-1
@@ -150,7 +150,8 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
*/
|
||||
protected function new_redis($server, $prefix = '', $password = '') {
|
||||
$redis = new Redis();
|
||||
$port = null;
|
||||
// Check if it isn't a Unix socket to set default port.
|
||||
$port = ($server[0] === '/') ? null : 6379;
|
||||
if (strpos($server, ':')) {
|
||||
$serverconf = explode(':', $server);
|
||||
$server = $serverconf[0];
|
||||
|
||||
+9
-2
@@ -4,10 +4,17 @@
|
||||
"description": "Moodle - the world's open source learning platform",
|
||||
"type": "project",
|
||||
"homepage": "https://moodle.org",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/moodlehq/php-webdriver.git"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "6.5.*",
|
||||
"phpunit/dbunit": "3.0.*",
|
||||
"moodlehq/behat-extension": "3.35.1",
|
||||
"mikey179/vfsstream": "^1.6"
|
||||
"moodlehq/behat-extension": "3.35.4",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"instaclick/php-webdriver": "dev-local as 1.x-dev"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+347
-233
File diff suppressed because it is too large
Load Diff
@@ -812,6 +812,10 @@ $CFG->admin = 'admin';
|
||||
// Example:
|
||||
// $CFG->behat_faildump_path = '/my/path/to/save/failure/dumps';
|
||||
//
|
||||
// You can make behat pause upon failure to help you diagnose and debug problems with your tests.
|
||||
//
|
||||
// $CFG->behat_pause_on_fail = true;
|
||||
//
|
||||
// You can specify db, selenium wd_host etc. for behat parallel run by setting following variable.
|
||||
// Example:
|
||||
// $CFG->behat_parallel_run = array (
|
||||
|
||||
@@ -161,6 +161,8 @@ if ($editform->is_cancelled()) {
|
||||
|
||||
if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
|
||||
// Deal with course creators - enrol them internally with default role.
|
||||
// Note: This does not respect capabilities, the creator will be assigned the default role.
|
||||
// This is an expected behaviour. See MDL-66683 for further details.
|
||||
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ class format_singleactivity extends format_base {
|
||||
/** @var cm_info the current activity. Use get_activity() to retrieve it. */
|
||||
private $activity = false;
|
||||
|
||||
/** @var int The category ID guessed from the form data. */
|
||||
private $categoryid = false;
|
||||
|
||||
/**
|
||||
* The URL to use for the specified course
|
||||
*
|
||||
@@ -145,6 +148,30 @@ class format_singleactivity extends format_base {
|
||||
*/
|
||||
public function course_format_options($foreditform = false) {
|
||||
static $courseformatoptions = false;
|
||||
|
||||
$fetchtypes = $courseformatoptions === false;
|
||||
$fetchtypes = $fetchtypes || ($foreditform && !isset($courseformatoptions['activitytype']['label']));
|
||||
|
||||
if ($fetchtypes) {
|
||||
$availabletypes = $this->get_supported_activities();
|
||||
if ($this->course) {
|
||||
// The course exists. Test against the course.
|
||||
$testcontext = context_course::instance($this->course->id);
|
||||
} else if ($this->categoryid) {
|
||||
// The course does not exist yet, but we have a category ID that we can test against.
|
||||
$testcontext = context_coursecat::instance($this->categoryid);
|
||||
} else {
|
||||
// The course does not exist, and we somehow do not have a category. Test capabilities against the system context.
|
||||
$testcontext = context_system::instance();
|
||||
}
|
||||
foreach (array_keys($availabletypes) as $activity) {
|
||||
$capability = "mod/{$activity}:addinstance";
|
||||
if (!has_capability($capability, $testcontext)) {
|
||||
unset($availabletypes[$activity]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($courseformatoptions === false) {
|
||||
$config = get_config('format_singleactivity');
|
||||
$courseformatoptions = array(
|
||||
@@ -153,9 +180,13 @@ class format_singleactivity extends format_base {
|
||||
'type' => PARAM_TEXT,
|
||||
),
|
||||
);
|
||||
|
||||
if (!empty($availabletypes) && !isset($availabletypes[$config->activitytype])) {
|
||||
$courseformatoptions['activitytype']['default'] = array_keys($availabletypes)[0];
|
||||
}
|
||||
}
|
||||
|
||||
if ($foreditform && !isset($courseformatoptions['activitytype']['label'])) {
|
||||
$availabletypes = $this->get_supported_activities();
|
||||
$courseformatoptionsedit = array(
|
||||
'activitytype' => array(
|
||||
'label' => new lang_string('activitytype', 'format_singleactivity'),
|
||||
@@ -183,6 +214,11 @@ class format_singleactivity extends format_base {
|
||||
*/
|
||||
public function create_edit_form_elements(&$mform, $forsection = false) {
|
||||
global $PAGE;
|
||||
|
||||
if (!$this->course && $submitvalues = $mform->getSubmitValues()) {
|
||||
$this->categoryid = $submitvalues['category'];
|
||||
}
|
||||
|
||||
$elements = parent::create_edit_form_elements($mform, $forsection);
|
||||
if (!$forsection && ($course = $PAGE->course) && !empty($course->format) &&
|
||||
$course->format !== 'site' && $course->format !== 'singleactivity') {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
@format @format_singleactivity
|
||||
Feature: Courses can be created in Single Activity mode
|
||||
In order to create a single activity course
|
||||
As a manager
|
||||
I need to create courses and set default values on them
|
||||
|
||||
Scenario: Create a course as a custom course creator
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| kevin | Kevin | the | kevin@example.com |
|
||||
And the following "roles" exist:
|
||||
| shortname | name | archetype |
|
||||
| creator | Creator | |
|
||||
And the following "system role assigns" exist:
|
||||
| user | role | contextlevel |
|
||||
| kevin | creator | System |
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Creator" role:
|
||||
| capability | permission |
|
||||
| moodle/course:create | Allow |
|
||||
| moodle/course:update | Allow |
|
||||
| moodle/course:manageactivities | Allow |
|
||||
| moodle/course:viewparticipants | Allow |
|
||||
| moodle/role:assign | Allow |
|
||||
| mod/quiz:addinstance | Allow |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My first course |
|
||||
| Course short name | myfirstcourse |
|
||||
| Format | Single activity format |
|
||||
And I press "Update format"
|
||||
Then I should see "Quiz" in the "Type of activity" "field"
|
||||
And I should not see "Forum" in the "Type of activity" "field"
|
||||
And I press "Save and display"
|
||||
And I should see "Adding a new Quiz"
|
||||
@@ -1123,11 +1123,8 @@ class behat_course extends behat_base {
|
||||
protected function get_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,3 +68,31 @@ Feature: Managers can create courses
|
||||
| id_enddate_day | 24 |
|
||||
| id_enddate_month | October |
|
||||
| id_enddate_year | 2016 |
|
||||
|
||||
Scenario: Create a course as a custom course creator
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| kevin | Kevin | the | kevin@example.com |
|
||||
And the following "roles" exist:
|
||||
| shortname | name | archetype |
|
||||
| creator | Creator | |
|
||||
And the following "system role assigns" exist:
|
||||
| user | role | contextlevel |
|
||||
| kevin | creator | System |
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Creator" role:
|
||||
| capability | permission |
|
||||
| moodle/course:create | Allow |
|
||||
| moodle/course:manageactivities | Allow |
|
||||
| moodle/course:viewparticipants | Allow |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My first course |
|
||||
| Course short name | myfirstcourse |
|
||||
And I press "Save and display"
|
||||
And I follow "Participants"
|
||||
Then I should see "Kevin the"
|
||||
And I should see "Teacher"
|
||||
|
||||
@@ -42,6 +42,9 @@ Feature: Browse course list and return back from enrolment page
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
When I log in as "user2"
|
||||
# In boost, if the viewport is such that we have the standard layout, then the click for the navigation block is in
|
||||
# the link"
|
||||
And I change window size to "small"
|
||||
And I open my profile in edit mode
|
||||
And I expand "Courses" node
|
||||
And I expand "Sample category" node
|
||||
|
||||
@@ -23,6 +23,7 @@ Feature: Converting rubric score to grades
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | <grade> | rubric |
|
||||
When I log in as "teacher1"
|
||||
And I change window size to "large"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I go to "Test assignment 1" advanced grading definition page
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -26,6 +26,7 @@ Feature: Rubrics can have levels with negative scores
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | 100 | rubric |
|
||||
And I change window size to "large"
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I go to "Test assignment 1" advanced grading definition page
|
||||
|
||||
@@ -77,7 +77,7 @@ Feature: A teacher checks the grade history report in a course
|
||||
| Student 2 | The greatest assignment ever | 50.00 | 70.00 | Teacher 2 |
|
||||
| Student 2 | Rewarding assignment | 60.00 | 80.00 | Teacher 2 |
|
||||
# Test filtering by assignment.
|
||||
And I click on "The greatest assignment ever" "option" in the "#id_itemid" "css_element"
|
||||
And I set the field "Grade item" to "The greatest assignment ever"
|
||||
And I press "Submit"
|
||||
And the following should exist in the "gradereport_history" table:
|
||||
| First name/Surname | Grade item | Original grade | Revised grade | Grader |
|
||||
@@ -87,7 +87,7 @@ Feature: A teacher checks the grade history report in a course
|
||||
| Student 1 | Rewarding assignment | | 60.00 | Teacher 1 |
|
||||
| Student 1 | Rewarding assignment | 60.00 | 80.00 | Teacher 2 |
|
||||
# Test filtering by grader.
|
||||
And I click on "Teacher 1" "option" in the "#id_grader" "css_element"
|
||||
And I set the field "Grader" to "Teacher 1"
|
||||
And I press "Submit"
|
||||
And the following should exist in the "gradereport_history" table:
|
||||
| First name/Surname | Grade item | Original grade | Revised grade | Grader |
|
||||
|
||||
@@ -93,7 +93,8 @@ class dropdown_attribute extends element {
|
||||
'value' => $option,
|
||||
'selected' => $selected == $option
|
||||
];
|
||||
}, array_keys($options))
|
||||
}, array_keys($options)),
|
||||
'label' => get_string('gradefor', 'gradereport_singleview', $this->label),
|
||||
);
|
||||
|
||||
return $OUTPUT->render_from_template('gradereport_singleview/dropdown_attribute', $context);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
{{!
|
||||
Dropdown attribute.
|
||||
}}
|
||||
<label for="{{name}}" class="accesshide">{{label}}</label>
|
||||
<select id="{{name}}" name="{{name}}" tabindex="1" {{#disabled}}disabled{{/disabled}}>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
|
||||
@@ -87,7 +87,7 @@ Feature: We can use Single view
|
||||
And the field "Grade for james (Student) 1" matches value "12.05"
|
||||
And the field "Exclude for holly (Student) 2" matches value "1"
|
||||
And I select "new grade item 1" from the "Select grade item..." singleselect
|
||||
And I click on "Very good" "option"
|
||||
And I set the field "Grade for james (Student) 1" to "Very good"
|
||||
And I press "Save"
|
||||
Then I should see "Grades were set for 1 items"
|
||||
And I press "Continue"
|
||||
@@ -98,7 +98,7 @@ Feature: We can use Single view
|
||||
And I log in as "teacher2"
|
||||
And I am on "Course 1" course homepage
|
||||
Given I navigate to "View > Single view" in the course gradebook
|
||||
And I click on "Student 4" "option"
|
||||
And I select "Student 4" from the "Select user..." singleselect
|
||||
And the "Exclude for Test assignment one" "checkbox" should be disabled
|
||||
And the "Override for Test assignment one" "checkbox" should be enabled
|
||||
|
||||
|
||||
@@ -195,9 +195,9 @@ Feature: We can set the grade to pass value
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test Forum 1 |
|
||||
| Description | Test |
|
||||
| Aggregate type | Average of ratings |
|
||||
| Grade to pass | 90 |
|
||||
| scale[modgrade_point] | 60 |
|
||||
| Ratings > Aggregate type | Average of ratings |
|
||||
| Ratings > scale[modgrade_point] | 60 |
|
||||
| Grade > Grade to pass | 90 |
|
||||
Then I should see "The grade to pass can not be greater than the maximum possible grade 60"
|
||||
|
||||
Scenario: Set a valid grade to pass for forum activity
|
||||
@@ -205,8 +205,8 @@ Feature: We can set the grade to pass value
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test Forum 1 |
|
||||
| Description | Test |
|
||||
| Aggregate type | Average of ratings |
|
||||
| Grade to pass | 90 |
|
||||
| Ratings > Aggregate type | Average of ratings |
|
||||
| Grade > Grade to pass | 90 |
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I click on "Edit forum Test Forum 1" "link"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thislanguage'] = 'Valencià_RACV';
|
||||
@@ -36,8 +36,9 @@ $string['cliincorrectvalueerror'] = 'Σφάλμα, λανθασμένη τιμή
|
||||
$string['cliincorrectvalueretry'] = 'Λανθασμένη τιμή. Παρακαλούμε προσπαθήστε ξανά.';
|
||||
$string['clitypevalue'] = 'πληκτρολογήστε μια τιμή';
|
||||
$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε τηνπροεπιλεγμένη τιμή ({$a})';
|
||||
$string['cliunknowoption'] = 'Μη αναγνωρίσιμες επιλογές: {$a}<br />
|
||||
Παρακαλούμε χρησιμοποιήστε την επιλογή --βοήθεια';
|
||||
$string['cliunknowoption'] = 'Μη αναγνωρίσιμες επιλογές:
|
||||
{$a}<br />
|
||||
Παρακαλούμε χρησιμοποιήστε την επιλογή --help (βοήθεια)';
|
||||
$string['cliyesnoprompt'] = 'πατήστε y (σημαίνει yes=ναι) ή πατήστε n (σημαίνει no=όχι)';
|
||||
$string['environmentrequireinstall'] = 'απαιτείται να εγκατασταθεί/ ενεργοποιηθεί';
|
||||
$string['environmentrequireversion'] = 'απαιτείται η έκδοση {$a->needed} ενώ εσείς έχετε την {$a->current}';
|
||||
|
||||
@@ -40,9 +40,9 @@ $string['cannotsavemd5file'] = 'Αδυναμία αποθήκευσης αρχε
|
||||
$string['cannotsavezipfile'] = 'Αδυναμία αποθήκευσης συμπιεσμένου αρχείου';
|
||||
$string['cannotunzipfile'] = 'Αδυναμία αποσυμπίεσης αρχείου';
|
||||
$string['componentisuptodate'] = 'Το στοιχείο λογισμικού είναι ενημερωμένο.';
|
||||
$string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε σφάλμα βάσης δεδομένων [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε κάποιο σφάλμα βάσης δεδομένων [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχείου που έγινε λήψη';
|
||||
$string['invalidmd5'] = 'Μη έγκυρο md5';
|
||||
$string['invalidmd5'] = 'Η μεταβλητή ελέγχου ήταν λανθασμένη - δοκιμάστε ξανά';
|
||||
$string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
|
||||
$string['remotedownloaderror'] = '<p>Απέτυχε η λήψη του τμήματος στον εξυπηρετητή σας. Παρακαλούμε επιβεβαιώστε τις ρυθμίσεις του διακομιστή μεσολάβησης (proxy)· η επέκταση PHP cURL συνιστάται θερμά.</p> <p>Πρέπει να κατεβάσετε το <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
|
||||
$string['wrongdestpath'] = 'Λανθασμένη διαδρομή προορισμού (πλήρες όνομα).';
|
||||
|
||||
@@ -37,7 +37,7 @@ $string['chooselanguagesub'] = 'Παρακαλούμε, επιλέξτε γλώ
|
||||
$string['clialreadyconfigured'] = 'Το αρχείο ρυθμίσεων config.php υπάρχει ήδη. Χρησιμοποιήστε το admin/cli/install_database.php για να εγκαταστήσετε το Moodle για αυτόν τον ιστότοπο.';
|
||||
$string['clialreadyinstalled'] = 'Το αρχείο ρυθμίσεων config.php υπάρχει ήδη. Χρησιμοποιήστε το admin/cli/install_database.php για να αναβαθμίσετε το Moodle για αυτόν τον ιστότοπο.';
|
||||
$string['cliinstallheader'] = 'Moodle {$a} πρόγραμμα εγκατάστασης γραμμής εντολών';
|
||||
$string['databasehost'] = 'Κεντρικός Υπολογιστής Βάσης Δεδομένων';
|
||||
$string['databasehost'] = 'Κεντρικός H/Y που φιλοξενεί την Βάση Δεδομένων';
|
||||
$string['databasename'] = 'Όνομα Βάσης Δεδομένων';
|
||||
$string['databasetypehead'] = 'Επιλογή οδηγού βάσης δεδομένων';
|
||||
$string['dataroot'] = 'Φάκελος δεδομένων';
|
||||
@@ -67,7 +67,7 @@ $string['pathserrcreatedataroot'] = 'Ο Φάκελος δεδομένων ({$a->
|
||||
$string['pathshead'] = 'Επιβεβαίωση Διαδρομών';
|
||||
$string['pathsrodataroot'] = 'Ο Φάκελος Δεδομένων δεν είναι εγγράψιμος.';
|
||||
$string['pathsroparentdataroot'] = 'Ο γονικός φάκελος ({$a->parent}) δεν είναι εγγράψιμος. Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργθεί από το πρόγραμμα εγκατάστασης.';
|
||||
$string['pathssubadmindir'] = 'Κάποιοι λίγοι κεντρικοί υπολογιστές ιστού χρησιμοποιούν το /admin ως ειδική διεύθυνση URL για την πρόσβαση σε κάποιο πίνακα ελέγχου ή κάτι τέτοιο. Δυστυχώς αυτό έρχεται σε αντίθεση με την τυπική τοποθεσία των σελίδων διαχείρισης (admin) του Moodle. Αυτό μπορεί να διορθωθεί με την μετονομασία του admin φακέλου στην εγκατάστασή σας, και βάζοντας αυτό το καινούργιο όνομα εδώ. Για παράδειγμα: <em>moodleadmin</em>. Αυτό θα διορθώσει όλους τους συνδέσμους με το admin στην διεύθυνσή τους σε όλη την εγκατάσταση του Moodle σας.';
|
||||
$string['pathssubadmindir'] = 'Κάποιοι λίγοι κεντρικοί υπολογιστές ιστού χρησιμοποιούν το /admin ως ειδική διεύθυνση URL για την πρόσβαση σε κάποιο πίνακα ελέγχου ή κάτι τέτοιο. Δυστυχώς αυτό έρχεται σε αντίθεση με την τυπική τοποθεσία των σελίδων διαχείρισης (admin) του Moodle. Αυτό μπορεί να διορθωθεί με την μετονομασία του admin φακέλου στην εγκατάστασή σας, και βάζοντας αυτό το καινούργιο όνομα εδώ. Για παράδειγμα: <em>moodleadmin</em>. Αυτό θα διορθώσει όλους τους συνδέσμους με το admin στην διεύθυνσή τους σε όλη την εγκατάσταση του Moodle σας.';
|
||||
$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p> <p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p> <p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p> <p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Η πλήρης διαδρομή του φακέλου που περιέχει τα αρχεία κώδικα του Moodle.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση (ιστού) από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
|
||||
|
||||
@@ -46,7 +46,7 @@ $string['dmlexceptiononinstall'] = '<p>Datu-basearen errorea gertatu da [{$a->er
|
||||
$string['downloadedfilecheckfailed'] = 'Jaitsitako fitxategiaren egiaztatzeak huts egin du.';
|
||||
$string['invalidmd5'] = 'Kontrolerako aldagaia gaizki zegoen - saiatu berriz ere';
|
||||
$string['missingrequiredfield'] = 'Beharrezko eremuren bat falta da.';
|
||||
$string['remotedownloaderror'] = '<p>Errorea osagaia zure zerbitzarian jaistean, mesedez egiaztatu proxy-ezarpenak, PHP cURL luzapena erabat gomendatzen da.</p>
|
||||
$string['remotedownloaderror'] = '<p>Errorea osagaia zure zerbitzarian jaistean, mesedez egiaztatu proxy-ezarpenak, PHP cURL hedapena erabat gomendatzen da.</p>
|
||||
<p> <a href="{$a->url}">{$a->url}</a> fitxategia eskuz jaitsi beharko zenuke, zure zerbitzariko "{$a->dest}"-ra kopiatu eta bertan deskonprimatu.</p>';
|
||||
$string['wrongdestpath'] = 'Bide desegokia';
|
||||
$string['wrongsourcebase'] = 'URL iturriaren oinarri akastuna';
|
||||
|
||||
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Datu-direktorioen baimena';
|
||||
$string['dbprefix'] = 'Taulen aurrizkia';
|
||||
$string['dirroot'] = 'Moodle direktorioa';
|
||||
$string['environmenthead'] = 'Zure ingurunea egiaztatzen...';
|
||||
$string['environmentsub2'] = 'Moodle-ko bertsio bakoitzak PHPko gutxieneko bertsioa eta derrigorrez instalatu beharreko PHP luzapen batzuk ditu. Ingurunearen azterketa oso bat egiten da instalazioa eta eguneraketa bakoitza egin aurretik. Mesedez, jarri harremanetan zerbitzariaren kudeatzailearekin ez badakizu bertsio berria edo PHP luzapenak nola instalatu.';
|
||||
$string['environmentsub2'] = 'Moodle-ko bertsio bakoitzak PHPko gutxieneko bertsioa eta derrigorrez instalatu beharreko PHP hedapen batzuk ditu. Ingurunearen azterketa oso bat egiten da instalazioa eta eguneraketa bakoitza egin aurretik. Mesedez, jarri harremanetan zerbitzariaren kudeatzailearekin ez badakizu bertsio berria edo PHP hedapenak nola instalatu.';
|
||||
$string['errorsinenvironment'] = 'Huts egin du ingurunearen egiaztatzeak!';
|
||||
$string['installation'] = 'Instalazioa';
|
||||
$string['langdownloaderror'] = 'Zoritxarrez "{$a}" hizkuntza ezin izan da jaitsi. Instalazio-prozesuak ingelesez jarraituko du.';
|
||||
@@ -83,7 +83,7 @@ $string['pathssubwwwroot'] = '<p>Moodle eskuragarri egongo den helbide osoa, hau
|
||||
<p>Oraingo helbidea egokia ez bada, mesedez aldatu ezazu URLa zure nabigatzailean eta instalazioa berriz abiatu ezazu.</p>';
|
||||
$string['pathsunsecuredataroot'] = 'Dataroot-en kokapena ez da segurua';
|
||||
$string['pathswrongadmindir'] = 'Admin direktorioa ez da existitzen';
|
||||
$string['phpextension'] = '{$a} PHP luzapena';
|
||||
$string['phpextension'] = '{$a} PHP hedapena';
|
||||
$string['phpversion'] = 'PHP bertsioa';
|
||||
$string['phpversionhelp'] = '<p>Moodle-k PHP 5.6.5 edo 7.1 bertsioetako bat behar du (7.0.x bertsioek muga batzuk dituzte). </p>
|
||||
<p>Zure bertsioa: {$a}</p>
|
||||
|
||||
@@ -44,11 +44,11 @@ $string['dataroot'] = 'Dossier de données';
|
||||
$string['datarootpermission'] = 'Droits d\'accès au dossier de données';
|
||||
$string['dbprefix'] = 'Préfixe des tables';
|
||||
$string['dirroot'] = 'Dossier Moodle';
|
||||
$string['environmenthead'] = 'Vérification de l\'environnement...';
|
||||
$string['environmenthead'] = 'Vérification de l\'environnement…';
|
||||
$string['environmentsub2'] = 'Chaque version de Moodle nécessite une version minimale de certains composants PHP et des extensions de PHP obligatoires. Une vérification complète de l\'environnement est effectuée avec chaque installation et chaque mise à jour. Veuillez contacter l\'administrateur du serveur si vous ne savez pas comment installer une nouvelle version ou activer des extensions de PHP.';
|
||||
$string['errorsinenvironment'] = 'Échec de la vérification de l\'environnement !';
|
||||
$string['installation'] = 'Installation';
|
||||
$string['langdownloaderror'] = 'La langue {$a} n\'a pas pu être téléchargée. La suite de l\'installation se déroulera en anglais. Vous pourrez télécharger et installer d\'autres langues à la fin de l\'installation';
|
||||
$string['langdownloaderror'] = 'La langue « {$a} » n\'a pas pu être téléchargée. La suite de l\'installation se déroulera en anglais. Vous pourrez télécharger et installer d\'autres langues à la fin de l\'installation';
|
||||
$string['memorylimithelp'] = '<p>La limite de mémoire de PHP sur votre serveur est actuellement de {$a}.</p>
|
||||
<p>Cette valeur trop basse risque de générer des problèmes de manque de mémoire pour Moodle, notamment si vous utilisez beaucoup de modules et/ou si vous avez un grand nombre d\'utilisateurs.</p>
|
||||
<p>Il est recommandé de configurer PHP avec une limite de mémoire aussi élevée que possible, par exemple 40 Mo. Vous pouvez obtenir cela de différentes façons :</p>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'fr';
|
||||
$string['thislanguage'] = 'Français pour Workplace';
|
||||
@@ -37,7 +37,7 @@ $string['cliincorrectvalueretry'] = 'Netočna vrijednost, pokušajte ponovno';
|
||||
$string['clitypevalue'] = 'unesite vrijednost';
|
||||
$string['clitypevaluedefault'] = 'unesite vrijednost ili pritisnite Enter za korištenje zadane vrijednosti ({$a})';
|
||||
$string['cliunknowoption'] = 'Nepoznate opcije: {$a} Molimo koristite --help opciju.';
|
||||
$string['cliyesnoprompt'] = 'unesite y (znači da) ili n (znači ne)';
|
||||
$string['cliyesnoprompt'] = 'unesite d (znači da) ili n (znači ne)';
|
||||
$string['environmentrequireinstall'] = 'je neophodno instalirati/omogućiti';
|
||||
$string['environmentrequireversion'] = 'neophodna inačica je {$a->needed}, a vi trenutačno koristite inačicu {$a->current}';
|
||||
$string['upgradekeyset'] = 'Ključ za ažuriranje (ostavite prazno kako ga ne bi zadali)';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'hr';
|
||||
$string['thislanguage'] = 'Croatian schools';
|
||||
@@ -36,12 +36,28 @@ $string['chooselanguagehead'] = 'Pilih bahasa';
|
||||
$string['chooselanguagesub'] = 'Silakan pilih bahasa untuk instalasi. Bahasa ini juga akan digunakan sebagai bahasa default untuk situs, meskipun mungkin akan diubah kemudian.';
|
||||
$string['clialreadyconfigured'] = 'Berkas konfigurasi config.php sudah ada. Silakan gunakan admin /cli/install_database.php untuk menginstal Moodle untuk situs ini.';
|
||||
$string['clialreadyinstalled'] = 'File konfigurasi config.php sudah ada. Silakan gunakan admin/cli/install_database.php untuk menginstal Moodle untuk situs ini.';
|
||||
$string['cliinstallheader'] = 'Program pemasangan baris perintah Moodle {$a}';
|
||||
$string['databasehost'] = 'Host basis data';
|
||||
$string['databasename'] = 'Nama basis data';
|
||||
$string['databasetypehead'] = 'Pilih pengandar basis data';
|
||||
$string['dataroot'] = 'Direktori data';
|
||||
$string['datarootpermission'] = 'Izin direktori data';
|
||||
$string['dbprefix'] = 'Prefiks tabel';
|
||||
$string['dirroot'] = 'Direktori Moodle';
|
||||
$string['environmenthead'] = 'Memeriksa sistem Anda ...';
|
||||
$string['environmentsub2'] = 'Setiap rilis Moodle memiliki beberapa persyaratan versi PHP minimum dan sejumlah ekstensi PHP wajib. Pemeriksaan komponen sistem akan dilakukan sebelum pemasangan dan peningkatan versi. Silakan hubungi administrator peladen jika Anda tidak tahu cara mamasang versi baru atau mengaktifkan ekstensi PHP.';
|
||||
$string['errorsinenvironment'] = 'Pemeriksaan sistem gagal!';
|
||||
$string['installation'] = 'Instalasi';
|
||||
$string['langdownloaderror'] = 'Sayangnya bahasa "{$a}" tidak dapat diunduh. Proses instalasi akan dilanjutkan dalam bahasa Inggris.';
|
||||
$string['memorylimithelp'] = '<p> Batas memori PHP untuk server Anda saat ini diatur ke {$a}. </p> <p> Ini dapat menyebabkan Moodle memiliki masalah memori di kemudian hari, terutama jika Anda memiliki banyak modul yang diaktifkan dan/atau banyak pengguna. </p> <p> Kami menyarankan Anda mengkonfigurasi PHP dengan batas yang lebih tinggi jika memungkinkan, seperti 40M. Ada beberapa cara untuk melakukan ini yang dapat Anda coba: </p>
|
||||
<ol> <li> Jika Anda bisa, kompilasi ulang PHP dengan <i> --enable-memory-limit </i>. Ini memungkinkan Moodle untuk mengatur batas memori itu sendiri. </li>
|
||||
<li> Jika Anda memiliki akses ke file php.ini, Anda dapat mengubah pengaturan <b> memory_limit </b> di sana menjadi sekitar 40M. Jika Anda tidak memiliki akses, Anda mungkin dapat meminta administrator untuk melakukan ini untuk Anda. </li>
|
||||
<li> Pada beberapa server PHP, Anda dapat membuat file .htaccess di direktori Moodle yang berisi baris ini: <blockquote> <div> php_value memory_limit 40M </div> </blockquote> <p> Namun, pada beberapa server ini tidak diizinkan <b> semua </b> halaman PHP tidak berfungsi (Anda akan melihat kesalahan ketika Anda melihat halaman) sehingga Anda Anda harus menghapus file .htaccess. </p> </li> </ol>';
|
||||
$string['paths'] = 'Jalur';
|
||||
$string['pathserrcreatedataroot'] = 'Direktori data ({$a->dataroot}) tidak dapat dibuat oleh installer.';
|
||||
$string['pathshead'] = 'Konfirmasi jalur';
|
||||
$string['pathsrodataroot'] = 'Direktori data root tidak dapat ditulisi.';
|
||||
$string['pathsroparentdataroot'] = 'Direktori induk ({$a->parent}) tidak dapat ditulisi. Direktori data ({$a->dataroot}) tidak dapat dibuat oleh installer.';
|
||||
$string['pathssubadmindir'] = 'Beberapa hosting menggunakan / admin sebagai URL khusus untuk Anda mengakses panel kontrol atau sesuatu. Sayangnya ini bertentangan dengan lokasi standar untuk halaman admin Moodle. Anda dapat memperbaikinya dengan mengganti nama direktori admin di instalasi Anda, dan meletakkan nama baru itu di sini. Misalnya: <em> moodleadmin </em>. Ini akan memperbaiki tautan admin di Moodle.';
|
||||
$string['pathssubdataroot'] = '<p> Direktori tempat Moodle akan menyimpan semua konten file yang diunggah oleh pengguna. </p> <p> Direktori ini harus dapat dibaca dan ditulis oleh pengguna server web (biasanya \'www-data\', \'nobody\', atau \' apache \'). </p> <p> Itu tidak boleh diakses secara langsung melalui web. </p> <p> Jika direktori saat ini tidak ada, proses instalasi akan berusaha membuatnya. </p>';
|
||||
$string['pathssubdirroot'] = '<p> Jalur lengkap ke direktori yang berisi kode Moodle. </p>';
|
||||
|
||||
@@ -57,7 +57,7 @@ $string['memorylimithelp'] = '<p>現在、サーバのPHPメモリ制限が {$a}
|
||||
<ol>
|
||||
<li>あなたがリコンパイル可能な場合、PHPを<i>--enable-memory-limit</i>オプションでコンパイルしてください。これにより、Moodle自身がメモリ制限を設定することが可能になります。</li>
|
||||
<li>あなたがphp.iniファイルにアクセスできる場合、<b>memory_limit</b>設定を40Mのように変更することができます。php.iniファイルにアクセスできない場合、管理者に変更を依頼してください。</li>
|
||||
<li>いくつかのPHPサーバでは下記の行を含む.htaccessファイルをMoodleディレクトリに作成することができます:
|
||||
<li>いくつかのPHPサーバでは以下の行を含む.htaccessファイルをMoodleディレクトリに作成することができます:
|
||||
<blockquote><div>php_value memory_limit 40M</div></blockquote>
|
||||
<p>しかし、この設定が<b>すべての</b>PHPページの動作を妨げる場合もあります。ページ閲覧中にエラーが表示される場合、.htaccessファイルを削除してください。</p></li>
|
||||
</ol>';
|
||||
|
||||
@@ -40,6 +40,6 @@ $string['cliunknowoption'] = 'Nieznana opcja:
|
||||
{$a}
|
||||
Proszę użyć pomocy.';
|
||||
$string['cliyesnoprompt'] = 'wpisz y (czyli tak) lub n (czyli nie)';
|
||||
$string['environmentrequireinstall'] = 'jest niezbędnę, żeby było zainstalowane/włączone';
|
||||
$string['environmentrequireinstall'] = 'jest niezbędne, aby było zainstalowane i włączone';
|
||||
$string['environmentrequireversion'] = 'wersja {$a->needed} jest niezbędna a ty używasz wersji {$a->current}';
|
||||
$string['upgradekeyset'] = 'Zaktualizuj klucz (pozostaw puste jeśli nie jest to skonfigurowane)';
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['clianswerno'] = 'n';
|
||||
$string['cliansweryes'] = 'y';
|
||||
$string['cliincorrectvalueretry'] = 'Valoarea nu este corectă, încercați din nou';
|
||||
$string['environmentrequireinstall'] = 'trebuie să fie instalat și activat';
|
||||
$string['environmentrequireversion'] = 'versiunea {$a->needed} este necesară iar dumneavoastră rulați {$a->current}';
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'Limbă';
|
||||
$string['moodlelogo'] = 'Logo Moodle';
|
||||
$string['next'] = 'Următoarea';
|
||||
$string['previous'] = 'Anterior';
|
||||
$string['reload'] = 'Reîncarcă';
|
||||
@@ -31,6 +31,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'மொழி';
|
||||
$string['moodlelogo'] = 'Moodle சின்னம்';
|
||||
$string['next'] = 'அடுத்தது';
|
||||
$string['previous'] = 'முன்னைய';
|
||||
$string['reload'] = 'மீள ஏற்றுக';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'zh_cn';
|
||||
$string['thislanguage'] = 'Workplace 简体中文版';
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'zh_tw';
|
||||
$string['thislanguage'] = 'Workplace 繁體中文版';
|
||||
@@ -1197,6 +1197,7 @@ $string['unsupportedphpversion7'] = 'PHP version 7 is not supported.';
|
||||
$string['unsupportedphpversion71'] = 'PHP version 7.1 is not supported.';
|
||||
$string['unsupportedphpversion72'] = 'PHP version 7.2 is not supported.';
|
||||
$string['unsupportedphpversion73'] = 'PHP version 7.3 is not supported.';
|
||||
$string['unsupportedphpversion74'] = 'PHP version 7.4 is not supported.';
|
||||
$string['unsuspenduser'] = 'Activate user account';
|
||||
$string['updateaccounts'] = 'Update existing accounts';
|
||||
$string['updatecomponent'] = 'Update component';
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -712,24 +712,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
window.setTimeout(function() {
|
||||
// Get the current element with focus.
|
||||
var focusElement = $(document.activeElement);
|
||||
var timeoutPromise = $.Deferred();
|
||||
|
||||
// Only close the menu if the input hasn't regained focus, and if the element still exists.
|
||||
// Due to the half a second delay, it is possible that the input element no longer exist
|
||||
// by the time this code is being executed.
|
||||
if (focusElement.attr('id') != inputElement.attr('id') && $('#' + state.inputId).length) {
|
||||
if (options.tags) {
|
||||
pendingPromise.then(function() {
|
||||
timeoutPromise.then(function() {
|
||||
return createItem(options, state, originalSelect);
|
||||
})
|
||||
.catch();
|
||||
}
|
||||
pendingPromise.then(function() {
|
||||
timeoutPromise.then(function() {
|
||||
return closeSuggestions(state);
|
||||
})
|
||||
.catch();
|
||||
}
|
||||
|
||||
pendingPromise.resolve();
|
||||
timeoutPromise.then(function() {
|
||||
return pendingPromise.resolve();
|
||||
})
|
||||
.catch();
|
||||
timeoutPromise.resolve();
|
||||
}, 500);
|
||||
});
|
||||
if (options.showSuggestions) {
|
||||
|
||||
+63
-18
@@ -59,6 +59,9 @@ define([
|
||||
/** @var {Object} iconSystem - Object extending core/iconsystem */
|
||||
var iconSystem = {};
|
||||
|
||||
/** @var {Array} blacklistedNestedHelpers - List of helpers that can't be called within other helpers */
|
||||
var blacklistedNestedHelpers = ['js'];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -357,6 +360,60 @@ define([
|
||||
return '[[_t_' + index + ']]';
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a helper function to be added to the context for rendering the a
|
||||
* template.
|
||||
*
|
||||
* This will parse the provided text before giving it to the helper function
|
||||
* in order to remove any blacklisted nested helpers to prevent one helper
|
||||
* from calling another.
|
||||
*
|
||||
* In particular to prevent the JS helper from being called from within another
|
||||
* helper because it can lead to security issues when the JS portion is user
|
||||
* provided.
|
||||
*
|
||||
* @param {function} helperFunction The helper function to add
|
||||
* @param {object} context The template context for the helper function
|
||||
* @return {Function} To be set in the context
|
||||
*/
|
||||
Renderer.prototype.addHelperFunction = function(helperFunction, context) {
|
||||
return function() {
|
||||
return function(sectionText, helper) {
|
||||
// Override the blacklisted helpers in the template context with
|
||||
// a function that returns an empty string for use when executing
|
||||
// other helpers. This is to prevent these helpers from being
|
||||
// executed as part of the rendering of another helper in order to
|
||||
// prevent any potential security issues.
|
||||
var originalHelpers = blacklistedNestedHelpers.reduce(function(carry, name) {
|
||||
if (context.hasOwnProperty(name)) {
|
||||
carry[name] = context[name];
|
||||
}
|
||||
|
||||
return carry;
|
||||
}, {});
|
||||
|
||||
blacklistedNestedHelpers.forEach(function(helperName) {
|
||||
context[helperName] = function() {
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
// Execute the helper with the modified context that doesn't include
|
||||
// the blacklisted nested helpers. This prevents the blacklisted
|
||||
// helpers from being called from within other helpers.
|
||||
var result = helperFunction.apply(this, [context, sectionText, helper]);
|
||||
|
||||
// Restore the original helper implementation in the context so that
|
||||
// any further rendering has access to them again.
|
||||
for (var name in originalHelpers) {
|
||||
context[name] = originalHelpers[name];
|
||||
}
|
||||
|
||||
return result;
|
||||
}.bind(this);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add some common helper functions to all context objects passed to templates.
|
||||
* These helpers match exactly the helpers available in php.
|
||||
@@ -371,24 +428,12 @@ define([
|
||||
this.requiredStrings = [];
|
||||
this.requiredJS = [];
|
||||
context.uniqid = (uniqInstances++);
|
||||
context.str = function() {
|
||||
return this.stringHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.pix = function() {
|
||||
return this.pixHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.js = function() {
|
||||
return this.jsHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.quote = function() {
|
||||
return this.quoteHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.shortentext = function() {
|
||||
return this.shortenTextHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.userdate = function() {
|
||||
return this.userDateHelper.bind(this, context);
|
||||
}.bind(this);
|
||||
context.str = this.addHelperFunction(this.stringHelper, context);
|
||||
context.pix = this.addHelperFunction(this.pixHelper, context);
|
||||
context.js = this.addHelperFunction(this.jsHelper, context);
|
||||
context.quote = this.addHelperFunction(this.quoteHelper, context);
|
||||
context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);
|
||||
context.userdate = this.addHelperFunction(this.userDateHelper, context);
|
||||
context.globals = {config: config};
|
||||
context.currentTheme = themeName;
|
||||
};
|
||||
|
||||
+227
-113
@@ -32,8 +32,12 @@ use Behat\Mink\Exception\DriverException;
|
||||
use Behat\Mink\Exception\ExpectationException;
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Behat\Mink\Element\Element;
|
||||
use Behat\Mink\Session;
|
||||
|
||||
require_once(__DIR__ . '/classes/component_named_selector.php');
|
||||
require_once(__DIR__ . '/classes/component_named_replacement.php');
|
||||
|
||||
/**
|
||||
* Steps definitions base class.
|
||||
*
|
||||
@@ -103,14 +107,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
|
||||
|
||||
// Throw exception, so dev knows it is not supported.
|
||||
if ($selector === 'named') {
|
||||
$exception = 'Using the "named" selector is deprecated as of 3.1. '
|
||||
.' Use the "named_partial" or use the "named_exact" selector instead.';
|
||||
throw new ExpectationException($exception, $this->getSession());
|
||||
}
|
||||
|
||||
// Returns the first match.
|
||||
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
|
||||
return count($items) ? reset($items) : null;
|
||||
@@ -125,12 +121,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @param string $selector The selector type (css, xpath, named...)
|
||||
* @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator...
|
||||
* @param Exception $exception Otherwise we throw expcetion with generic info
|
||||
* @param NodeElement $node Spins around certain DOM node instead of the whole page
|
||||
* @param NodeElement $container Restrict the search to just children of the specified container
|
||||
* @param int $timeout Forces a specific time out (in seconds). If 0 is provided the default timeout will be applied.
|
||||
* @return array NodeElements list
|
||||
*/
|
||||
protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
|
||||
|
||||
protected function find_all($selector, $locator, $exception = false, $container = false, $timeout = false) {
|
||||
// Throw exception, so dev knows it is not supported.
|
||||
if ($selector === 'named') {
|
||||
$exception = 'Using the "named" selector is deprecated as of 3.1. '
|
||||
@@ -140,7 +135,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
|
||||
// Generic info.
|
||||
if (!$exception) {
|
||||
|
||||
// With named selectors we can be more specific.
|
||||
if (($selector == 'named_exact') || ($selector == 'named_partial')) {
|
||||
$exceptiontype = $locator[0];
|
||||
@@ -159,12 +153,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
$exception = new ElementNotFoundException($this->getSession(), $exceptiontype, null, $exceptionlocator);
|
||||
}
|
||||
|
||||
$params = array('selector' => $selector, 'locator' => $locator);
|
||||
// Pushing $node if required.
|
||||
if ($node) {
|
||||
$params['node'] = $node;
|
||||
}
|
||||
|
||||
// How much we will be waiting for the element to appear.
|
||||
if (!$timeout) {
|
||||
$timeout = self::get_timeout();
|
||||
@@ -176,44 +164,87 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
$microsleep = true;
|
||||
}
|
||||
|
||||
// Normalise the values in order to perform the search.
|
||||
$normalised = $this->normalise_selector($selector, $locator, $container ?: $this->getSession()->getPage());
|
||||
$selector = $normalised['selector'];
|
||||
$locator = $normalised['locator'];
|
||||
$container = $normalised['container'];
|
||||
|
||||
if (empty($container)) {
|
||||
$container = $this->getSession()->getPage();
|
||||
}
|
||||
|
||||
// Waits for the node to appear if it exists, otherwise will timeout and throw the provided exception.
|
||||
return $this->spin(
|
||||
function($context, $args) {
|
||||
|
||||
// If no DOM node provided look in all the page.
|
||||
if (empty($args['node'])) {
|
||||
return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']);
|
||||
}
|
||||
|
||||
// For nodes contained in other nodes we can not use the basic named selectors
|
||||
// as they include unions and they would look for matches in the DOM root.
|
||||
$elementxpath = $context->getSession()->getSelectorsHandler()->selectorToXpath($args['selector'], $args['locator']);
|
||||
|
||||
// Split the xpath in unions and prefix them with the container xpath.
|
||||
$unions = explode('|', $elementxpath);
|
||||
foreach ($unions as $key => $union) {
|
||||
$union = trim($union);
|
||||
|
||||
// We are in the container node.
|
||||
if (strpos($union, '.') === 0) {
|
||||
$union = substr($union, 1);
|
||||
} else if (strpos($union, '/') !== 0) {
|
||||
// Adding the path separator in case it is not there.
|
||||
$union = '/' . $union;
|
||||
}
|
||||
$unions[$key] = $args['node']->getXpath() . $union;
|
||||
}
|
||||
|
||||
// We can not use usual Element::find() as it prefixes with DOM root.
|
||||
return $context->getSession()->getDriver()->find(implode('|', $unions));
|
||||
},
|
||||
$params,
|
||||
$timeout,
|
||||
$exception,
|
||||
$microsleep
|
||||
function() use ($selector, $locator, $container) {
|
||||
return $container->findAll($selector, $locator);
|
||||
}, [], $timeout, $exception, $microsleep
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the locator and selector.
|
||||
*
|
||||
* @param string $selector The type of thing to search
|
||||
* @param mixed $locator The locator value. Can be an array, but is more likely a string.
|
||||
* @param Element $container An optional container to search within
|
||||
* @return array The selector, locator, and container to search within
|
||||
*/
|
||||
public function normalise_selector(string $selector, $locator, Element $container): array {
|
||||
// Check for specific transformations for this selector type.
|
||||
$transformfunction = "transform_find_for_{$selector}";
|
||||
if (method_exists('behat_selectors', $transformfunction)) {
|
||||
// A selector-specific transformation exists.
|
||||
// Perform initial transformation of the selector within the current container.
|
||||
$normalised = behat_selectors::{$transformfunction}($this, $locator, $container);
|
||||
$selector = $normalised['selector'];
|
||||
$locator = $normalised['locator'];
|
||||
$container = $normalised['container'];
|
||||
}
|
||||
|
||||
// Normalise the css and xpath selector types.
|
||||
if ('css_element' === $selector) {
|
||||
$selector = 'css';
|
||||
} else if ('xpath_element' === $selector) {
|
||||
$selector = 'xpath';
|
||||
}
|
||||
|
||||
// Convert to a named selector where the selector type is not a known selector.
|
||||
$converttonamed = !$this->getSession()->getSelectorsHandler()->isSelectorRegistered($selector);
|
||||
$converttonamed = $converttonamed && 'xpath' !== $selector;
|
||||
if ($converttonamed) {
|
||||
if (behat_partial_named_selector::is_deprecated_selector($selector)) {
|
||||
if ($replacement = behat_partial_named_selector::get_deprecated_replacement($selector)) {
|
||||
error_log("The '{$selector}' selector has been replaced with {$replacement}");
|
||||
$selector = $replacement;
|
||||
}
|
||||
} else if (behat_exact_named_selector::is_deprecated_selector($selector)) {
|
||||
if ($replacement = behat_exact_named_selector::get_deprecated_replacement($selector)) {
|
||||
error_log("The '{$selector}' selector has been replaced with {$replacement}");
|
||||
$selector = $replacement;
|
||||
}
|
||||
}
|
||||
|
||||
$allowedpartialselectors = behat_partial_named_selector::get_allowed_selectors();
|
||||
$allowedexactselectors = behat_exact_named_selector::get_allowed_selectors();
|
||||
if (isset($allowedpartialselectors[$selector])) {
|
||||
$locator = behat_selectors::normalise_named_selector($allowedpartialselectors[$selector], $locator);
|
||||
$selector = 'named_partial';
|
||||
} else if (isset($allowedexactselectors[$selector])) {
|
||||
$locator = behat_selectors::normalise_named_selector($allowedexactselectors[$selector], $locator);
|
||||
$selector = 'named_exact';
|
||||
} else {
|
||||
throw new ExpectationException("The '{$selector}' selector type is not registered.", $this->getSession()->getDriver());
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'selector' => $selector,
|
||||
'locator' => $locator,
|
||||
'container' => $container,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds DOM nodes in the page using named selectors.
|
||||
*
|
||||
@@ -237,27 +268,14 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
public function __call($name, $arguments) {
|
||||
|
||||
if (substr($name, 0, 5) !== 'find_') {
|
||||
throw new coding_exception('The "' . $name . '" method does not exist');
|
||||
if (substr($name, 0, 5) === 'find_') {
|
||||
return call_user_func_array([$this, 'find'], array_merge(
|
||||
[substr($name, 5)],
|
||||
$arguments
|
||||
));
|
||||
}
|
||||
|
||||
// Only the named selector identifier.
|
||||
$cleanname = substr($name, 5);
|
||||
|
||||
// All named selectors shares the interface.
|
||||
if (count($arguments) !== 1) {
|
||||
throw new coding_exception('The "' . $cleanname . '" named selector needs the locator as it\'s single argument');
|
||||
}
|
||||
|
||||
// Redirecting execution to the find method with the specified selector.
|
||||
// It will detect if it's pointing to an unexisting named selector.
|
||||
return $this->find('named_partial',
|
||||
array(
|
||||
$cleanname,
|
||||
behat_context_helper::escape($arguments[0])
|
||||
)
|
||||
);
|
||||
throw new coding_exception("The '{$name}' method does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,12 +386,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_selected_node($selectortype, $element) {
|
||||
|
||||
// Getting Mink selector and locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
|
||||
// Returns the NodeElement.
|
||||
return $this->find($selector, $locator);
|
||||
return $this->find($selectortype, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,7 +398,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_text_selector_node($selectortype, $element) {
|
||||
|
||||
// Getting Mink selector and locator.
|
||||
list($selector, $locator) = $this->transform_text_selector($selectortype, $element);
|
||||
|
||||
@@ -404,18 +416,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_node_in_container($selectortype, $element, $containerselectortype, $containerelement) {
|
||||
|
||||
// Gets the container, it will always be text based.
|
||||
$containernode = $this->get_text_selector_node($containerselectortype, $containerelement);
|
||||
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
|
||||
// Specific exception giving info about where can't we find the element.
|
||||
$locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"';
|
||||
$exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg);
|
||||
|
||||
// Looks for the requested node inside the container node.
|
||||
return $this->find($selector, $locator, $exception, $containernode);
|
||||
return $this->find($selectortype, $element, $exception, $containernode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,14 +441,14 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return array Contains the selector and the locator expected by Mink.
|
||||
*/
|
||||
protected function transform_selector($selectortype, $element) {
|
||||
|
||||
// Here we don't know if an allowed text selector is being used.
|
||||
$selectors = behat_selectors::get_allowed_selectors();
|
||||
if (!isset($selectors[$selectortype])) {
|
||||
throw new ExpectationException('The "' . $selectortype . '" selector type does not exist', $this->getSession());
|
||||
}
|
||||
|
||||
return behat_selectors::get_behat_selector($selectortype, $element, $this->getSession());
|
||||
$normalised = $this->normalise_selector($selectortype, $element, $this->getSession()->getPage());
|
||||
return [$normalised['selector'], $normalised['locator']];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,63 +485,66 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* Spins around an element until it exists
|
||||
*
|
||||
* @throws ExpectationException
|
||||
* @param string $element
|
||||
* @param string $locator
|
||||
* @param string $selectortype
|
||||
* @return void
|
||||
*/
|
||||
protected function ensure_element_exists($element, $selectortype) {
|
||||
|
||||
// Getting the behat selector & locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
|
||||
protected function ensure_element_exists($locator, $selectortype) {
|
||||
// Exception if it timesout and the element is still there.
|
||||
$msg = 'The "' . $element . '" element does not exist and should exist';
|
||||
$msg = "The '{$locator}' element does not exist and should";
|
||||
$exception = new ExpectationException($msg, $this->getSession());
|
||||
|
||||
// Normalise the values in order to perform the search.
|
||||
$normalised = $this->normalise_selector($selectortype, $locator, $this->getSession()->getPage());
|
||||
$selector = $normalised['selector'];
|
||||
$locator = $normalised['locator'];
|
||||
$container = $normalised['container'];
|
||||
|
||||
// It will stop spinning once the find() method returns true.
|
||||
$this->spin(
|
||||
function($context, $args) {
|
||||
// We don't use behat_base::find as it is already spinning.
|
||||
if ($context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
|
||||
function() use ($selector, $locator, $container) {
|
||||
if ($container->find($selector, $locator)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
array('selector' => $selector, 'locator' => $locator),
|
||||
[],
|
||||
self::get_extended_timeout(),
|
||||
$exception,
|
||||
true
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Spins until the element does not exist
|
||||
*
|
||||
* @throws ExpectationException
|
||||
* @param string $element
|
||||
* @param string $locator
|
||||
* @param string $selectortype
|
||||
* @return void
|
||||
*/
|
||||
protected function ensure_element_does_not_exist($element, $selectortype) {
|
||||
|
||||
// Getting the behat selector & locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
|
||||
protected function ensure_element_does_not_exist($locator, $selectortype) {
|
||||
// Exception if it timesout and the element is still there.
|
||||
$msg = 'The "' . $element . '" element exists and should not exist';
|
||||
$msg = "The '{$locator}' element exists and should not exist";
|
||||
$exception = new ExpectationException($msg, $this->getSession());
|
||||
|
||||
// Normalise the values in order to perform the search.
|
||||
$normalised = $this->normalise_selector($selectortype, $locator, $this->getSession()->getPage());
|
||||
$selector = $normalised['selector'];
|
||||
$locator = $normalised['locator'];
|
||||
$container = $normalised['container'];
|
||||
|
||||
// It will stop spinning once the find() method returns false.
|
||||
$this->spin(
|
||||
function($context, $args) {
|
||||
// We don't use behat_base::find() as we are already spinning.
|
||||
if (!$context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
|
||||
return true;
|
||||
function() use ($selector, $locator, $container) {
|
||||
if ($container->find($selector, $locator)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
array('selector' => $selector, 'locator' => $locator),
|
||||
// Note: We cannot use $this because the find will then be $this->find(), which leads us to a nested spin().
|
||||
// We cannot nest spins because the outer spin times out before the inner spin completes.
|
||||
[],
|
||||
self::get_extended_timeout(),
|
||||
$exception,
|
||||
true
|
||||
@@ -665,11 +675,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
|
||||
switch ($windowsize) {
|
||||
case "small":
|
||||
$width = 640;
|
||||
$height = 480;
|
||||
$width = 1024;
|
||||
$height = 768;
|
||||
break;
|
||||
case "medium":
|
||||
$width = 1024;
|
||||
$width = 1366;
|
||||
$height = 768;
|
||||
break;
|
||||
case "large":
|
||||
@@ -979,6 +989,56 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert page names to URLs for steps like 'When I am on the "[page name]" page'.
|
||||
*
|
||||
* You should override this as appropriate for your plugin. The method
|
||||
* {@link behat_navigation::resolve_core_page_url()} is a good example.
|
||||
*
|
||||
* Your overridden method should document the recognised page types with
|
||||
* a table like this:
|
||||
*
|
||||
* Recognised page names are:
|
||||
* | Page | Description |
|
||||
*
|
||||
* @param string $page name of the page, with the component name removed e.g. 'Admin notification'.
|
||||
* @return moodle_url the corresponding URL.
|
||||
* @throws Exception with a meaningful error message if the specified page cannot be found.
|
||||
*/
|
||||
protected function resolve_page_url(string $page): moodle_url {
|
||||
throw new Exception('Component "' . get_class($this) .
|
||||
'" does not support the generic \'When I am on the "' . $page .
|
||||
'" page\' navigation step.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
|
||||
*
|
||||
* A typical example might be:
|
||||
* When I am on the "Test quiz" "mod_quiz > Responses report" page
|
||||
* which would cause this method in behat_mod_quiz to be called with
|
||||
* arguments 'Responses report', 'Test quiz'.
|
||||
*
|
||||
* You should override this as appropriate for your plugin. The method
|
||||
* {@link behat_navigation::resolve_core_page_instance_url()} is a good example.
|
||||
*
|
||||
* Your overridden method should document the recognised page types with
|
||||
* a table like this:
|
||||
*
|
||||
* Recognised page names are:
|
||||
* | Type | identifier meaning | Description |
|
||||
*
|
||||
* @param string $type identifies which type of page this is, e.g. 'Attempt review'.
|
||||
* @param string $identifier identifies the particular page, e.g. 'Test quiz > student > Attempt 1'.
|
||||
* @return moodle_url the corresponding URL.
|
||||
* @throws Exception with a meaningful error message if the specified page cannot be found.
|
||||
*/
|
||||
protected function resolve_page_instance_url(string $type, string $identifier): moodle_url {
|
||||
throw new Exception('Component "' . get_class($this) .
|
||||
'" does not support the generic \'When I am on the "' . $identifier .
|
||||
'" "' . $type . '" page\' navigation step.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required timeout in seconds.
|
||||
*
|
||||
@@ -1028,4 +1088,58 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
public static function get_extended_timeout() : int {
|
||||
return self::get_real_timeout(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the exact named selectors for the component.
|
||||
*
|
||||
* Named selectors are what make Behat steps like
|
||||
* Then I should see "Useful text" in the "General" "fieldset"
|
||||
* work. Here, "fieldset" is the named selector, and "General" is the locator.
|
||||
*
|
||||
* If you override this method in your plugin (e.g. mod_mymod), to define
|
||||
* new selectors specific to your plugin. For example, if you returned
|
||||
* new behat_component_named_selector('Thingy',
|
||||
* [".//some/xpath//img[contains(@alt, %locator%)]/.."])
|
||||
* then
|
||||
* Then I should see "Useful text" in the "Whatever" "mod_mymod > Thingy"
|
||||
* would work.
|
||||
*
|
||||
* This method should return a list of {@link behat_component_named_selector} and
|
||||
* the docs on that class explain how it works.
|
||||
*
|
||||
* @return behat_component_named_selector[]
|
||||
*/
|
||||
public static function get_exact_named_selectors(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the partial named selectors for the component.
|
||||
*
|
||||
* Like the exact named selectors above, but the locator only
|
||||
* needs to match part of the text. For example, the standard
|
||||
* "button" is a partial selector, so:
|
||||
* When I click "Save" "button"
|
||||
* will activate "Save changes".
|
||||
*
|
||||
* @return behat_component_named_selector[]
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the Mink named replacements for the component.
|
||||
*
|
||||
* Named replacements allow you to define parts of an xpath that can be reused multiple times, or in multiple
|
||||
* xpaths.
|
||||
*
|
||||
* This method should return a list of {@link behat_component_named_replacement} and the docs on that class explain
|
||||
* how it works.
|
||||
*
|
||||
* @return behat_component_named_replacement[]
|
||||
*/
|
||||
public static function get_named_replacements(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,9 @@ class behat_context_helper {
|
||||
* @return behat_base
|
||||
*/
|
||||
public static function get($classname) {
|
||||
if (self::$environment->hasContextClass($classname)) {
|
||||
return self::$environment->getContext($classname);
|
||||
}
|
||||
|
||||
$suitename = self::$environment->getSuite()->getName();
|
||||
// If default suite, then get the default theme name.
|
||||
@@ -114,6 +117,16 @@ class behat_context_helper {
|
||||
return self::$environment->getContext($classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether there is a context of the specified classname.
|
||||
*
|
||||
* @param string $classname
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_context(string $classname): bool {
|
||||
return self::$environment->hasContextClass($classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates string to XPath literal.
|
||||
*
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/named_selector.php');
|
||||
require_once(__DIR__ . '/exact_named_selector.php');
|
||||
require_once(__DIR__ . '/partial_named_selector.php');
|
||||
|
||||
use Behat\Mink\Exception\ExpectationException as ExpectationException;
|
||||
use Behat\Mink\Element\Element;
|
||||
|
||||
/**
|
||||
* Moodle selectors manager.
|
||||
@@ -47,32 +49,13 @@ class behat_selectors {
|
||||
* @return array Contains the selector and the locator expected by Mink.
|
||||
*/
|
||||
public static function get_behat_selector($selectortype, $element, Behat\Mink\Session $session) {
|
||||
// Note: This function is not deprecated, but not the recommended way of doing things.
|
||||
$normalised = $session->normalise_selector($selectortype, $element, $session->getPage());
|
||||
$selector = $normalised['selector'];
|
||||
$locator = $normalised['locator'];
|
||||
|
||||
// CSS and XPath selectors locator is one single argument.
|
||||
if ($selectortype == 'css_element' || $selectortype == 'xpath_element') {
|
||||
$selector = str_replace('_element', '', $selectortype);
|
||||
$locator = $element;
|
||||
} else {
|
||||
// Named selectors uses arrays as locators including the type of named selector.
|
||||
$allowedselectors = self::get_allowed_selectors();
|
||||
if (!isset($allowedselectors[$selectortype])) {
|
||||
throw new ExpectationException('The "' . $selectortype . '" selector not registered.', $session);
|
||||
}
|
||||
$locator = array($allowedselectors[$selectortype], behat_context_helper::escape($element));
|
||||
|
||||
// Get the selector which should be used.
|
||||
$allowedpartialselectors = behat_partial_named_selector::get_allowed_selectors();
|
||||
$allowedexactselectors = behat_exact_named_selector::get_allowed_selectors();
|
||||
if (isset($allowedpartialselectors[$selectortype])) {
|
||||
$selector = 'named_partial';
|
||||
} else if (isset($allowedexactselectors[$selectortype])) {
|
||||
$selector = 'named_exact';
|
||||
} else {
|
||||
throw new ExpectationException('The "' . $selectortype . '" selector not registered.', $session);
|
||||
}
|
||||
}
|
||||
|
||||
return array($selector, $locator);
|
||||
return [$selector, $locator];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,4 +81,40 @@ class behat_selectors {
|
||||
behat_exact_named_selector::get_allowed_text_selectors()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the selector and locator for a named partial.
|
||||
*
|
||||
* @param string $selector The selector name
|
||||
* @param string $locator The value to normalise
|
||||
* @return array
|
||||
*/
|
||||
public static function normalise_named_selector(string $selector, string $locator): array {
|
||||
return [
|
||||
$selector,
|
||||
behat_context_helper::escape($locator),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the selector for a field.
|
||||
*
|
||||
* @param string $label The label to find
|
||||
* @param Element $container The container to look within
|
||||
* @return array The selector, locator, and container to search within
|
||||
*/
|
||||
public static function transform_find_for_field(behat_base $context, string $label, Element $container): array {
|
||||
$hasfieldset = strpos($label, '>');
|
||||
if (false !== $hasfieldset) {
|
||||
list($containerlabel, $label) = explode(">", $label, 2);
|
||||
$container = $context->find_fieldset(trim($containerlabel), $container);
|
||||
$label = trim($label);
|
||||
}
|
||||
|
||||
return [
|
||||
'selector' => 'named_partial',
|
||||
'locator' => self::normalise_named_selector('field', $label),
|
||||
'container' => $container,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A class for recording the definition of Mink replacements.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class for recording the definition of Mink replacements for use in Mink selectors.
|
||||
*
|
||||
* These are comprised of a source string, and a replacement.
|
||||
*
|
||||
* During use the source string is converted from the string to be in the format:
|
||||
*
|
||||
* %[component]/[string]%
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* %mod_forum/title%
|
||||
*
|
||||
* Mink replacements are used in xpath translation to translate regularly used items such as title.
|
||||
* Here is an example from the upstream Mink project:
|
||||
*
|
||||
* '%tagTextMatch%' => 'contains(normalize-space(string(.)), %locator%)'
|
||||
*
|
||||
* And can be used in an xpath:
|
||||
*
|
||||
* .//label[%tagTextMatch%]
|
||||
*
|
||||
* This would be expanded to:
|
||||
*
|
||||
* .//label[contains(normalize-space(string(.)), %locator%)]
|
||||
*
|
||||
* Replacements can also be used in other replacements, as long as that replacement is defined later.
|
||||
*
|
||||
* '%linkMatch%' => '(%idMatch% or %tagTextMatch% or %titleMatch% or %relMatch%)'
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_component_named_replacement {
|
||||
/** @var string */
|
||||
protected $from;
|
||||
|
||||
/** @var string */
|
||||
protected $to;
|
||||
|
||||
/**
|
||||
* Create the replacement.
|
||||
*
|
||||
* @param string $from this is the old selector that should no longer be used.
|
||||
* For example 'group_message'.
|
||||
* @param string $to this is the new equivalent that should be used instead.
|
||||
* For example 'core_message > Message'.
|
||||
*/
|
||||
public function __construct(string $from, string $to) {
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 'from' part of the replacement, formatted for the component.
|
||||
*
|
||||
* @param string $component
|
||||
* @return string
|
||||
*/
|
||||
public function get_from(string $component): string {
|
||||
return "%{$component}/{$this->from}%";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 'to' part of the replacement.
|
||||
*
|
||||
* @return string Target xpath
|
||||
*/
|
||||
public function get_to(): string {
|
||||
return $this->to;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class representing a named selector that can be used in Behat tests.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a named selector that can be used in Behat tests.
|
||||
*
|
||||
* Named selectors are what make Behat steps like
|
||||
* Then I should see "Useful text" in the "General" "fieldset"
|
||||
* Here, "fieldset" is the named selector, and "General" is the locator.
|
||||
*
|
||||
* Selectors can either be exact, in which case the locator needs to
|
||||
* match exactly, or can be partial, for example the way
|
||||
* When I click "Save" "button"
|
||||
* will trigger a "Save changes" button.
|
||||
*
|
||||
* Instances of this class get returned by the get_exact_named_selectors()
|
||||
* and get_partial_named_selectors() methods in classes like behat_mod_mymod.
|
||||
* The code that makes the magic work is in the trait behat_named_selector
|
||||
* used by both behat_exact_named_selector and behat_partial_named_selector.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_component_named_selector {
|
||||
/** @var string */
|
||||
protected $alias;
|
||||
|
||||
/** @var array List of xpaths */
|
||||
protected $xpaths;
|
||||
|
||||
/** @var string */
|
||||
protected $istextselector;
|
||||
|
||||
/**
|
||||
* Create the selector definition.
|
||||
*
|
||||
* As an example, if you define
|
||||
* new behat_component_named_selector('Message',
|
||||
* [".//*[@data-conversation-id]//img[contains(@alt, %locator%)]/.."])
|
||||
* in get_partial_named_selectors in behat_message in
|
||||
* message/tests/behat/behat_message.php, then steps like
|
||||
* When "Group 1" "core_message > Message" should exist
|
||||
* will work.
|
||||
*
|
||||
* Text selectors are things that contain other things (e.g. some particular text), e.g.
|
||||
* Then I can see "Some text" in the "Whatever" "text_selector"
|
||||
* whereas non-text selectors are atomic things, like
|
||||
* When I click the "Whatever" "widget".
|
||||
*
|
||||
* @param string $alias The 'friendly' name of the thing. This will be prefixed with the component name.
|
||||
* For example, if the mod_mymod plugin, says 'Thingy', then "mod_mymod > Thingy" becomes a selector.
|
||||
* @param array $xpaths A list of xpaths one or more XPaths that the selector gets transformed into.
|
||||
* @param bool $istextselector Whether this selector can also be used as a text selector.
|
||||
*/
|
||||
public function __construct(string $alias, array $xpaths, bool $istextselector = true) {
|
||||
$this->alias = $alias;
|
||||
$this->xpaths = $xpaths;
|
||||
$this->istextselector = $istextselector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a text selector.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_text_selector(): bool {
|
||||
return $this->istextselector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the selector.
|
||||
* This is a back-end feature and contains a namespaced md5 of the human-readable name.
|
||||
*
|
||||
* @param string $component
|
||||
* @return string
|
||||
*/
|
||||
public function get_name(string $component): string {
|
||||
return implode('_', [$component, md5($this->alias)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alias of the selector.
|
||||
* This is the human-readable name that you would typically interact with.
|
||||
*
|
||||
* @param string $component
|
||||
* @return string
|
||||
*/
|
||||
public function get_alias(string $component): string {
|
||||
return implode(" > ", [$component, $this->alias]);;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of combined xpaths.
|
||||
*
|
||||
* @return string The list of xpaths combined with the xpath | (OR) operator
|
||||
*/
|
||||
public function get_combined_xpath(): string {
|
||||
return implode(' | ', $this->xpaths);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@
|
||||
*/
|
||||
class behat_exact_named_selector extends \Behat\Mink\Selector\ExactNamedSelector {
|
||||
|
||||
// Use the named selector trait.
|
||||
use behat_named_selector;
|
||||
|
||||
/**
|
||||
* Creates selector instance.
|
||||
*/
|
||||
@@ -63,6 +66,9 @@ class behat_exact_named_selector extends \Behat\Mink\Selector\ExactNamedSelector
|
||||
'text_exact' => 'text',
|
||||
);
|
||||
|
||||
/** @var List of deprecated selectors */
|
||||
protected static $deprecatedselectors = [];
|
||||
|
||||
/**
|
||||
* Allowed selectors getter.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moodle-specific common functions for named selectors.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common functions for named selectors.
|
||||
*
|
||||
* This has to be a trait, because we need this in both the classes
|
||||
* behat_exact_named_selector and behat_partial_named_selector, and
|
||||
* those classes have to be subclasses of \Behat\Mink\Selector\ExactNamedSelector
|
||||
* and \Behat\Mink\Selector\PartialNamedSelector. This trait is a way achieve
|
||||
* that without duplciated code.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait behat_named_selector {
|
||||
|
||||
/**
|
||||
* Registers new XPath selector with specified name.
|
||||
*
|
||||
* @param string $component
|
||||
* @param behat_component_named_selector $selector
|
||||
*/
|
||||
public function register_component_selector(string $component, behat_component_named_selector $selector) {
|
||||
$alias = $selector->get_alias($component);
|
||||
$name = $selector->get_name($component);
|
||||
static::$allowedselectors[$alias] = $name;
|
||||
|
||||
if ($selector->is_text_selector()) {
|
||||
static::$allowedtextselectors[$alias] = $name;
|
||||
}
|
||||
|
||||
// We must use Reflection here. The replacements property is private and cannot be accessed otherwise.
|
||||
// This is due to an API limitation in Mink.
|
||||
$rc = new \ReflectionClass(\Behat\Mink\Selector\NamedSelector::class);
|
||||
$r = $rc->getProperty('replacements');
|
||||
$r->setAccessible(true);
|
||||
$replacements = $r->getValue($this);
|
||||
|
||||
$selectorxpath = strtr($selector->get_combined_xpath(), $replacements);
|
||||
|
||||
parent::registerNamedXpath($name, $selectorxpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers new XPath selector with specified name.
|
||||
*
|
||||
* @param string $component
|
||||
* @param behat_component_named_replacement $replacement
|
||||
*/
|
||||
public function register_replacement(string $component, behat_component_named_replacement $replacement) {
|
||||
// We must use Reflection here. The replacements property is private and cannot be accessed otherwise.
|
||||
// This is due to an API limitation in Mink.
|
||||
$rc = new \ReflectionClass(\Behat\Mink\Selector\NamedSelector::class);
|
||||
$r = $rc->getProperty('replacements');
|
||||
$r->setAccessible(true);
|
||||
$existing = $r->getValue($this);
|
||||
|
||||
$from = $replacement->get_from($component);
|
||||
|
||||
if (isset($existing[$from])) {
|
||||
throw new \coding_exception("A named replacement already exists in the partial named selector for '{$from}'. " .
|
||||
"Replacement names must be unique, and should be namespaced to the component");
|
||||
}
|
||||
|
||||
$translatedto = strtr($replacement->get_to(), $existing);
|
||||
$this->registerReplacement($from, $translatedto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the specified selector has been deprecated and marked for replacement.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_deprecated_selector(string $selector): bool {
|
||||
return array_key_exists($selector, static::$deprecatedselectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the replacement name of a deprecated selector if any exists.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return string
|
||||
*/
|
||||
public static function get_deprecated_replacement(string $selector) {
|
||||
return static::$deprecatedselectors[$selector];
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@
|
||||
*/
|
||||
class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSelector {
|
||||
|
||||
// Use the named selector trait.
|
||||
use behat_named_selector;
|
||||
|
||||
/**
|
||||
* Creates selector instance.
|
||||
*/
|
||||
@@ -74,6 +77,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
|
||||
'table_row' => 'table_row',
|
||||
'xpath_element' => 'xpath_element',
|
||||
'form_row' => 'form_row',
|
||||
'autocomplete' => 'autocomplete',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -111,6 +115,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
|
||||
'form_row' => 'form_row',
|
||||
'autocomplete_selection' => 'autocomplete_selection',
|
||||
'autocomplete_suggestions' => 'autocomplete_suggestions',
|
||||
'autocomplete' => 'autocomplete',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -198,6 +203,9 @@ XPATH
|
||||
XPATH
|
||||
, 'autocomplete_suggestions' => <<<XPATH
|
||||
.//ul[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-suggestions', ' '))]/li[@role='option'][contains(normalize-space(.), %locator%)]
|
||||
XPATH
|
||||
, 'autocomplete' => <<<XPATH
|
||||
.//descendant::input[@id = //label[contains(normalize-space(string(.)), %locator%)]/@for]/ancestor::*[@data-fieldtype = 'autocomplete']
|
||||
XPATH
|
||||
);
|
||||
|
||||
@@ -243,6 +251,10 @@ XPATH
|
||||
],
|
||||
];
|
||||
|
||||
/** @var List of deprecated selectors */
|
||||
protected static $deprecatedselectors = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Allowed selectors getter.
|
||||
*
|
||||
|
||||
@@ -31,6 +31,9 @@ require_once(__DIR__ . '/behat_command.php');
|
||||
require_once(__DIR__ . '/behat_config_manager.php');
|
||||
|
||||
require_once(__DIR__ . '/../../filelib.php');
|
||||
require_once(__DIR__ . '/../../clilib.php');
|
||||
|
||||
use Behat\Mink\Session;
|
||||
|
||||
/**
|
||||
* Init/reset utilities for Behat database and dataroot
|
||||
@@ -373,4 +376,38 @@ class behat_util extends testing_util {
|
||||
// $CFG values from the old run. @see set_config.
|
||||
initialise_cfg();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause execution immediately.
|
||||
*
|
||||
* @param Session $session
|
||||
* @param string $message The message to show when pausing.
|
||||
* This will be passed through cli_ansi_format so appropriate ANSI formatting and features are available.
|
||||
*/
|
||||
public static function pause(Session $session, string $message) {
|
||||
$posixexists = function_exists('posix_isatty');
|
||||
|
||||
// Make sure this step is only used with interactive terminal (if detected).
|
||||
if ($posixexists && !@posix_isatty(STDOUT)) {
|
||||
throw new ExpectationException('Break point should only be used with interactive terminal.', $session);
|
||||
}
|
||||
|
||||
// Save the cursor position, ring the bell, and add a new line.
|
||||
fwrite(STDOUT, cli_ansi_format("<cursor:save><bell><newline>"));
|
||||
|
||||
// Output the formatted message and reset colour back to normal.
|
||||
$formattedmessage = cli_ansi_format("{$message}<colour:normal>");
|
||||
fwrite(STDOUT, $formattedmessage);
|
||||
|
||||
// Wait for input.
|
||||
fread(STDIN, 1024);
|
||||
|
||||
// Move the cursor back up to the previous position, then restore the original position stored earlier, and move
|
||||
// it back down again.
|
||||
fwrite(STDOUT, cli_ansi_format("<cursor:up><cursor:up><cursor:restore><cursor:down><cursor:down>"));
|
||||
|
||||
// Add any extra lines back if the provided message was spread over multiple lines.
|
||||
$linecount = count(explode("\n", $formattedmessage));
|
||||
fwrite(STDOUT, str_repeat(cli_ansi_format("<cursor:down>"), $linecount - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class behat_form_autocomplete extends behat_form_text {
|
||||
*/
|
||||
public function set_value($value) {
|
||||
if (!$this->running_javascript()) {
|
||||
throw new coding_exception('Setting the valid of an autocomplete field requires javascript.');
|
||||
throw new coding_exception('Setting the value of an autocomplete field requires javascript.');
|
||||
}
|
||||
|
||||
// Set the value of the autocomplete's input.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Custom Moodle engine for mustache.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\output;
|
||||
|
||||
/**
|
||||
* Custom Moodle engine for mustache.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mustache_engine extends \Mustache_Engine {
|
||||
/**
|
||||
* @var mustache_helper_collection
|
||||
*/
|
||||
private $helpers;
|
||||
|
||||
/**
|
||||
* @var string[] Names of helpers that aren't allowed to be called within other helpers.
|
||||
*/
|
||||
private $blacklistednestedhelpers = [];
|
||||
|
||||
/**
|
||||
* Mustache engine constructor.
|
||||
*
|
||||
* This provides an additional option to the parent \Mustache_Engine implementation:
|
||||
* $options = [
|
||||
* // A list of helpers (by name) to prevent from executing within the rendering
|
||||
* // of other helpers.
|
||||
* 'blacklistednestedhelpers' => ['js']
|
||||
* ];
|
||||
* @param array $options [description]
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
if (isset($options['blacklistednestedhelpers'])) {
|
||||
$this->blacklistednestedhelpers = $options['blacklistednestedhelpers'];
|
||||
}
|
||||
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current set of Mustache helpers.
|
||||
*
|
||||
* @see Mustache_Engine::setHelpers
|
||||
*
|
||||
* @return \Mustache_HelperCollection
|
||||
*/
|
||||
public function getHelpers()
|
||||
{
|
||||
if (!isset($this->helpers)) {
|
||||
$this->helpers = new mustache_helper_collection(null, $this->blacklistednestedhelpers);
|
||||
}
|
||||
|
||||
return $this->helpers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Custom Moodle helper collection for mustache.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\output;
|
||||
|
||||
/**
|
||||
* Custom Moodle helper collection for mustache.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mustache_helper_collection extends \Mustache_HelperCollection {
|
||||
|
||||
/**
|
||||
* @var string[] Names of helpers that aren't allowed to be called within other helpers.
|
||||
*/
|
||||
private $blacklistednestedhelpers = [];
|
||||
|
||||
/**
|
||||
* Helper Collection constructor.
|
||||
*
|
||||
* Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
|
||||
*
|
||||
* @throws \Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
|
||||
*
|
||||
* @param array|\Traversable $helpers (default: null)
|
||||
* @param string[] $blacklistednestedhelpers Names of helpers that aren't allowed to be called within other helpers.
|
||||
*/
|
||||
public function __construct($helpers = null, array $blacklistednestedhelpers = []) {
|
||||
$this->blacklistednestedhelpers = $blacklistednestedhelpers;
|
||||
parent::__construct($helpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a helper to this collection.
|
||||
*
|
||||
* This function has overridden the parent implementation to provide blacklist
|
||||
* functionality for certain helpers to prevent them being called from within
|
||||
* other helpers. This is because the JavaScript helper can be used in a
|
||||
* security exploit if it can be nested.
|
||||
*
|
||||
* The function will wrap callable helpers in an anonymous function that strips
|
||||
* out the blacklisted helpers from the source string before giving it to the
|
||||
* helper function. This prevents the blacklisted helper functions from being
|
||||
* called by nested render functions from within other helpers.
|
||||
*
|
||||
* @see \Mustache_HelperCollection::add()
|
||||
* @param string $name
|
||||
* @param mixed $helper
|
||||
*/
|
||||
public function add($name, $helper)
|
||||
{
|
||||
$blacklist = $this->blacklistednestedhelpers;
|
||||
|
||||
if (is_callable($helper) && !empty($blacklist)) {
|
||||
$helper = function($source, \Mustache_LambdaHelper $lambdahelper) use ($helper, $blacklist) {
|
||||
|
||||
// Temporarily override the blacklisted helpers to return nothing
|
||||
// so that they can't be executed from within other helpers.
|
||||
$disabledhelpers = $this->disable_helpers($blacklist);
|
||||
// Call the original function with the modified sources.
|
||||
$result = call_user_func($helper, $source, $lambdahelper);
|
||||
// Restore the original blacklisted helper implementations now
|
||||
// that this helper has finished executing so that the rest of
|
||||
// the rendering process continues to work correctly.
|
||||
$this->restore_helpers($disabledhelpers);
|
||||
// Lastly parse the returned string to strip out any unwanted helper
|
||||
// tags that were added through variable substitution (or other means).
|
||||
// This is done because a secondary render is called on the result
|
||||
// of a helper function if it still includes mustache tags. See
|
||||
// the section function of Mustache_Compiler for details.
|
||||
return $this->strip_blacklisted_helpers($blacklist, $result);
|
||||
};
|
||||
}
|
||||
|
||||
parent::add($name, $helper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a list of helpers (by name) by changing their implementation to
|
||||
* simply return an empty string.
|
||||
*
|
||||
* @param string[] $names List of helper names to disable
|
||||
* @return \Closure[] The original helper functions indexed by name
|
||||
*/
|
||||
private function disable_helpers($names) {
|
||||
$disabledhelpers = [];
|
||||
|
||||
foreach ($names as $name) {
|
||||
if ($this->has($name)) {
|
||||
$function = $this->get($name);
|
||||
// Null out the helper. Must call parent::add here to avoid
|
||||
// a recursion problem.
|
||||
parent::add($name, function() {
|
||||
return '';
|
||||
});
|
||||
|
||||
$disabledhelpers[$name] = $function;
|
||||
}
|
||||
}
|
||||
|
||||
return $disabledhelpers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original helper implementations. Typically used after disabling
|
||||
* a helper.
|
||||
*
|
||||
* @param \Closure[] $helpers The helper functions indexed by name
|
||||
*/
|
||||
private function restore_helpers($helpers) {
|
||||
foreach ($helpers as $name => $function) {
|
||||
// Restore the helper functions. Must call parent::add here to avoid
|
||||
// a recursion problem.
|
||||
parent::add($name, $function);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given string and remove any reference to blacklisted helpers.
|
||||
*
|
||||
* E.g.
|
||||
* $blacklist = ['js'];
|
||||
* $string = "core, move, {{#js}} some nasty JS hack {{/js}}"
|
||||
* result: "core, move, {{}}"
|
||||
*
|
||||
* @param string[] $blacklist List of helper names to strip
|
||||
* @param string $string String to parse
|
||||
* @return string Parsed string
|
||||
*/
|
||||
public function strip_blacklisted_helpers($blacklist, $string) {
|
||||
$starttoken = \Mustache_Tokenizer::T_SECTION;
|
||||
$endtoken = \Mustache_Tokenizer::T_END_SECTION;
|
||||
if ($endtoken == '/') {
|
||||
$endtoken = '\/';
|
||||
}
|
||||
|
||||
$regexes = array_map(function($name) use ($starttoken, $endtoken) {
|
||||
// We only strip out the name of the helper (excluding delimiters)
|
||||
// the user is able to change the delimeters on a per template
|
||||
// basis so they may not be curly braces.
|
||||
return '/\s*' . $starttoken . '\s*'. $name . '\W+.*' . $endtoken . '\s*' . $name . '\s*/';
|
||||
}, $blacklist);
|
||||
|
||||
// This will strip out unwanted helpers from the $source string
|
||||
// before providing it to the original helper function.
|
||||
// E.g.
|
||||
// Before:
|
||||
// "core, move, {{#js}} some nasty JS hack {{/js}}"
|
||||
// After:
|
||||
// "core, move, {{}}"
|
||||
return preg_replace_callback($regexes, function() {
|
||||
return '';
|
||||
}, $string);
|
||||
}
|
||||
}
|
||||
@@ -227,3 +227,59 @@ function cli_logo($padding=2, $return=false) {
|
||||
cli_write($logo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitute cursor, colour, and bell placeholders in a CLI output to ANSI escape characters when ANSI is available.
|
||||
*
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
function cli_ansi_format(string $message): string {
|
||||
global $CFG;
|
||||
|
||||
$replacements = [
|
||||
"<newline>" => "\n",
|
||||
"<bell>" => "\007",
|
||||
|
||||
// Cursor movement: https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html.
|
||||
"<cursor:save>" => "\033[s",
|
||||
"<cursor:restore>" => "\033[u",
|
||||
"<cursor:up>" => "\033[1A",
|
||||
"<cursor:down>" => "\033[1B",
|
||||
"<cursor:forward>" => "\033[1C",
|
||||
"<cursor:back>" => "\033[1D",
|
||||
];
|
||||
|
||||
$colours = [
|
||||
'normal' => '0;0',
|
||||
'black' => '0;30',
|
||||
'darkGray' => '1;30',
|
||||
'blue' => '0;34',
|
||||
'lightBlue' => '1;34',
|
||||
'green' => '0;32',
|
||||
'lightGreen' => '1;32',
|
||||
'cyan' => '0;36',
|
||||
'lightCyan' => '1;36',
|
||||
'red' => '0;31',
|
||||
'lightRed' => '1;31',
|
||||
'purple' => '0;35',
|
||||
'lightPurple' => '1;35',
|
||||
'brown' => '0;33',
|
||||
'yellow' => '1;33',
|
||||
'lightYellow' => '0;93',
|
||||
'lightGray' => '0;37',
|
||||
'white' => '1;37',
|
||||
];
|
||||
|
||||
foreach ($colours as $colour => $code) {
|
||||
$replacements["<colour:{$colour}>"] = "\033[{$code}m";
|
||||
}
|
||||
|
||||
// Windows don't support ANSI code by default, but does if ANSICON is available.
|
||||
$isansicon = getenv('ANSICON');
|
||||
if (($CFG->ostype === 'WINDOWS') && empty($isansicon)) {
|
||||
return str_replace(array_keys($replacements), '', $message);
|
||||
}
|
||||
|
||||
return str_replace(array_keys($replacements), array_values($replacements), $message);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ Feature: Tinymce with enable/disable function.
|
||||
|
||||
@javascript
|
||||
Scenario: Check disable Tinymce editor.
|
||||
When I click on "option[value=1]" "css_element" in the "select#id_mycontrol" "css_element"
|
||||
When I set the field "My control" to "Disable"
|
||||
Then the "class" attribute of "a#id_myeditor_pdw_toggle" "css_element" should contain "mceButtonDisabled"
|
||||
And the "class" attribute of "table#id_myeditor_formatselect" "css_element" should contain "mceListBoxDisabled"
|
||||
And the "class" attribute of "a#id_myeditor_bold" "css_element" should contain "mceButtonDisabled"
|
||||
@@ -39,7 +39,7 @@ Feature: Tinymce with enable/disable function.
|
||||
|
||||
@javascript
|
||||
Scenario: Check enable Tinymce editor.
|
||||
When I click on "option[value=0]" "css_element" in the "select#id_mycontrol" "css_element"
|
||||
When I set the field "My control" to "Enable"
|
||||
Then the "class" attribute of "a#id_myeditor_pdw_toggle" "css_element" should contain "mceButtonEnabled"
|
||||
And the "class" attribute of "table#id_myeditor_formatselect" "css_element" should contain "mceListBoxEnabled"
|
||||
And the "class" attribute of "a#id_myeditor_bold" "css_element" should contain "mceButtonEnabled"
|
||||
|
||||
@@ -1628,3 +1628,14 @@ function restrict_php_version_72(&$result) {
|
||||
function restrict_php_version_73(&$result) {
|
||||
return restrict_php_version($result, '7.3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current PHP version is greater than or equal to
|
||||
* PHP version 7.4.
|
||||
*
|
||||
* @param object $result an environment_results instance
|
||||
* @return bool result of version check
|
||||
*/
|
||||
function restrict_php_version_74(&$result) {
|
||||
return restrict_php_version($result, '7.4');
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class processor implements \core_analytics\classifier, \core_analytics\regresso
|
||||
/**
|
||||
* The required version of the python package that performs all calculations.
|
||||
*/
|
||||
const REQUIRED_PIP_PACKAGE_VERSION = '0.0.5';
|
||||
const REQUIRED_PIP_PACKAGE_VERSION = '1.0.1';
|
||||
|
||||
/**
|
||||
* The path to the Python bin.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user